add "recently added" list
authorLukas Hägele <lukas.haegele93@web.de>
Sun, 9 Mar 2025 14:57:45 +0000 (15:57 +0100)
committerLukas Hägele <lukas.haegele93@web.de>
Sun, 9 Mar 2025 14:57:45 +0000 (15:57 +0100)
content/.template.md.tmp
content/schoko-nusskuchen-mit-kirschen.md
html/index.html
html/style.css
src/html.c
src/main.c
src/util.h

index 30a5f8cdd211d15869b3f08db698e18a0a6cb6ec..584008d0fac22dd1f308d17942db708d0ee92ee1 100644 (file)
@@ -1,6 +1,10 @@
 ---
 #!ini
 
+[details]
+date = 20.01.2025 18:53
+portions = 4
+
 [tags]
 ---
 
index 65896191852fa28b7deffc6bf51588b3666c003b..62bc2534c9afb87598a1302d8c84d3b7ac64f99b 100644 (file)
@@ -1,6 +1,9 @@
 ---
 #!ini
 
+[details]
+date = 02.03.2025 13:55
+
 [tags]
 kuchen
 ---
index 9e5e346bf51111a0c42c9f956f7d1320fee883e8..2525ce4e2d377e4c5ea6991754935161dc475037 100755 (executable)
@@ -1 +1 @@
-<!DOCTYPE html><html lang="de-DE"><head><meta charset="utf-8"><meta content="width=device-width, initial-scale=1" name="viewport"><title>Meine Rezeptsammlung | lhaegele.de</title><link href="/favicon.svg" rel="icon"><link href="/style.css" rel="stylesheet"></head><body><header><h1><a href="/">Meine Rezeptsammlung</a></h1></header><main><article class="overview"><h2>Rezepte</h2><ul class="recipes"><li><a href="/recipes/bohnenmus.html">Bohnenmus</a></li><li><a href="/recipes/briegelschmiere.html">Briegelschmiere</a></li><li><a href="/recipes/brokkoli-nudelauflauf.html">Brokkoli Nudelauflauf</a></li><li><a href="/recipes/burger.html">Burger</a></li><li><a href="/recipes/cannelloni-mit-spinatfuellung.html">Cannelloni mit Spinatfüllung</a></li><li><a href="/recipes/chilaquiles-rancheros.html">Chilaquiles Rancheros</a></li><li><a href="/recipes/chili-con-carne_karin.html">Chili con Carne (Karin)</a></li><li><a href="/recipes/donauwelle.html">Donauwelle</a></li><li><a href="/recipes/einfaches-salatdressing.html">Einfaches Salatdressing</a></li><li><a href="/recipes/feta-oliven-dip.html">Feta-Oliven-Dip</a></li><li><a href="/recipes/fettuccine-mit-trueffeloel.html">Fettuccine mit Trüffelöl</a></li><li><a href="/recipes/gebratene-schweinemedaillons.html">Gebratene Schweinemedaillons</a></li><li><a href="/recipes/gebratene-spaetzle-mit-bacon.html">Gebratene Spätzle mit Bacon</a></li><li><a href="/recipes/gnocchi-bacon-pfanne.html">Gnocchi-Bacon-Pfanne</a></li><li><a href="/recipes/gnocchi-hackfleisch-auflauf.html">Gnocchi-Hackfleisch-Auflauf</a></li><li><a href="/recipes/gnocchi-mit-speck-zucchini.html">Gnocchi mit Speck-Zucchini</a></li><li><a href="/recipes/griesssuppe.html">Grießsuppe</a></li><li><a href="/recipes/gulaschsuppe.html">Gulaschsuppe</a></li><li><a href="/recipes/hackbaellchen-gefuellt-mit-mozzarella.html">Hackbällchen gefüllt mit Mozzarella</a></li><li><a href="/recipes/hackbaellchen-in-senfsosse.html">Hackbällchen in Senfsoße</a></li><li><a href="/recipes/hackbaellchenpfanne-mit-paprikagemuese.html">Hackbällchenpfanne mit Paprikagemüse</a></li><li><a href="/recipes/hackfleischsosse.html">Hackfleischsoße</a></li><li><a href="/recipes/haehnchen-nuggets-auf-cremiger-curry-lauch-pasta.html">Hähnchen Nuggets auf cremiger Curry-Lauch-Pasta</a></li><li><a href="/recipes/haehnchenbrust-in-salbei-thymian-marinade.html">Hähnchenbrust in Salbei-Thymian-Marinade</a></li><li><a href="/recipes/haehnchengeschnetzeltes-mit-spaetzle.html">Hähnchengeschnetzeltes mit Spätzle</a></li><li><a href="/recipes/haferbrei.html">Haferbrei</a></li><li><a href="/recipes/harissa-haenchen-mit-zucchini.html">Harissa-Hähnchen mit Zucchini</a></li><li><a href="/recipes/hazans-basilikum-pesto.html">Hazans Basilikum Pesto</a></li><li><a href="/recipes/hefeteig.html">Hefeteig</a></li><li><a href="/recipes/joghurtdressing.html">Joghurtdressing</a></li><li><a href="/recipes/knoblauchbaguette.html">Knoblauchbaguette</a></li><li><a href="/recipes/lachssahnesosse.html">Lachssahnesoße</a></li><li><a href="/recipes/lasagne.html">Lasagne</a></li><li><a href="/recipes/lauchfrischkaese.html">Lauchfrischkäse</a></li><li><a href="/recipes/lauchgemuese.html">Lauchgemüse</a></li><li><a href="/recipes/linsenbolognese.html">Linsenbolognese</a></li><li><a href="/recipes/linseneintopf.html">Linseneintopf</a></li><li><a href="/recipes/marmorkuchen.html">Marmorkuchen</a></li><li><a href="/recipes/mediterranes-gemuese.html">Mediterranes Gemüse</a></li><li><a href="/recipes/nusszopf.html">Nusszopf</a></li><li><a href="/recipes/orzo-nudel-risotto-mit-rauchigen-pilzen.html">Orzo-Nudel-Risotto mit rauchigen Pilzen</a></li><li><a href="/recipes/pfannkuchen-auflauf.html">Pfannkuchen-Auflauf</a></li><li><a href="/recipes/pfannkuchenteig.html">Pfannkuchenteig</a></li><li><a href="/recipes/power-muesli.html">Power-Müsli</a></li><li><a href="/recipes/putengeschnetzeltes.html">Putengeschnetzeltes</a></li><li><a href="/recipes/quarkoelteig.html">Quarkölteig</a></li><li><a href="/recipes/radieschenfrischkaese.html">Radieschenfrischkäse</a></li><li><a href="/recipes/rauchiges-schweinefilet.html">Rauchiges Schweinefilet</a></li><li><a href="/recipes/reiseintopf-mit-hackfleisch.html">Reiseintopf mit Hackfleisch</a></li><li><a href="/recipes/reiseintopf-mit-haenchenbrust.html">Reiseintopf mit Hähnchenbrust</a></li><li><a href="/recipes/salsa-dip.html">Salsa-Dip</a></li><li><a href="/recipes/schinkennudeln.html">Schinkennudeln</a></li><li><a href="/recipes/schoko-nusskuchen-mit-kirschen.html">Schoko-Nusskuchen mit Kirschen</a></li><li><a href="/recipes/schwedische-frikadellen-mit-kartoffelstampf.html">Schwedische Frikadellen mit Kartoffelstampf</a></li><li><a href="/recipes/schweinelachssteaks-mit-schupfnudeln.html">Schweinelachssteaks mit Schupfnudeln</a></li><li><a href="/recipes/selbstgebackenes-knoblauchbrot.html">Selbstgebackenes Knoblauchbrot</a></li><li><a href="/recipes/spaetzle.html">Spätzle</a></li><li><a href="/recipes/spinatquiche.html">Spinatquiche</a></li><li><a href="/recipes/thailaendisches-kokos-limetten-curry.html">Thailändisches Kokos-Limetten-Curry</a></li><li><a href="/recipes/thunfischsalat.html">Thunfischsalat</a></li><li><a href="/recipes/thunfischsosse.html">Thunfischsoße</a></li><li><a href="/recipes/tomatensosse.html">Tomatensoße</a></li><li><a href="/recipes/tortilla.html">Tortilla</a></li><li><a href="/recipes/toskanischer-filettopf.html">Toskanischer Filettopf</a></li><li><a href="/recipes/vietnamesisches-rindfleisch.html">Vietnamesisches Rindfleisch</a></li><li><a href="/recipes/weihnachtliche-entenbrust-an-wintergemuese.html">Weihnachtliche Entenbrust an Wintergemüse</a></li><li><a href="/recipes/wurstsalat.html">Wurstsalat</a></li><li><a href="/recipes/zitronen-haenchenkeule-mit-tabbouleh.html">Zitronen-Hähnchenkeule mit Tabbouleh</a></li><li><a href="/recipes/zitronenkuchen.html">Zitronenkuchen</a></li><li><a href="/recipes/zucchini-lasagne.html">Zucchini-Lasagne</a></li><li><a href="/recipes/zucchinicremesuppe.html">Zucchinicremesuppe</a></li><li><a href="/recipes/zuckerglasur.html">Zuckerglasur</a></li></ul><h2>Tags</h2><ul class="tags"><li><a href="/tags/amerikanisch.html">#amerikanisch</a></li><li><a href="/tags/auflauf.html">#auflauf</a></li><li><a href="/tags/beilagen.html">#beilagen</a></li><li><a href="/tags/deutsch.html">#deutsch</a></li><li><a href="/tags/dip.html">#dip</a></li><li><a href="/tags/eintopf.html">#eintopf</a></li><li><a href="/tags/französisch.html">#französisch</a></li><li><a href="/tags/frühstück.html">#frühstück</a></li><li><a href="/tags/grillen.html">#grillen</a></li><li><a href="/tags/grundlagen.html">#grundlagen</a></li><li><a href="/tags/hauptgericht.html">#hauptgericht</a></li><li><a href="/tags/hellofresh.html">#hellofresh</a></li><li><a href="/tags/hähnchen.html">#hähnchen</a></li><li><a href="/tags/italienisch.html">#italienisch</a></li><li><a href="/tags/kuchen.html">#kuchen</a></li><li><a href="/tags/mexikanisch.html">#mexikanisch</a></li><li><a href="/tags/nudeln.html">#nudeln</a></li><li><a href="/tags/party.html">#party</a></li><li><a href="/tags/pfannkuchen.html">#pfannkuchen</a></li><li><a href="/tags/reis.html">#reis</a></li><li><a href="/tags/salat.html">#salat</a></li><li><a href="/tags/scharf.html">#scharf</a></li><li><a href="/tags/schnell.html">#schnell</a></li><li><a href="/tags/schupfnudeln.html">#schupfnudeln</a></li><li><a href="/tags/schwedisch.html">#schwedisch</a></li><li><a href="/tags/soße.html">#soße</a></li><li><a href="/tags/spanisch.html">#spanisch</a></li><li><a href="/tags/spätzle.html">#spätzle</a></li><li><a href="/tags/suppe.html">#suppe</a></li><li><a href="/tags/tex-mex.html">#tex-mex</a></li><li><a href="/tags/thailändisch.html">#thailändisch</a></li><li><a href="/tags/thermomix.html">#thermomix</a></li><li><a href="/tags/vegan.html">#vegan</a></li><li><a href="/tags/vegetarisch.html">#vegetarisch</a></li><li><a href="/tags/vesper.html">#vesper</a></li><li><a href="/tags/vietnamesisch.html">#vietnamesisch</a></li><li><a href="/tags/weihnachten.html">#weihnachten</a></li></ul></article></main></body></html>
\ No newline at end of file
+<!DOCTYPE html><html lang="de-DE"><head><meta charset="utf-8"><meta content="width=device-width, initial-scale=1" name="viewport"><title>Meine Rezeptsammlung | lhaegele.de</title><link href="/favicon.svg" rel="icon"><link href="/style.css" rel="stylesheet"></head><body><header><h1><a href="/">Meine Rezeptsammlung</a></h1></header><main><article class="overview"><h2>Zuletzt hinzugefügt</h2><ul class="recent"><li><a href="/recipes/schoko-nusskuchen-mit-kirschen.html">Schoko-Nusskuchen mit Kirschen (02.03.2025)</a></li><li><a href="/recipes/brokkoli-nudelauflauf.html">Brokkoli Nudelauflauf (20.01.2025)</a></li></ul><h2>Rezepte</h2><ul class="recipes"><li><a href="/recipes/bohnenmus.html">Bohnenmus</a></li><li><a href="/recipes/briegelschmiere.html">Briegelschmiere</a></li><li><a href="/recipes/brokkoli-nudelauflauf.html">Brokkoli Nudelauflauf</a></li><li><a href="/recipes/burger.html">Burger</a></li><li><a href="/recipes/cannelloni-mit-spinatfuellung.html">Cannelloni mit Spinatfüllung</a></li><li><a href="/recipes/chilaquiles-rancheros.html">Chilaquiles Rancheros</a></li><li><a href="/recipes/chili-con-carne_karin.html">Chili con Carne (Karin)</a></li><li><a href="/recipes/donauwelle.html">Donauwelle</a></li><li><a href="/recipes/einfaches-salatdressing.html">Einfaches Salatdressing</a></li><li><a href="/recipes/feta-oliven-dip.html">Feta-Oliven-Dip</a></li><li><a href="/recipes/fettuccine-mit-trueffeloel.html">Fettuccine mit Trüffelöl</a></li><li><a href="/recipes/gebratene-schweinemedaillons.html">Gebratene Schweinemedaillons</a></li><li><a href="/recipes/gebratene-spaetzle-mit-bacon.html">Gebratene Spätzle mit Bacon</a></li><li><a href="/recipes/gnocchi-bacon-pfanne.html">Gnocchi-Bacon-Pfanne</a></li><li><a href="/recipes/gnocchi-hackfleisch-auflauf.html">Gnocchi-Hackfleisch-Auflauf</a></li><li><a href="/recipes/gnocchi-mit-speck-zucchini.html">Gnocchi mit Speck-Zucchini</a></li><li><a href="/recipes/griesssuppe.html">Grießsuppe</a></li><li><a href="/recipes/gulaschsuppe.html">Gulaschsuppe</a></li><li><a href="/recipes/hackbaellchen-gefuellt-mit-mozzarella.html">Hackbällchen gefüllt mit Mozzarella</a></li><li><a href="/recipes/hackbaellchen-in-senfsosse.html">Hackbällchen in Senfsoße</a></li><li><a href="/recipes/hackbaellchenpfanne-mit-paprikagemuese.html">Hackbällchenpfanne mit Paprikagemüse</a></li><li><a href="/recipes/hackfleischsosse.html">Hackfleischsoße</a></li><li><a href="/recipes/haehnchen-nuggets-auf-cremiger-curry-lauch-pasta.html">Hähnchen Nuggets auf cremiger Curry-Lauch-Pasta</a></li><li><a href="/recipes/haehnchenbrust-in-salbei-thymian-marinade.html">Hähnchenbrust in Salbei-Thymian-Marinade</a></li><li><a href="/recipes/haehnchengeschnetzeltes-mit-spaetzle.html">Hähnchengeschnetzeltes mit Spätzle</a></li><li><a href="/recipes/haferbrei.html">Haferbrei</a></li><li><a href="/recipes/harissa-haenchen-mit-zucchini.html">Harissa-Hähnchen mit Zucchini</a></li><li><a href="/recipes/hazans-basilikum-pesto.html">Hazans Basilikum Pesto</a></li><li><a href="/recipes/hefeteig.html">Hefeteig</a></li><li><a href="/recipes/joghurtdressing.html">Joghurtdressing</a></li><li><a href="/recipes/knoblauchbaguette.html">Knoblauchbaguette</a></li><li><a href="/recipes/lachssahnesosse.html">Lachssahnesoße</a></li><li><a href="/recipes/lasagne.html">Lasagne</a></li><li><a href="/recipes/lauchfrischkaese.html">Lauchfrischkäse</a></li><li><a href="/recipes/lauchgemuese.html">Lauchgemüse</a></li><li><a href="/recipes/linsenbolognese.html">Linsenbolognese</a></li><li><a href="/recipes/linseneintopf.html">Linseneintopf</a></li><li><a href="/recipes/marmorkuchen.html">Marmorkuchen</a></li><li><a href="/recipes/mediterranes-gemuese.html">Mediterranes Gemüse</a></li><li><a href="/recipes/nusszopf.html">Nusszopf</a></li><li><a href="/recipes/orzo-nudel-risotto-mit-rauchigen-pilzen.html">Orzo-Nudel-Risotto mit rauchigen Pilzen</a></li><li><a href="/recipes/pfannkuchen-auflauf.html">Pfannkuchen-Auflauf</a></li><li><a href="/recipes/pfannkuchenteig.html">Pfannkuchenteig</a></li><li><a href="/recipes/power-muesli.html">Power-Müsli</a></li><li><a href="/recipes/putengeschnetzeltes.html">Putengeschnetzeltes</a></li><li><a href="/recipes/quarkoelteig.html">Quarkölteig</a></li><li><a href="/recipes/radieschenfrischkaese.html">Radieschenfrischkäse</a></li><li><a href="/recipes/rauchiges-schweinefilet.html">Rauchiges Schweinefilet</a></li><li><a href="/recipes/reiseintopf-mit-hackfleisch.html">Reiseintopf mit Hackfleisch</a></li><li><a href="/recipes/reiseintopf-mit-haenchenbrust.html">Reiseintopf mit Hähnchenbrust</a></li><li><a href="/recipes/salsa-dip.html">Salsa-Dip</a></li><li><a href="/recipes/schinkennudeln.html">Schinkennudeln</a></li><li><a href="/recipes/schoko-nusskuchen-mit-kirschen.html">Schoko-Nusskuchen mit Kirschen</a></li><li><a href="/recipes/schwedische-frikadellen-mit-kartoffelstampf.html">Schwedische Frikadellen mit Kartoffelstampf</a></li><li><a href="/recipes/schweinelachssteaks-mit-schupfnudeln.html">Schweinelachssteaks mit Schupfnudeln</a></li><li><a href="/recipes/selbstgebackenes-knoblauchbrot.html">Selbstgebackenes Knoblauchbrot</a></li><li><a href="/recipes/spaetzle.html">Spätzle</a></li><li><a href="/recipes/spinatquiche.html">Spinatquiche</a></li><li><a href="/recipes/thailaendisches-kokos-limetten-curry.html">Thailändisches Kokos-Limetten-Curry</a></li><li><a href="/recipes/thunfischsalat.html">Thunfischsalat</a></li><li><a href="/recipes/thunfischsosse.html">Thunfischsoße</a></li><li><a href="/recipes/tomatensosse.html">Tomatensoße</a></li><li><a href="/recipes/tortilla.html">Tortilla</a></li><li><a href="/recipes/toskanischer-filettopf.html">Toskanischer Filettopf</a></li><li><a href="/recipes/vietnamesisches-rindfleisch.html">Vietnamesisches Rindfleisch</a></li><li><a href="/recipes/weihnachtliche-entenbrust-an-wintergemuese.html">Weihnachtliche Entenbrust an Wintergemüse</a></li><li><a href="/recipes/wurstsalat.html">Wurstsalat</a></li><li><a href="/recipes/zitronen-haenchenkeule-mit-tabbouleh.html">Zitronen-Hähnchenkeule mit Tabbouleh</a></li><li><a href="/recipes/zitronenkuchen.html">Zitronenkuchen</a></li><li><a href="/recipes/zucchini-lasagne.html">Zucchini-Lasagne</a></li><li><a href="/recipes/zucchinicremesuppe.html">Zucchinicremesuppe</a></li><li><a href="/recipes/zuckerglasur.html">Zuckerglasur</a></li></ul><h2>Tags</h2><ul class="tags"><li><a href="/tags/amerikanisch.html">#amerikanisch</a></li><li><a href="/tags/auflauf.html">#auflauf</a></li><li><a href="/tags/beilagen.html">#beilagen</a></li><li><a href="/tags/deutsch.html">#deutsch</a></li><li><a href="/tags/dip.html">#dip</a></li><li><a href="/tags/eintopf.html">#eintopf</a></li><li><a href="/tags/französisch.html">#französisch</a></li><li><a href="/tags/frühstück.html">#frühstück</a></li><li><a href="/tags/grillen.html">#grillen</a></li><li><a href="/tags/grundlagen.html">#grundlagen</a></li><li><a href="/tags/hauptgericht.html">#hauptgericht</a></li><li><a href="/tags/hellofresh.html">#hellofresh</a></li><li><a href="/tags/hähnchen.html">#hähnchen</a></li><li><a href="/tags/italienisch.html">#italienisch</a></li><li><a href="/tags/kuchen.html">#kuchen</a></li><li><a href="/tags/mexikanisch.html">#mexikanisch</a></li><li><a href="/tags/nudeln.html">#nudeln</a></li><li><a href="/tags/party.html">#party</a></li><li><a href="/tags/pfannkuchen.html">#pfannkuchen</a></li><li><a href="/tags/reis.html">#reis</a></li><li><a href="/tags/salat.html">#salat</a></li><li><a href="/tags/scharf.html">#scharf</a></li><li><a href="/tags/schnell.html">#schnell</a></li><li><a href="/tags/schupfnudeln.html">#schupfnudeln</a></li><li><a href="/tags/schwedisch.html">#schwedisch</a></li><li><a href="/tags/soße.html">#soße</a></li><li><a href="/tags/spanisch.html">#spanisch</a></li><li><a href="/tags/spätzle.html">#spätzle</a></li><li><a href="/tags/suppe.html">#suppe</a></li><li><a href="/tags/tex-mex.html">#tex-mex</a></li><li><a href="/tags/thailändisch.html">#thailändisch</a></li><li><a href="/tags/thermomix.html">#thermomix</a></li><li><a href="/tags/vegan.html">#vegan</a></li><li><a href="/tags/vegetarisch.html">#vegetarisch</a></li><li><a href="/tags/vesper.html">#vesper</a></li><li><a href="/tags/vietnamesisch.html">#vietnamesisch</a></li><li><a href="/tags/weihnachten.html">#weihnachten</a></li></ul></article></main></body></html>
\ No newline at end of file
index e4d5af0a3ba896c5516929d4be0815a877784547..d661af2d8dfeb4b7532131e455fe55685718c201 100644 (file)
@@ -84,7 +84,11 @@ ul {
     ul.recipes {
         column-count: 3;
     }
-
+    ul.recent {
+        display: flex;
+        flex-direction: column;
+        justify-content: center;
+    }
     ul.tags {
         column-count: 4;
     }
index 681cb1170834d0712e88c697322fa3fe426d9a46..fd53e5ca6984c497acae35491e0ee63af0610e4b 100644 (file)
@@ -73,9 +73,25 @@ typedef struct html_tag
     str Content;
 } html_tag;
 
