Makefile, AppCache and numerous other changes

This commit is contained in:
relikd
2019-11-25 18:09:40 +01:00
parent 57351a5e12
commit 40d483bc38
98 changed files with 1310 additions and 306 deletions

View File

@@ -0,0 +1,14 @@
{
"name": "42 recipes",
"display": "standalone",
"background_color" : "#EAE9E7",
"theme_color": "#DC3A59",
"scope": ".",
"icons": [{
"src": "img/icon-180.png",
"sizes": "180x180"
},{
"src": "img/icon-196.png",
"sizes": "196x196"
}]
}

BIN
src/assets/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

View File

@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 22.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="favicon" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 1200 1200" enable-background="new 0 0 1200 1200" xml:space="preserve">
<polygon fill="#FFFFFF" points="952.7,1090.5 233.7,1090.5 66.4,907.3 64.1,672.2 233.7,532 233.7,111.5 952.7,111.5 952.7,523
1135.8,658.6 1131.3,925.4 "/>
<path d="M609.4,1165.1h-19.1c-63.2-2.7-125.5-23.9-176.2-61.5c-91.5,23.5-193,9.9-271.2-44.5C60.1,1004.4,6.8,910,0,811.3v-43.7
c7.7-121,88.1-232.7,200.1-278.8c-0.3-118.2,0-315.6-0.1-433.9h800c0,118.2,0,315.7,0,434c112.3,45.7,192.3,157.9,200,278.8v42.6
c-5.1,61.5-25.7,122.2-62.9,171.8c-47.7,65.1-120.2,111.4-199.5,126.5c-50.3,10.9-102.5,6-152.2-5.4
C735,1141.5,672.4,1162,609.4,1165.1z M656.9,1068.1c93.2-23.3,164-113,165-209c-2.1-24.3,16.4-48.5,41.1-50
c23.2-2.6,45.6,15.8,48.2,38.7c0.3,62.4-17.4,124.9-51.5,177.1c97.8,7.8,195.5-52.8,232.5-143.5c29.1-67.5,23.9-148.8-14-211.9
c-24.6-43-64.2-76.8-109.5-96.4c-19.2-8.1-38.5-16.2-57.8-24.2c-0.2-106,0-291.1-0.1-397c-207.7,0-415.3,0-623,0
c-0.1,105.9,0.2,291-0.2,396.9c-28.3,12.1-57.7,22.2-84.2,37.9C147,620,106,678.2,93.2,742.5c-17,79.5,11,166.4,71.4,220.8
c46.7,43.4,111.5,66.4,175.1,62c-28.9-43.7-46.5-95.1-50.6-147.4c-0.1-14.1-2.5-29.3,3.7-42.6c8.4-18.7,30.5-30.1,50.6-25.1
c18.9,3.7,33.8,21.3,34.9,40.5c-2.3,68.4,31.1,136.3,85.1,177.9C517,1071.2,590.9,1085.8,656.9,1068.1z"/>
<path d="M688.3,316.1L688.3,316.1c-24.6,0-44.7,20-44.7,44.7v222.6c0,24.6,20,44.7,44.7,44.7l0,0c24.6,0,44.7-20,44.7-44.7V360.6
C732.9,336.1,713,316.1,688.3,316.1z"/>
<path d="M512.3,316.1L512.3,316.1c-24.6,0-44.7,20-44.7,44.7v222.6c0,24.6,20,44.7,44.7,44.7l0,0c24.6,0,44.7-20,44.7-44.7V360.6
C557,336.1,537,316.1,512.3,316.1z"/>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

BIN
src/assets/img/icon-180.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

BIN
src/assets/img/icon-196.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

BIN
src/assets/img/icon-32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 485 B

View File

