From ec9ff5a26b6c47147c08d7e5438840d8623aad37 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Lukas=20H=C3=A4gele?= Date: Sun, 3 Nov 2024 16:20:17 +0100 Subject: [PATCH] add initial styling --- favicon.svg | 2 ++ src/html.c | 102 +++++++++++++++++++++++++++++++++++----------------- src/main.c | 60 +++++++++++++++---------------- style.css | 71 ++++++++++++++++++++++++++++++++++++ 4 files changed, 172 insertions(+), 63 deletions(-) create mode 100644 favicon.svg create mode 100644 style.css diff --git a/favicon.svg b/favicon.svg new file mode 100644 index 0000000..8cf2c98 --- /dev/null +++ b/favicon.svg @@ -0,0 +1,2 @@ + + diff --git a/src/html.c b/src/html.c index 0fe77f4..e11256c 100644 --- a/src/html.c +++ b/src/html.c @@ -16,6 +16,7 @@ X(head) \ X(meta) \ X(title) \ + X(link) \ X(style) \ X(body) \ X(header) \ @@ -73,7 +74,6 @@ typedef struct html_tag typedef struct { - str Title; html_tag* Tags; } html_meta; @@ -124,7 +124,7 @@ createMeta(arena* Arena) } static inline void -html_addAttribute(html_element* Element, str Key, str Child, arena* Arena) +html_prependAttribute(html_element* Element, str Key, str Child, arena* Arena) { html_attribute* Attribute = ARENA_PUSH_STRUCT(Arena, html_attribute); { @@ -132,6 +132,7 @@ html_addAttribute(html_element* Element, str Key, str Child, arena* Arena) Attribute->Child = Child; } + Attribute->Next = Element->Attribute; Element->Attribute = Attribute; } @@ -163,6 +164,34 @@ html_appendContent(html_element* Target, str ContentStr, arena* Arena) html_appendElement(Target->Child, Content); } +static inline html_element* +getTitle(html* Html) +{ + html_element* Title = Html->Title; + html_element* ChildSentinel = Title->Child; + html_element* Content = ChildSentinel->Next; + + return Content; +} + +static void +html_setTitle(html* Html, str TitleStr) +{ + html_element* Title = getTitle(Html); + + ASSERT(Title->ElementName = HTML_ELEMENT_NAME_content); + Title->Content = TitleStr; +} + +static str +html_getTitleStr(html* Html) +{ + html_element* Title = getTitle(Html); + + str TitleStr = Title->Content; + return TitleStr; +} + static inline void html_appendArticleSection(html* Html, html_element* Section) { @@ -199,7 +228,7 @@ html_appendTagListToArticle(html* Html, str TagsDir, arena* Arena) } str Link = str_endUnbounded(uPath); - html_addAttribute(TagLink, STR_LITERAL("href"), Link, Arena); + html_prependAttribute(TagLink, STR_LITERAL("href"), Link, Arena); html_appendContent(TagLink, STR_LITERAL("#"), Arena); html_appendContent(TagLink, Tag->Content, Arena); html_appendChild(TagElement, TagLink); @@ -223,7 +252,7 @@ html_createDefault(arena* Arena) html_element* HtmlElement = html_createElement(HTML_ELEMENT_NAME_html, Arena); { - html_addAttribute(HtmlElement, STR_LITERAL("lang"), STR_LITERAL("de-DE"), Arena); + html_prependAttribute(HtmlElement, STR_LITERAL("lang"), STR_LITERAL("de-DE"), Arena); html_appendChild(Root, HtmlElement); html_element* Head = html_createElement(HTML_ELEMENT_NAME_head, Arena); @@ -232,27 +261,41 @@ html_createDefault(arena* Arena) html_element* MetaCharset = html_createElement(HTML_ELEMENT_NAME_meta, Arena); { - html_addAttribute(MetaCharset, STR_LITERAL("charset"), STR_LITERAL("utf-8"), Arena); + html_prependAttribute(MetaCharset, STR_LITERAL("charset"), STR_LITERAL("utf-8"), Arena); html_appendChild(Head, MetaCharset); } html_element* MetaViewport = html_createElement(HTML_ELEMENT_NAME_meta, Arena); { - html_addAttribute(MetaViewport, STR_LITERAL("name"), STR_LITERAL("viewport"), Arena); - html_addAttribute(MetaViewport, STR_LITERAL("content"), STR_LITERAL("width=device-width, initial-scale=1"), Arena); + html_prependAttribute(MetaViewport, STR_LITERAL("name"), STR_LITERAL("viewport"), Arena); + html_prependAttribute(MetaViewport, STR_LITERAL("content"), STR_LITERAL("width=device-width, initial-scale=1"), Arena); html_appendChild(Head, MetaViewport); } - /* todo: add later? */ html_element* Title = html_createElement(HTML_ELEMENT_NAME_title, Arena); { - static str Untitled = STR_LITERAL("[untitled]"); Html->Title = Title; - html_appendContent(Title, Untitled, Arena); + html_appendContent(Title, STR_LITERAL("[untitled]"), Arena); + html_appendContent(Title, STR_LITERAL(" | lhaegele.de"), Arena); + html_appendChild(Head, Title); } + html_element* LinkIcon = html_createElement(HTML_ELEMENT_NAME_link, Arena); + { + html_prependAttribute(LinkIcon, STR_LITERAL("rel"), STR_LITERAL("icon"), Arena); + html_prependAttribute(LinkIcon, STR_LITERAL("href"), STR_LITERAL("/favicon.svg"), Arena); + html_appendChild(Head, LinkIcon); + } + + html_element* LinkCss = html_createElement(HTML_ELEMENT_NAME_link, Arena); + { + html_prependAttribute(LinkCss, STR_LITERAL("rel"), STR_LITERAL("stylesheet"), Arena); + html_prependAttribute(LinkCss, STR_LITERAL("href"), STR_LITERAL("/style.css"), Arena); + html_appendChild(Head, LinkCss); + } + /* todo: add style? */ } @@ -266,8 +309,16 @@ html_createDefault(arena* Arena) html_element* Heading = html_createElement(HTML_ELEMENT_NAME_h1, Arena); { - html_appendContent(Heading, STR_LITERAL("Meine Rezeptsammlung"), Arena); html_appendChild(Header, Heading); + + html_element* HeadingLink = html_createElement(HTML_ELEMENT_NAME_a, Arena); + { + html_prependAttribute(HeadingLink, STR_LITERAL("href"), STR_LITERAL("/"), Arena); + html_appendContent(HeadingLink, STR_LITERAL("Meine Rezeptsammlung"), Arena); + html_appendChild(Heading, HeadingLink); + } + + html_setTitle(Html, STR_LITERAL("Meine Rezeptsammlung")); } } @@ -347,7 +398,7 @@ parseHeading(str Line, html* Html, arena* Arena) if (Heading->ElementName == HTML_ELEMENT_NAME_h1) { - Html->Meta->Title = HeadingContent; + html_setTitle(Html, HeadingContent); } } @@ -603,7 +654,12 @@ serializeElement(str* Target, html_element* Parent) } str_append(Target, STR_LITERAL(">")); - if (Child->ElementName != HTML_ELEMENT_NAME_meta) + if ((Child->ElementName == HTML_ELEMENT_NAME_meta) || + (Child->ElementName == HTML_ELEMENT_NAME_link)) + { + /* void elements don't have a closing tag */ + } + else { serializeElement(Target, Child); @@ -615,26 +671,6 @@ serializeElement(str* Target, html_element* Parent) } } -static void -html_setTitle(html* Html, str TitleStr) -{ - html_element* Title = Html->Title; - html_element* ChildSentinel = Title->Child; - html_element* Content = ChildSentinel->Next; - - ASSERT(Content->ElementName = HTML_ELEMENT_NAME_content); - Content->Content = TitleStr; -} - -static str -html_getTitle(html* Html) -{ - html_meta* Meta = Html->Meta; - - str Title = Meta->Title; - return Title; -} - static str html_toString(html* Html, arena* Arena) { diff --git a/src/main.c b/src/main.c index 1afec6c..d8f660c 100644 --- a/src/main.c +++ b/src/main.c @@ -6,8 +6,6 @@ * static site generator for my recipe collection * * @todo - * - (german) unicode support - * * - markdown parser * - multithreaded (one thread per recipe) * - output status from main thread? @@ -17,6 +15,8 @@ * - date created * - date changed (optional) * + * - use `section` on landing page for recipe and tag sections + * * - images (optional) * * - output to html directory @@ -232,7 +232,7 @@ appendRecipeLink(html_element* RecipeElement, recipe* Recipe, arena* Arena) } str Link = str_endUnbounded(uPath); - html_addAttribute(RecipeLink, STR_LITERAL("href"), Link, Arena); + html_prependAttribute(RecipeLink, STR_LITERAL("href"), Link, Arena); html_appendContent(RecipeLink, Recipe->Name, Arena); html_appendChild(RecipeElement, RecipeLink); } @@ -435,7 +435,7 @@ main(int ArgumentCount, char** Arguments) } Recipe->Html = html_parseMarkdown(FileStr, &Context.MainArena); - Recipe->Name = html_getTitle(Recipe->Html); + Recipe->Name = html_getTitleStr(Recipe->Html); } /* allocate tag sentinel */ @@ -505,8 +505,29 @@ main(int ArgumentCount, char** Arguments) /* todo: compress site creation? */ html* MainPage = html_createDefault(&Context.MainArena); { - /* todo: remove title from overview? */ - html_setTitle(MainPage, STR_LITERAL("Überblick")); + /* recipes */ + html_element* Recipes = html_createElement(HTML_ELEMENT_NAME_h2, &Context.MainArena); + { + html_appendArticleSection(MainPage, Recipes); + html_appendContent(Recipes, STR_LITERAL("Rezepte"), &Context.MainArena); + + html_element* RecipeList = html_createElement(HTML_ELEMENT_NAME_ul, &Context.MainArena); + { + html_appendArticleSection(MainPage, RecipeList); + html_prependAttribute(RecipeList, STR_LITERAL("class"), STR_LITERAL("recipes"), &Context.MainArena); + + for (recipe* Recipe = Context.Recipes->Next; + Recipe != Context.Recipes; + Recipe = Recipe->Next) + { + html_element* RecipeElement = html_createElement(HTML_ELEMENT_NAME_li, &Context.MainArena); + { + html_appendChild(RecipeList, RecipeElement); + appendRecipeLink(RecipeElement, Recipe, &Context.MainArena); + } + } + } + } /* tags */ html_element* Tags = html_createElement(HTML_ELEMENT_NAME_h2, &Context.MainArena); @@ -517,6 +538,7 @@ main(int ArgumentCount, char** Arguments) html_element* TagList = html_createElement(HTML_ELEMENT_NAME_ul, &Context.MainArena); { 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) @@ -535,7 +557,7 @@ main(int ArgumentCount, char** Arguments) } str Link = str_endUnbounded(uPath); - html_addAttribute(TagLink, STR_LITERAL("href"), Link, &Context.MainArena); + 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); @@ -545,29 +567,6 @@ main(int ArgumentCount, char** Arguments) } } - /* recipes */ - html_element* Recipes = html_createElement(HTML_ELEMENT_NAME_h2, &Context.MainArena); - { - html_appendArticleSection(MainPage, Recipes); - html_appendContent(Recipes, STR_LITERAL("Rezepte"), &Context.MainArena); - - html_element* RecipeList = html_createElement(HTML_ELEMENT_NAME_ul, &Context.MainArena); - { - html_appendArticleSection(MainPage, RecipeList); - - for (recipe* Recipe = Context.Recipes->Next; - Recipe != Context.Recipes; - Recipe = Recipe->Next) - { - html_element* RecipeElement = html_createElement(HTML_ELEMENT_NAME_li, &Context.MainArena); - { - html_appendChild(RecipeList, RecipeElement); - appendRecipeLink(RecipeElement, Recipe, &Context.MainArena); - } - } - } - } - /* serialize html tree */ { arena TempArena = Context.MainArena; @@ -663,6 +662,7 @@ main(int ArgumentCount, char** Arguments) } fprintf(stdout, "Done.\n"); + /* todo: output stats (memory consumption, runtime, ...) */ return 0; } diff --git a/style.css b/style.css new file mode 100644 index 0000000..3c6518c --- /dev/null +++ b/style.css @@ -0,0 +1,71 @@ +:root { + --header-color: white; + --text-color: rgb(200 200 200); + --visited-color: rgb(150 150 150); +} + +body { + display: flex; + flex-direction: column; + margin: 0px; +} + +header { + background-color: rgb(10 10 10); + color: var(--header-color); + display: flex; + justify-content: center; +} + +header > h1 { + margin-top: 1rem; + margin-bottom: 1rem; +} + +header a:link, a:visited, a:hover { + color: var(--header-color); + text-decoration: none; +} + +main { + background-color: rgb(21 21 21); + display: flex; + justify-content: center; +} + +h1, h2, h3 { + color: var(--header-color); + text-align: center; +} + +ul { + color: var(--text-color); + justify-content: center; + max-width: 1000px; +} + +ul.recipes { + column-count: 3; +} + +ul.tags { + column-count: 4; +} + +li { + margin-top: 0.5rem; + margin-bottom: 0.5rem; +} + +ul a:link { + color: var(--text-color); +} + +ul a:hover { + text-decoration: none; +} + +ul a:visited { + color: var(--visited-color); +} + -- 2.39.5