+typedef struct
+{
+    u16 Year;
+    u8 Month;
+    u8 Day;
+    u8 Hour;
+    u8 Minute;
+} html_datetime;
+
+typedef struct
+{
+    str Str;
+    html_datetime Datetime;
+} html_date;
+
 typedef struct
 {
     html_tag* Tags;
+    html_date Date;
 } html_meta;
 
 typedef struct
@@ -512,6 +528,37 @@ parseParagraph(str Line, html_element* PrevElement, arena* Arena)
 #endif
 }
 
+static inline u16
+u16AtOffset(str Str, memory_size Offset, memory_size Length)
+{
+    str Substr = str_view(Str, Offset, Length);
+    u16 Result = str_toU16(Substr);
+    return Result;
+}
+
+static inline html_datetime
+calcEpoch(str Str)
+{
+    html_datetime Datetime = {0};
+
+    /* DD.MM.YYYY HH:MM */
+    Datetime.Day     = u16AtOffset(Str,  0, 2);
+    Datetime.Month   = u16AtOffset(Str,  3, 2);
+    Datetime.Year    = u16AtOffset(Str,  6, 4);
+    Datetime.Hour    = u16AtOffset(Str, 11, 2);
+    Datetime.Minute  = u16AtOffset(Str, 14, 2);
+
+    return Datetime;
+}
+
+typedef enum
+{
+    SECTION_TYPE_none,
+
+    SECTION_TYPE_details,
+    SECTION_TYPE_tags
+} SECTION_TYPE;
+
 static html*
 html_parseMarkdown(str Source, arena* Arena)
 {
@@ -522,8 +569,7 @@ html_parseMarkdown(str Source, arena* Arena)
     b32 ExpectShebang = 0;
     b32 SkipIniSection = 0;
 
-    // todo: remove
-    //b32 PreviousLineWasEmpty = 0;
+    SECTION_TYPE SectionType = SECTION_TYPE_none;
 
     html_element* PrevElement = Html->Article;
     for (str Line = str_getLine(Source);
@@ -541,7 +587,6 @@ html_parseMarkdown(str Source, arena* Arena)
                 {
                     Frontmatter = 1;
                     ExpectShebang = 1;
-                    //PreviousLineWasEmpty = 0;
                 }
                 else if (str_startsWith(Stripped, STR_LITERAL("#")))
                 {
@@ -551,19 +596,14 @@ html_parseMarkdown(str Source, arena* Arena)
                         str_startsWith(Line, STR_LITERAL("*")))
                 {
                     PrevElement = parseListItem(Stripped, PrevElement, Arena);
-                    //PreviousLineWasEmpty = 0;
                 }
                 else if (Stripped.Length > 0)
                 {
-                    /* todo: check if `PreviousLineWasEmpty` is really necessary */
                     PrevElement = parseParagraph(Stripped, PrevElement, Arena);
-                    //PrevElement = parseParagraph(Stripped, PreviousLineWasEmpty, PrevElement, Arena);
-                    //PreviousLineWasEmpty = 0;
                 }
             }
             else
             {
-                //PreviousLineWasEmpty = 1;
                 PrevElement = Html->Article;
             }
         }
