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

6
.gitignore vendored
View File

@@ -1,3 +1,9 @@
bin/*
build-state/*
data/distribution/*
!data/distribution/contents.lr
# Created by https://www.gitignore.io/api/macos # Created by https://www.gitignore.io/api/macos
# Edit at https://www.gitignore.io/?templates=macos # Edit at https://www.gitignore.io/?templates=macos

3
.gitmodules vendored Normal file
View File

@@ -0,0 +1,3 @@
[submodule "src/packages/force-update"]
path = src/packages/force-update
url = https://github.com/relikd/lektor-force-update-plugin

38
Makefile Normal file
View File

@@ -0,0 +1,38 @@
PROJDIR := 'src'
help:
@echo
@echo 'make clean - Removes all temporary server-build files (not ./bin)'
@echo 'make server - Start lektor server with live change updates'
@echo 'make build - Build deployable website into ./bin'
@echo
@echo 'make find-links - Search for cross reference between recipes'
@echo
# Project build & clean
clean:
@cd '$(PROJDIR)' && \
temp_path="$$(lektor project-info --output-path)" && \
if [[ -d "$$temp_path" ]]; then \
echo "rm -rf $$temp_path"; rm -rf "$$temp_path"; \
fi
server:
@cd '$(PROJDIR)' && \
(rm content/recipes; ln -s ../../data/development/ content/recipes) && \
lektor server
build:
@cd '$(PROJDIR)' && \
(rm content/recipes; ln -s ../../data/distribution/ content/recipes) && \
lektor build --output-path ../bin --buildstate-path ../build-state -f ENABLE_APPCACHE
# Helper methods on all recipes
find-links:
@echo
@cd '$(PROJDIR)/content/recipes' && \
find */*.lr -exec grep --color=auto -i ".\.\./[^ ]*" -o {} + \
|| echo 'nothing found.'
@echo

View File

