Initial
This commit is contained in:
32
.gitignore
vendored
Normal file
32
.gitignore
vendored
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
# Created by https://www.gitignore.io/api/macos
|
||||||
|
# Edit at https://www.gitignore.io/?templates=macos
|
||||||
|
|
||||||
|
### macOS ###
|
||||||
|
# General
|
||||||
|
.DS_Store
|
||||||
|
.AppleDouble
|
||||||
|
.LSOverride
|
||||||
|
|
||||||
|
# Icon must end with two \r
|
||||||
|
Icon
|
||||||
|
|
||||||
|
# Thumbnails
|
||||||
|
._*
|
||||||
|
|
||||||
|
# Files that might appear in the root of a volume
|
||||||
|
.DocumentRevisions-V100
|
||||||
|
.fseventsd
|
||||||
|
.Spotlight-V100
|
||||||
|
.TemporaryItems
|
||||||
|
.Trashes
|
||||||
|
.VolumeIcon.icns
|
||||||
|
.com.apple.timemachine.donotpresent
|
||||||
|
|
||||||
|
# Directories potentially created on remote AFP share
|
||||||
|
.AppleDB
|
||||||
|
.AppleDesktop
|
||||||
|
Network Trash Folder
|
||||||
|
Temporary Items
|
||||||
|
.apdisk
|
||||||
|
|
||||||
|
# End of https://www.gitignore.io/api/macos
|
||||||
7
LICENSE
Normal file
7
LICENSE
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
Copyright 2019 Oleg Geier
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
55
README.md
Normal file
55
README.md
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
Lektor recipes
|
||||||
|
==============
|
||||||
|
|
||||||
|
Generating a static site for recipes.
|
||||||
|
Small, fast, multi-language, indexed.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Styling is optimized for desktop, mobile, and print output.
|
||||||
|
At some point I may add search filters and offline archives for mobile devices.
|
||||||
|
|
||||||
|
This project is built upon [Lektor](https://github.com/lektor/lektor/).
|
||||||
|
|
||||||
|
|
||||||
|
Install
|
||||||
|
-------
|
||||||
|
|
||||||
|
1. [Download](https://www.getlektor.com/) Lektor and follow the instructions.
|
||||||
|
|
||||||
|
2. Clone this repository and change to the `src` directory.
|
||||||
|
|
||||||
|
3. Run `lektor server` to run a local server and preview the page. **Note:** Open http://127.0.0.1:5000/en/ instead of the default `/` path.\**
|
||||||
|
|
||||||
|
|
||||||
|
### Deploy
|
||||||
|
|
||||||
|
You need to add a deployment setting to the project file.
|
||||||
|
Either apply something from the [official docs](https://www.getlektor.com/docs/deployment/),
|
||||||
|
or run a custom rsync command:
|
||||||
|
|
||||||
|
```
|
||||||
|
rsync -rclzv --delete --exclude=.* SRC DST
|
||||||
|
```
|
||||||
|
|
||||||
|
\** You don't have to worry about the redirect.
|
||||||
|
The `root/index.html` is copied to the destination.
|
||||||
|
Instead, you could also delete `root/` and change the project file.
|
||||||
|
Set `url_prefix` to `/` for one of the alternates.
|
||||||
|
|
||||||
|
|
||||||
|
### Modify
|
||||||
|
|
||||||
|
Thanks to Lektor you have a simple content management system (see screenshot below).
|
||||||
|
Two things to note:
|
||||||
|
|
||||||
|
1. Measurements have to be added manually to settings. Don't forget to __pluralize__ (c, cup, cups, etc.)
|
||||||
|
|
||||||
|
2. You can __group ingredients__ if the line ends with a colon (`:`)
|
||||||
|
|
||||||
|
Also, see [Lektor docs](https://www.getlektor.com/docs/) and [jinja2 template](https://jinja.palletsprojects.com/en/2.10.x/templates/) documentation.
|
||||||
|
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
10
src/assets/static/col2.js
Normal file
10
src/assets/static/col2.js
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
(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);
|
||||||
|
}
|
||||||
|
})();
|
||||||
9
src/assets/static/lozad.min.js
vendored
Normal file
9
src/assets/static/lozad.min.js
vendored
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
/*! 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}}});
|
||||||
153
src/assets/static/style.css
Normal file
153
src/assets/static/style.css
Normal file
@@ -0,0 +1,153 @@
|
|||||||
|
:root { --cTxt: #222;
|
||||||
|
--cBg1: #FAF9F7; --cBg2: #EAE9E7; --cBg3: #9A9997;
|
||||||
|
--cRed1: #DC3A59; --cRed2: #AA203A; --cRed3: #EE6A84;
|
||||||
|
}
|
||||||
|
|
||||||
|
.center { text-align: center }
|
||||||
|
.small { font-size: 0.8em }
|
||||||
|
.large { font-size: 1.3em }
|
||||||
|
.xlarge { font-size: 1.6em }
|
||||||
|
.italic { font-style: italic }
|
||||||
|
.bold { font-weight: bold }
|
||||||
|
.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 }
|
||||||
|
|
||||||
|
ul.no-bullets { padding: unset; margin: unset; list-style: none }
|
||||||
|
ul.li-lg-space li { padding-bottom: 0.3em }
|
||||||
|
|
||||||
|
/*
|
||||||
|
* General
|
||||||
|
*/
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
header #logo { font-size: 42px; display: block; margin-bottom: 15px }
|
||||||
|
header a { color: var(--cTxt) }
|
||||||
|
header, h1 { text-align: center }
|
||||||
|
header, footer, .page {
|
||||||
|
background: var(--cBg2);
|
||||||
|
max-width: 1060px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 20px 30px;
|
||||||
|
}
|
||||||
|
.page { background: var(--cBg1) }
|
||||||
|
nav ul { padding: unset }
|
||||||
|
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 print {
|
||||||
|
header, footer { display: none }
|
||||||
|
body, .page { background-color: #FFF }
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Components
|
||||||
|
*/
|
||||||
|
.tags { display: flex; flex-wrap: wrap; justify-content: center }
|
||||||
|
.tags > * {
|
||||||
|
background-color: #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;
|
||||||
|
}
|
||||||
|
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) {
|
||||||
|
.cluster dd { margin-left: 0 }
|
||||||
|
.cluster dd a { white-space: unset }
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Grid overview
|
||||||
|
*/
|
||||||
|
.pagination { text-align: center; margin-top: 1em; }
|
||||||
|
.recipe-tile {
|
||||||
|
background-color: 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;
|
||||||
|
font: bold 25px/150px 'Courier New', monospace;
|
||||||
|
}
|
||||||
|
a:hover .recipe-tile img { mix-blend-mode: overlay }
|
||||||
|
.recipe-tile p { height: 2.6em; margin: 0.3em 10px; overflow-y: auto }
|
||||||
|
.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); */
|
||||||
|
@media screen and (max-width: 1120px) { .tile-grid { max-width: 848px } }
|
||||||
|
@media screen and (max-width: 908px) { .tile-grid { max-width: 636px } }
|
||||||
|
@media screen and (max-width: 696px) { .tile-grid { max-width: 424px !important } }
|
||||||
|
@media screen and (max-width: 484px) { .tile-grid { max-width: 212px !important } }
|
||||||
|
|
||||||
|
@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 }
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Individual recipe
|
||||||
|
*/
|
||||||
|
.recipe h2 { font-size: 0.8em; margin: 0 0 1em 0 }
|
||||||
|
.recipe #img-carousel { padding: 0 50px; margin: 0 -30px }
|
||||||
|
.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 #directions ul { list-style-type: circle }
|
||||||
|
/* Colored, 3-part, difficulty bar */
|
||||||
|
.difficulty.easy > div:nth-child(1) { background-color: #3C3 }
|
||||||
|
.difficulty.medium > div:nth-child(1),
|
||||||
|
.difficulty.medium > div:nth-child(2) { background-color: #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 > div:nth-child(1) { border-radius: 50% 0 0 50% }
|
||||||
|
.difficulty > div:nth-child(3) { border-radius: 0 50% 50% 0 }
|
||||||
|
.difficulty > div {
|
||||||
|
display: inline-block;
|
||||||
|
width: 1em; height: 1em;
|
||||||
|
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(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 and (orientation: portrait) {
|
||||||
|
#img-carousel img:not(:first-child) { display: none }
|
||||||
|
.recipe #metrics { float: unset; padding-bottom: 1em }
|
||||||
|
}
|
||||||
1
src/content/contents.lr
Normal file
1
src/content/contents.lr
Normal file
@@ -0,0 +1 @@
|
|||||||
|
_model: root
|
||||||
5
src/content/groupby/contents.lr
Normal file
5
src/content/groupby/contents.lr
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
_model: clusters
|
||||||
|
---
|
||||||
|
_template: querylist.html
|
||||||
|
---
|
||||||
|
_slug: recipes/by
|
||||||
1
src/content/groupby/difficulty/contents+de.lr
Normal file
1
src/content/groupby/difficulty/contents+de.lr
Normal file
@@ -0,0 +1 @@
|
|||||||
|
name: Aufwand
|
||||||
1
src/content/groupby/difficulty/contents+en.lr
Normal file
1
src/content/groupby/difficulty/contents+en.lr
Normal file
@@ -0,0 +1 @@
|
|||||||
|
name: Difficulty
|
||||||
9
src/content/groupby/difficulty/contents.lr
Normal file
9
src/content/groupby/difficulty/contents.lr
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
sort_key: 10
|
||||||
|
---
|
||||||
|
group_key: difficulty
|
||||||
|
---
|
||||||
|
xdata:
|
||||||
|
|
||||||
|
easy
|
||||||
|
medium
|
||||||
|
hard
|
||||||
1
src/content/groupby/ingredient/contents+de.lr
Normal file
1
src/content/groupby/ingredient/contents+de.lr
Normal file
@@ -0,0 +1 @@
|
|||||||
|
name: Zutaten
|
||||||
1
src/content/groupby/ingredient/contents+en.lr
Normal file
1
src/content/groupby/ingredient/contents+en.lr
Normal file
@@ -0,0 +1 @@
|
|||||||
|
name: Ingredients
|
||||||
3
src/content/groupby/ingredient/contents.lr
Normal file
3
src/content/groupby/ingredient/contents.lr
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
sort_key: 5
|
||||||
|
---
|
||||||
|
group_key: ingredients
|
||||||
1
src/content/groupby/rating/contents+de.lr
Normal file
1
src/content/groupby/rating/contents+de.lr
Normal file
@@ -0,0 +1 @@
|
|||||||
|
name: Bewertung
|
||||||
1
src/content/groupby/rating/contents+en.lr
Normal file
1
src/content/groupby/rating/contents+en.lr
Normal file
@@ -0,0 +1 @@
|
|||||||
|
name: Rating
|
||||||
5
src/content/groupby/rating/contents.lr
Normal file
5
src/content/groupby/rating/contents.lr
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
sort_key: 15
|
||||||
|
---
|
||||||
|
group_key: rating
|
||||||
|
---
|
||||||
|
reverse_order: yes
|
||||||
1
src/content/groupby/time/contents+de.lr
Normal file
1
src/content/groupby/time/contents+de.lr
Normal file
@@ -0,0 +1 @@
|
|||||||
|
name: Zeit
|
||||||
1
src/content/groupby/time/contents+en.lr
Normal file
1
src/content/groupby/time/contents+en.lr
Normal file
@@ -0,0 +1 @@
|
|||||||
|
name: Time
|
||||||
13
src/content/groupby/time/contents.lr
Normal file
13
src/content/groupby/time/contents.lr
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
sort_key: 20
|
||||||
|
---
|
||||||
|
group_key: time
|
||||||
|
---
|
||||||
|
xdata:
|
||||||
|
|
||||||
|
15
|
||||||
|
30
|
||||||
|
60
|
||||||
|
120
|
||||||
|
180
|
||||||
|
360
|
||||||
|
9999
|
||||||
1
src/content/recipes/contents.lr
Normal file
1
src/content/recipes/contents.lr
Normal file
@@ -0,0 +1 @@
|
|||||||
|
_model: recipes
|
||||||
16
src/content/recipes/vanilla-cut-out-cookies/contents+de.lr
Normal file
16
src/content/recipes/vanilla-cut-out-cookies/contents+de.lr
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
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.
|
||||||
26
src/content/recipes/vanilla-cut-out-cookies/contents+en.lr
Normal file
26
src/content/recipes/vanilla-cut-out-cookies/contents+en.lr
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
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 it’s 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 it’s a little sticky, add a little more flour (try 1-2 tbsp); if it’s 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 won’t 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 don’t 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
|
||||||
11
src/content/recipes/vanilla-cut-out-cookies/contents.lr
Normal file
11
src/content/recipes/vanilla-cut-out-cookies/contents.lr
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
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
|
||||||
BIN
src/content/recipes/vanilla-cut-out-cookies/image.jpg
Normal file
BIN
src/content/recipes/vanilla-cut-out-cookies/image.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 58 KiB |
BIN
src/content/recipes/vanilla-cut-out-cookies/image2.jpg
Normal file
BIN
src/content/recipes/vanilla-cut-out-cookies/image2.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 70 KiB |
BIN
src/content/recipes/vanilla-cut-out-cookies/image3.jpg
Normal file
BIN
src/content/recipes/vanilla-cut-out-cookies/image3.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 41 KiB |
1
src/content/settings/contents+de.lr
Normal file
1
src/content/settings/contents+de.lr
Normal file
@@ -0,0 +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
|
||||||
1
src/content/settings/contents+en.lr
Normal file
1
src/content/settings/contents+en.lr
Normal file
@@ -0,0 +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
|
||||||
9
src/content/settings/contents.lr
Normal file
9
src/content/settings/contents.lr
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
_model: settings
|
||||||
|
---
|
||||||
|
_hidden: yes
|
||||||
|
---
|
||||||
|
replace_frac: yes
|
||||||
|
---
|
||||||
|
replace_temp: yes
|
||||||
|
---
|
||||||
|
show_empty_tags: no
|
||||||
1
src/content/tags/bread/contents+de.lr
Normal file
1
src/content/tags/bread/contents+de.lr
Normal file
@@ -0,0 +1 @@
|
|||||||
|
name: Brot
|
||||||
1
src/content/tags/bread/contents.lr
Normal file
1
src/content/tags/bread/contents.lr
Normal file
@@ -0,0 +1 @@
|
|||||||
|
name: Bread
|
||||||
1
src/content/tags/cake/contents+de.lr
Normal file
1
src/content/tags/cake/contents+de.lr
Normal file
@@ -0,0 +1 @@
|
|||||||
|
name: Kuchen
|
||||||
1
src/content/tags/cake/contents.lr
Normal file
1
src/content/tags/cake/contents.lr
Normal file
@@ -0,0 +1 @@
|
|||||||
|
name: Cake
|
||||||
1
src/content/tags/chocolate/contents+de.lr
Normal file
1
src/content/tags/chocolate/contents+de.lr
Normal file
@@ -0,0 +1 @@
|
|||||||
|
name: Schokolade
|
||||||
1
src/content/tags/chocolate/contents.lr
Normal file
1
src/content/tags/chocolate/contents.lr
Normal file
@@ -0,0 +1 @@
|
|||||||
|
name: Chocolate
|
||||||
5
src/content/tags/contents.lr
Normal file
5
src/content/tags/contents.lr
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
_model: tags
|
||||||
|
---
|
||||||
|
_slug: recipes/tags
|
||||||
|
---
|
||||||
|
_template: querylist.html
|
||||||
1
src/content/tags/cookies/contents+de.lr
Normal file
1
src/content/tags/cookies/contents+de.lr
Normal file
@@ -0,0 +1 @@
|
|||||||
|
name: Kekse
|
||||||
1
src/content/tags/cookies/contents.lr
Normal file
1
src/content/tags/cookies/contents.lr
Normal file
@@ -0,0 +1 @@
|
|||||||
|
name: Cookies
|
||||||
1
src/content/tags/dip/contents.lr
Normal file
1
src/content/tags/dip/contents.lr
Normal file
@@ -0,0 +1 @@
|
|||||||
|
name: Dip
|
||||||
1
src/content/tags/dressing/contents.lr
Normal file
1
src/content/tags/dressing/contents.lr
Normal file
@@ -0,0 +1 @@
|
|||||||
|
name: Dressing
|
||||||
1
src/content/tags/drinks/contents.lr
Normal file
1
src/content/tags/drinks/contents.lr
Normal file
@@ -0,0 +1 @@
|
|||||||
|
name: Drinks
|
||||||
1
src/content/tags/glutenfree/contents+de.lr
Normal file
1
src/content/tags/glutenfree/contents+de.lr
Normal file
@@ -0,0 +1 @@
|
|||||||
|
name: Glutenfrei
|
||||||
1
src/content/tags/glutenfree/contents.lr
Normal file
1
src/content/tags/glutenfree/contents.lr
Normal file
@@ -0,0 +1 @@
|
|||||||
|
name: Glutenfree
|
||||||
1
src/content/tags/ingredient/contents+de.lr
Normal file
1
src/content/tags/ingredient/contents+de.lr
Normal file
@@ -0,0 +1 @@
|
|||||||
|
name: Zutat
|
||||||
1
src/content/tags/ingredient/contents.lr
Normal file
1
src/content/tags/ingredient/contents.lr
Normal file
@@ -0,0 +1 @@
|
|||||||
|
name: Ingredient
|
||||||
1
src/content/tags/main-dish/contents+de.lr
Normal file
1
src/content/tags/main-dish/contents+de.lr
Normal file
@@ -0,0 +1 @@
|
|||||||
|
name: Hauptspeise
|
||||||
1
src/content/tags/main-dish/contents.lr
Normal file
1
src/content/tags/main-dish/contents.lr
Normal file
@@ -0,0 +1 @@
|
|||||||
|
name: Main dish
|
||||||
1
src/content/tags/raw/contents.lr
Normal file
1
src/content/tags/raw/contents.lr
Normal file
@@ -0,0 +1 @@
|
|||||||
|
name: Raw
|
||||||
1
src/content/tags/salad/contents+de.lr
Normal file
1
src/content/tags/salad/contents+de.lr
Normal file
@@ -0,0 +1 @@
|
|||||||
|
name: Salat
|
||||||
1
src/content/tags/salad/contents.lr
Normal file
1
src/content/tags/salad/contents.lr
Normal file
@@ -0,0 +1 @@
|
|||||||
|
name: Salad
|
||||||
1
src/content/tags/sauce/contents+de.lr
Normal file
1
src/content/tags/sauce/contents+de.lr
Normal file
@@ -0,0 +1 @@
|
|||||||
|
name: Soße
|
||||||
1
src/content/tags/sauce/contents.lr
Normal file
1
src/content/tags/sauce/contents.lr
Normal file
@@ -0,0 +1 @@
|
|||||||
|
name: Sauce
|
||||||
1
src/content/tags/spread/contents+de.lr
Normal file
1
src/content/tags/spread/contents+de.lr
Normal file
@@ -0,0 +1 @@
|
|||||||
|
name: Aufstrich
|
||||||
1
src/content/tags/spread/contents.lr
Normal file
1
src/content/tags/spread/contents.lr
Normal file
@@ -0,0 +1 @@
|
|||||||
|
name: Spread
|
||||||
1
src/content/tags/sweet/contents+de.lr
Normal file
1
src/content/tags/sweet/contents+de.lr
Normal file
@@ -0,0 +1 @@
|
|||||||
|
name: Süßes
|
||||||
1
src/content/tags/sweet/contents.lr
Normal file
1
src/content/tags/sweet/contents.lr
Normal file
@@ -0,0 +1 @@
|
|||||||
|
name: Sweet
|
||||||
1
src/content/tags/xmas/contents+de.lr
Normal file
1
src/content/tags/xmas/contents+de.lr
Normal file
@@ -0,0 +1 @@
|
|||||||
|
name: Weihnachten
|
||||||
1
src/content/tags/xmas/contents.lr
Normal file
1
src/content/tags/xmas/contents.lr
Normal file
@@ -0,0 +1 @@
|
|||||||
|
name: Xmas
|
||||||
25
src/databags/i18n+de.ini
Normal file
25
src/databags/i18n+de.ini
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
[duration]
|
||||||
|
label = Zeit
|
||||||
|
day = Tag
|
||||||
|
days = Tage
|
||||||
|
hour = Std
|
||||||
|
hours = Std
|
||||||
|
min = Min
|
||||||
|
mins = Min
|
||||||
|
|
||||||
|
[yield]
|
||||||
|
label = Menge
|
||||||
|
|
||||||
|
[difficulty]
|
||||||
|
label = Aufwand
|
||||||
|
_unset = Aufwand unklar
|
||||||
|
easy = Einfach
|
||||||
|
medium = Mittel
|
||||||
|
hard = Schwer
|
||||||
|
|
||||||
|
[title]
|
||||||
|
latest = Zuletzt hinzugefügt
|
||||||
|
all_recipes = Alle Rezepte
|
||||||
|
recipes = Rezepte
|
||||||
|
ingredients = Zutaten
|
||||||
|
directions = Zubereitung
|
||||||
25
src/databags/i18n+en.ini
Normal file
25
src/databags/i18n+en.ini
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
[duration]
|
||||||
|
label = Time
|
||||||
|
day = day
|
||||||
|
days = days
|
||||||
|
hour = hour
|
||||||
|
hours = hours
|
||||||
|
min = minutes
|
||||||
|
mins = min
|
||||||
|
|
||||||
|
[yield]
|
||||||
|
label = Yield
|
||||||
|
|
||||||
|
[difficulty]
|
||||||
|
label = Difficulty
|
||||||
|
_unset = Difficulty not set
|
||||||
|
easy = Easy
|
||||||
|
medium = Medium
|
||||||
|
hard = Hard
|
||||||
|
|
||||||
|
[title]
|
||||||
|
latest = Latest recipes
|
||||||
|
all_recipes = All recipes
|
||||||
|
recipes = Recipes
|
||||||
|
ingredients = Ingredients
|
||||||
|
directions = Directions
|
||||||
44
src/models/cluster.ini
Normal file
44
src/models/cluster.ini
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
[model]
|
||||||
|
name = Cluster
|
||||||
|
label = {{ this.name }}
|
||||||
|
hidden = yes
|
||||||
|
protected = yes
|
||||||
|
|
||||||
|
[children]
|
||||||
|
enabled = no
|
||||||
|
|
||||||
|
[attachments]
|
||||||
|
enabled = no
|
||||||
|
|
||||||
|
[fields.name]
|
||||||
|
label = Name
|
||||||
|
width = 1/2
|
||||||
|
type = string
|
||||||
|
|
||||||
|
[fields.group_key]
|
||||||
|
label = Grouping Attribute
|
||||||
|
width = 1/3
|
||||||
|
type = string
|
||||||
|
|
||||||
|
[fields.sort_key]
|
||||||
|
label = Sort order
|
||||||
|
width = 1/5
|
||||||
|
default = 0
|
||||||
|
type = sort_key
|
||||||
|
|
||||||
|
[fields.null_fallback]
|
||||||
|
label = Null Fallback
|
||||||
|
width = 1/3
|
||||||
|
type = string
|
||||||
|
default = ???
|
||||||
|
|
||||||
|
[fields.reverse_order]
|
||||||
|
label = Reverse Sort
|
||||||
|
width = 1/5
|
||||||
|
type = boolean
|
||||||
|
|
||||||
|
[fields.xdata]
|
||||||
|
label = Extended Data
|
||||||
|
description = Used for ordinal sort order or merging integer cluster
|
||||||
|
width = 1/2
|
||||||
|
type = strings
|
||||||
12
src/models/clusters.ini
Normal file
12
src/models/clusters.ini
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
[model]
|
||||||
|
name = Cluster
|
||||||
|
label = Cluster
|
||||||
|
hidden = yes
|
||||||
|
protected = yes
|
||||||
|
|
||||||
|
[children]
|
||||||
|
model = cluster
|
||||||
|
order_by = sort_key
|
||||||
|
|
||||||
|
[attachments]
|
||||||
|
enabled = no
|
||||||
67
src/models/recipe.ini
Normal file
67
src/models/recipe.ini
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
[model]
|
||||||
|
name = Recipe
|
||||||
|
label = {{ this._id }}
|
||||||
|
hidden = yes
|
||||||
|
|
||||||
|
[children]
|
||||||
|
enabled = no
|
||||||
|
|
||||||
|
[fields.name]
|
||||||
|
label = Name
|
||||||
|
width = 2/3
|
||||||
|
type = string
|
||||||
|
size = large
|
||||||
|
|
||||||
|
[fields.date]
|
||||||
|
label = Date / Datum
|
||||||
|
width = 1/3
|
||||||
|
type = date
|
||||||
|
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
|
||||||
|
|
||||||
|
[fields.difficulty]
|
||||||
|
label = Difficulty
|
||||||
|
width = 1/8
|
||||||
|
type = select
|
||||||
|
choices = easy, medium, hard
|
||||||
|
choice_labels = Easy, Medium, Hard
|
||||||
|
|
||||||
|
[fields.rating]
|
||||||
|
label = Rating
|
||||||
|
width = 1/8
|
||||||
|
type = select
|
||||||
|
choices = 1, 2, 3, 4, 5
|
||||||
|
choice_labels = ★☆☆☆☆, ★★☆☆☆, ★★★☆☆, ★★★★☆, ★★★★★
|
||||||
|
|
||||||
|
[fields.yield]
|
||||||
|
label = Yield / Menge
|
||||||
|
width = 1/2
|
||||||
|
type = string
|
||||||
|
|
||||||
|
[fields.ingredients]
|
||||||
|
label = Ingredients / Zutaten
|
||||||
|
description = 42 g Ingredient, Notes (add additional measures in settings)
|
||||||
|
width = 2/3
|
||||||
|
type = strings
|
||||||
|
|
||||||
|
[fields.tags]
|
||||||
|
label = Tags / Kategorie
|
||||||
|
width = 1/3
|
||||||
|
type = checkboxes
|
||||||
|
source = site.query('/tags', alt)
|
||||||
|
|
||||||
|
[fields.directions]
|
||||||
|
label = Directions / Zubereitung
|
||||||
|
description = Markdown formatting applies: ### Header, __bold__, _italic_
|
||||||
|
type = markdown
|
||||||
|
|
||||||
|
[fields.source]
|
||||||
|
label = Source / Quelle
|
||||||
|
type = url
|
||||||
|
size = small
|
||||||
16
src/models/recipes.ini
Normal file
16
src/models/recipes.ini
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
[model]
|
||||||
|
name = Recipes
|
||||||
|
label = Recipes
|
||||||
|
hidden = yes
|
||||||
|
protected = yes
|
||||||
|
|
||||||
|
[attachments]
|
||||||
|
enabled = no
|
||||||
|
|
||||||
|
[children]
|
||||||
|
model = recipe
|
||||||
|
order_by = name
|
||||||
|
|
||||||
|
[pagination]
|
||||||
|
enabled = yes
|
||||||
|
per_page = 60
|
||||||
6
src/models/root.ini
Normal file
6
src/models/root.ini
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
[model]
|
||||||
|
name = Root Page
|
||||||
|
label = root
|
||||||
|
|
||||||
|
[attachments]
|
||||||
|
enabled = no
|
||||||
34
src/models/settings.ini
Normal file
34
src/models/settings.ini
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
[model]
|
||||||
|
name = Settings
|
||||||
|
label = Settings
|
||||||
|
hidden = yes
|
||||||
|
protected = yes
|
||||||
|
|
||||||
|
[children]
|
||||||
|
enabled = no
|
||||||
|
|
||||||
|
[attachments]
|
||||||
|
enabled = no
|
||||||
|
|
||||||
|
[fields.measures]
|
||||||
|
label = Measures
|
||||||
|
description = Comma 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
|
||||||
|
|
||||||
|
[fields.replace_frac]
|
||||||
|
label = Replace 1/2 with ½, ⅔, et.c
|
||||||
|
width = 1/5
|
||||||
|
type = boolean
|
||||||
|
|
||||||
|
[fields.replace_temp]
|
||||||
|
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
|
||||||
18
src/models/tag.ini
Normal file
18
src/models/tag.ini
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
[model]
|
||||||
|
name = Tag
|
||||||
|
label = {{ this.name }}
|
||||||
|
hidden = yes
|
||||||
|
|
||||||
|
[attachments]
|
||||||
|
enabled = no
|
||||||
|
|
||||||
|
[children]
|
||||||
|
replaced_with = site.query('/recipes', alt).filter(F.tags.contains(this))
|
||||||
|
|
||||||
|
[pagination]
|
||||||
|
enabled = yes
|
||||||
|
per_page = 60
|
||||||
|
|
||||||
|
[fields.name]
|
||||||
|
label = Name
|
||||||
|
type = string
|
||||||
12
src/models/tags.ini
Normal file
12
src/models/tags.ini
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
[model]
|
||||||
|
name = Tags
|
||||||
|
label = Tags
|
||||||
|
hidden = yes
|
||||||
|
protected = yes
|
||||||
|
|
||||||
|
[children]
|
||||||
|
model = tag
|
||||||
|
order_by = name
|
||||||
|
|
||||||
|
[attachments]
|
||||||
|
enabled = no
|
||||||
5
src/packages/helper/.gitignore
vendored
Normal file
5
src/packages/helper/.gitignore
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
dist
|
||||||
|
build
|
||||||
|
*.pyc
|
||||||
|
*.pyo
|
||||||
|
*.egg-info
|
||||||
3
src/packages/helper/README.md
Normal file
3
src/packages/helper/README.md
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# Helper
|
||||||
|
|
||||||
|
Just some python functions that are necessary for the project.
|
||||||
251
src/packages/helper/lektor_helper.py
Normal file
251
src/packages/helper/lektor_helper.py
Normal file
@@ -0,0 +1,251 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from lektor.pluginsystem import Plugin
|
||||||
|
from lektor.databags import Databags
|
||||||
|
import unicodedata
|
||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
|
||||||
|
# -------
|
||||||
|
# Sorting
|
||||||
|
|
||||||
|
|
||||||
|
def sortKeyInt(x):
|
||||||
|
return int(x[0]) if x[0] else 0
|
||||||
|
|
||||||
|
|
||||||
|
def sortKeyStr(x):
|
||||||
|
return noUmlaut(x[0]).lower()
|
||||||
|
|
||||||
|
|
||||||
|
def groupByDictSort(dic, sorter=None, reverse=False):
|
||||||
|
if type(sorter) == list: # sort by pre-defined, ordered list
|
||||||
|
return sorted(dic, reverse=bool(reverse), key=lambda x:
|
||||||
|
sorter.index(x[0]) if x[0] in sorter else 0)
|
||||||
|
fn = sortKeyInt if sorter == 'int' else sortKeyStr
|
||||||
|
return sorted(dic, reverse=bool(reverse), key=fn)
|
||||||
|
|
||||||
|
# -----------------------
|
||||||
|
# Pure text manupulations
|
||||||
|
|
||||||
|
|
||||||
|
def noUmlaut(text):
|
||||||
|
try:
|
||||||
|
text = unicode(text, 'utf-8')
|
||||||
|
except (TypeError, NameError):
|
||||||
|
pass
|
||||||
|
text = unicodedata.normalize('NFD', text)
|
||||||
|
text = text.encode('ascii', 'ignore')
|
||||||
|
text = text.decode("utf-8")
|
||||||
|
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]
|
||||||
|
except ValueError:
|
||||||
|
res += ' ' + x
|
||||||
|
return res.lstrip()
|
||||||
|
|
||||||
|
|
||||||
|
def numFillWithText(num, fill=u'★', empty=u'☆', total=5):
|
||||||
|
num = int(num) if num else 0
|
||||||
|
return fill * num + empty * (total - num)
|
||||||
|
|
||||||
|
# ------------------
|
||||||
|
# Array manipulation
|
||||||
|
|
||||||
|
|
||||||
|
def updateSet_if(dic, parent, parentkey, value):
|
||||||
|
try:
|
||||||
|
key = parent[parentkey]
|
||||||
|
except KeyError:
|
||||||
|
return
|
||||||
|
if not key:
|
||||||
|
key = ''
|
||||||
|
try:
|
||||||
|
dic[key]
|
||||||
|
except KeyError:
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
def splitIngredientLine(line):
|
||||||
|
state = 1
|
||||||
|
capture = False
|
||||||
|
indices = [0, len(line)]
|
||||||
|
for i, char in enumerate(line):
|
||||||
|
if char.isspace():
|
||||||
|
capture = False
|
||||||
|
indices[state] = i
|
||||||
|
state += 1
|
||||||
|
continue
|
||||||
|
elif capture:
|
||||||
|
continue
|
||||||
|
elif state == 1 and char in '0123456789-.,':
|
||||||
|
state -= 1
|
||||||
|
elif state > 1:
|
||||||
|
break
|
||||||
|
capture = True
|
||||||
|
return indices
|
||||||
|
|
||||||
|
|
||||||
|
def parseIngredientLine(line, measureList=[], rep_frac=False):
|
||||||
|
idx = splitIngredientLine(line)
|
||||||
|
val = line[:idx[0]]
|
||||||
|
if rep_frac:
|
||||||
|
val = replaceFractions(val)
|
||||||
|
measure = line[idx[0]:idx[1]].lstrip()
|
||||||
|
if measure.lower() in measureList:
|
||||||
|
name = line[idx[1]:].lstrip()
|
||||||
|
else:
|
||||||
|
measure = ''
|
||||||
|
name = line[idx[0]:].lstrip()
|
||||||
|
note = ''
|
||||||
|
name_note = name.split(',', 1)
|
||||||
|
if len(name_note) > 1:
|
||||||
|
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))
|
||||||
|
|
||||||
|
# ----------------
|
||||||
|
# Main entry point
|
||||||
|
|
||||||
|
|
||||||
|
class HelperPlugin(Plugin):
|
||||||
|
name = u'Helper'
|
||||||
|
description = u'Some helper methods, filters, and templates.'
|
||||||
|
alt = None
|
||||||
|
availableTags = set()
|
||||||
|
|
||||||
|
# -----------
|
||||||
|
# Event hooks
|
||||||
|
# -----------
|
||||||
|
|
||||||
|
def on_before_build_all(self, builder, **extra):
|
||||||
|
# display only tags that contain at least one recipe
|
||||||
|
pad = self.env.new_pad()
|
||||||
|
for r in pad.query('recipes'):
|
||||||
|
self.availableTags.update(r['tags'])
|
||||||
|
|
||||||
|
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_process_template_context(self, context, **extra):
|
||||||
|
self.alt = context['alt']
|
||||||
|
|
||||||
|
def on_setup_env(self, **extra):
|
||||||
|
# self.env.load_config().iter_alternatives()
|
||||||
|
# pad = self.env.new_pad()
|
||||||
|
# pad.query('groupby', alt=alt)
|
||||||
|
|
||||||
|
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']
|
||||||
|
|
||||||
|
for line in recipe['ingredients']:
|
||||||
|
line = line.strip()
|
||||||
|
if not line:
|
||||||
|
continue
|
||||||
|
elif line.endswith(':'):
|
||||||
|
yield {'group': line}
|
||||||
|
else:
|
||||||
|
yield parseIngredientLine(line, meaList, repFrac)
|
||||||
|
|
||||||
|
def groupByAttribute(recipeList, attribute):
|
||||||
|
groups = dict()
|
||||||
|
for recipe in recipeList:
|
||||||
|
if attribute == 'ingredients':
|
||||||
|
for ing in ingredientsForRecipe(recipe):
|
||||||
|
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['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
|
||||||
38
src/packages/helper/setup.py
Normal file
38
src/packages/helper/setup.py
Normal 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_helper.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-helper',
|
||||||
|
packages=find_packages(),
|
||||||
|
py_modules=['lektor_helper'],
|
||||||
|
# url='[link to your repository]',
|
||||||
|
version='0.1',
|
||||||
|
classifiers=[
|
||||||
|
'Framework :: Lektor',
|
||||||
|
'Environment :: Plugins',
|
||||||
|
],
|
||||||
|
entry_points={
|
||||||
|
'lektor.plugins': [
|
||||||
|
'helper = lektor_helper:HelperPlugin',
|
||||||
|
]
|
||||||
|
}
|
||||||
|
)
|
||||||
14
src/recipes.lektorproject
Normal file
14
src/recipes.lektorproject
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
[project]
|
||||||
|
name = recipe lekture
|
||||||
|
url_style = relative
|
||||||
|
|
||||||
|
[alternatives.en]
|
||||||
|
name = English
|
||||||
|
primary = yes
|
||||||
|
url_prefix = /en/
|
||||||
|
locale = en_US
|
||||||
|
|
||||||
|
[alternatives.de]
|
||||||
|
name = German
|
||||||
|
url_prefix = /de/
|
||||||
|
locale = de_DE
|
||||||
1
src/root/index.html
Normal file
1
src/root/index.html
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<meta http-equiv="refresh" content="0; URL='en/'" />
|
||||||
39
src/templates/cluster.html
Normal file
39
src/templates/cluster.html
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
{% extends "layout.html" %}
|
||||||
|
{% block title %}{{ this.name }}{% endblock %}
|
||||||
|
{% block body %}
|
||||||
|
|
||||||
|
{%- if this.group_key in ['rating', 'time'] -%}
|
||||||
|
{%- set sortType = 'int' -%}
|
||||||
|
{%- elif this.xdata -%}
|
||||||
|
{%- set sortType = this.xdata + [''] -%}
|
||||||
|
{%- endif -%}
|
||||||
|
|
||||||
|
{%- set all = site.query('/recipes', this.alt) | groupByAttribute(this.group_key) | groupSort(sortType, this.reverse_order) -%}
|
||||||
|
|
||||||
|
{%- if this.group_key == 'time' -%}
|
||||||
|
{%- set all = all | groupMergeCluster(this.xdata, this.reverse_order) -%}
|
||||||
|
{%- endif -%}
|
||||||
|
|
||||||
|
<h1>{{ this.name }}</h1>
|
||||||
|
<dl class="cluster">
|
||||||
|
{%- for attrib, recipes in all -%}
|
||||||
|
<dt>{%- if this.group_key == 'rating' -%}
|
||||||
|
{{ attrib | rating }}
|
||||||
|
{%- elif not attrib -%}
|
||||||
|
{{ this.null_fallback }}
|
||||||
|
{%- elif this.group_key == 'time' -%}
|
||||||
|
{{ attrib | duration(this.xdata) }}
|
||||||
|
{%- elif this.group_key == 'difficulty' -%}
|
||||||
|
{{ localize('difficulty', attrib) }}
|
||||||
|
{%- else -%}
|
||||||
|
{{ attrib }}
|
||||||
|
{%- endif -%}</dt>
|
||||||
|
<dd>
|
||||||
|
{%- set pipe = joiner(' | ') -%}
|
||||||
|
{%- for recipe in recipes | sort(attribute='name') -%}
|
||||||
|
{{ pipe() }}<a href="{{ recipe|url }}">{{ recipe.name }}</a>
|
||||||
|
{%- endfor -%}
|
||||||
|
</dd>
|
||||||
|
{%- endfor %}
|
||||||
|
</dl>
|
||||||
|
{% endblock %}
|
||||||
48
src/templates/layout.html
Normal file
48
src/templates/layout.html
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
<!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>
|
||||||
|
<header>
|
||||||
|
<a id="logo" href="{{ site.get('/', alt=this.alt)|url }}">recipe lekture</a>
|
||||||
|
<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>
|
||||||
|
{%- 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>
|
||||||
|
<div class="page">
|
||||||
|
{% block body %}{% endblock %}
|
||||||
|
</div>
|
||||||
|
<footer>{#--#}
|
||||||
|
<table width="100%">{#--#}
|
||||||
|
<td>Build with <a href="https://www.getlektor.com/">Lektor</a>, template by <a href="https://github.com/relikd/lektor-recipes">relikd</a>.</td>{#--#}
|
||||||
|
<td class="xlarge" width="1em">
|
||||||
|
{%- if this.alt == 'de' -%}
|
||||||
|
<a href="{{ '.'|url(alt='en') }}" title="zur englischen Seite wechseln">🇱🇷</a>
|
||||||
|
{%- else -%}
|
||||||
|
<a href="{{ '.'|url(alt='de') }}" title="switch to german page">🇩🇪</a>
|
||||||
|
{%- endif -%}
|
||||||
|
</td>{#--#}
|
||||||
|
</table>{#--#}
|
||||||
|
</footer>
|
||||||
|
<script type="text/javascript">const observer = lozad(); observer.observe();</script>
|
||||||
|
</body>
|
||||||
33
src/templates/macros/pagination.html
Normal file
33
src/templates/macros/pagination.html
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
{%- macro render_pagination_all(pagination) -%}
|
||||||
|
{%- if pagination.pages > 1 -%}
|
||||||
|
<div class=pagination>
|
||||||
|
{%- for page in pagination.iter_pages() %}
|
||||||
|
{% if page -%}
|
||||||
|
{%- if page != pagination.page -%}
|
||||||
|
<a href="{{ pagination.for_page(page)|url }}">{{ page }}</a>
|
||||||
|
{%- else -%}
|
||||||
|
<strong>{{ page }}</strong>
|
||||||
|
{%- endif -%}
|
||||||
|
{%- else -%}
|
||||||
|
<span class=ellipsis>...</span>
|
||||||
|
{%- endif -%}
|
||||||
|
{%- endfor %}
|
||||||
|
</div>
|
||||||
|
{%- endif -%}
|
||||||
|
{%- endmacro -%}
|
||||||
|
|
||||||
|
{% macro render_pagination_prev_next(pagination) %}
|
||||||
|
<div class="pagination">
|
||||||
|
{% if pagination.has_prev %}
|
||||||
|
<a href="{{ pagination.prev|url }}">« Previous</a>
|
||||||
|
{% else %}
|
||||||
|
<span class="disabled">« Previous</span>
|
||||||
|
{% endif %}
|
||||||
|
| {{ pagination.page }} |
|
||||||
|
{% if pagination.has_next %}
|
||||||
|
<a href="{{ pagination.next|url }}">Next »</a>
|
||||||
|
{% else %}
|
||||||
|
<span class="disabled">Next »</span>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endmacro %}
|
||||||
21
src/templates/macros/recipes.html
Normal file
21
src/templates/macros/recipes.html
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
{%- macro render_recipe_list(recipes, limit=0) -%}
|
||||||
|
<div class="tile-grid">
|
||||||
|
{%- for recipe in recipes -%}
|
||||||
|
{%- if limit == 0 or loop.index <= limit -%}
|
||||||
|
{%- 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 -%}
|
||||||
|
</div>
|
||||||
|
<p>{{ recipe.name }}</p>
|
||||||
|
</div>{#--#}
|
||||||
|
</a>
|
||||||
|
{%- endif -%}
|
||||||
|
{%- endfor %}
|
||||||
|
</div>
|
||||||
|
{%- endmacro -%}
|
||||||
10
src/templates/querylist.html
Normal file
10
src/templates/querylist.html
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
{% extends "layout.html" %}
|
||||||
|
{% block title %}{{ this.datamodel.name }}{% endblock %}
|
||||||
|
{% block body %}
|
||||||
|
<h1>{{ this.datamodel.name }}</h1>
|
||||||
|
<ul class="li-lg-space">
|
||||||
|
{%- for cluster in this.children %}
|
||||||
|
<li><a href="{{ cluster|url }}">{{ cluster.name }}</a></li>
|
||||||
|
{%- endfor %}
|
||||||
|
</ul>
|
||||||
|
{% endblock %}
|
||||||
63
src/templates/recipe.html
Normal file
63
src/templates/recipe.html
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
{% extends "layout.html" %}
|
||||||
|
{% block title %}{{ this.name }}{% endblock %}
|
||||||
|
{% block body %}
|
||||||
|
<article class="recipe">
|
||||||
|
<section id="img-carousel" class="v-scroll center">
|
||||||
|
{%- for img in this.attachments.images|sort(attribute='record_label') %}
|
||||||
|
<img src="{{ img|url }}" height="400px">
|
||||||
|
{%- endfor %}
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{% if this.source -%}
|
||||||
|
<div id="source" class="small center">
|
||||||
|
<a href="{{ this.source }}">⤳ {{ this.source.host }}</a>
|
||||||
|
</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>
|
||||||
|
{%- else %}
|
||||||
|
<span class="small">{{ localize('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>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section id="ingredients">
|
||||||
|
<h2>{{ localize('title.ingredients') }}:</h2>
|
||||||
|
<ul class="no-bullets li-lg-space">
|
||||||
|
{%- for ing in this|enumIngredients %}
|
||||||
|
{%- if ing['group'] %}
|
||||||
|
<li class="dark-red bold mrgTopMd">{{ ing['group'] }}</li>
|
||||||
|
{%- else %}
|
||||||
|
<li>
|
||||||
|
{%- if ing['value'] %}{{ ing['value'] }} {% endif -%}
|
||||||
|
{%- if ing['measure'] %}{{ ing['measure'] }} {% endif -%}
|
||||||
|
<span class="light-red">{{ ing['name'] }}</span>
|
||||||
|
{%- if ing['note'] -%}
|
||||||
|
<span class="small italic">{{ ', ' ~ ing['note'] }}</span>
|
||||||
|
{%- endif -%}
|
||||||
|
</li>
|
||||||
|
{%- endif %}
|
||||||
|
{%- endfor %}
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section id="directions">
|
||||||
|
<h2>{{ localize('title.directions') }}:</h2>
|
||||||
|
{% if site.get('settings', alt=this.alt)['replace_temp'] -%}
|
||||||
|
{{ this.directions|string|replace('°C', '℃')|replace('°F', '℉')|markdown }}
|
||||||
|
{% else -%}
|
||||||
|
{{ this.directions }}
|
||||||
|
{% endif -%}
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<div style="clear: both;"></div>
|
||||||
|
</article>
|
||||||
|
{% endblock %}
|
||||||
9
src/templates/recipes.html
Normal file
9
src/templates/recipes.html
Normal file
@@ -0,0 +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 body %}
|
||||||
|
<h1>{{ localize('title.recipes') }}</h1>
|
||||||
|
{{ render_recipe_list(this.pagination.items) }}
|
||||||
|
{{ render_pagination_all(this.pagination) }}
|
||||||
|
{% endblock %}
|
||||||
8
src/templates/root.html
Normal file
8
src/templates/root.html
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
{% extends "layout.html" %}
|
||||||
|
{% from "macros/recipes.html" import render_recipe_list %}
|
||||||
|
{% block body %}
|
||||||
|
<h1>{{ localize('title.latest') }}</h1>
|
||||||
|
<div class="latest">
|
||||||
|
{{ render_recipe_list(site.query('recipes', this.alt) | sort(attribute='date', reverse=True), limit=6) }}
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
9
src/templates/tag.html
Normal file
9
src/templates/tag.html
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
{% extends "layout.html" %}
|
||||||
|
{% from "macros/recipes.html" import render_recipe_list %}
|
||||||
|
{% from "macros/pagination.html" import render_pagination_all %}
|
||||||
|
{% block title %}{{ this.name }}{% endblock %}
|
||||||
|
{% block body %}
|
||||||
|
<h1>Tag: {{ this.name }}</h1>
|
||||||
|
{{ render_recipe_list(this.pagination.items) }}
|
||||||
|
{{ render_pagination_all(this.pagination) }}
|
||||||
|
{% endblock %}
|
||||||
Reference in New Issue
Block a user