@@ -591,9 +631,13 @@ html_parseMarkdown(str Source, arena* Arena)
                 if (0);
                 else if (str_startsWith(Stripped, STR_LITERAL("[")))
                 {
-                    if (str_equals(Stripped, STR_LITERAL("[tags]")))
+                    if (str_equals(Stripped, STR_LITERAL("[details]")))
                     {
-                        /* skip section header */
+                        SectionType = SECTION_TYPE_details;
+                    }
+                    else if (str_equals(Stripped, STR_LITERAL("[tags]")))
+                    {
+                        SectionType = SECTION_TYPE_tags;
                     }
                     else
                     {
@@ -607,8 +651,32 @@ html_parseMarkdown(str Source, arena* Arena)
                 }
                 else if (Stripped.Length > 0)
                 {
-                    html_tag* Sentinel = Html->Meta->Tags;
-                    appendTag(Sentinel, Stripped, Arena);
+                    switch (SectionType)
+                    {
+                        case SECTION_TYPE_details:
+                        {
+                            if (str_startsWith(Stripped, STR_LITERAL("date")))
+                            {
+                                html_date* Date = &Html->Meta->Date;
+                                Stripped = str_removeStart(Stripped, STR_LITERAL("date"));
+                                Stripped = str_removeStart(Stripped, STR_LITERAL("="));
+                                Date->Str = Stripped;
+                                Date->Datetime = calcEpoch(Date->Str);
+                            }
+                            else if (str_startsWith(Stripped, STR_LITERAL("portions")))
+                            {
+                                /* todo: implement to process "portions" info */
+                            }
+                        } break;
+
+                        case SECTION_TYPE_tags:
+                        {
+                            html_tag* Sentinel = Html->Meta->Tags;
+                            appendTag(Sentinel, Stripped, Arena);
+                        } break;
+
+                        INVALID_DEFAULT;
+                    }
                 }
                 else
                 {
index 084958f27024b19382188878cf07006245d91c7f..3b00a8d6fce1904b18cbabab11d0a331d7400998 100644 (file)
@@ -53,6 +53,14 @@ typedef struct recipe
     str Name;
 } recipe;
 
+typedef struct date
+{
+    struct date* Next;
+    struct date* Prev;
+
+    recipe* Recipe;
+} date;
+
 typedef struct image
 {
     struct image* Next;
@@ -194,6 +202,47 @@ searchImage(recipe* Recipe, image* Images)
     return Image;
 }
 
+static inline b32 isValid_datetime(html_datetime Datetime)
+{
+    b32 Result = 0;
+
+    if (Datetime.Year && Datetime.Month && Datetime.Day &&
+        Datetime.Hour && Datetime.Minute)
+    {
+        Result = 1;
+    }
+
+    return Result;
+}
+
+static inline b32 isNewerThan(html_datetime D, html_datetime Ref)
+{
+    b32 Result = 0;
+
+    if (D.Year > Ref.Year)
+    {
+        Result = 1;
+    }
+    else if (D.Month > Ref.Month)
+    {
+        Result = 1;
+    }
+    else if (D.Day > Ref.Day)
+    {
+        Result = 1;
+    }
+    else if (D.Hour > Ref.Hour)
+    {
+        Result = 1;
+    }
+    else if (D.Minute > Ref.Minute)
+    {
+        Result = 1;
+    }
+
+    return Result;
+}
+
 static u32
 generateHtmlFile(str Filename, html* Html, arena* Arena)
 {
@@ -237,22 +286,55 @@ generateHtmlFile(str Filename, html* Html, arena* Arena)
 }
 
 static void
-appendRecipeLink(html_element* RecipeElement, recipe* Recipe, arena* Arena)
+appendLink(html_element* Parent, str Link, str Content, arena* Arena)
 {
     html_element* RecipeLink = html_createElement(HTML_ELEMENT_NAME_a, Arena);
+    html_appendContent(RecipeLink, Content, Arena);
+    html_prependAttribute(RecipeLink, STR_LITERAL("href"), Link, Arena);
+    html_appendChild(Parent, RecipeLink);
+}
+
+static str
+generateRecipePath(recipe* Recipe, arena* Arena)
+{
+    str_unbounded uPath = str_startUnbounded(Arena);
     {
-        str_unbounded uPath = str_startUnbounded(Arena);
-        {
-            str_append(&uPath.Str, STR_LITERAL("/recipes/"));
-            str_append(&uPath.Str, str_stripExtension(Recipe->Filename));
-            str_append(&uPath.Str, STR_LITERAL(".html"));
-        }
-        str Link = str_endUnbounded(uPath);
+        str_append(&uPath.Str, STR_LITERAL("/recipes/"));
+        str_append(&uPath.Str, str_stripExtension(Recipe->Filename));
+        str_append(&uPath.Str, STR_LITERAL(".html"));
+    }
+
+    str Path = str_endUnbounded(uPath);
+    return Path;
+}
 
-        html_prependAttribute(RecipeLink, STR_LITERAL("href"), Link, Arena);
-        html_appendContent(RecipeLink, Recipe->Name, Arena);
-        html_appendChild(RecipeElement, RecipeLink);
+static void
+appendRecipeLink(html_element* RecipeElement, recipe* Recipe, arena* Arena)
+{
+    str Path = generateRecipePath(Recipe, Arena);
+    appendLink(RecipeElement, Path, Recipe->Name, Arena);
+}
+
+static void
+appendRecipeLinkWithDatetime(html_element* RecipeElement, date* Date, arena* Arena)
+{
+    recipe* Recipe = Date->Recipe;
+
+    str Path = generateRecipePath(Recipe, Arena);
+
+    str_unbounded uContent = str_startUnbounded(Arena);
+    {
+        str_append(&uContent.Str, Recipe->Name);
+        str_append(&uContent.Str, STR_LITERAL(" ("));
+        html_date Date = Recipe->Html->Meta->Date;
+        /* cut off the time */
+        str DateStr = str_view(Date.Str, 0u, 10u);
+        str_append(&uContent.Str, DateStr);
+        str_append(&uContent.Str, STR_LITERAL(")"));
     }
+    str Content = str_endUnbounded(uContent);
+
+    appendLink(RecipeElement, Path, Content, Arena);
 }
 
 int
@@ -268,6 +350,7 @@ main(int ArgumentCount, char** Arguments)
         recipe* Recipes;
         tag*    Tags;
         image*  Images;
+        date*   Dates;
 
         arena MainArena;
     } Context = {0};