@@ -0,0 +1,36 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 22.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="glutenfree" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 1000 1000" style="enable-background:new 0 0 1000 1000;" xml:space="preserve">
<style type="text/css">
.st0{fill:#66CCCC;}
.st1{fill:#FFB903;stroke:#000000;stroke-width:16;stroke-miterlimit:10;}
</style>
<circle id="back" cx="500" cy="500" r="500"/>
<circle id="color" class="st0" cx="500" cy="500" r="400"/>
<g id="grain">
<path class="st1" d="M628.1,358.7c4.3-42.9,19.3-65.4,29.3-75c45.7-44.3,96.9-65.9,114.1-47.7c17.2,18.2-6.1,68.9-51.8,113.3
c-8.3,8.1-29.8,25.2-74.3,26.5"/>
<path class="st1" d="M558.5,422.5c-10.5-21.3-19-56.9-18.9-93.8c0.2-63.9,20.6-115.2,45.5-114.8c25,0.4,45,52.3,44.8,116.2
c-0.1,15.5-1.3,30.4-3.5,43.9"/>
<ellipse transform="matrix(9.121996e-03 -1 1 9.121996e-03 248.7508 1087.4275)" class="st1" cx="673.1" cy="418.2" rx="45.1" ry="115.6"/>
<path class="st1" d="M541.2,461.9c14.9-2.6,31.5-3.8,49-3.7c63.9,0.8,115.1,21.5,114.6,46.4c-0.6,24.9-52.8,44.5-116.6,43.8
c-46.7-0.5-88.2-12.9-106.1-28.6"/>
<ellipse transform="matrix(1.272804e-03 -1 1 1.272804e-03 85.9093 914.6296)" class="st1" cx="500.9" cy="414.3" rx="115.6" ry="45.1"/>
<path class="st1" d="M390.1,590.6c-10.5-21.3-19-56.9-18.9-93.8c0.2-63.9,20.6-115.2,45.5-114.8c25,0.4,45,52.3,44.8,116.2
c-0.1,15.5-1.3,30.4-3.5,43.9"/>
<ellipse transform="matrix(9.121996e-03 -1 1 9.121996e-03 -86.1381 1085.9659)" class="st1" cx="504.9" cy="586.4" rx="45.1" ry="115.6"/>
<path class="st1" d="M372.7,630c14.9-2.6,31.5-3.8,49-3.7c63.9,0.8,115.1,21.5,114.6,46.4c-0.6,24.9-52.8,44.5-116.6,43.8
c-46.7-0.5-88.2-12.9-106.1-28.6"/>
<ellipse transform="matrix(1.272804e-03 -1 1 1.272804e-03 -250.5945 913.9741)" class="st1" cx="332.3" cy="582.4" rx="115.6" ry="45.1"/>
</g>
<path id="stick" d="M236.3,777.6l-10.7-10.7c-11.8-11.8-15.1-27.9-7.4-35.6l77.8-77.8l53.7,53.7L271.9,785
C264.1,792.8,248.1,789.4,236.3,777.6z"/>
<g id="cross">
<polygon points="123.2,175.1 65.8,257 876.8,824.9 934.2,743 "/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@@ -0,0 +1,42 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 22.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="glutenfree" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 1000 1000" style="enable-background:new 0 0 1000 1000;" xml:space="preserve">
<style type="text/css">
.st0{fill:#66CCCC;}
.st1{fill:#E8BB00;stroke:#000000;stroke-width:16;stroke-miterlimit:10;}
</style>
<circle id="back" cx="500" cy="500" r="500"/>
<circle id="color" class="st0" cx="500" cy="500" r="400"/>
<g id="grain">
<path class="st1" d="M491.7,256.5c-19.8-24.2-23.6-43.4-23.4-53.4c0.7-46.2,15.9-83.4,34-82.9s32.2,38.4,31.5,84.6
c-0.1,8.4-2.4,28.2-24.5,51.7"/>
<path class="st1" d="M488.7,324.8c-16.3-5.5-38.9-19.4-57.8-38.4c-32.6-32.8-48.5-69.6-35.5-82.2c13-12.6,49.9,3.8,82.5,36.6
c7.9,8,14.9,16.2,20.7,24.3"/>
<ellipse transform="matrix(0.7135 -0.7006 0.7006 0.7135 -28.7219 457.6503)" class="st1" cx="545.3" cy="263.9" rx="83.8" ry="32.7"/>
<path class="st1" d="M500.1,353.9c6.3-9,14.2-18.1,23.2-27c33.1-32.3,70-48,82.5-34.9s-4.2,49.9-37.3,82.2
c-24.2,23.7-51.8,38.6-69.1,39.7"/>
<ellipse transform="matrix(0.708 -0.7062 0.7062 0.708 -114.4428 423.593)" class="st1" cx="455" cy="350.2" rx="32.7" ry="83.8"/>
<path class="st1" d="M488.6,497.3c-16.3-5.5-38.9-19.4-57.8-38.4c-32.6-32.8-48.5-69.6-35.5-82.2c13-12.6,49.9,3.8,82.5,36.6
c7.9,8,14.9,16.2,20.7,24.3"/>
<ellipse transform="matrix(0.7135 -0.7006 0.7006 0.7135 -149.5211 507.0457)" class="st1" cx="545.3" cy="436.4" rx="83.8" ry="32.7"/>
<path class="st1" d="M499.9,526.4c6.3-9,14.2-18.1,23.2-27c33.1-32.3,70-48,82.5-34.9s-4.2,49.9-37.3,82.2
c-24.2,23.7-51.8,38.6-69.1,39.7"/>
<ellipse transform="matrix(0.708 -0.7062 0.7062 0.708 -236.4149 473.8377)" class="st1" cx="454.8" cy="522.8" rx="32.7" ry="83.8"/>
<path class="st1" d="M487.9,670.7c-16.3-5.5-38.9-19.4-57.8-38.4c-32.6-32.8-48.5-69.6-35.5-82.2s49.9,3.8,82.5,36.6
c7.9,8,14.9,16.2,20.7,24.3"/>
<ellipse transform="matrix(0.7135 -0.7006 0.7006 0.7135 -271.2175 556.2379)" class="st1" cx="544.6" cy="609.8" rx="83.8" ry="32.7"/>
<path class="st1" d="M499.2,699.8c6.3-9,14.2-18.1,23.2-27c33.1-32.3,70-48,82.5-34.9s-4.2,49.9-37.3,82.2
c-24.2,23.7-51.8,38.6-69.1,39.7"/>
<ellipse transform="matrix(0.708 -0.7062 0.7062 0.708 -358.9856 523.9573)" class="st1" cx="454.1" cy="696.1" rx="32.7" ry="83.8"/>
</g>
<path id="stick" d="M505,884h-10c-11,0-20-9-20-20V754h50v110C525,875,516,884,505,884z"/>
<g id="cross">
<polygon points="123.2,175.1 65.8,257 876.8,824.9 934.2,743 "/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 22.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="raw" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 1000 1000" style="enable-background:new 0 0 1000 1000;" xml:space="preserve">
<style type="text/css">
.st0{fill:#993300;}
.st1{fill:#FFD3C0;}
</style>
<circle id="back" cx="500" cy="500" r="500"/>
<circle id="color" class="st0" cx="500" cy="500" r="400"/>
<g id="RAW">
<polygon id="w" class="st1" points="787,350 771.2,500 742.7,500 747.4,455 677.9,455 682.7,500 654.1,500 638.4,350 578,350
609.6,650 669.9,650 660.4,560 689,560 693.7,605 731.8,605 736.5,560 764.9,560 755.4,650 815.8,650 847.3,350 "/>
<path id="a" class="st1" d="M566,560L566,560l-6.3-60l0,0l-9.5-90l0,0l-6.3-60l0,0l0,0h-60.3l0,0h-16.7l0,0h-60.3l0,0l0,0l-6.3,60
l0,0l-9.5,90l0,0l-6.3,60l0,0l-9.5,90h60.3l9.5-90h60.8l9.5,90h60.3L566,560z M451.2,500l9.5-90H490l9.5,90H451.2z"/>
<path id="r" class="st1" d="M169.9,350l-0.1,301.1L230,650v-90l0,0c62.5,0,60,0,60,45v45h60v-45c0-45,0-75-45-75c45,0,45-30,45-75
c0-105,0-105-120-105C212.5,350,169.9,350,169.9,350z M230,500c0-16,0-75.3,0-90c60,0,60,0,60,45S290,500,230,500z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 22.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="vegan" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 1000 1000" style="enable-background:new 0 0 1000 1000;" xml:space="preserve">
<style type="text/css">
.st0{fill:#41A748;}
</style>
<circle id="back" cx="500" cy="500" r="500"/>
<circle id="color" class="st0" cx="500" cy="500" r="400"/>
<path id="v" d="M433.3,676.4c-32.9-86.9-65.2-170.8-133.3-236.9c-38.8-37.6-104-94.5-161-101.6c81.9,69.1,150.2,154.8,198.3,250.8
c45.8,91.5,81.9,210,81.9,313.2h97.5c-0.8-73.5,10.6-149.3,36.4-218.2c18.7-50,44.9-111.8,86.1-147.6c46-1.7,99.3-5.3,124.2-49.5
c18.9-33.7,13.2-72.6,23.4-108.5c10-35.2,34.6-60.8,50.2-93c-21.6,21.9-53.1,44.4-82.1,55.5c-27.2,10.4-57.3,6.6-85.7,10.2
c-97.9,12.4-119.4,96.8-91.4,181.2c16.9-79.3,45.6-123,130.3-130.3c-8.4,30.5-47.8,55-67.7,77.7c-31.8,36.3-60.1,66-86.9,115.9
c-26.8,50-32.7,75.3-54.4,108.9C482.5,728.3,449.2,726.1,433.3,676.4z"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 22.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="fork_knife" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 1000 1000" style="enable-background:new 0 0 1000 1000;" xml:space="preserve">
<path id="path" d="M210.3,0l-26.8,366.6c-9.8,53.6,118.8,100.8,116.6,155.4l-6.6,411.4c-2.6,66.6,56.6,66.6,56.6,66.6
s59.2,0,56.6-66.6L400.4,522c-2-54.6,115.6-100.8,116.6-155.4L490.4,0H457l-3.4,306.6l-70,13.4L367,0h-33.4l-16.6,320l-70-13.4
L243.5,0H210.3z M817,0C655,0,627,166,627,500c0,58,56.8,66.6,84,66.6l-5.4,366.6c-6,66.4,60.6,66.6,60.6,66.6s50.6,0,50.6-66.6"/>
</svg>

After

Width:  |  Height:  |  Size: 730 B

View File

@@ -1,10 +0,0 @@
(function(){// show at least 2 columns on mobile devices
var viewport = document.head.querySelector("meta[name=viewport]");
if (viewport && screen.width < 485) {
document.head.removeChild(viewport);
var x = document.createElement("meta");
x.setAttribute("name", "viewport");
x.setAttribute("content", "width=485");
document.head.appendChild(x);
}
})();

View File

@@ -1,9 +0,0 @@
/*! lozad.js - v1.9.0 - 2019-02-09
* https://github.com/ApoorvSaxena/lozad.js
* Copyright (c) 2019 Apoorv Saxena; Licensed MIT */
!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):t.lozad=e()}(this,function(){"use strict";var g=Object.assign||function(t){for(var e=1;e<arguments.length;e++){var r=arguments[e];for(var o in r)Object.prototype.hasOwnProperty.call(r,o)&&(t[o]=r[o])}return t},n="undefined"!=typeof document&&document.documentMode,l={rootMargin:"0px",threshold:0,load:function(t){if("picture"===t.nodeName.toLowerCase()){var e=document.createElement("img");n&&t.getAttribute("data-iesrc")&&(e.src=t.getAttribute("data-iesrc")),t.getAttribute("data-alt")&&(e.alt=t.getAttribute("data-alt")),t.appendChild(e)}if("video"===t.nodeName.toLowerCase()&&!t.getAttribute("data-src")&&t.children){for(var r=t.children,o=void 0,a=0;a<=r.length-1;a++)(o=r[a].getAttribute("data-src"))&&(r[a].src=o);t.load()}t.getAttribute("data-src")&&(t.src=t.getAttribute("data-src")),t.getAttribute("data-srcset")&&t.setAttribute("srcset",t.getAttribute("data-srcset")),t.getAttribute("data-background-image")&&(t.style.backgroundImage="url('"+t.getAttribute("data-background-image")+"')"),t.getAttribute("data-toggle-class")&&t.classList.toggle(t.getAttribute("data-toggle-class"))},loaded:function(){}};
/**
* Detect IE browser
* @const {boolean}
* @private
*/function f(t){t.setAttribute("data-loaded",!0)}var b=function(t){return"true"===t.getAttribute("data-loaded")};return function(){var r,o,a=0<arguments.length&&void 0!==arguments[0]?arguments[0]:".lozad",t=1<arguments.length&&void 0!==arguments[1]?arguments[1]:{},e=g({},l,t),n=e.root,i=e.rootMargin,d=e.threshold,c=e.load,u=e.loaded,s=void 0;return window.IntersectionObserver&&(s=new IntersectionObserver((r=c,o=u,function(t,e){t.forEach(function(t){(0<t.intersectionRatio||t.isIntersecting)&&(e.unobserve(t.target),b(t.target)||(r(t.target),f(t.target),o(t.target)))})}),{root:n,rootMargin:i,threshold:d})),{observe:function(){for(var t=function(t){var e=1<arguments.length&&void 0!==arguments[1]?arguments[1]:document;return t instanceof Element?[t]:t instanceof NodeList?t:e.querySelectorAll(t)}(a,n),e=0;e<t.length;e++)b(t[e])||(s?s.observe(t[e]):(c(t[e]),f(t[e]),u(t[e])))},triggerLoad:function(t){b(t)||(c(t),f(t),u(t))},observer:s}}});

View File

@@ -0,0 +1,39 @@
(function(){// main entry
handleAppCache();
updateViewport();
})();
function updateViewport() {// show at least 2 columns on mobile devices
var viewport = document.head.querySelector("meta[name=viewport]");
if (viewport && screen.width < 485) {
document.head.removeChild(viewport);
var x = document.createElement("meta");
x.setAttribute("name", "viewport");
x.setAttribute("content", "width=485");
document.head.appendChild(x);
}
}
function handleAppCache() {// update cache status icon
var cache = window.applicationCache;
if (cache) {
cache.addEventListener('updateready', update);
cache.addEventListener('cached', ready); // initial
cache.addEventListener('noupdate', ready); // consecutive
cache.addEventListener('downloading', busy);
cache.addEventListener('obsolete', failed);
if(cache.status===cache.UPDATEREADY){update()}
if(cache.status===cache.IDLE){window.onload=(event)=>{ready()};}
function update(){ready(); cache.swapCache(); window.location.reload()}
function busy(){document.getElementById('cache-status').style='background:darkorange'}
function ready(){document.getElementById('cache-status').style='background:forestgreen'}
function failed(){document.getElementById('cache-status').style='background:red'}
}
}
/*! lozad.js - v1.9.0 - 2019-02-09
* https://github.com/ApoorvSaxena/lozad.js
* Copyright (c) 2019 Apoorv Saxena; Licensed MIT */
!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):t.lozad=e()}(this,function(){"use strict";var g=Object.assign||function(t){for(var e=1;e<arguments.length;e++){var r=arguments[e];for(var o in r)Object.prototype.hasOwnProperty.call(r,o)&&(t[o]=r[o])}return t},n="undefined"!=typeof document&&document.documentMode,l={rootMargin:"0px",threshold:0,load:function(t){if("picture"===t.nodeName.toLowerCase()){var e=document.createElement("img");n&&t.getAttribute("data-iesrc")&&(e.src=t.getAttribute("data-iesrc")),t.getAttribute("data-alt")&&(e.alt=t.getAttribute("data-alt")),t.appendChild(e)}if("video"===t.nodeName.toLowerCase()&&!t.getAttribute("data-src")&&t.children){for(var r=t.children,o=void 0,a=0;a<=r.length-1;a++)(o=r[a].getAttribute("data-src"))&&(r[a].src=o);t.load()}t.getAttribute("data-src")&&(t.src=t.getAttribute("data-src")),t.getAttribute("data-srcset")&&t.setAttribute("srcset",t.getAttribute("data-srcset")),t.getAttribute("data-background-image")&&(t.style.backgroundImage="url('"+t.getAttribute("data-background-image")+"')"),t.getAttribute("data-toggle-class")&&t.classList.toggle(t.getAttribute("data-toggle-class"))},loaded:function(){}};
/**
* Detect IE browser
* @const {boolean}
* @private
*/function f(t){t.setAttribute("data-loaded",!0)}var b=function(t){return"true"===t.getAttribute("data-loaded")};return function(){var r,o,a=0<arguments.length&&void 0!==arguments[0]?arguments[0]:".lozad",t=1<arguments.length&&void 0!==arguments[1]?arguments[1]:{},e=g({},l,t),n=e.root,i=e.rootMargin,d=e.threshold,c=e.load,u=e.loaded,s=void 0;return window.IntersectionObserver&&(s=new IntersectionObserver((r=c,o=u,function(t,e){t.forEach(function(t){(0<t.intersectionRatio||t.isIntersecting)&&(e.unobserve(t.target),b(t.target)||(r(t.target),f(t.target),o(t.target)))})}),{root:n,rootMargin:i,threshold:d})),{observe:function(){for(var t=function(t){var e=1<arguments.length&&void 0!==arguments[1]?arguments[1]:document;return t instanceof Element?[t]:t instanceof NodeList?t:e.querySelectorAll(t)}(a,n),e=0;e<t.length;e++)b(t[e])||(s?s.observe(t[e]):(c(t[e]),f(t[e]),u(t[e])))},triggerLoad:function(t){b(t)||(c(t),f(t),u(t))},observer:s}}});

View File

@@ -12,11 +12,22 @@
.dark-red { color: var(--cRed2) }
.light-red { color: var(--cRed3) }
.mrgTopMd { margin-top: 0.7em }
.v-scroll { overflow-x: scroll; white-space: nowrap; -webkit-overflow-scrolling: touch }
.v-scroll { overflow-y: hidden; white-space: nowrap; -webkit-overflow-scrolling: touch }
ul.no-bullets { padding: unset; margin: unset; list-style: none }
ul.li-lg-space li { padding-bottom: 0.3em }
i.icon:before {
content: '';
display: inline-block;
vertical-align: bottom;
width: 1em; height: 1em
}
i.icon.gf:before {background-image: url('../img/icon-glutenfree.svg')}
i.icon.raw:before {background-image: url('../img/icon-raw.svg')}
i.icon.veg:before {background-image: url('../img/icon-vegan.svg')}
i.icon.yield:before {background-image: url('../img/icon-yield.svg.svg')}
/*
* General
*/
@@ -24,11 +35,15 @@ a { color: var(--cRed2); text-decoration: none }
a:hover { color: var(--cRed1) }
body {
font-family: 'Verdana', sans-serif;
background-color: var(--cBg3);
color: var(--cTxt);
margin: unset;
background: var(--cBg3); color: var(--cTxt);
}
header #logo { font-size: 42px; display: block; margin-bottom: 15px }
#logo { font-size: 42px; display: block; margin-bottom: 15px }
#cache-status {
position: absolute; right: 10px; top: 10px;
border-radius: 50%; width: 10px; height: 10px;
}
header { position: relative }
header a { color: var(--cTxt) }
header, h1 { text-align: center }
header, footer, .page {
@@ -43,33 +58,32 @@ nav ul li { display: inline-block; margin: 0.1em 0.5em }
nav ul li a.active { text-decoration: overline }
footer table { margin: -10px 0 }
@media(max-width: 485px) { body { font-size: 1.4em } }
@media screen and (max-width: 485px) { body { font-size: 1.4em } }
@media print {
header, footer { display: none }
body, .page { background-color: #FFF }
body, .page { background: #FFF }
}
/*
* Components
* Parts & Components
*/
.tags { display: flex; flex-wrap: wrap; justify-content: center }
.tags > * {
background-color: #FFF;
background: #FFF;
border: 1px solid var(--cRed1);
border-radius: 0.3em;
padding: 0.3em 0.5em;
margin: 0.2em;
}
.tags a:hover, .tags .active, a:hover .recipe-tile {
background-color: var(--cRed1);
color: #FFF;
.tags a:hover, .tags .active, a:hover .recipe-tile, .recipe-tile .hover .time {
background: var(--cRed1); color: #FFF;
}
header .tags { max-width: 600px; margin: 0 auto }
.cluster dt { margin-top: 0.7em; font-size: 1.6em }
.cluster dd { margin-top: 0.4em }
.cluster dd a { white-space: nowrap }
@media(max-width: 500px) {
@media(max-width: 32em) {
.cluster dd { margin-left: 0 }
.cluster dd a { white-space: unset }
}
@@ -77,25 +91,31 @@ header .tags { max-width: 600px; margin: 0 auto }
/*
* Grid overview
*/
.pagination { text-align: center; margin-top: 1em; }
.pagination { text-align: center; margin-top: 1em }
.recipe-tile {
background-color: var(--cBg2);
color: var(--cTxt);
background: var(--cBg2); color: var(--cTxt);
display: inline-block;
vertical-align: top;
margin: 6px;
width: 200px;
text-align: center;
}
.recipe-tile .img-placeholder {
background-color: #777;
color: var(--cBg2);
width: 200px;
height: 150px;
.recipe-tile .placeholder {
font: bold 25px/150px 'Courier New', monospace;
background: #777; color: var(--cBg2);
}
a:hover .recipe-tile img { mix-blend-mode: overlay }
.recipe-tile p { height: 2.6em; margin: 0.3em 10px; overflow-y: auto }
.recipe-tile p { height: 2.5em; margin: 0.3em 10px; overflow-y: auto }
.recipe-tile img, .recipe-tile .overlay { display: block; width: 200px; height: 150px }
.recipe-tile .overlay { position: absolute }
.recipe-tile .icon-bar { position: absolute; bottom: 3px; right: 3px }
.recipe-tile .icon-bar i.icon { margin-left: 2px; font-size: 28px }
a:hover .recipe-tile .hover { display: block; background: #0006 }
.recipe-tile .hover { display: none; height: 100% }
.recipe-tile .hover .time {
position: relative; top: -1.25em;
font: bold 1.1em/1.3em monospace;
}
/* snap to column grid */
.tile-grid { width: fit-content; max-width: 1060px; margin: 0 auto }
.latest .tile-grid { max-width: 636px }
/* max-width = prev + 2*30; width = x * (200 + 2*6); */
@@ -107,9 +127,9 @@ a:hover .recipe-tile img { mix-blend-mode: overlay }
@media print and (orientation: portrait) { .tile-grid { width: 636px } }
@media print and (orientation: landscape) { .tile-grid { width: 1060px } }
@media print {
a:hover .recipe-tile img { mix-blend-mode: unset !important }
.recipe-tile, .recipe-tile .img-placeholder, a:hover .recipe-tile {
background-color: #FFF; color: #000 }
.recipe-tile .overlay { display: none }
.recipe-tile, a:hover .recipe-tile, .recipe-tile .placeholder {
background: #FFF; color: #000 }
}
/*
@@ -120,16 +140,19 @@ a:hover .recipe-tile img { mix-blend-mode: overlay }
.recipe #source { margin-left: -1em; margin-bottom: -1.5em }
.recipe #metrics { float: right; margin: 0 0 15px 25px; max-width: 180px }
.recipe #metrics > * { text-indent: -20px; margin-left: 20px; padding-top: 0.3em }
.recipe #ingredients { float: left; margin: 0 25px 15px 0; max-width: 300px }
.recipe #ingredients { float: left; margin: 0 30px 15px 0; max-width: 300px }
.recipe #ingredients a { line-height: 1em }
.recipe #directions ul { list-style-type: circle }
.recipe #directions dl dt { color: var(--cRed2); font-weight: bold }
.recipe #directions dl dd { margin-bottom: 1em }
/* Colored, 3-part, difficulty bar */
.difficulty.easy > div:nth-child(1) { background-color: #3C3 }
.difficulty.easy > div:nth-child(1) { background: #3C3 }
.difficulty.medium > div:nth-child(1),
.difficulty.medium > div:nth-child(2) { background-color: #FC3 }
.difficulty.medium > div:nth-child(2) { background: #FC3 }
.difficulty.hard > div:nth-child(1),
.difficulty.hard > div:nth-child(2),
.difficulty.hard > div:nth-child(3) { background-color: #F30 }
.difficulty > * { vertical-align: middle; }
.difficulty.hard > div:nth-child(3) { background: #F30 }
.difficulty > * { vertical-align: middle }
.difficulty > div:nth-child(1) { border-radius: 50% 0 0 50% }
.difficulty > div:nth-child(3) { border-radius: 0 50% 50% 0 }
.difficulty > div {
@@ -138,16 +161,26 @@ a:hover .recipe-tile img { mix-blend-mode: overlay }
border: 1px solid #555;
}
@media screen and (max-width: 800px) {
.recipe #img-carousel img { height: auto; width: 100%; padding: 0 }
.recipe #metrics { float: unset; max-width: fit-content; margin: 20px auto }
@media screen and (max-width: 50em) {
.recipe h1 { margin-bottom: 0 }
.recipe #img-carousel { height: calc(75vw - 2*50px) }
.recipe #img-carousel img { height: 100%; max-width: 100%; padding: 0 }
.recipe #metrics { float: unset; max-width: max-content; margin: .5em auto 2em }
.recipe #metrics > *:not(:first-child) { margin-right: -100vw; max-width: 50vw }
}
@media screen and (max-width: 40em), print and (orientation: portrait) {
.recipe #ingredients { float: unset; max-width: 100% }
}
@media screen and (max-width: 32em) {
.recipe #img-carousel { padding: 0 10px; height: calc(75vw - 2*10px) }
}
@media(max-width: 600px) { .recipe #ingredients { float: unset; max-width: 100% } }
@media(max-width: 500px) { .recipe #img-carousel { padding: 0 10px } }
@media print { #source, #rating, .difficulty { display: none } }
@media print and (orientation: landscape) { #img-carousel img { display: none } }
@media print {
h1 { margin-top:0 }
#source, #rating, .difficulty, #img-carousel { display: none }
}
/*@media print and (orientation: landscape) { #img-carousel img { display: none } }
@media print and (orientation: portrait) {
#img-carousel img:not(:first-child) { display: none }
.recipe #metrics { float: unset; padding-bottom: 1em }
}
}*/

View File

@@ -0,0 +1,2 @@
enabled = yes
endswith = .appcache

View File

@@ -0,0 +1,3 @@
_template: cache.manifest
---
_model: none

View File

@@ -10,4 +10,4 @@ xdata:
120
180
360
9999
1440

View File

@@ -1 +0,0 @@
_model: recipes

View File

@@ -1,16 +0,0 @@
name: Vanille Ausstech-Kekse
---
yield: 26-28 Kekse
---
ingredients:
1/2 Tasse Margarine, oder Kokos Öl
1/2 Tasse Ahornsirup
1/2 TL Vanille Extrakt
1/4 TL Vanilleshote
1 Prise Salz
2 1/4 Tassen Mehl, glutenfrei
---
directions:
No translation yet. Click the flag (🇱🇷) at the bottom.

View File

@@ -1,26 +0,0 @@
name: Vanilla cut-out cookies
---
yield: 26-28 cookies
---
ingredients:
1/2 cup non-dairy butter, or coconut oil
1/2 cup maple syrup
1/2 tsp vanilla extract
1/4 tsp vanilla bean
dash salt
2 1/4 cups gluten-free flour blend
---
directions:
1) Preheat oven to 350°F. Line 2 cookie sheets with parchment paper. Prepare a rolling area with two additional sheets of parchment paper for that, and have your cookie cutter(s) handy.
2) Place butter in a large mixing bowl and whip it with a mixer until its creamy. Add sweetener, vanilla extract and bean, and salt and mix once again to combine. Add in flour and use a wooden spoon to mix. Then get in there with your hands and mix everything together by working the dough until you can shape it into a ball {note: as depending on the flour mix you use there may be a slight variance, know that the consistency of the dough should not be sticky but should press together when pinched — be sure to knead it really well first for some time — if its a little sticky, add a little more flour (try 1-2 tbsp); if its a little dry add a little more sweetener (try 1 tbsp)}. Shape the dough into 2 balls and then flatten each into a disk.
3) Roll out one of the dough balls between two sheets of parchment paper to ¼” thickness {or thinner or thicker depending on how you want your cookies to turn out}. Use a cookie cutter to cut out the cookies. Carefully transfer to a prepared cookie sheets, spacing them ½” apart {they wont spread as they bake}. Gather up any dough scraps and repeat until all dough is used up. Repeat the process with the second dough ball.
4) Bake in a pre-heated oven for approximately 11-13 minutes, until the edges just begin to become golden. Remove from oven and place on a cooling rack. {Note: cookies will harden a little within minutes of cooling, so dont overbake}. Allow the cookies to cool for 10 minutes and enjoy!
__Note:__
You can make your own glutenfree flour blend by combining:
1 cup brown rice flour, ¾ cup tapioca starch, ½ cup sweet rice flour, ½ tsp guar gum

View File

@@ -1,11 +0,0 @@
tags: cookies, sweet, xmas, glutenfree
---
time: 30
---
rating: 4
---
difficulty: easy
---
source: https://www.unconventionalbaker.com/recipes/gluten-free-vegan-vanilla-cut-out-cookies/
---
date: 2019-05-15

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

View File

@@ -1 +1 @@
measures: EL, TL, kg, g, L, dl, cl, ml, cm, Msp, Prise, Tasse, Tassen, Dose, Dosen, kleine, Bund, Packung, Packungen, Scheibe, Scheiben, Schuss, Stängel, Tropfen, Tube
measures: EL TL kg g L dl cl ml cm Msp Prise Tasse Tassen Dose Dosen kleine große Bund Packung Packungen Scheibe Scheiben Schuss Stängel Tropfen Tube

View File

@@ -1 +1 @@
measures: kg, g, L, dl, cl, ml, oz, lb, pt, qt, cm, tsp, tbsp, c, cup, cups, pkg, pck, drop, drops, tube, dash, dashes, ounce, ounces, small, medium, large, box, can, pinch, tin, clove, cloves
measures: kg g L dl cl ml oz lb pt qt cm inch tsp tbsp c cup cups pkg pkgs drop drops tube dash dashes ounce ounces small medium large box can tin pinch pinches clove cloves stick sticks bunch handful splash splashes stem stems slice slices

View File

@@ -5,5 +5,3 @@ _hidden: yes
replace_frac: yes
---
replace_temp: yes
---
show_empty_tags: no

View File

@@ -1 +1 @@
name: Brot
name: Brot

View File

@@ -1 +1 @@
name: Bread
name: Bread

View File

@@ -1 +1 @@
name: Kuchen
name: Kuchen

View File

@@ -1 +1 @@
name: Cake
name: Cake

View File

@@ -0,0 +1 @@
name: Käse

View File

@@ -0,0 +1 @@
name: Cheese

View File

@@ -1 +0,0 @@
name: Schokolade

View File

@@ -1 +0,0 @@
name: Chocolate

View File

@@ -1 +1 @@
name: Kekse
name: Keks

View File

@@ -1 +1 @@
name: Cookies
name: Cookie

View File

@@ -0,0 +1 @@
name: Kuchenboden

View File

@@ -0,0 +1 @@
name: Crust

View File

@@ -1 +1 @@
name: Dip
name: Dip

View File

@@ -1 +0,0 @@
name: Dressing

View File

@@ -1 +1 @@
name: Drinks
name: Drink

View File

@@ -1 +1 @@
name: Glutenfrei
name: Glutenfrei

View File

@@ -1 +1 @@
name: Glutenfree
name: Gluten-free

View File

@@ -1 +1 @@
name: Zutat
name: Zutat

View File

@@ -1 +1 @@
name: Ingredient
name: Ingredient

View File

@@ -1 +1 @@
name: Hauptspeise
name: Hauptspeise

View File

@@ -1 +1 @@
name: Main dish
name: Main dish

View File

@@ -0,0 +1 @@
name: Muffin

View File

@@ -1 +1 @@
name: Raw
name: Raw

View File

@@ -1 +1 @@
name: Salat
name: Salat

View File

@@ -1 +1 @@
name: Salad
name: Salad

View File

@@ -1 +0,0 @@
name: Soße

View File

@@ -1 +0,0 @@
name: Sauce

View File

@@ -0,0 +1 @@
name: Herzhaft

View File

@@ -0,0 +1 @@
name: Savory

View File

@@ -1 +1 @@
name: Aufstrich
name: Aufstrich

View File

@@ -1 +1 @@
name: Spread
name: Spread

View File

@@ -1 +1 @@
name: Süßes
name: Süß

View File

@@ -1 +1 @@
name: Sweet
name: Sweet

View File

@@ -1 +1 @@
name: Weihnachten
name: Weihnachten

View File

@@ -1 +1 @@
name: Xmas
name: Xmas

View File

@@ -1,11 +1,5 @@
[duration]
label = Zeit
day = Tag
days = Tage
hour = Std
hours = Std
min = Min
mins = Min
[yield]
label = Menge
@@ -17,6 +11,9 @@ easy = Einfach
medium = Mittel
hard = Schwer
[ingredients]
recipeLink = ⤳Rezept
[title]
latest = Zuletzt hinzugefügt
all_recipes = Alle Rezepte

View File

@@ -1,11 +1,5 @@
[duration]
label = Time
day = day
days = days
hour = hour
hours = hours
min = minutes
mins = min
[yield]
label = Yield
@@ -17,6 +11,9 @@ easy = Easy
medium = Medium
hard = Hard
[ingredients]
recipeLink = ⤳recipe
[title]
latest = Latest recipes
all_recipes = All recipes

View File

@@ -21,9 +21,8 @@ size = large
[fields.time]
label = Time / Zeit
width = 1/8
type = select
choices = 5, 10, 15, 20, 30, 45, 60, 75, 90, 105, 120, 150, 180, 240, 360, 480, 720, 1440
choice_labels = 5m, 10m, 15m, 20m, 30m, 45m, 1h, 1h 15m, 1h 30m, 1h 45m, 2h, 2h 30m, 3h, 4h, 6h, 8h, 12h, 24h
type = integer
addon_label = min
[fields.difficulty]
label = Difficulty

View File

@@ -12,10 +12,10 @@ enabled = no
[fields.measures]
label = Measures
description = Comma separated list
description = Space separated list
width = 3/5
type = text
default = kg, g, L, dl, cl, ml, oz, lb, pt, qt, cm, tsp, tbsp, c, cup, cups, pkg, pck
default = kg g L dl cl ml oz lb pt qt cm tsp tbsp c cup cups pkg pck
[fields.replace_frac]
label = Replace 1/2 with ½, ⅔, et.c
@@ -26,9 +26,3 @@ type = boolean
label = Replace °C/°F with ℃/℉
width = 1/5
type = boolean
[fields.show_empty_tags]
label = Show empty tags
description = Even if no recipes exist in that category
width = 1/4
type = boolean

View File

@@ -1,9 +1,9 @@
# -*- coding: utf-8 -*-
from lektor.pluginsystem import Plugin
from lektor.pluginsystem import Plugin, get_plugin
from lektor.databags import Databags
from markupsafe import Markup
from datetime import datetime
import unicodedata
import os
import shutil
# -------
# Sorting
@@ -39,20 +39,17 @@ def noUmlaut(text):
return str(text)
def pluralize(n, single, multi):
if n == 0:
return ''
return u'{} {}'.format(n, single if n == 1 else multi)
def replaceFractions(txt):
res = ''
for x in txt.split():
try:
i = ['1/2', '1/3', '2/3', '1/4', '3/4', '1/8', '-'].index(x)
res += [u'½', u'', u'', u'¼', u'¾', u'', u' - '][i]
i = ['1/2', '1/3', '2/3', '1/4', '3/4', '1/8'].index(x)
res += [u'½', u'', u'', u'¼', u'¾', u''][i]
except ValueError:
res += ' ' + x
if x in u'-–—':
res += u' - '
else:
res += ' ' + x
return res.lstrip()
@@ -77,24 +74,6 @@ def updateSet_if(dic, parent, parentkey, value):
dic[key] = set()
dic[key].add(value)
def updateSet_addMultiple(dic, key, others):
try:
dic[key]
except KeyError:
dic[key] = set()
dic[key].update(others)
def findCluster(key, clusterList=[30, 60, 120]):
key = int(key) if key else 0
if key > 0:
for cluster in clusterList:
if key < cluster:
key = cluster
break
return key
# --------------------
# Ingredient splitting
@@ -105,13 +84,14 @@ def splitIngredientLine(line):
indices = [0, len(line)]
for i, char in enumerate(line):
if char.isspace():
capture = False
indices[state] = i
state += 1
if capture:
capture = False
indices[state] = i
state += 1
continue
elif capture:
continue
elif state == 1 and char in '0123456789-.,':
elif state == 1 and char in u'0123456789-–—.,':
state -= 1
elif state > 1:
break
@@ -127,6 +107,9 @@ def parseIngredientLine(line, measureList=[], rep_frac=False):
measure = line[idx[0]:idx[1]].lstrip()
if measure.lower() in measureList:
name = line[idx[1]:].lstrip()
# if name.startswith('of '):
# measure += ' of'
# name = name[3:]
else:
measure = ''
name = line[idx[0]:].lstrip()
@@ -136,19 +119,18 @@ def parseIngredientLine(line, measureList=[], rep_frac=False):
name, note = [x.strip() for x in name_note]
return {'value': val, 'measure': measure, 'name': name, 'note': note}
# --------------------
# Other Helper methods
def groupByMergeCluster(dic, arr=[30, 60, 120], reverse=False):
arr = sorted([int(x) for x in arr])
groups = dict()
for key, recipes in dic:
key = findCluster(key, arr)
if key == 0 and not reverse:
key = ''
updateSet_addMultiple(groups, key, recipes)
return sorted(groups.items(), reverse=bool(reverse))
def replace_atref_urls(text, label=None):
if '@' not in text:
return text
result = list()
for x in text.split():
if x[0] == '@':
x = x[1:]
result.append(u'<a href="{}">{}</a>'.format(x, label or x))
else:
result.append(x)
return Markup(' '.join(result))
# ----------------
# Main entry point
@@ -157,68 +139,56 @@ def groupByMergeCluster(dic, arr=[30, 60, 120], reverse=False):
class HelperPlugin(Plugin):
name = u'Helper'
description = u'Some helper methods, filters, and templates.'
alt = None
availableTags = set()
buildTime = None
settings = dict()
translations = dict()
# -----------
# Event hooks
# -----------
def on_before_build_all(self, builder, **extra):
# display only tags that contain at least one recipe
def processCLI(self, extra_flags):
useCache = bool(extra_flags.get('ENABLE_APPCACHE'))
plugin = get_plugin('force-update', self.env)
if plugin.enabled and not useCache:
plugin.enabled = False
print('AppCache: ' + ('ENABLED' if useCache else 'DISABLED'))
self.env.jinja_env.globals['ENABLE_APPCACHE'] = useCache
def processSettings(self):
bag = Databags(self.env)
pad = self.env.new_pad()
for r in pad.query('recipes'):
self.availableTags.update(r['tags'])
for alt in self.env.load_config().iter_alternatives():
set = pad.get('settings', alt=alt)
self.translations[alt] = bag.lookup('i18n+' + alt)
self.settings[alt] = {
'measures': set['measures'].lower().split(),
'replFrac': set['replace_frac']
}
def on_after_prune(self, builder, **extra):
# redirect to /en/
for file in ['index.html']:
src_f = os.path.join(self.env.root_path, 'root', file)
if os.path.exists(src_f):
dst_f = os.path.join(builder.destination_path, file)
with open(dst_f, 'wb') as df:
with open(src_f, 'rb') as sf:
shutil.copyfileobj(sf, df)
def on_before_build_all(self, builder, **extra):
build_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
print('Build time: ' + build_time)
self.env.jinja_env.globals['DATE_NOW'] = build_time
# update project settings once per build
self.processCLI(getattr(builder, 'extra_flags'))
self.processSettings()
def on_process_template_context(self, context, **extra):
self.alt = context['alt']
# def on_process_template_context(self, context, **extra):
# pass
def on_setup_env(self, **extra):
# self.env.load_config().iter_alternatives()
# pad = self.env.new_pad()
# pad.query('groupby', alt=alt)
def localizeDic(alt, partA, partB=None):
if alt not in self.translations:
raise RuntimeError(
'localize() expects first parameter to be an alternate')
if partB is None:
partA, partB = partA.split('.', 1)
return self.translations[alt][partA][partB]
def localizeDic(key, subkey=None):
bag = Databags(self.env).lookup('i18n+{}.{}'.format(self.alt, key))
return bag[subkey] if subkey else bag
def to_duration(time, cluster=None):
time = int(time) if time else 0
if (time <= 0):
return ''
# Calls itself without cluster argument
if cluster:
cluster = [int(x) for x in cluster]
idx = cluster.index(time)
if idx == 0:
return '<' + to_duration(time)
timeA = to_duration(cluster[idx - 1])
if idx + 1 >= len(cluster):
return '>' + timeA
else:
return u'{} {}'.format(timeA, to_duration(time))
days = time // (60 * 24)
time -= days * (60 * 24)
L = localizeDic('duration')
return ' '.join([
pluralize(days, L['day'], L['days']),
pluralize(time // 60, L['hour'], L['hours']),
pluralize(time % 60, L['min'], L['mins'])]).strip()
def ingredientsForRecipe(recipe):
set = self.env.new_pad().get('settings', alt=self.alt)
meaList = [x.strip() for x in set['measures'].lower().split(',')]
repFrac = set['replace_frac']
def ingredientsForRecipe(recipe, alt='en'):
meaList = self.settings[alt]['measures']
repFrac = self.settings[alt]['replFrac']
for line in recipe['ingredients']:
line = line.strip()
@@ -229,23 +199,21 @@ class HelperPlugin(Plugin):
else:
yield parseIngredientLine(line, meaList, repFrac)
def groupByAttribute(recipeList, attribute):
def groupByAttribute(recipeList, attribute, alt='en'):
groups = dict()
for recipe in recipeList:
if attribute == 'ingredients':
for ing in ingredientsForRecipe(recipe):
for ing in ingredientsForRecipe(recipe, alt):
updateSet_if(groups, ing, 'name', recipe)
else:
updateSet_if(groups, recipe, attribute, recipe)
# groups[undefinedKey].update(groups.pop('_undefined'))
return groups.items()
self.env.jinja_env.filters['duration'] = to_duration
self.env.jinja_env.filters['rating'] = numFillWithText
self.env.jinja_env.filters['replaceFractions'] = replaceFractions
self.env.jinja_env.filters['enumIngredients'] = ingredientsForRecipe
self.env.jinja_env.filters['replaceAtRefURLs'] = replace_atref_urls
self.env.jinja_env.filters['groupByAttribute'] = groupByAttribute
self.env.jinja_env.filters['groupSort'] = groupByDictSort
self.env.jinja_env.filters['groupMergeCluster'] = groupByMergeCluster
self.env.jinja_env.globals['localize'] = localizeDic
self.env.jinja_env.globals['availableTags'] = self.availableTags

5
src/packages/time-duration/.gitignore vendored Normal file
View File

@@ -0,0 +1,5 @@
dist
build
*.pyc
*.pyo
*.egg-info

View File

@@ -0,0 +1,4 @@
# Time Duration
This plugin converts integer numbers to a human readable duration.
E.g. 90 -> 1 hour 30 minutes

View File

@@ -0,0 +1,86 @@
# -*- coding: utf-8 -*-
from lektor.pluginsystem import Plugin
durationLocale = {
'de': {'day': 'Tag', 'hour': 'Std', 'min': 'Min',
'days': 'Tage', 'hours': 'Std', 'mins': 'Min'},
'en': {'day': 'day', 'hour': 'hour', 'min': 'min',
'days': 'days', 'hours': 'hours', 'mins': 'min'}
}
# -----------
# Single Time
def pluralize(n, single, multi):
if n == 0:
return ''
return u'{} {}'.format(n, single if n == 1 else multi)
def to_duration(time, alt='en'):
time = int(time) if time else 0
if (time <= 0):
return ''
days = time // (60 * 24)
time -= days * (60 * 24)
L = durationLocale[alt]
return ' '.join([
pluralize(days, L['day'], L['days']),
pluralize(time // 60, L['hour'], L['hours']),
pluralize(time % 60, L['min'], L['mins'])]).strip()
# ------------
# Time Cluster
def to_time_in_cluster(time, cluster, alt='en'):
for idx, x in enumerate(cluster):
x = int(x)
if x == time:
if idx == 0:
timeB = to_duration(time, alt)
return '<' + timeB
else:
timeA = to_duration(cluster[idx - 1], alt)
timeB = to_duration(time - 1, alt)
return u'{} {}'.format(timeA, timeB)
else:
return '>' + to_duration(cluster[-1], alt)
def find_in_cluster(key, clusterList=[30, 60, 120]):
key = int(key) if key else 0
if key > 0:
for cluster in clusterList:
if key < cluster:
key = cluster
break
else:
key = clusterList[-1] + 1
return key
def group_by_time_cluster(dic, arr=[30, 60, 120], reverse=False):
arr = sorted([int(x) for x in arr])
groups = dict()
for key, recipes in dic:
key = find_in_cluster(key, arr)
if key == 0 and not reverse:
key = ''
try:
groups[key]
except KeyError:
groups[key] = set()
groups[key].update(recipes)
return sorted(groups.items(), reverse=bool(reverse))
class TimeDurationPlugin(Plugin):
name = u'Time Duration'
description = u'Convert int to duration. E.g., 90 -> "1hr 30min".'
def on_setup_env(self, **extra):
self.env.jinja_env.filters['duration'] = to_duration
self.env.jinja_env.filters['durationCluster'] = to_time_in_cluster
self.env.jinja_env.filters['groupTimeCluster'] = group_by_time_cluster

View File

@@ -0,0 +1,38 @@
import ast
import io
import re
from setuptools import setup, find_packages
with io.open('README.md', 'rt', encoding="utf8") as f:
readme = f.read()
_description_re = re.compile(r'description\s+=\s+(?P<description>.*)')
with open('lektor_time_duration.py', 'rb') as f:
description = str(ast.literal_eval(_description_re.search(
f.read().decode('utf-8')).group(1)))
setup(
author=u'relikd',
author_email='oleg@relikd.de',
description=description,
keywords='Lektor plugin',
license='MIT',
long_description=readme,
long_description_content_type='text/markdown',
name='lektor-time-duration',
packages=find_packages(),
py_modules=['lektor_time_duration'],
# url='[link to your repository]',
version='0.1',
classifiers=[
'Framework :: Lektor',
'Environment :: Plugins',
],
entry_points={
'lektor.plugins': [
'time-duration = lektor_time_duration:TimeDurationPlugin',
]
}
)

View File

@@ -0,0 +1,54 @@
CACHE MANIFEST
# Date build: {{ DATE_NOW }}
{%- macro _print_(items) -%}
{%- for item in items -%}
{{ item|replace('../', '', 1) }}
{% endfor -%}
{%- endmacro -%}
{%- macro _add_(list, item) -%}
{{- list.append(item|url) or pass -}}
{%- endmacro -%}
{%- set root = site.get('/', this.alt) -%}
{%- set assetList = [] -%}
{%- for asset in root.pad.asset_root.children recursive -%}
{%- if asset.__class__.__name__ != 'Directory' -%}
{{- _add_(assetList, asset) -}}
{%- endif -%}
{{- loop(asset.children) -}}
{%- endfor -%}
{%- set cacheList = [] -%}
{{- _add_(cacheList, root) -}}
{%- for x in root.children if x != this recursive -%}
{{- _add_(cacheList, x) -}}
{%- set pg = x.datamodel.pagination_config -%}
{%- if pg.enabled -%}
{%- for page in range(2, pg.count_pages(x) + 1) -%}
{{- _add_(cacheList, pg.get_record_for_page(x, page)) -}}
{%- endfor -%}
{%- endif -%}
{% set img = x.attachments.images|sort(attribute='record_label')|first -%}
{%- if img -%}
{{- _add_(cacheList, img.thumbnail(200, 150, 'crop')) -}}
{%- endif -%}
{%- if x.datamodel.has_own_children -%}
{{- loop(x.children) -}}
{%- endif -%}
{%- endfor -%}
{#- Generate cache file index #}
# static
{{ _print_(assetList) -}}
# index
{{ _print_(cacheList) -}}
{#- All other requests are forwarded #}
NETWORK:
*

View File

@@ -8,10 +8,10 @@
{%- set sortType = this.xdata + [''] -%}
{%- endif -%}
{%- set all = site.query('/recipes', this.alt) | groupByAttribute(this.group_key) | groupSort(sortType, this.reverse_order) -%}
{%- set all = site.query('/recipes', this.alt) | groupByAttribute(this.group_key, this.alt) | groupSort(sortType, this.reverse_order) -%}
{%- if this.group_key == 'time' -%}
{%- set all = all | groupMergeCluster(this.xdata, this.reverse_order) -%}
{%- set all = all | groupTimeCluster(this.xdata, this.reverse_order) -%}
{%- endif -%}
<h1>{{ this.name }}</h1>
@@ -22,9 +22,9 @@
{%- elif not attrib -%}
{{ this.null_fallback }}
{%- elif this.group_key == 'time' -%}
{{ attrib | duration(this.xdata) }}
{{ attrib | durationCluster(this.xdata, this.alt) }}
{%- elif this.group_key == 'difficulty' -%}
{{ localize('difficulty', attrib) }}
{{ localize(this.alt, 'difficulty', attrib) }}
{%- else -%}
{{ attrib }}
{%- endif -%}</dt>

View File

@@ -1,31 +1,39 @@
<!doctype html>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=0.75">
<script type="text/javascript" src="{{ '/static/col2.js'|url }}"></script>
<script type="text/javascript" src="{{ '/static/lozad.min.js'|url }}"></script>
<link rel="stylesheet" href="{{ '/static/style.css'|url }}">
<title>{% block title %}Welcome{% endblock %} · recipe lekture</title>
<body>
{%- if ENABLE_APPCACHE %}
<html manifest="{{ site.get('app.appcache', alt=this.alt)|url }}">
{% endif -%}
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=0.75">
<script type="text/javascript" src="{{ '/static/script.js'|url }}"></script>
<link rel="stylesheet" href="{{ '/static/style.css'|url }}">
<link rel="icon" sizes="32x32" href="{{ '/img/icon-32.png'|url }}">
<link rel="icon" sizes="196x196" href="{{ '/img/icon-196.png'|url }}">
<link rel="apple-touch-icon" sizes="180x180" href="{{ '/img/icon-180.png'|url }}">
<link rel="manifest" href="{{ '/app.webmanifest'|asseturl }}">
<title>{% block title %}Welcome{% endblock %} · recipe lekture</title>
</head>
<body> {#- ontouchstart="" #}
<header>
<a id="logo" href="{{ site.get('/', alt=this.alt)|url }}">recipe lekture</a>
{%- if ENABLE_APPCACHE %}
<i id="cache-status" title="cache status"></i>
{%- endif %}
<nav>
<ul>
{%- set allRecipes = site.get('recipes', this.alt) %}
<li><a {% if this == allRecipes %}class="active"{% endif %} href="{{ allRecipes|url }}">{{ localize('title.all_recipes') }}</a></li>
<li><a {% if this == allRecipes %}class="active"{% endif %} href="{{ allRecipes|url }}">{{ localize(this.alt, 'title.all_recipes') }}</a></li>
{%- for navpage in site.query('/groupby', this.alt) %}
<li><a {% if this.is_child_of(navpage) %}class="active"{% endif %} href="{{ navpage|url }}">{{ navpage.name }}</a></li>
{%- endfor %}
</ul>
</nav>
<div class="tags small">
{%- set allowEmptyTags = site.get('settings', alt=this.alt)['show_empty_tags'] -%}
{%- for tag in site.query('/tags', this.alt) %}
{%- if allowEmptyTags or tag._id in availableTags -%}
<a {%
if this.is_child_of(tag) or (this.tags and tag._id in this.tags) -%}
class="active"
{%- endif %} href="{{ tag|url }}">{{ tag.name }}</a>
{%- endif -%}
{%- endfor %}
</div>
</header>

View File

@@ -5,13 +5,24 @@
{%- set img = recipe.attachments.images|sort(attribute='record_label')|first -%}
<a href="{{ recipe|url }}">{#--#}
<div class="recipe-tile">
<div class="img-placeholder">
{%- if img -%}
<img class="lozad" width="200" height="150" data-src="{{ img.thumbnail(200, 150)|url }}" />
{%- else -%}
No Image
{%- endif -%}
{#- overlay on hover and always-visible icons #}
<div class="overlay">
<div class="hover"><div class="time">{{ recipe.time|duration(recipe.alt) }}</div></div>
<div class="icon-bar">
{%- if 'raw' in recipe.tags -%}<i class="icon raw"></i>{%- endif -%}
{%- if 'glutenfree' in recipe.tags -%}<i class="icon gf"></i>{%- endif -%}
</div>
</div>
{#- show image or placeholder text #}
{% if img -%}
<img class="lozad" data-src="{{ img.thumbnail(200, 150, 'crop')|url }}">
{%- else -%}
<div class="placeholder">No Image</div>
{%- endif -%}
{#- recipe title #}
<p>{{ recipe.name }}</p>
</div>{#--#}
</a>

View File

@@ -2,37 +2,42 @@
{% block title %}{{ this.name }}{% endblock %}
{% block body %}
<article class="recipe">
<!-- date added: {{ this.date }} -->
<section id="img-carousel" class="v-scroll center">
{%- for img in this.attachments.images|sort(attribute='record_label') %}
<img src="{{ img|url }}" height="400px">
<img class="lozad" data-src="{{ img|url }}" height="400">
{%- endfor %}
</section>
{% if this.source -%}
<div id="source" class="small center">
<a href="{{ this.source }}">⤳ {{ this.source.host }}</a>
{%- if this.source.host -%}
<a href="{{ this.source }}">⤳ {{ this.source.host }}</a>
{%- else -%}
⤳ {{ this.source }}
{%- endif -%}
</div>
{% endif %}
<h1>{{ this.name }}</h1>
<section id="metrics" class="small">
<div id="rating" class="xlarge">{{ this.rating|rating }}</div>
<div class="difficulty {{this.difficulty}}">
<div></div><div></div><div></div>
{%- if this.difficulty %}
<span>{{ localize('difficulty', this.difficulty) }}</span>
<span>{{ localize(this.alt, 'difficulty', this.difficulty) }}</span>
{%- else %}
<span class="small">{{ localize('difficulty._unset') }}</span>
<span class="small">{{ localize(this.alt, 'difficulty._unset') }}</span>
{%- endif %}
</div>
<div>{{ localize('duration.label') }}: {{ this.time|duration if this.time else '—' }}</div>
<div>{{ localize('yield.label') }}: {{ this.yield if this.yield else '—' }}</div>
<div>{{ localize(this.alt, 'duration.label') }}: {{ this.time|duration(this.alt) if this.time else '—' }}</div>
<div>{{ localize(this.alt, 'yield.label') }}: {{ this.yield or '—' }}</div>
</section>
<section id="ingredients">
<h2>{{ localize('title.ingredients') }}:</h2>
<h2>{{ localize(this.alt, 'title.ingredients') }}:</h2>
<ul class="no-bullets li-lg-space">
{%- for ing in this|enumIngredients %}
{%- for ing in this|enumIngredients(this.alt) %}
{%- if ing['group'] %}
<li class="dark-red bold mrgTopMd">{{ ing['group'] }}</li>
{%- else %}
@@ -41,7 +46,7 @@
{%- if ing['measure'] %}{{ ing['measure'] }} {% endif -%}
<span class="light-red">{{ ing['name'] }}</span>
{%- if ing['note'] -%}
<span class="small italic">{{ ', ' ~ ing['note'] }}</span>
<span class="small italic">{{ ', ' ~ ing['note'] | replaceAtRefURLs(label=localize(this.alt, 'ingredients.recipeLink')) }}</span>
{%- endif -%}
</li>
{%- endif %}
@@ -50,9 +55,9 @@
</section>
<section id="directions">
<h2>{{ localize('title.directions') }}:</h2>
<h2>{{ localize(this.alt, 'title.directions') }}:</h2>
{% if site.get('settings', alt=this.alt)['replace_temp'] -%}
{{ this.directions|string|replace('°C', '℃')|replace('°F', '℉')|markdown }}
{{ this.directions.html|replace('°C', '℃')|replace('°F', '℉') }}
{% else -%}
{{ this.directions }}
{% endif -%}

View File

@@ -1,9 +1,9 @@
{% extends "layout.html" %}
{% from "macros/recipes.html" import render_recipe_list %}
{% from "macros/pagination.html" import render_pagination_all %}
{% block title %}{{ localize('title.recipes') }}{% endblock %}
{% block title %}{{ localize(this.alt, 'title.recipes') }}{% endblock %}
{% block body %}
<h1>{{ localize('title.recipes') }}</h1>
<h1>{{ localize(this.alt, 'title.recipes') }}</h1>
{{ render_recipe_list(this.pagination.items) }}
{{ render_pagination_all(this.pagination) }}
{% endblock %}

View File

@@ -1,7 +1,7 @@
{% extends "layout.html" %}
{% from "macros/recipes.html" import render_recipe_list %}
{% block body %}
<h1>{{ localize('title.latest') }}</h1>
<h1>{{ localize(this.alt, 'title.latest') }}</h1>
<div class="latest">
{{ render_recipe_list(site.query('recipes', this.alt) | sort(attribute='date', reverse=True), limit=6) }}
</div>