@@ -1,51 +1,47 @@
Lektor recipes Lektor recipes
============== ==============
Generating a static site for recipes. Static site generator for recipes; built upon [Lektor](https://github.com/lektor/lektor/).
Small, fast, multi-language, indexed.
![screenshot](img1.jpg) ![screenshot](img1.jpg)
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/). Features
-------
- Responsive design (desktop, mobile, print)
- Mobile application (AppCache & app manifest)
- Offline cache (local storage [150 recipes ~ 3mb])
- Multi-language (DE & EN, more can be added)
- Blazing fast (due to cache and minimal data usage)
- Indexed (group by time, ingredients, or tags)
- ~~static search~~ (**not yet**, but coming soon…)
Install Install
------- -------
1. [Download](https://www.getlektor.com/) Lektor and follow the instructions. 1. Download [Lektor](https://www.getlektor.com/) and follow the instructions.
2. Clone this repository and change to the `src` directory. 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.\** 3. Run `make server` to run a local server and preview the page.
4. For distribution run `make build` and add an [official deploy](https://www.getlektor.com/docs/deployment/).
### 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 ### Modify
Thanks to Lektor you have a simple content management system (see screenshot below). Thanks to Lektor you have a simple content management system (see screenshot below).
Two things to note: A few things to note:
1. Measurements have to be added manually to settings. Don't forget to __pluralize__ (c, cup, cups, etc.) 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 (`:`) 2. You can add __ingredient groups__ if the line ends with a colon (`:`)
3. The preferred __image size__ is `800x600`. Please scale all images down to save bandwidth. Try to keep an aspect ratio of 4:3 for the first image, or it will be cropped on the recipe overview page! All other images will be shown unmodified in whatever aspect ratio is provided (individual recipe page)
4. __AppCache__ is disabled during development. However, you can pass `-f ENABLE_APPCACHE` to any `lektor` command to enable it. The makefile does this by default for the `build` target.
Also, see [Lektor docs](https://www.getlektor.com/docs/) and [jinja2 template](https://jinja.palletsprojects.com/en/2.10.x/templates/) documentation. Also, see [Lektor docs](https://www.getlektor.com/docs/) and [jinja2 template](https://jinja.palletsprojects.com/en/2.10.x/templates/) documentation.

View File

@@ -0,0 +1,19 @@
yield: 20×20cm Backform
---
ingredients:
2 Tassen Walnüsse
1 Tasse Kakao
1/4 TL Salz
2 1/2 Tassen Datteln, weich, Medjool
Optional:
1 Tasse Mandeln, grob gehackt
---
directions:
Walnüsse im Mixer fein hacken. Kakao und Salz hinzugeben und miteinander vermixen.
Datteln nach und nach einzeln hinzugeben, während der Mixer weiter läuft. Die Mixtur sollte bröckelig sein aber beim Zusammendrücken leicht zusammenhalten (wenn es nicht zusammenhält, noch mehr Datteln hinzugeben)
In einer Schüssel die Mandeln mit der Mixtur vermischen und in eine Kuchenform pressen. Bis zum Servieren im Kühlschrank, luftdicht verschlossen halten.

View File

@@ -0,0 +1,19 @@
yield: 8×8 square pan
---
ingredients:
2 cups walnuts
1 cup cocoa powder
1/4 tsp salt
2 1/2 cups dates, soft, medjool
Optional:
1 cup almonds, roughly chopped
---
directions:
Blend walnuts on high until finely ground. Add the cocoa and salt. Pulse to combine.
Add the dates one at a time through the feed tube of the food processor while it is running. What you should end up with is a mix that appears rather like cake crumbs. But when pressed, will easily stick together (if the mixture does not hold together well, add more dates).
In a large bowl, combine the walnut-cocoa mix with the chopped almonds. Press into a lined cake pan or mold. Place in the fridge until ready to serve. Store in an airtight container.

View File

@@ -0,0 +1,13 @@
name: Brownies, Raw
---
tags: cake, glutenfree, raw, sweet
---
time: 15
---
difficulty: easy
---
rating: 4
---
source: https://mynewroots.org/site/2011/04/the-raw-brownie-2/
---
date: 2013-09-01

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

View File

@@ -0,0 +1,133 @@
name: Glutenfreies Mehl
---
yield: 9 Tassen
---
ingredients:
6 Tassen Mehl
3 Tassen Stärke
---
directions:
1) Mindestens 12 Stärken (leichtes Mehl) und 1 mittleres Mehl mischen. Je nach Geschmack und Textur noch ein weiteres Mehl (mittel oder schwer) hinzufügen.
2) Mehl mit einem Löffel umfüllen und mit einem Messer glattstreichen.
3) In einen luftdichten Behälter geben und gut schütteln.
4) An einem dunklen, trockenen Ort oder im Kühlschrank aufbewahren.
### Existierende Mischungen
#### [Cara's All-Purpose Mischung](https://forkandbeans.com/2013/02/13/the-best-gluten-free-flour-blend/)
- 3 Tassen __Braunes Reismehl__, extrafein gemahlen
- 3 Tassen __Sorghummehl__, extrafein gemahlen
- 1.5 Tassen __Pfeilwurzmehl__
- 1.5 Tassen __Kartoffelstärke__
Für die [Leichte Mischung](https://forkandbeans.com/2015/11/18/gluten-free-flour-blend-for-cakes/) (perfekt für Kuchen, Cupcakes, Muffins, etc.) Sorghum ersetzen durch Weißes Reismehl.
#### [Sarah's All-Purpose Mischung](https://sarahbakesgfree.com/2012/05/sarahs-gluten-free-flour-blend.html)
- 4 Tassen __Braunes Reismehl__
- 2 Tassen __Weißes Reismehl__
- 2 Tassen __Kartoffelstärke__
- 1/2 Tasse __Tapiokastärke__
- 1/2 Tasse __Maisstärke__
- 5 TL __Xanthan__
### Mischung selbst zusammen stellen
#### Leichte Mehle:
Das sind alle Stärken — ein Muss bei der Herstellung von glutenfreien Mehlen.
<dl>
<dt>Pfeilwurzmehl</dt>
<dd>
Diese leicht verdauliche Stärke wird aus einer Kombination mehrerer Wurzeln gewonnen. Es kann als gesündere Alternative für Maisstärke, zur Verdickung von Soßen verwendet werden.
</dd>
<dt>Maisstärke</dt>
<dd>
Aus Mais gemahlen, ist diese Stärke ein hervorragendes Binde- und Verdickungsmittel. Maisstärke erzeugt bspw. bei Brot eine großartige Kruste.
</dd>
<dt>Kartoffelstärke</dt>
<dd>
Ausgezeichnete Stärke um Backwaren mit Feuchtigkeit zu versorgen. Bitte beachte, dass Kartoffelstärke und Kartoffelmehl zwei verschiedene Dinge sind Etikett sorgfältig lesen.
</dd>
<dt>Tapiokastärke / Tapiokamehl</dt>
<dd>
Aus der Maniokwurzel extrahiert und gebleicht, wird Tapiokastärke typischerweise als Verdickungsmittel in Rezepten verwendet. Dies ist eine aromatisierte Stärke. Sie verleiht dem Backgut eine gewisse Leichtigkeit in der Textur.
</dd>
</dl>
#### Mittlere Mehle:
Obwohl diese Mehle an sich nahrhaft sind, sind sie bei der Verwendung in einer Rezeptur etwas leichter und stabiler. Sie können in Verbindung mit einer Stärke auch einzeln verwendet werden.
<dl>
<dt>Favabohne / Ackerbohne</dt>
<dd>
Die Favabohne kommt typischerweise in einer Mischung aus Kichererbsenmehl vor. Sie lässt den Teig schön aufgehen, hat aber einen ausgeprägten Geschmack.
</dd>
<dt>Kichererbse</dt>
<dd>
Einer der besten glutenfreien Mehle, der einzige Haken ist ein sehr ausgeprägter Bohnengeschmack. Lässt die Backwaren außergewöhnlich gut aufgehen. Kann einzeln mit Stärke verwendet werden.
</dd>
<dt>Hirse</dt>
<dd>
Mit einem trockenen und leicht nussigen Geschmack ist Hirsemehl ein überwiegend stärkehaltiges Getreide. Der Proteingehalt gleicht dem von Vollkornmehl.
</dd>
<dt>Hafer</dt>
<dd>
Dieses Mehl, das direkt aus Hafer gemahlen wird, ist reich an Vitaminen und Ballaststoffen. Es ergibt einen schönen, gleichmäßigen Geschmack und lässt die Backwaren gut aufgehen. Kann einzeln oder in Kombination mit anderen Mehlen verwendet werden.
</dd>
<dt>Quinoa</dt>
<dd>
Trotz seines sehr ausgeprägten Geschmacks ist Quinoa ein weiteres großartiges Mehl. Es kann einzeln oder in Kombination mit anderen Mehlen verwendet werden. Es ist nahrhaft, enthält viele Mineralien und Vitamine, und produziert eine hervorragende Textur.
</dd>
<dt>Sorghum</dt>
<dd>
Sorghum neigt dazu, die Textur und Leichtigkeit von Weizenmehl nachzuahmen und verleiht den Backwaren eine gewisse Zartheit. Aus diesem Grund ist es eines meiner Lieblingsmehle.
</dd>
<dt>Weißer Reis</dt>
<dd>
Berühmt dafür, eine kiesige Textur zu erzeugen. Bei diesem Mehl ist es wichtig, einen sehr feinen Mahlgrad zu wählen. Es ist ein sehr verbreitetes Mehl, das aus gutem Grund in vielen Mischungen verwendet wird. Es ist leicht und liefert hervorragende Ergebnisse. <b>Hinweis:</b> Süßer Reis unterscheidet sich vom weißem Reis und sollte eher wie eine Stärke (und in kleineren Mengen) verwendet werden.
</dd>
</dl>
#### Schwere Mehle:
Das sind dichtere und nahrhaftere Mehle. Sie werden selten einzeln verwendet, sondern in Kombination mit mittleren Mehlen.
<dl>
<dt>Mandel</dt>
<dd>
Mit einem schönen Kick an Proteinen ist Mandelmehl eine gute Möglichkeit, deinen Backwaren einen butterigen Geschmack zu verleihen. Es ist ein gutes Bindemittel und verleiht Feuchtigkeit (besonders wenn du keine Eier verwendest). Für Paleo-Diät sind Nuss- und Kokosmehle eine Korn-freie Alternative.
</dd>
<dt>Amaranth</dt>
<dd>
Aus den Samen der Amaranthpflanze gewonnen, ist dieses gemahlene Mehl dichter und nährstoffreicher als die meisten Mehle. Es kann in Backwaren die wenig aufgehen einzeln verwendet werden, oder in Kombination mit einer Mischung aus mittleren Mehlen.
</dd>
<dt>Brauner Reis</dt>
<dd>
Braunes Reismehl ist sehr vergleichbar mit Vollkornmehl. Es hat viele Nährstoffe und verleiht deinem Rezept eine gute Struktur. Idealerweise sollte es superfein gemahlen sein sonst entsteht eine kiesige Textur. Es kann mit einer Stärke einzeln verwendet werden oder in Kombination mit anderen mittleren Mehl(en).
</dd>
<dt>Buchweizen</dt>
<dd>
Lass dich sich nicht von dem Namen täuschen, Buchweizen wird nicht aus Weizen gewonnen, sondern aus einer Frucht. Dieses Mehl kann deinem Backgut einen schönen braunen Farbton verleihen, ist voll von Nährstoffen und Dichte. Es muss in Kombination mit einer Stärke und einem mittlerem Mehl verwendet werden, insbesondere für stark aufgehende Rezepte.
</dd>
<dt>Kokosnuss</dt>
<dd>
Kokosnuss absorbiert sehr viel Flüssigkeit im Rezept. Deshalb nur in kleineren Mengen (ca. 1/4 Tasse) und in Kombination mit anderen Mehlen verwenden.
</dd>
<dt>Mais</dt>
<dd>
Ein herzhaftes, dichtes Mehl. Maismehl kann der Mehlmischung eine schöne Textur verleihen, ähnlich wie bei einem Maisbrot.
</dd>
<dt>Teff</dt>
<dd>
Teff ist ein einzigartiges, aromatisiertes Vollkorn Mehl, das in äthiopischen Lebensmitteln verwendet wird. Es ist leicht, erzeugt aber gleichzeitig eine dichte Textur, so dass es am besten in kleineren Mengen (wie Kokosmehl) und in Kombination mit anderen Mehlen verwendet wird.
</dd>
</dl>

View File

@@ -0,0 +1,134 @@
name: Gluten-Free Flour
---
yield: 9 cups
---
ingredients:
6 cups flour
3 cups starch
---
directions:
1) You'll need 12 starches (light flour) and at least 1 medium flour. Based on the prefered flavor and texture, you can mix in another flour (medium or heavy).
2) Spoon out flours with a spoon and level off with a knife.
3) Place into an air-tight container and shake well.
4) Keep stored in a dark, dry place or in the refrigerator.
### Existing blends
#### [Cara's All-Purpose Blend](https://forkandbeans.com/2013/02/13/the-best-gluten-free-flour-blend/)
- 3 cups brown rice flour, superfine ground
- 3 cups sorghum flour, superfine ground
- 1.5 cups arrowroot powder
- 1.5 cups potato starch
For the [Light Blend](https://forkandbeans.com/2015/11/18/gluten-free-flour-blend-for-cakes/) (perfect for cakes, cupcakes, muffins, etc.) replace sorghum with white rice flour.
#### [Sarah's All-Purpose Blend](https://sarahbakesgfree.com/2012/05/sarahs-gluten-free-flour-blend.html)
- 4 cups brown rice flour
- 2 cups white rice flour
- 2 cups potato starch
- 1/2 cup tapioca flour
- 1/2 cup corn starch
- 5 tsp xanthan gum
### Create your own blend
#### Light based flours:
These are all of the starches — a must when creating a blend of gluten free flours.
<dl>
<dt>Arrowroot Powder</dt>
<dd>
This easy-to-digest starch is extracted from a combination of several plant rootstocks. It can be used as a healthier sub for cornstarch in thickening up sauces and gravies.
</dd>
<dt>Corn Starch</dt>
<dd>
Ground from corn, this starch makes for a great binder and thickening agent. Check for a great non GMO brand because cornstarch can add a great outer crust for your breads.
</dd>
<dt>Potato Starch</dt>
<dd>
This is my go-to starch for it's ability to add moisture into baked goods. Please note that potato starch and potato flour are two different things read the label carefully.
</dd>
<dt>Tapioca Starch / Tapioca Flour</dt>
<dd>
Extracted and bleached from the cassava root, tapioca starch is typically used as a thickening agent in recipes. This is a flavored starch to use in a blend for its ability to bring a certain lightness in texture to the baked good.
</dd>
</dl>
#### Medium based flours:
Though nutritious in their own right, these flours are a bit lighter when used in a recipe and are more stable to be used alone paired with a starch.
<dl>
<dt>Fava Bean</dt>
<dd>
Fava bean can typically be found with a mix of garbanzo bean flour. It yields a really nice rise but has a distinct flavor.
</dd>
<dt>Garbanzo Bean / Chickpea</dt>
<dd>
One of the best result-producing gluten-free flours, the only catch is its a very distinct bean flavor. The rise in your baked goods will be exceptional and can be used alone with a starch.
</dd>
<dt>Millet</dt>
<dd>
With a dry and slighty nutty flavor, millet flour is a predominantly starchy grain with a protein content that is similar to whole wheat flour.
</dd>
<dt>Oat</dt>
<dd>
Ground straight from oats, this flour is rich in vitamins and fiber. It yields a nice even flavor and a great rise to your baked goods. Can be used alone or in a combination of other flours.
</dd>
<dt>Quinoa</dt>
<dd>
Despite it's very distinct flavor, quinoa is another great medium-based flour that can work alone or in combination to other flours. It's nutritious, dense with minerals and vitamins, and produces a great texture.
</dd>
<dt>Sorghum</dt>
<dd>
Sorghum tends to mimic the texture and lightness of wheat flour and will give your baked goods a certain tenderness. For this reason, it's one of my go-to flours in my flour blend.
</dd>
<dt>White Rice</dt>
<dd>
Notorious for yielding a gritty texture, it's important to get the finest ground for this flour. It's a very common flour used in blends for good reason, it's light and gives great results. <b>Note:</b> Sweet Rice is different from White Rice and should be used more like a starch and in smaller amounts.
</dd>
</dl>
#### Heavy based flours:
These are the more dense and nutritious flours that are rarely used alone and will need to be used in tandem with another medium-based flour.
<dl>
<dt>Almond</dt>
<dd>
With a nice kick of protein, nut meal is a great way to give your baked goods a buttery flavor. It works best if you add a little into your mix (especially if you do not use eggs) to yield a nice binding result and overall moisture into your recipe. If you are Paleo, nut and coconut flours are you to-go grain-free flours.
</dd>
<dt>Amaranth</dt>
<dd>
Derived from the seeds of the amaranth plant, this stone ground flour is denser and more nutritious than most flours. It can be used alone in low rise baked goods or in combination with a mix of medium flours.
</dd>
<dt>Brown Rice</dt>
<dd>
Brown rice flour is very comparable to whole wheat flour, with its dense nutrition and great structure it lends to your recipe. It's best if you use it in superfine ground form this way you will avoid that gritty texture. It can be used alone with a starch or in combination with another medium flour(s).
</dd>
<dt>Buckwheat</dt>
<dd>
Don't be fooled by its name, buckwheat is not derived by wheat but rather a fruit. This flour can give your baked good a nice brown hue, full of nutrition and density. It needs to be used in combination with a starch and medium flour, especially for higher rising recipes.
</dd>
<dt>Coconut</dt>
<dd>
Coconut has a great way of absorbing the liquid in a recipe, which is why it needs to be used in smaller amounts (think 1/4 cup) and in combo with other flours.
</dd>
<dt>Corn</dt>
<dd>
A hearty, dense flour, corn flour can add a nice texture to your flour blend, similar to a corn bread toothsome feel.
</dd>
<dt>Teff</dt>
<dd>
A unique flavored whole grain, Teff is a very common flour used in Ethiopian food. It's light but creates a dense texture at the same time so it's best if used in smaller amounts (like coconut flour) and in combination with other flours.
</dd>
</dl>

View File

@@ -0,0 +1,9 @@
tags: bread, glutenfree, ingredient
---
time: 5
---
difficulty: easy
---
source: https://forkandbeans.com/2013/12/30/guide-gluten-free-flours/
---
date: 2013-08-09

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 KiB

View File

@@ -1,7 +1,5 @@
name: Vanille Ausstech-Kekse name: Vanille Ausstech-Kekse
--- ---
yield: 26-28 Kekse
---
ingredients: ingredients:
1/2 Tasse Margarine, oder Kokos Öl 1/2 Tasse Margarine, oder Kokos Öl
@@ -9,7 +7,7 @@ ingredients:
1/2 TL Vanille Extrakt 1/2 TL Vanille Extrakt
1/4 TL Vanilleshote 1/4 TL Vanilleshote
1 Prise Salz 1 Prise Salz
2 1/4 Tassen Mehl, glutenfrei 2 1/4 Tassen Mehl, glutenfrei, @../gluten-free-flour/
--- ---
directions: directions:

View File

@@ -0,0 +1,24 @@
name: Vanilla cut-out 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
1 dash salt
2 1/4 cups flour, gluten-free, @../gluten-free-flour/
---
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

View File

@@ -6,6 +6,8 @@ rating: 4
--- ---
difficulty: easy difficulty: easy
--- ---
source: https://www.unconventionalbaker.com/recipes/gluten-free-vegan-vanilla-cut-out-cookies/ yield: 26-28
---
source: https://unconventionalbaker.com/recipes/gluten-free-vegan-vanilla-cut-out-cookies/
--- ---
date: 2019-05-15 date: 2019-05-15

View File

Before

Width:  |  Height:  |  Size: 58 KiB

After

Width:  |  Height:  |  Size: 58 KiB

View File

Before

Width:  |  Height:  |  Size: 70 KiB

After

Width:  |  Height:  |  Size: 70 KiB

View File

Before

Width:  |  Height:  |  Size: 41 KiB

After

Width:  |  Height:  |  Size: 41 KiB

View File

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

196
data/export-yummy.py Normal file
View File

@@ -0,0 +1,196 @@
#!/usr/bin/env python3
import sqlite3
import os
import sys
from datetime import datetime
'''
Usage: python3 generate-alternates.py '…/YummySoup.library/Database.SQL'
You may have to adjust `mapTag`, `clearUTF()`, and `slugify()` below.
Output is generated in the current folder under `yummysoup-exported`.
'''
# check if input param is SQL database file
try:
inputPath = os.path.abspath(sys.argv[1])
if not os.path.isfile(inputPath) or not inputPath.upper().endswith('SQL'):
raise Exception()
base = os.path.dirname(inputPath)
print('connecting...')
db = sqlite3.connect(inputPath)
except Exception:
print()
print(f'usage: {os.path.basename(sys.argv[0])} "path/to/db.SQL"')
print('(e.g., "…/YummySoup! Librarys.library/Library Database.SQL")')
print()
exit()
# create output export dir if necessary
_out = os.path.abspath('./yummysoup-exported/')
if not os.path.exists(_out):
os.mkdir(_out)
# map old tags to new one. Should be all available tags in YummySoup!
# right hand side must be lower case string or None
mapTag = {
'': None,
'Weihnachten': 'xmas',
'Wurst': None,
'Dressing': 'dressing',
'Soße': 'sauce',
'Hauptspeise': 'main-dish',
'Süßes': 'sweet',
'Zutat': 'ingredient',
'Raw': 'raw',
'Aufstrich': 'spread',
'Brot': 'bread',
'Kuchen': 'cake',
'Kekse': 'cookies',
'trocken': None,
'Salat': 'salad',
'Drink': 'drinks',
'Riegel': None,
'Schokolade': 'chocolate',
'Dip': 'dip',
'fruchtig': None,
'Glutenfrei': 'glutenfree'
}
def ttoint(txt):
i, n = txt.split(' ') if txt else (0, 'M')
return int(i) * [1, 60, 1440]['MST'.index(n[0])]
# def matchTime(time):
# if time in [0, 25, 135, 165, 300]:
# return [None, 30, 150, 150, 360][[0, 25, 135, 165, 300].index(time)]
# prev = 99999
# val = time
# for x in [5, 10, 15, 20, 30, 45, 60, 75, 90, 105,
# 120, 150, 180, 240, 360, 480, 720, 1440]:
# diff = abs(time - x)
# if diff < prev:
# prev = diff
# val = x
# elif diff == prev:
# print(time)
# return val
def clearUTF(txt):
return txt.replace('\\U00df', 'ß').replace('\\U00f1', 'ñ')\
.replace('\\U00c4', 'Ä').replace('\\U00e4', 'ä')\
.replace('\\U00d6', 'Ö').replace('\\U00f6', 'ö')\
.replace('\\U00dc', 'Ü').replace('\\U00fc', 'ü')
def slugify(txt):
return txt.lower().replace(' ', '-').replace(':', '').replace('ß', 'ss')\
.replace('(', '').replace(')', '').replace(',', '').replace('ê', 'e')\
.replace('ä', 'ae').replace('ü', 'ue').replace('ö', 'oe').strip('-')
def formatIngredient(info):
try:
if info['isG'] in ['YES', '1']:
return '\n' + info['nam']
except KeyError:
pass
txt = info['nam'].replace(',', ' ')
if info['mea']:
txt = '{} {}'.format(info['mea'], txt)
if info['qua']:
txt = '{} {}'.format(info['qua'], txt)
if info['met']:
txt = '{}, {}'.format(txt, info['met'])
return txt
def ingredientToStr(txt):
res = ''
for ing in clearUTF(txt).split('},'):
ing = ing.strip('{()} \n')
info = {'qua': '', 'mea': '', 'nam': '', 'met': ''}
for prop in ing.split(';'):
if not prop:
continue
k, v = [x.strip('\n "') for x in prop.split('=')]
info[k[:3]] = v
res += '\n' + formatIngredient(info)
return res
def directionsToStr(txt):
return txt.replace('<font face="" size="">', '').replace(' ', '')\
.replace('</font>', '').replace('<br>', '').replace('<b>', '__').\
replace('</b>', '__').replace('<i>', '_').replace('</i>', '_')\
.replace('', '°C').replace(' °C', '°C')\
.replace('½', '1/2').replace('¼', '1/4').replace('', '1/8')\
.replace('', '1/3').replace('', '2/3').replace('¾', '3/4')
def prnt(key, val, inline=True):
return '' if not val else '{}:{}{}\n---\n'.format(
key, ' ' if inline else '\n\n', str(val).strip())
def export(slug, content, img):
output = os.path.join(_out, slug)
for i in range(10):
folder = output
if i > 0:
folder += '-%d' % i
if not os.path.isdir(folder):
output = folder
break
os.mkdir(output)
with open(os.path.join(output, 'contents.lr'), 'w') as f:
f.write(txt.strip().rstrip('-'))
for i in range(1, 10):
src = img % i
dest = os.path.join(output, f'image{"" if i == 1 else i}.jpg')
if not os.path.isfile(src):
break
with open(src, 'rb') as a, open(dest, 'wb') as b:
b.write(a.read())
print('exporting...')
for row in db.cursor().execute('''SELECT * FROM ZRECIPES'''):
difficulty, rating, date, img = row[4], row[7], row[9], row[10]
duration, tags, name, yields = row[12:15], row[15], row[17], row[21]
notes, directions, source, ingredients = row[23], row[25], row[26], row[27]
# preprocess
date = datetime.fromtimestamp(date + 978307200).strftime('%Y-%m-%d')
img = os.path.join(base, 'Images', img + '-Image%d.jpg')
duration = sum([ttoint(x) for x in duration]) # matchTime()
tags = ', '.join(sorted([mapTag[x] for x in tags.split(',') if mapTag[x]]))
slug = slugify(name)
if yields:
y = yields.split(' ')
if len(y) == 3 and y[1].endswith('form'):
yields = '{} {}'.format(y[2], y[1])
txt = ''
txt += prnt('name', name)
txt += prnt('tags', tags)
txt += prnt('time', duration)
txt += prnt('difficulty', [None, 'easy', 'medium', 'hard'][difficulty])
txt += prnt('rating', rating)
txt += prnt('yield', yields)
txt += prnt('ingredients', ingredientToStr(ingredients), False)
desc = directionsToStr(directions)
if notes:
desc = '{}\n\n__Notes:__ {}'.format(desc.strip(), notes)
txt += prnt('directions', desc, False)
txt += prnt('source', source)
txt += prnt('date', date)
export(slug, txt, img)
db.close()
print('done.')

View File

@@ -0,0 +1,59 @@
#!/usr/bin/env python3
import os
import sys
'''
Usage: python3 generate-alternates.py development/*
Input is a recipe folder.
Will take the `contents.lr` and extract `contents+de.lr` and `contents+en.lr`.
The content will be identical but its easier to edit this way.
No necessary redundant data fields.
'''
def prnt(key, val, inline=True):
return '' if not val else '{}:{}{}\n---\n'.format(
key, ' ' if inline else '\n\n', str(val).strip())
def splitContent(path):
mode = 1
idx = 0
with open(os.path.join(path, 'contents.lr'), 'r') as fin:
tmp = ['', '']
for line in fin:
if mode == 1:
tag = line.split(':')[0]
if tag in ['name', 'yield', 'ingredients', 'directions']:
idx = 1
else:
idx = 0
tmp[idx] += line
mode = 2
else:
tmp[idx] += line
if line == '---\n':
mode = 1
tmp[1] = tmp[1][:-4]
return tmp
def writeSplit(path):
de_file = os.path.join(path, 'contents+de.lr')
if not os.path.isdir(path) or os.path.exists(de_file):
return
print(path)
content = splitContent(path)
if not content[1]:
return
with open(de_file, 'w') as f:
f.write(content[1])
with open(os.path.join(path, 'contents+en.lr'), 'w') as f:
f.write(content[1])
with open(os.path.join(path, 'contents.lr'), 'w') as f:
f.write(content[0])
for x in sys.argv[1:]:
writeSplit(x)

1
requirements.txt Normal file
View File

@@ -0,0 +1 @@
lektor

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) } .dark-red { color: var(--cRed2) }
.light-red { color: var(--cRed3) } .light-red { color: var(--cRed3) }
.mrgTopMd { margin-top: 0.7em } .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.no-bullets { padding: unset; margin: unset; list-style: none }
ul.li-lg-space li { padding-bottom: 0.3em } 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 * General
*/ */
@@ -24,11 +35,15 @@ a { color: var(--cRed2); text-decoration: none }
a:hover { color: var(--cRed1) } a:hover { color: var(--cRed1) }
body { body {
font-family: 'Verdana', sans-serif; font-family: 'Verdana', sans-serif;
background-color: var(--cBg3);
color: var(--cTxt);
margin: unset; 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 a { color: var(--cTxt) }
header, h1 { text-align: center } header, h1 { text-align: center }
header, footer, .page { 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 } nav ul li a.active { text-decoration: overline }
footer table { margin: -10px 0 } 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 { @media print {
header, footer { display: none } 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 { display: flex; flex-wrap: wrap; justify-content: center }
.tags > * { .tags > * {
background-color: #FFF; background: #FFF;
border: 1px solid var(--cRed1); border: 1px solid var(--cRed1);
border-radius: 0.3em; border-radius: 0.3em;
padding: 0.3em 0.5em; padding: 0.3em 0.5em;
margin: 0.2em; margin: 0.2em;
} }
.tags a:hover, .tags .active, a:hover .recipe-tile { .tags a:hover, .tags .active, a:hover .recipe-tile, .recipe-tile .hover .time {
background-color: var(--cRed1); background: var(--cRed1); color: #FFF;
color: #FFF;
} }
header .tags { max-width: 600px; margin: 0 auto } header .tags { max-width: 600px; margin: 0 auto }
.cluster dt { margin-top: 0.7em; font-size: 1.6em } .cluster dt { margin-top: 0.7em; font-size: 1.6em }
.cluster dd { margin-top: 0.4em } .cluster dd { margin-top: 0.4em }
.cluster dd a { white-space: nowrap } .cluster dd a { white-space: nowrap }
@media(max-width: 500px) { @media(max-width: 32em) {
.cluster dd { margin-left: 0 } .cluster dd { margin-left: 0 }
.cluster dd a { white-space: unset } .cluster dd a { white-space: unset }
} }
@@ -77,25 +91,31 @@ header .tags { max-width: 600px; margin: 0 auto }
/* /*
* Grid overview * Grid overview
*/ */
.pagination { text-align: center; margin-top: 1em; } .pagination { text-align: center; margin-top: 1em }
.recipe-tile { .recipe-tile {
background-color: var(--cBg2); background: var(--cBg2); color: var(--cTxt);
color: var(--cTxt);
display: inline-block; display: inline-block;
vertical-align: top; vertical-align: top;
margin: 6px; margin: 6px;
width: 200px; width: 200px;
text-align: center; text-align: center;
} }
.recipe-tile .img-placeholder { .recipe-tile .placeholder {
background-color: #777;
color: var(--cBg2);
width: 200px;
height: 150px;
font: bold 25px/150px 'Courier New', monospace; 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.5em; margin: 0.3em 10px; overflow-y: auto }
.recipe-tile p { height: 2.6em; 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 } .tile-grid { width: fit-content; max-width: 1060px; margin: 0 auto }
.latest .tile-grid { max-width: 636px } .latest .tile-grid { max-width: 636px }
/* max-width = prev + 2*30; width = x * (200 + 2*6); */ /* 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: portrait) { .tile-grid { width: 636px } }
@media print and (orientation: landscape) { .tile-grid { width: 1060px } } @media print and (orientation: landscape) { .tile-grid { width: 1060px } }
@media print { @media print {
a:hover .recipe-tile img { mix-blend-mode: unset !important } .recipe-tile .overlay { display: none }
.recipe-tile, .recipe-tile .img-placeholder, a:hover .recipe-tile { .recipe-tile, a:hover .recipe-tile, .recipe-tile .placeholder {
background-color: #FFF; color: #000 } 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 #source { margin-left: -1em; margin-bottom: -1.5em }
.recipe #metrics { float: right; margin: 0 0 15px 25px; max-width: 180px } .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 #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 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 */ /* 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(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(1),
.difficulty.hard > div:nth-child(2), .difficulty.hard > div:nth-child(2),
.difficulty.hard > div:nth-child(3) { background-color: #F30 } .difficulty.hard > div:nth-child(3) { background: #F30 }
.difficulty > * { vertical-align: middle; } .difficulty > * { vertical-align: middle }
.difficulty > div:nth-child(1) { border-radius: 50% 0 0 50% } .difficulty > div:nth-child(1) { border-radius: 50% 0 0 50% }
.difficulty > div:nth-child(3) { border-radius: 0 50% 50% 0 } .difficulty > div:nth-child(3) { border-radius: 0 50% 50% 0 }
.difficulty > div { .difficulty > div {
@@ -138,16 +161,26 @@ a:hover .recipe-tile img { mix-blend-mode: overlay }
border: 1px solid #555; border: 1px solid #555;
} }
@media screen and (max-width: 800px) { @media screen and (max-width: 50em) {
.recipe #img-carousel img { height: auto; width: 100%; padding: 0 } .recipe h1 { margin-bottom: 0 }
.recipe #metrics { float: unset; max-width: fit-content; margin: 20px auto } .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 {
@media print and (orientation: landscape) { #img-carousel img { display: none } } 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) { @media print and (orientation: portrait) {
#img-carousel img:not(:first-child) { display: none } #img-carousel img:not(:first-child) { display: none }
.recipe #metrics { float: unset; padding-bottom: 1em } .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 120
180 180
360 360
9999 1440

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 +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_frac: yes
--- ---
replace_temp: yes replace_temp: yes
---
show_empty_tags: no

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 +0,0 @@
name: Dressing

View File

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

View File

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

View File

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

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: Süßes name: Süß

View File

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

View File

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

View File

@@ -21,9 +21,8 @@ size = large
[fields.time] [fields.time]
label = Time / Zeit label = Time / Zeit
width = 1/8 width = 1/8
type = select type = integer
choices = 5, 10, 15, 20, 30, 45, 60, 75, 90, 105, 120, 150, 180, 240, 360, 480, 720, 1440 addon_label = min
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] [fields.difficulty]
label = Difficulty label = Difficulty

View File

@@ -12,10 +12,10 @@ enabled = no
[fields.measures] [fields.measures]
label = Measures label = Measures
description = Comma separated list description = Space separated list
width = 3/5 width = 3/5
type = text 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] [fields.replace_frac]
label = Replace 1/2 with ½, ⅔, et.c label = Replace 1/2 with ½, ⅔, et.c
@@ -26,9 +26,3 @@ type = boolean
label = Replace °C/°F with ℃/℉ label = Replace °C/°F with ℃/℉
width = 1/5 width = 1/5
type = boolean 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 -*- # -*- coding: utf-8 -*-
from lektor.pluginsystem import Plugin from lektor.pluginsystem import Plugin, get_plugin
from lektor.databags import Databags from lektor.databags import Databags
from markupsafe import Markup
from datetime import datetime
import unicodedata import unicodedata
import os
import shutil
# ------- # -------
# Sorting # Sorting
@@ -39,20 +39,17 @@ def noUmlaut(text):
return str(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): def replaceFractions(txt):
res = '' res = ''
for x in txt.split(): for x in txt.split():
try: try:
i = ['1/2', '1/3', '2/3', '1/4', '3/4', '1/8', '-'].index(x) i = ['1/2', '1/3', '2/3', '1/4', '3/4', '1/8'].index(x)
res += [u'½', u'', u'', u'¼', u'¾', u'', u' - '][i] res += [u'½', u'', u'', u'¼', u'¾', u''][i]
except ValueError: except ValueError:
res += ' ' + x if x in u'-–—':
res += u' - '
else:
res += ' ' + x
return res.lstrip() return res.lstrip()
@@ -77,24 +74,6 @@ def updateSet_if(dic, parent, parentkey, value):
dic[key] = set() dic[key] = set()
dic[key].add(value) 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 # Ingredient splitting
@@ -105,13 +84,14 @@ def splitIngredientLine(line):
indices = [0, len(line)] indices = [0, len(line)]
for i, char in enumerate(line): for i, char in enumerate(line):
if char.isspace(): if char.isspace():
capture = False if capture:
indices[state] = i capture = False
state += 1 indices[state] = i
state += 1
continue continue
elif capture: elif capture:
continue continue
elif state == 1 and char in '0123456789-.,': elif state == 1 and char in u'0123456789-–—.,':
state -= 1 state -= 1
elif state > 1: elif state > 1:
break break
@@ -127,6 +107,9 @@ def parseIngredientLine(line, measureList=[], rep_frac=False):
measure = line[idx[0]:idx[1]].lstrip() measure = line[idx[0]:idx[1]].lstrip()
if measure.lower() in measureList: if measure.lower() in measureList:
name = line[idx[1]:].lstrip() name = line[idx[1]:].lstrip()
# if name.startswith('of '):
# measure += ' of'
# name = name[3:]
else: else:
measure = '' measure = ''
name = line[idx[0]:].lstrip() 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] name, note = [x.strip() for x in name_note]
return {'value': val, 'measure': measure, 'name': name, 'note': note} return {'value': val, 'measure': measure, 'name': name, 'note': note}
# --------------------
# Other Helper methods
def replace_atref_urls(text, label=None):
def groupByMergeCluster(dic, arr=[30, 60, 120], reverse=False): if '@' not in text:
arr = sorted([int(x) for x in arr]) return text
groups = dict() result = list()
for key, recipes in dic: for x in text.split():
key = findCluster(key, arr) if x[0] == '@':
if key == 0 and not reverse: x = x[1:]
key = '' result.append(u'<a href="{}">{}</a>'.format(x, label or x))
updateSet_addMultiple(groups, key, recipes) else:
return sorted(groups.items(), reverse=bool(reverse)) result.append(x)
return Markup(' '.join(result))
# ---------------- # ----------------
# Main entry point # Main entry point
@@ -157,68 +139,56 @@ def groupByMergeCluster(dic, arr=[30, 60, 120], reverse=False):
class HelperPlugin(Plugin): class HelperPlugin(Plugin):
name = u'Helper' name = u'Helper'
description = u'Some helper methods, filters, and templates.' description = u'Some helper methods, filters, and templates.'
alt = None buildTime = None
availableTags = set() settings = dict()
translations = dict()
# ----------- # -----------
# Event hooks # Event hooks
# ----------- # -----------
def on_before_build_all(self, builder, **extra): def processCLI(self, extra_flags):
# display only tags that contain at least one recipe 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() pad = self.env.new_pad()
for r in pad.query('recipes'): for alt in self.env.load_config().iter_alternatives():
self.availableTags.update(r['tags']) 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): def on_before_build_all(self, builder, **extra):
# redirect to /en/ build_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
for file in ['index.html']: print('Build time: ' + build_time)
src_f = os.path.join(self.env.root_path, 'root', file) self.env.jinja_env.globals['DATE_NOW'] = build_time
if os.path.exists(src_f): # update project settings once per build
dst_f = os.path.join(builder.destination_path, file) self.processCLI(getattr(builder, 'extra_flags'))
with open(dst_f, 'wb') as df: self.processSettings()
with open(src_f, 'rb') as sf:
shutil.copyfileobj(sf, df)
def on_process_template_context(self, context, **extra): # def on_process_template_context(self, context, **extra):
self.alt = context['alt'] # pass
def on_setup_env(self, **extra): def on_setup_env(self, **extra):
# self.env.load_config().iter_alternatives() def localizeDic(alt, partA, partB=None):
# pad = self.env.new_pad() if alt not in self.translations:
# pad.query('groupby', alt=alt) 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): def ingredientsForRecipe(recipe, alt='en'):
bag = Databags(self.env).lookup('i18n+{}.{}'.format(self.alt, key)) meaList = self.settings[alt]['measures']
return bag[subkey] if subkey else bag repFrac = self.settings[alt]['replFrac']
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']: for line in recipe['ingredients']:
line = line.strip() line = line.strip()
@@ -229,23 +199,21 @@ class HelperPlugin(Plugin):
else: else:
yield parseIngredientLine(line, meaList, repFrac) yield parseIngredientLine(line, meaList, repFrac)
def groupByAttribute(recipeList, attribute): def groupByAttribute(recipeList, attribute, alt='en'):
groups = dict() groups = dict()
for recipe in recipeList: for recipe in recipeList:
if attribute == 'ingredients': if attribute == 'ingredients':
for ing in ingredientsForRecipe(recipe): for ing in ingredientsForRecipe(recipe, alt):
updateSet_if(groups, ing, 'name', recipe) updateSet_if(groups, ing, 'name', recipe)
else: else:
updateSet_if(groups, recipe, attribute, recipe) updateSet_if(groups, recipe, attribute, recipe)
# groups[undefinedKey].update(groups.pop('_undefined')) # groups[undefinedKey].update(groups.pop('_undefined'))
return groups.items() return groups.items()
self.env.jinja_env.filters['duration'] = to_duration
self.env.jinja_env.filters['rating'] = numFillWithText self.env.jinja_env.filters['rating'] = numFillWithText
self.env.jinja_env.filters['replaceFractions'] = replaceFractions self.env.jinja_env.filters['replaceFractions'] = replaceFractions
self.env.jinja_env.filters['enumIngredients'] = ingredientsForRecipe 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['groupByAttribute'] = groupByAttribute
self.env.jinja_env.filters['groupSort'] = groupByDictSort 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['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 + [''] -%} {%- set sortType = this.xdata + [''] -%}
{%- endif -%} {%- 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' -%} {%- if this.group_key == 'time' -%}
{%- set all = all | groupMergeCluster(this.xdata, this.reverse_order) -%} {%- set all = all | groupTimeCluster(this.xdata, this.reverse_order) -%}
{%- endif -%} {%- endif -%}
<h1>{{ this.name }}</h1> <h1>{{ this.name }}</h1>
@@ -22,9 +22,9 @@
{%- elif not attrib -%} {%- elif not attrib -%}
{{ this.null_fallback }} {{ this.null_fallback }}
{%- elif this.group_key == 'time' -%} {%- elif this.group_key == 'time' -%}
{{ attrib | duration(this.xdata) }} {{ attrib | durationCluster(this.xdata, this.alt) }}
{%- elif this.group_key == 'difficulty' -%} {%- elif this.group_key == 'difficulty' -%}
{{ localize('difficulty', attrib) }} {{ localize(this.alt, 'difficulty', attrib) }}
{%- else -%} {%- else -%}
{{ attrib }} {{ attrib }}
{%- endif -%}</dt> {%- endif -%}</dt>

View File

@@ -1,31 +1,39 @@
<!doctype html> <!doctype html>
<meta charset="utf-8"> {%- if ENABLE_APPCACHE %}
<meta name="viewport" content="width=device-width, initial-scale=0.75"> <html manifest="{{ site.get('app.appcache', alt=this.alt)|url }}">
<script type="text/javascript" src="{{ '/static/col2.js'|url }}"></script> {% endif -%}
<script type="text/javascript" src="{{ '/static/lozad.min.js'|url }}"></script> <head>
<link rel="stylesheet" href="{{ '/static/style.css'|url }}"> <meta charset="utf-8">
<title>{% block title %}Welcome{% endblock %} · recipe lekture</title> <meta name="viewport" content="width=device-width, initial-scale=0.75">
<body> <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> <header>
<a id="logo" href="{{ site.get('/', alt=this.alt)|url }}">recipe lekture</a> <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> <nav>
<ul> <ul>
{%- set allRecipes = site.get('recipes', this.alt) %} {%- 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) %} {%- 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> <li><a {% if this.is_child_of(navpage) %}class="active"{% endif %} href="{{ navpage|url }}">{{ navpage.name }}</a></li>
{%- endfor %} {%- endfor %}
</ul> </ul>
</nav> </nav>
<div class="tags small"> <div class="tags small">
{%- set allowEmptyTags = site.get('settings', alt=this.alt)['show_empty_tags'] -%}
{%- for tag in site.query('/tags', this.alt) %} {%- for tag in site.query('/tags', this.alt) %}
{%- if allowEmptyTags or tag._id in availableTags -%}
<a {% <a {%
if this.is_child_of(tag) or (this.tags and tag._id in this.tags) -%} if this.is_child_of(tag) or (this.tags and tag._id in this.tags) -%}
class="active" class="active"
{%- endif %} href="{{ tag|url }}">{{ tag.name }}</a> {%- endif %} href="{{ tag|url }}">{{ tag.name }}</a>
{%- endif -%}
{%- endfor %} {%- endfor %}
</div> </div>
</header> </header>

View File

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

View File

@@ -2,15 +2,20 @@
{% block title %}{{ this.name }}{% endblock %} {% block title %}{{ this.name }}{% endblock %}
{% block body %} {% block body %}
<article class="recipe"> <article class="recipe">
<!-- date added: {{ this.date }} -->
<section id="img-carousel" class="v-scroll center"> <section id="img-carousel" class="v-scroll center">
{%- for img in this.attachments.images|sort(attribute='record_label') %} {%- 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 %} {%- endfor %}
</section> </section>
{% if this.source -%} {% if this.source -%}
<div id="source" class="small center"> <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> </div>
{% endif %} {% endif %}
<h1>{{ this.name }}</h1> <h1>{{ this.name }}</h1>
@@ -20,19 +25,19 @@
<div class="difficulty {{this.difficulty}}"> <div class="difficulty {{this.difficulty}}">
<div></div><div></div><div></div> <div></div><div></div><div></div>
{%- if this.difficulty %} {%- if this.difficulty %}
<span>{{ localize('difficulty', this.difficulty) }}</span> <span>{{ localize(this.alt, 'difficulty', this.difficulty) }}</span>
{%- else %} {%- else %}
<span class="small">{{ localize('difficulty._unset') }}</span> <span class="small">{{ localize(this.alt, 'difficulty._unset') }}</span>
{%- endif %} {%- endif %}
</div> </div>
<div>{{ localize('duration.label') }}: {{ this.time|duration if this.time else '—' }}</div> <div>{{ localize(this.alt, 'duration.label') }}: {{ this.time|duration(this.alt) if this.time else '—' }}</div>
<div>{{ localize('yield.label') }}: {{ this.yield if this.yield else '—' }}</div> <div>{{ localize(this.alt, 'yield.label') }}: {{ this.yield or '—' }}</div>
</section> </section>
<section id="ingredients"> <section id="ingredients">
<h2>{{ localize('title.ingredients') }}:</h2> <h2>{{ localize(this.alt, 'title.ingredients') }}:</h2>
<ul class="no-bullets li-lg-space"> <ul class="no-bullets li-lg-space">
{%- for ing in this|enumIngredients %} {%- for ing in this|enumIngredients(this.alt) %}
{%- if ing['group'] %} {%- if ing['group'] %}
<li class="dark-red bold mrgTopMd">{{ ing['group'] }}</li> <li class="dark-red bold mrgTopMd">{{ ing['group'] }}</li>
{%- else %} {%- else %}
@@ -41,7 +46,7 @@
{%- if ing['measure'] %}{{ ing['measure'] }} {% endif -%} {%- if ing['measure'] %}{{ ing['measure'] }} {% endif -%}
<span class="light-red">{{ ing['name'] }}</span> <span class="light-red">{{ ing['name'] }}</span>
{%- if ing['note'] -%} {%- 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 -%} {%- endif -%}
</li> </li>
{%- endif %} {%- endif %}
@@ -50,9 +55,9 @@
</section> </section>
<section id="directions"> <section id="directions">
<h2>{{ localize('title.directions') }}:</h2> <h2>{{ localize(this.alt, 'title.directions') }}:</h2>
{% if site.get('settings', alt=this.alt)['replace_temp'] -%} {% 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 -%} {% else -%}
{{ this.directions }} {{ this.directions }}
{% endif -%} {% endif -%}

View File

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

View File

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