@@ -578,6 +661,52 @@ main(int ArgumentCount, char** Arguments)
         }
     }
 
+    /* allocate date sentinel */
+    {
+        date* DateSentinel = ARENA_PUSH_STRUCT(&Context.MainArena, date);
+        {
+            DateSentinel->Next = DateSentinel;
+            DateSentinel->Prev = DateSentinel;
+
+            Context.Dates = DateSentinel;
+        }
+    }
+
+    /* distill sorted date list */
+    {
+        recipe* RecipeSentinel = Context.Recipes;
+        for (recipe* At = RecipeSentinel->Next; At != RecipeSentinel; At = At->Next)
+        {
+            html_datetime Datetime = At->Html->Meta->Date.Datetime;
+            if (isValid_datetime(Datetime))
+            {
+                date* Date = ARENA_PUSH_STRUCT(&Context.MainArena, date);
+                Date->Recipe = At;
+
+                date* DateSentinel = Context.Dates;
+                date* Next = DateSentinel;
+                if (DateSentinel->Next != DateSentinel)
+                {
+                    for (date* Comp = DateSentinel->Next; Comp != DateSentinel; Comp = Comp->Next)
+                    {
+                        html_datetime CompDatetime = Comp->Recipe->Html->Meta->Date.Datetime;
+                        if (isNewerThan(Datetime, CompDatetime))
+                        {
+                            Next = Comp;
+                            break;
+                        }
+                    }
+                }
+
+                Date->Next = Next;
+                Date->Prev = Next->Prev;
+
+                Next->Prev->Next = Date;
+                Next->Prev = Date;
+            }
+        }
+    }
+
     /* generate html output */
     {
         /* main page */
@@ -586,6 +715,33 @@ main(int ArgumentCount, char** Arguments)
             html* MainPage = html_createDefault(&Context.MainArena);
             html_setArticleClass(MainPage, STR_LITERAL("overview"), &Context.MainArena);
 
+            /* recently added */
+            {
+                html_element* Recent = html_createElement(HTML_ELEMENT_NAME_h2, &Context.MainArena);
+                html_appendArticleSection(MainPage, Recent);
+                html_appendContent(Recent, STR_LITERAL("Zuletzt hinzugefügt"), &Context.MainArena);
+
+                html_element* RecentList = html_createElement(HTML_ELEMENT_NAME_ul, &Context.MainArena);
+                html_appendArticleSection(MainPage, RecentList);
+                html_prependAttribute(RecentList, STR_LITERAL("class"), STR_LITERAL("recent"), &Context.MainArena);
+
+                u8 RecentCount = 0u;
+                date* Sentinel = Context.Dates;
+                for (date* At = Sentinel->Next; At != Sentinel; At = At->Next)
+                {
+                    html_element* RecentElement = html_createElement(HTML_ELEMENT_NAME_li, &Context.MainArena);
+                    {
+                        html_appendChild(RecentList, RecentElement);
+                        appendRecipeLinkWithDatetime(RecentElement, At, &Context.MainArena);
+                    }
+
+                    if (++RecentCount > 3)
+                    {
+                        break;
+                    }
+                }
+            }
+
             /* recipes */
             {
                 html_element* Recipes = html_createElement(HTML_ELEMENT_NAME_h2, &Context.MainArena);
@@ -608,60 +764,61 @@ main(int ArgumentCount, char** Arguments)
                         }
                     }
                 }
+            }
 
-                /* tags */
+            /* tags */
+            {
+                html_element* Tags = html_createElement(HTML_ELEMENT_NAME_h2, &Context.MainArena);
+                html_appendArticleSection(MainPage, Tags);
+                html_appendContent(Tags, STR_LITERAL("Tags"), &Context.MainArena);
+
+                html_element* TagList = html_createElement(HTML_ELEMENT_NAME_ul, &Context.MainArena);
                 {
-                    html_element* Tags = html_createElement(HTML_ELEMENT_NAME_h2, &Context.MainArena);
-                    html_appendArticleSection(MainPage, Tags);
-                    html_appendContent(Tags, STR_LITERAL("Tags"), &Context.MainArena);
+                    html_appendArticleSection(MainPage, TagList);
+                    html_prependAttribute(TagList, STR_LITERAL("class"), STR_LITERAL("tags"), &Context.MainArena);
 
-                    html_element* TagList = html_createElement(HTML_ELEMENT_NAME_ul, &Context.MainArena);
+                    tag* Sentinel = Context.Tags;
+                    for (tag* Tag = Sentinel->Next; Tag != Sentinel; Tag = Tag->Next)
                     {
-                        html_appendArticleSection(MainPage, TagList);
-                        html_prependAttribute(TagList, STR_LITERAL("class"), STR_LITERAL("tags"), &Context.MainArena);
-
-                        tag* Sentinel = Context.Tags;
-                        for (tag* Tag = Sentinel->Next; Tag != Sentinel; Tag = Tag->Next)
+                        html_element* TagElement = html_createElement(HTML_ELEMENT_NAME_li, &Context.MainArena);
                         {
-                            html_element* TagElement = html_createElement(HTML_ELEMENT_NAME_li, &Context.MainArena);
-                            {
-                                html_appendChild(TagList, TagElement);
+                            html_appendChild(TagList, TagElement);
 
-                                html_element* TagLink = html_createElement(HTML_ELEMENT_NAME_a, &Context.MainArena);
+                            html_element* TagLink = html_createElement(HTML_ELEMENT_NAME_a, &Context.MainArena);
+                            {
+                                str_unbounded uPath = str_startUnbounded(&Context.MainArena);
                                 {
-                                    str_unbounded uPath = str_startUnbounded(&Context.MainArena);
-                                    {
-                                        str_append(&uPath.Str, STR_LITERAL("/tags/"));
-                                        str_append(&uPath.Str, Tag->Name);
-                                        str_append(&uPath.Str, STR_LITERAL(".html"));
-                                    }
-                                    str Link = str_endUnbounded(uPath);
-
-                                    html_prependAttribute(TagLink, STR_LITERAL("href"), Link, &Context.MainArena);
-                                    html_appendContent(TagLink, STR_LITERAL("#"), &Context.MainArena);
-                                    html_appendContent(TagLink, Tag->Name, &Context.MainArena);
-                                    html_appendChild(TagElement, TagLink);
+                                    str_append(&uPath.Str, STR_LITERAL("/tags/"));
+                                    str_append(&uPath.Str, Tag->Name);
+                                    str_append(&uPath.Str, STR_LITERAL(".html"));
                                 }
+                                str Link = str_endUnbounded(uPath);
+
+                                html_prependAttribute(TagLink, STR_LITERAL("href"), Link, &Context.MainArena);
+                                html_appendContent(TagLink, STR_LITERAL("#"), &Context.MainArena);
+                                html_appendContent(TagLink, Tag->Name, &Context.MainArena);
+                                html_appendChild(TagElement, TagLink);
                             }
                         }
                     }
                 }
+            }
 
-                /* serialize html tree */
+
+            /* serialize html tree */
+            {
+                arena TempArena = Context.MainArena;
+                str_unbounded uPath = str_startUnbounded(&TempArena);
                 {
-                    arena TempArena = Context.MainArena;
-                    str_unbounded uPath = str_startUnbounded(&TempArena);
-                    {
-                        appendTerminatedDirectory(&uPath.Str, Context.OutputDir);
-                        str_append(&uPath.Str, STR_LITERAL("index.html"));
-                    }
-                    str Filepath = str_endUnbounded(uPath);
+                    appendTerminatedDirectory(&uPath.Str, Context.OutputDir);
+                    str_append(&uPath.Str, STR_LITERAL("index.html"));
+                }
+                str Filepath = str_endUnbounded(uPath);
 
-                    if (generateHtmlFile(Filepath, MainPage, &Context.MainArena) != 0)
-                    {
-                        fprintf(stderr, "Error while generating main page. Stopping.\n");
-                        return -1;
-                    }
+                if (generateHtmlFile(Filepath, MainPage, &Context.MainArena) != 0)
+                {
+                    fprintf(stderr, "Error while generating main page. Stopping.\n");
+                    return -1;
                 }
             }
         }
index f50288c47510a142c80ff8150d474b73876e4eb3..d31ff216294a324020c8c4eefd177582d4ede957 100644 (file)
@@ -10,6 +10,8 @@
 
 #define ASSERT(Cond_)       if ((Cond_) == 0) { *(volatile int*)0 = 0; }
 #define INVALID_CODE_PATH   ASSERT(!"invalid code path")
+#define INVALID_DEFAULT     default: { INVALID_CODE_PATH; break; }
+#define NOT_YET_IMPLEMENTED ASSERT(!"not yet implemented")
 
 #define MIN(A_, B_) (((A_) < (B_)) ? (A_) : (B_))
 
@@ -17,6 +19,7 @@
 
 #include <stdint.h>
 typedef uint8_t  u8;
+typedef uint16_t u16;
 typedef uint32_t u32;
 typedef uint64_t u64;
 typedef int32_t  s32;
@@ -297,6 +300,40 @@ str_startsWith(str A, str Start)
     return Result;
 }
 
+static inline str
+str_view(str Str, memory_size Offset, memory_size Length)
+{
+    str Result = {0};
+
+    Result.Base = &Str.Base[Offset];
+    Result.Length = Length;
+    Result.Capacity = (Str.Capacity) ? (Str.Capacity - Offset) : 0u;
+
+    return Result;
+}
+
+static inline str
+str_viewAtOffset(str Str, memory_size Offset)
+{
+    memory_size Length = Str.Length - Offset;
+    str Result = str_view(Str, Offset, Length);
+    return Result;
+}
+
+static inline str
+str_removeStart(str Str, str Start)
+{
+    str Result = Str;
+
+    if (str_startsWith(Str, Start))
+    {
+        Result = str_viewAtOffset(Str, Start.Length);
+        Result = str_stripHead(Result);
+    }
+
+    return Result;
+}
+
 static inline b32
 str_endsWith(str A, str End)
 {
@@ -438,3 +475,27 @@ str_fromCString(arena* Arena, char* CStr)
     return Result;
 }
 
+static inline u16
+str_toU16(str Str)
+{
+    u16 Result = 0u;
+
+    for (memory_size i = 0u; i < Str.Length; i++)
+    {
+        memory_size Index = Str.Length - i - 1u;
+        u8 DigitLetter = Str.Base[Index];
+        u8 Digit = DigitLetter - '0';
+
+        u16 DecadeFactor = 1;
+        memory_size Decade = i;
+        while (Decade--)
+        {
+            DecadeFactor *= 10;
+        }
+
+        Result += Digit * DecadeFactor;
+    }
+
+    return Result;
+}
+