From a4e35942fee745e64db5d0099ba2582e0fb4417e Mon Sep 17 00:00:00 2001 From: Dennis Fink Date: Thu, 18 Jun 2026 17:57:51 +0200 Subject: Build the first usable version of the blog This adds the Lektor project structure, models, templates, styling, feeds, categories, tags, archives, custom Markdown heading handling, and the initial content needed to make the blog usable. --- Still Thinking About It.lektorproject | 11 + assets/static/css/.djlintrc | 14 + assets/static/css/blog.css | 10 + assets/static/css/categories.css | 61 ++ assets/static/css/category.css | 17 + assets/static/css/libre_baskerville.css | 73 ++ assets/static/css/modern_normalize.css | 194 ++++++ assets/static/css/style.css | 753 +++++++++++++++++++++ ...libre-baskerville-v24-latin_latin-ext-500.woff2 | Bin 0 -> 36692 bytes ...baskerville-v24-latin_latin-ext-500italic.woff2 | Bin 0 -> 38664 bytes ...libre-baskerville-v24-latin_latin-ext-600.woff2 | Bin 0 -> 36916 bytes ...baskerville-v24-latin_latin-ext-600italic.woff2 | Bin 0 -> 38864 bytes ...libre-baskerville-v24-latin_latin-ext-700.woff2 | Bin 0 -> 35944 bytes ...baskerville-v24-latin_latin-ext-700italic.woff2 | Bin 0 -> 38192 bytes ...re-baskerville-v24-latin_latin-ext-italic.woff2 | Bin 0 -> 37936 bytes ...e-baskerville-v24-latin_latin-ext-regular.woff2 | Bin 0 -> 35232 bytes assets/static/images/auto.svg | 3 + assets/static/images/dark.svg | 3 + assets/static/images/email.svg | 8 + assets/static/images/favicon.png | Bin 0 -> 1132 bytes assets/static/images/light.svg | 3 + assets/static/images/logo.svg | 21 + assets/static/images/mastodon.svg | 8 + assets/static/images/opengraph.png | Bin 0 -> 27513 bytes assets/static/images/rss.svg | 4 + assets/static/images/xmpp.svg | 8 + configs/atom.ini | 18 + configs/index-pages.ini | 23 + configs/tags.ini | 4 + content/about/contents.lr | 29 + content/about/me.jpg | Bin 0 -> 211145 bytes content/blog/back-to-blogging/contents.lr | 43 ++ content/blog/contents.lr | 3 + content/categories/contents.lr | 1 + content/categories/site-notes/contents.lr | 1 + content/contents.lr | 3 + databags/blog.ini | 2 + justfile | 14 + models/blog-post.ini | 39 ++ models/blog.ini | 17 + models/categories.ini | 9 + models/category.ini | 11 + models/index.ini | 8 + models/page.ini | 11 + .../lektor_blog_markdown_headings.py | 152 +++++ packages/blog-markdown-headings/setup.py | 13 + templates/.djlintrc | 14 + templates/about.html | 11 + templates/base.html | 167 +++++ templates/blog-month-index.html | 54 ++ templates/blog-post.html | 31 + templates/blog-year-index.html | 54 ++ templates/blog.html | 15 + templates/categories.html | 53 ++ templates/category.html | 51 ++ templates/index.html | 15 + templates/macros/blog.html | 100 +++ templates/macros/pagination.html | 221 ++++++ templates/page.html | 3 + templates/tag.html | 59 ++ 60 files changed, 2440 insertions(+) create mode 100644 Still Thinking About It.lektorproject create mode 100644 assets/static/css/.djlintrc create mode 100644 assets/static/css/blog.css create mode 100644 assets/static/css/categories.css create mode 100644 assets/static/css/category.css create mode 100644 assets/static/css/libre_baskerville.css create mode 100644 assets/static/css/modern_normalize.css create mode 100644 assets/static/css/style.css create mode 100644 assets/static/fonts/libre-baskerville-v24-latin_latin-ext-500.woff2 create mode 100644 assets/static/fonts/libre-baskerville-v24-latin_latin-ext-500italic.woff2 create mode 100644 assets/static/fonts/libre-baskerville-v24-latin_latin-ext-600.woff2 create mode 100644 assets/static/fonts/libre-baskerville-v24-latin_latin-ext-600italic.woff2 create mode 100644 assets/static/fonts/libre-baskerville-v24-latin_latin-ext-700.woff2 create mode 100644 assets/static/fonts/libre-baskerville-v24-latin_latin-ext-700italic.woff2 create mode 100644 assets/static/fonts/libre-baskerville-v24-latin_latin-ext-italic.woff2 create mode 100644 assets/static/fonts/libre-baskerville-v24-latin_latin-ext-regular.woff2 create mode 100644 assets/static/images/auto.svg create mode 100644 assets/static/images/dark.svg create mode 100644 assets/static/images/email.svg create mode 100644 assets/static/images/favicon.png create mode 100644 assets/static/images/light.svg create mode 100644 assets/static/images/logo.svg create mode 100644 assets/static/images/mastodon.svg create mode 100644 assets/static/images/opengraph.png create mode 100644 assets/static/images/rss.svg create mode 100644 assets/static/images/xmpp.svg create mode 100644 configs/atom.ini create mode 100644 configs/index-pages.ini create mode 100644 configs/tags.ini create mode 100644 content/about/contents.lr create mode 100644 content/about/me.jpg create mode 100644 content/blog/back-to-blogging/contents.lr create mode 100644 content/blog/contents.lr create mode 100644 content/categories/contents.lr create mode 100644 content/categories/site-notes/contents.lr create mode 100644 content/contents.lr create mode 100644 databags/blog.ini create mode 100644 justfile create mode 100644 models/blog-post.ini create mode 100644 models/blog.ini create mode 100644 models/categories.ini create mode 100644 models/category.ini create mode 100644 models/index.ini create mode 100644 models/page.ini create mode 100644 packages/blog-markdown-headings/lektor_blog_markdown_headings.py create mode 100644 packages/blog-markdown-headings/setup.py create mode 100644 templates/.djlintrc create mode 100644 templates/about.html create mode 100644 templates/base.html create mode 100644 templates/blog-month-index.html create mode 100644 templates/blog-post.html create mode 100644 templates/blog-year-index.html create mode 100644 templates/blog.html create mode 100644 templates/categories.html create mode 100644 templates/category.html create mode 100644 templates/index.html create mode 100644 templates/macros/blog.html create mode 100644 templates/macros/pagination.html create mode 100644 templates/page.html create mode 100644 templates/tag.html diff --git a/Still Thinking About It.lektorproject b/Still Thinking About It.lektorproject new file mode 100644 index 0000000..92e8963 --- /dev/null +++ b/Still Thinking About It.lektorproject @@ -0,0 +1,11 @@ +[project] +name = Still Thinking About It +url = https://dennisfink.me +url_style = absolute +output_path = build + +[packages] +lektor-atom = 0.4.0 +lektor-markdown-image-attrs = 0.2.1 +lektor-index-pages = 1.1.0 +lektor-tags = 0.5.2 diff --git a/assets/static/css/.djlintrc b/assets/static/css/.djlintrc new file mode 100644 index 0000000..5882faa --- /dev/null +++ b/assets/static/css/.djlintrc @@ -0,0 +1,14 @@ +{ +"blank_line_before_tag": "macro", +"blank_line_after_tag": "endmacro,extends,import,from", +"profile": "jinja", +"format_css": true, +"css": { +"indent_size": 2 +}, +"format_js": true, +"js": { +"indent_size": 2 +}, +"indent": "2" +} diff --git a/assets/static/css/blog.css b/assets/static/css/blog.css new file mode 100644 index 0000000..48a716b --- /dev/null +++ b/assets/static/css/blog.css @@ -0,0 +1,10 @@ +@charset "UTF-8"; + +article > p:first-of-type:first-letter { + color: var(--blue); + float: left; + font-weight: 700; + font-size: 3.8rem; + line-height: 3.8rem; + margin: 0.2em 0.1em 0 0; +} diff --git a/assets/static/css/categories.css b/assets/static/css/categories.css new file mode 100644 index 0000000..5e26021 --- /dev/null +++ b/assets/static/css/categories.css @@ -0,0 +1,61 @@ +@charset "UTF-8"; + +article > ul { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 0.5rem; + list-style: none; + margin: 0; + padding: 0; + + @media (max-width: 500px) { + grid-template-columns: 1fr; + } + + li { + max-inline-size: unset; + } + + a { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 1rem; + border: 1px solid var(--foreground); + border-radius: 0.5rem; + height: 100%; + + &:hover, + &:hover > dl { + border-color: var(--blue); + /*background-color: var(--blue);*/ + color: var(--blue); + } + + h3 { + margin: 0; + } + + dl { + display: flex; + flex-direction: column; + color: var(--dim-foreground); + gap: 0.25rem; + font-size: 1.2rem; + font-style: italic; + line-height: initial; + margin: 0; + + div { + display: flex; + flex-direction: row; + gap: 0.25rem; + } + + dd { + margin: 0; + max-inline-size: unset; + } + } + } +} diff --git a/assets/static/css/category.css b/assets/static/css/category.css new file mode 100644 index 0000000..e3567d8 --- /dev/null +++ b/assets/static/css/category.css @@ -0,0 +1,17 @@ +@charset "UTF-8"; + +article > table { + border-collapse: collapse; + + td { + vertical-align: top; + padding-bottom: 0.25rem; + } + + td:first-child { + color: var(--dim-foreground); + font-style: italic; + padding-right: 2rem; + white-space: nowrap; + } +} diff --git a/assets/static/css/libre_baskerville.css b/assets/static/css/libre_baskerville.css new file mode 100644 index 0000000..fa61e73 --- /dev/null +++ b/assets/static/css/libre_baskerville.css @@ -0,0 +1,73 @@ +@charset "UTF-8"; + +@font-face { + font-display: swap; + font-family: "Libre Baskerville"; + font-style: normal; + font-weight: 400; + src: url("/static/fonts/libre-baskerville-v24-latin_latin-ext-regular.woff2") + format("woff2"); +} + +@font-face { + font-display: swap; + font-family: "Libre Baskerville"; + font-style: italic; + font-weight: 400; + src: url("/static/fonts/libre-baskerville-v24-latin_latin-ext-italic.woff2") + format("woff2"); +} + +@font-face { + font-display: swap; + font-family: "Libre Baskerville"; + font-style: normal; + font-weight: 500; + src: url("/static/fonts/libre-baskerville-v24-latin_latin-ext-500.woff2") + format("woff2"); +} + +@font-face { + font-display: swap; + font-family: "Libre Baskerville"; + font-style: italic; + font-weight: 500; + src: url("/static/fonts/libre-baskerville-v24-latin_latin-ext-500italic.woff2") + format("woff2"); +} + +@font-face { + font-display: swap; + font-family: "Libre Baskerville"; + font-style: normal; + font-weight: 600; + src: url("/static/fonts/libre-baskerville-v24-latin_latin-ext-600.woff2") + format("woff2"); +} + +@font-face { + font-display: swap; + font-family: "Libre Baskerville"; + font-style: italic; + font-weight: 600; + src: url("/static/fonts/libre-baskerville-v24-latin_latin-ext-600italic.woff2") + format("woff2"); +} + +@font-face { + font-display: swap; + font-family: "Libre Baskerville"; + font-style: normal; + font-weight: 700; + src: url("/static/fonts/libre-baskerville-v24-latin_latin-ext-700.woff2") + format("woff2"); +} + +@font-face { + font-display: swap; + font-family: "Libre Baskerville"; + font-style: italic; + font-weight: 700; + src: url("/static/fonts/libre-baskerville-v24-latin_latin-ext-700italic.woff2") + format("woff2"); +} diff --git a/assets/static/css/modern_normalize.css b/assets/static/css/modern_normalize.css new file mode 100644 index 0000000..708b6a2 --- /dev/null +++ b/assets/static/css/modern_normalize.css @@ -0,0 +1,194 @@ +/*! modern-normalize v3.0.1 | MIT License | https://github.com/sindresorhus/modern-normalize */ +/* +Document +======== +*/ +/** +Use a better box model (opinionated). +*/ +*, +::before, +::after { + box-sizing: border-box; +} + +html { + /* Improve consistency of default fonts in all browsers. (https://github.com/sindresorhus/modern-normalize/issues/3) */ + font-family: + system-ui, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, + "Apple Color Emoji", "Segoe UI Emoji"; + line-height: 1.15; + /* 1. Correct the line height in all browsers. */ + -webkit-text-size-adjust: 100%; + /* 2. Prevent adjustments of font size after orientation changes in iOS. */ + tab-size: 4; + /* 3. Use a more readable tab size (opinionated). */ +} + +/* +Sections +======== +*/ +body { + margin: 0; + /* Remove the margin in all browsers. */ +} + +/* +Text-level semantics +==================== +*/ +/** +Add the correct font weight in Chrome and Safari. +*/ +b, +strong { + font-weight: bolder; +} + +/** +1. Improve consistency of default fonts in all browsers. (https://github.com/sindresorhus/modern-normalize/issues/3) +2. Correct the odd 'em' font sizing in all browsers. +*/ +code, +kbd, +samp, +pre { + font-family: + ui-monospace, SFMono-Regular, Consolas, "Liberation Mono", Menlo, monospace; + /* 1 */ + font-size: 1em; + /* 2 */ +} + +/** +Add the correct font size in all browsers. +*/ +small { + font-size: 80%; +} + +/** +Prevent 'sub' and 'sup' elements from affecting the line height in all browsers. +*/ +sub, +sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline; +} + +sub { + bottom: -0.25em; +} + +sup { + top: -0.5em; +} + +/* +Tabular data +============ +*/ +/** +Correct table border color inheritance in Chrome and Safari. (https://issues.chromium.org/issues/40615503, https://bugs.webkit.org/show_bug.cgi?id=195016) +*/ +table { + border-color: currentcolor; +} + +/* +Forms +===== +*/ +/** +1. Change the font styles in all browsers. +2. Remove the margin in Firefox and Safari. +*/ +button, +input, +optgroup, +select, +textarea { + font-family: inherit; + /* 1 */ + font-size: 100%; + /* 1 */ + line-height: 1.15; + /* 1 */ + margin: 0; + /* 2 */ +} + +/** +Correct the inability to style clickable types in iOS and Safari. +*/ +button, +[type="button"], +[type="reset"], +[type="submit"] { + -webkit-appearance: button; +} + +/** +Remove the padding so developers are not caught out when they zero out 'fieldset' elements in all browsers. +*/ +legend { + padding: 0; +} + +/** +Add the correct vertical alignment in Chrome and Firefox. +*/ +progress { + vertical-align: baseline; +} + +/** +Correct the cursor style of increment and decrement buttons in Safari. +*/ +::-webkit-inner-spin-button, +::-webkit-outer-spin-button { + height: auto; +} + +/** +1. Correct the odd appearance in Chrome and Safari. +2. Correct the outline style in Safari. +*/ +[type="search"] { + -webkit-appearance: textfield; + /* 1 */ + outline-offset: -2px; + /* 2 */ +} + +/** +Remove the inner padding in Chrome and Safari on macOS. +*/ +::-webkit-search-decoration { + -webkit-appearance: none; +} + +/** +1. Correct the inability to style clickable types in iOS and Safari. +2. Change font properties to 'inherit' in Safari. +*/ +::-webkit-file-upload-button { + -webkit-appearance: button; + /* 1 */ + font: inherit; + /* 2 */ +} + +/* +Interactive +=========== +*/ +/* +Add the correct display in Chrome and Safari. +*/ +summary { + display: list-item; +} diff --git a/assets/static/css/style.css b/assets/static/css/style.css new file mode 100644 index 0000000..ca7443a --- /dev/null +++ b/assets/static/css/style.css @@ -0,0 +1,753 @@ +@charset "UTF-8"; + +/* +============================================================================= +CUSTOM PROPERTIES +============================================================================= +*/ +:root { + /* Named palette */ + --black: light-dark(#575279, #393b44); + --red: light-dark(#b4637a, #c94f6d); + --green: light-dark(#618774, #81b29a); + --yellow: light-dark(#ea9d34, #dbc074); + --blue: light-dark(#286983, #719cd6); + --magenta: light-dark(#907aa9, #9d79d6); + --cyan: light-dark(#56949f, #63cdcf); + --white: light-dark(#e5e9f0, #dfdfe0); + --orange: light-dark(#d7827e, #f4a261); + --pink: light-dark(#d685af, #d67ad2); + /* Dim palette */ + --dim-black: light-dark(#504c6b, #30323a); + --dim-red: light-dark(#a5576d, #ab435d); + --dim-green: light-dark(#597668, #6e9783); + --dim-yellow: light-dark(#dd9024, #baa363); + --dim-blue: light-dark(#295e73, #6085b6); + --dim-magenta: light-dark(#816b9a, #8567b7); + --dim-cyan: light-dark(#50848c, #54aeb0); + --dim-white: light-dark(#c8cfde, #bebebe); + --dim-foreground: light-dark(#4c4769, #aeafb0); + /* Bright palette */ + --bright-black: light-dark(#5f5695, #575860); + --bright-red: light-dark(#c26d85, #d16983); + --bright-green: light-dark(#629f81, #8ebaa4); + --bright-yellow: light-dark(#eea846, #e0c989); + --bright-blue: light-dark(#2d81a3, #86abdc); + --bright-magenta: light-dark(#9a80b9, #baa1e2); + --bright-cyan: light-dark(#5ca7b4, #7ad5d6); + --bright-white: light-dark(#e6ebf3, #e4e4e5); + --bright-foreground: light-dark(#625c87, #d6d6d7); + /* Semantic */ + --foreground: light-dark(#575279, #cdcecf); + --background: light-dark(#faf4ed, #192330); + /* Layout */ + --measure: 60ch; + --column-min-width: calc(1/3 * 100vw); + --column-max-width: 100%; + /* Gradient border — reused in header, footer */ + --gradient-border: linear-gradient( + to right, + var(--foreground), + var(--green), + var(--foreground) + ); + + /* Responsive layout */ + @media (min-width: 500px) { + --column-max-width: var(--measure); + } + + &:has(input[name="theme-switcher"][value="light dark"]:checked) { + color-scheme: light dark; + } + + &:has(input[name="theme-switcher"][value="light"]:checked) { + color-scheme: light; + } + + &:has(input[name="theme-switcher"][value="dark"]:checked) { + color-scheme: dark; + } +} + +/* +============================================================================= +BASE +============================================================================= + */ +html { + font-size: 14px; +} + +body { + display: flex; + flex-direction: column; + min-height: 100vh; + min-width: var(--column-min-width); + max-width: var(--column-max-width); + margin: 0 auto; + background-color: var(--background); + color: var(--foreground); + font-family: "Libre Baskerville", Georgia, serif; + font-size: clamp(1rem, 1.5vw, 1.4rem); + line-height: 1.6; + + @media (max-width: 500px) { + padding: 0 1.5rem; + } + + & > footer { + border-image-slice: 1; + border-image-source: var(--gradient-border); + border-image-width: 1px 0 0 0; + border-top: 1px solid; + display: flex; + flex-direction: row; + justify-content: space-between; + margin-bottom: 2rem; + margin-top: auto; + padding-top: 0.5rem; + + @media (max-width: 500px) { + flex-direction: column; + gap: 2rem; + } + + h2 { + font-size: clamp(1.2rem, 2.5vw, 1.7rem); + font-style: italic; + font-weight: 400; + line-height: 1; + } + + section div { + display: flex; + flex-direction: row; + gap: 0.25rem; + } + + & > section[aria-label="Follow me"] a { + background-color: var(--green); + display: inline-block; + height: 3rem; + mask-size: cover; + width: 3rem; + + &:hover { + background-color: var(--blue); + } + + &#atom { + mask-image: url("/static/images/rss.svg"); + } + + &#mastodon { + mask-image: url("/static/images/mastodon.svg"); + } + + &#xmpp { + mask-image: url("/static/images/xmpp.svg"); + } + + &#email { + mask-image: url("/static/images/email.svg"); + } + } + + ul { + list-style: none; + margin: 0; + padding: 0; + } + } +} + +/* +============================================================================= +TYPOGRAPHY +============================================================================= +*/ + +/* --- Headings --- */ +h1 { + font-size: clamp(2rem, 4vw, 3.2rem); + font-weight: 700; + line-height: 1; + margin: 0 0 0.5rem 0; +} + +h2 { + font-size: clamp(1.5rem, 3vw, 2.2rem); + font-style: italic; + font-weight: 500; + line-height: 1; + margin: 1rem 0; +} + +h3 { + font-size: clamp(1.2rem, 2.5vw, 1.7rem); + font-style: italic; + font-weight: 400; + line-height: 1; + margin-bottom: 1rem; +} + +/* --- Body text --- */ +p { + font-size: clamp(1rem, 1.5vw, 1.4rem); + line-height: 1.5em; + margin: 0; + text-align: justify; + text-wrap: pretty; + word-spacing: 0.16em; + + &.center { + text-align: center; + } +} + +blockquote { + font-style: italic; + border-left: 2px solid var(--blue); + margin: 1em 0 1em 0.5em; + padding-left: 0.5em; +} + +/* --- Lists --- */ +ul, +ol, +dl { + font-size: clamp(1rem, 1.5vw, 1.4rem); + line-height: 2rem; +} + +li, +dd { + max-inline-size: var(--measure); +} + +/* --- Rules --- */ +hr { + border: 0; + border-top: 2px solid var(--blue); + margin: 1em 0; +} + +/* --- Links --- */ +a { + color: var(--green); + text-decoration: none; + text-decoration-thickness: 0.05em; + text-underline-offset: 0.1em; + + &:hover { + color: var(--blue); + } +} + +/* +============================================================================= +SKIP LINK (accessibility) +============================================================================= + */ +.skip-link { + background: #fff; + color: #000; + left: 0; + padding: 0.5rem 1rem; + position: absolute; + text-decoration: none; + top: 0; + transform: translateY(-120%); + transition: transform 0.2s ease; + z-index: 1000; + + &:focus, + &:focus-visible { + transform: translateY(0); + } +} + +.visually-hidden { + clip: rect(0 0 0 0); + clip-path: inset(50%); + height: 1px; + overflow: hidden; + position: absolute; + white-space: nowrap; + width: 1px; +} + +/* +============================================================================= +SITE HEADER +============================================================================= +*/ +body > header { + display: flex; + flex-direction: column; + gap: 1rem; + justify-content: space-evenly; + margin-top: 4rem; + padding-bottom: 1rem; + border-bottom: 1px solid; + border-image-slice: 1; + border-image-source: var(--gradient-border); + border-image-width: 0 0 1px 0; + + & > div { + display: flex; + flex-direction: row; + justify-content: space-between; + gap: 0.25rem; + } + + svg { + max-width: 4em; + max-height: 4em; + fill: var(--green); + + @media (max-width: 500px) { + display: none; + } + } + + p { + font-size: clamp(1.1rem, 2vw, 1.75rem); + font-style: italic; + max-width: unset; + } + + /* --- Navigation --- */ + nav { + ul { + display: flex; + flex-direction: row; + gap: 1rem; + list-style: none; + margin: 0; + padding: 0; + } + + a[aria-current="page"] { + color: var(--blue); + + &:hover { + color: var(--bright-blue); + } + } + } + + /* --- Theme switcher --- */ + fieldset { + align-items: center; + border: 0; + display: flex; + flex-direction: row; + gap: 0.25rem; + justify-content: flex-start; + margin: 0; + padding: 0; + + input[type="radio"] { + display: none; + + &:checked + span { + background-color: var(--yellow); + } + } + + label span { + background-color: var(--foreground); + cursor: pointer; + display: block; + height: 1.5rem; + mask-size: cover; + width: 1.5rem; + } + } +} + +#lightmode + span, +#automode + span, +#darkmode + span { + mask-repeat: no-repeat; + mask-position: 50% 50%; +} + +#lightmode + span { + mask-image: url("/static/images/light.svg"); +} + +#automode + span { + mask-image: url("/static/images/auto.svg"); +} + +#darkmode + span { + mask-image: url("/static/images/dark.svg"); +} + +/* +============================================================================= +MAIN +============================================================================= +*/ +main { + display: flex; + flex-direction: column; + margin-bottom: 2rem; + margin-top: 2rem; +} + +/* +============================================================================= +ARTICLES +============================================================================= +*/ +article { + max-width: var(--column-max-width); + min-width: var(--column-min-width); + + & > header { + display: flex; + flex-direction: row; + align-items: start; + justify-content: space-between; + + &:not(:has(nav)) { + margin-bottom: 2rem; + } + + & > div { + display: flex; + flex-direction: column; + gap: 0.75rem; + max-width: calc(2 / 3 * 100%); + + /* --- Article metadata --- */ + & > dl { + align-items: center; + display: flex; + flex-direction: row; + flex-wrap: wrap; + font-size: 1rem; + gap: calc(1 / 3 * 1rem); + margin: 0; + + div { + align-items: center; + border: 1px solid var(--foreground); + border-radius: 0.5rem; + display: flex; + flex-direction: row; + } + + dt { + color: var(--bright-blue); + padding-left: calc(1 / 3 * 1rem); + padding-right: calc(1 / 3 * 1rem); + } + + dd { + margin: 0; + padding-right: calc(1 / 3 * 1rem); + + a { + color: var(--green); + } + } + + @media (max-width: 700px) { + margin-top: 2rem; + } + + @media (max-width: 500px) { + flex-direction: column; + align-items: flex-start; + } + } + } + + h2 { + margin-bottom: 0.25rem; + margin-top: 0; + } + + /* --- TOC --- */ + & > nav { + border: 1px solid var(--foreground); + border-radius: 0.5rem; + font-size: 1rem; + line-height: 1.4; + padding: 0.75rem; + max-width: calc(1 / 3 * 100%); + + & > p { + color: var(--blue); + font-size: 1em; + font-weight: 700; + line-height: 1.2; + margin: 0 0 0.5rem 0; + } + + ol { + font-size: 1em; + line-height: 1.4; + margin: 0; + padding-left: 1.5em; + + ol { + margin-top: 0.25rem; + padding-left: 1.25em; + } + } + + li { + max-inline-size: none; + + & + li { + margin-top: 0.25rem; + } + } + + a { + overflow-wrap: anywhere; + } + + @media (max-width: 700px) { + margin-top: 2rem; + } + } + } + + /* --- Article body --- */ + p { + max-inline-size: var(--measure); + + & + p { + margin-top: 2rem; + } + } + + & > h3:not(:first-of-type) { + margin-top: 2rem; + } + + & > footer { + color: var(--dim-foreground); + font-style: italic; + margin-top: 1.5rem; + + p { + display: inline; + font-size: 1.2rem; + line-height: 1.5; + margin: 0; + padding: 0; + } + + ul { + font-size: 1.2rem; + line-height: 1.5; + margin: 0; + padding: 0; + display: inline-flex; + flex-wrap: wrap; + list-style: none; + } + + li { + display: flex; + max-inline-size: none; + } + } + + /* --- Article separator --- */ + & + hr { + align-items: center; + background: none; + border: 0; + display: flex; + height: 1em; + justify-content: center; + margin: 4em 0; + position: relative; + width: 100%; + + &::before { + background-image: var(--gradient-border); + content: ""; + height: 1px; + left: 0; + position: absolute; + right: 0; + top: 50%; + } + + &::after { + background-color: var(--background); + color: var(--blue); + content: "§"; + padding: 0 1rem; + position: relative; + transform: rotate(90deg); + z-index: 1; + } + } +} + +/* +============================================================================= +PAGINATION +============================================================================= + */ +nav:is( + [aria-label="Page navigation"], + [aria-label="Year archive navigation"], + [aria-label="Monthly archive navigation"] +) { + display: flex; + flex-direction: row; + justify-content: center; + + ol { + align-items: center; + border: 1px solid var(--foreground); + border-radius: 0.5rem; + display: flex; + flex-direction: row; + gap: 0.25rem; + list-style: none; + margin: 2rem 0; + padding: 0.25rem; + } + + a, + span { + align-items: center; + display: flex; + height: 2rem; + justify-content: center; + min-width: 2rem; + padding-inline: 0.5rem; + } + + & a { + border-radius: 0.5rem; + + &:hover, + &[aria-current="page"]:hover { + background-color: var(--blue); + color: var(--foreground); + } + + &[aria-current="page"] { + color: var(--blue); + } + } + + span[aria-disabled="true"] { + color: var(--dim-foreground); + } +} + +/* +============================================================================= +IMAGES +============================================================================= +*/ +img { + height: auto; + max-width: 100%; + + &.center { + display: block; + margin-inline: auto; + } + + &.round { + border-radius: 100%; + } + + &.w25 { + width: 25%; + } + + &.w33 { + width: calc(1 / 3 * 100%); + } + + &.w50 { + width: 50%; + } + + &.w66 { + width: calc(2 / 3 * 100%); + } + + &.w75 { + width: 75%; + } + + &.breakout-xs { + width: calc(100% + 16rem); + transform: translateX(-8rem); + } + + &.breakout-sm { + width: calc(100% + 24rem); + transform: translateX(-12rem); + } + + &.breakout-md { + width: calc(100% + 32rem); + transform: translateX(-16rem); + } + + &.breakout-lg { + width: calc(100% + 48rem); + transform: translateX(-24rem); + } + + &.breakout-xl { + width: calc(100% + 64rem); + transform: translateX(-32rem); + } + + &.breakout-full { + width: 100vw; + margin-left: calc(50% - 50vw); + } + + &.breakout-xs, + &.breakout-sm, + &.breakout-md, + &.breakout-lg, + &.breakout-xl, + &.breakout-full { + max-width: 100vw; + } + + @media (max-width: 500px) { + &.breakout-xs, + &.breakout-sm, + &.breakout-md, + &.breakout-lg, + &.breakout-xl { + width: 100vw; + transform: none; + margin-left: calc(50% - 50vw); + } + } +} + +.missing-publication-date-warning { + background: var(--red); + color: var(--foreground); + font-weight: 700; + font-size: 1.1rem; +} + +.missing-publication-date-warning > dt { + color: var(--foreground); +} diff --git a/assets/static/fonts/libre-baskerville-v24-latin_latin-ext-500.woff2 b/assets/static/fonts/libre-baskerville-v24-latin_latin-ext-500.woff2 new file mode 100644 index 0000000..4c373fe Binary files /dev/null and b/assets/static/fonts/libre-baskerville-v24-latin_latin-ext-500.woff2 differ diff --git a/assets/static/fonts/libre-baskerville-v24-latin_latin-ext-500italic.woff2 b/assets/static/fonts/libre-baskerville-v24-latin_latin-ext-500italic.woff2 new file mode 100644 index 0000000..7e780a9 Binary files /dev/null and b/assets/static/fonts/libre-baskerville-v24-latin_latin-ext-500italic.woff2 differ diff --git a/assets/static/fonts/libre-baskerville-v24-latin_latin-ext-600.woff2 b/assets/static/fonts/libre-baskerville-v24-latin_latin-ext-600.woff2 new file mode 100644 index 0000000..f94f429 Binary files /dev/null and b/assets/static/fonts/libre-baskerville-v24-latin_latin-ext-600.woff2 differ diff --git a/assets/static/fonts/libre-baskerville-v24-latin_latin-ext-600italic.woff2 b/assets/static/fonts/libre-baskerville-v24-latin_latin-ext-600italic.woff2 new file mode 100644 index 0000000..067b7fb Binary files /dev/null and b/assets/static/fonts/libre-baskerville-v24-latin_latin-ext-600italic.woff2 differ diff --git a/assets/static/fonts/libre-baskerville-v24-latin_latin-ext-700.woff2 b/assets/static/fonts/libre-baskerville-v24-latin_latin-ext-700.woff2 new file mode 100644 index 0000000..4e0f4e7 Binary files /dev/null and b/assets/static/fonts/libre-baskerville-v24-latin_latin-ext-700.woff2 differ diff --git a/assets/static/fonts/libre-baskerville-v24-latin_latin-ext-700italic.woff2 b/assets/static/fonts/libre-baskerville-v24-latin_latin-ext-700italic.woff2 new file mode 100644 index 0000000..5351437 Binary files /dev/null and b/assets/static/fonts/libre-baskerville-v24-latin_latin-ext-700italic.woff2 differ diff --git a/assets/static/fonts/libre-baskerville-v24-latin_latin-ext-italic.woff2 b/assets/static/fonts/libre-baskerville-v24-latin_latin-ext-italic.woff2 new file mode 100644 index 0000000..733d0dd Binary files /dev/null and b/assets/static/fonts/libre-baskerville-v24-latin_latin-ext-italic.woff2 differ diff --git a/assets/static/fonts/libre-baskerville-v24-latin_latin-ext-regular.woff2 b/assets/static/fonts/libre-baskerville-v24-latin_latin-ext-regular.woff2 new file mode 100644 index 0000000..3c2170e Binary files /dev/null and b/assets/static/fonts/libre-baskerville-v24-latin_latin-ext-regular.woff2 differ diff --git a/assets/static/images/auto.svg b/assets/static/images/auto.svg new file mode 100644 index 0000000..5032980 --- /dev/null +++ b/assets/static/images/auto.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/static/images/dark.svg b/assets/static/images/dark.svg new file mode 100644 index 0000000..ccb82f5 --- /dev/null +++ b/assets/static/images/dark.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/static/images/email.svg b/assets/static/images/email.svg new file mode 100644 index 0000000..2c197d4 --- /dev/null +++ b/assets/static/images/email.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/assets/static/images/favicon.png b/assets/static/images/favicon.png new file mode 100644 index 0000000..cb4a2f8 Binary files /dev/null and b/assets/static/images/favicon.png differ diff --git a/assets/static/images/light.svg b/assets/static/images/light.svg new file mode 100644 index 0000000..56e1084 --- /dev/null +++ b/assets/static/images/light.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/static/images/logo.svg b/assets/static/images/logo.svg new file mode 100644 index 0000000..a3090ad --- /dev/null +++ b/assets/static/images/logo.svg @@ -0,0 +1,21 @@ + + + + + + + + + diff --git a/assets/static/images/mastodon.svg b/assets/static/images/mastodon.svg new file mode 100644 index 0000000..f6b8203 --- /dev/null +++ b/assets/static/images/mastodon.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/assets/static/images/opengraph.png b/assets/static/images/opengraph.png new file mode 100644 index 0000000..c44994f Binary files /dev/null and b/assets/static/images/opengraph.png differ diff --git a/assets/static/images/rss.svg b/assets/static/images/rss.svg new file mode 100644 index 0000000..0a6bed9 --- /dev/null +++ b/assets/static/images/rss.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/static/images/xmpp.svg b/assets/static/images/xmpp.svg new file mode 100644 index 0000000..9d33519 --- /dev/null +++ b/assets/static/images/xmpp.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/configs/atom.ini b/configs/atom.ini new file mode 100644 index 0000000..84b78ec --- /dev/null +++ b/configs/atom.ini @@ -0,0 +1,18 @@ +[latest] +name = Still Thinking About It — Latest posts +source_path = /blog +url_path = /blog/latest.xml +item_model = blog-post +item_date_field = publication_date +blog_author_field = author +item_author_field = author + +[all] +name = Still Thinking About It — All posts +source_path = /blog +url_path = /blog/all.xml +item_model = blog-post +item_date_field = publication_date +blog_author_field = author +item_author_field = author +limit = 0 diff --git a/configs/index-pages.ini b/configs/index-pages.ini new file mode 100644 index 0000000..bc428ce --- /dev/null +++ b/configs/index-pages.ini @@ -0,0 +1,23 @@ +[year] +parent_path = /blog +key = '{.year:04d}'.format(item.publication_date) +template = blog-year-index.html +subindex = month + +[year.fields] +date = this.children.first().publication_date.replace(month=1, day=1) +year = this.key|int +previous_year_page = site.get('/blog@index-pages/year/{:04d}'.format((this.key|int) - 1)) +next_year_page = site.get('/blog@index-pages/year/{:04d}'.format((this.key|int) + 1)) +first_year_page = index_pages('year')|sort(attribute='year')|first +last_year_page = index_pages('year')|sort(attribute='year')|last + +[year.month] +key = '{.month:02d}'.format(item.publication_date) +template = blog-month-index.html + +[year.month.fields] +# this.parent is the year-index page this month-index belongs to. +date = this.parent.date.replace(month=this.key|int) +year = this.parent.year +month = this.key|int diff --git a/configs/tags.ini b/configs/tags.ini new file mode 100644 index 0000000..3ecc49a --- /dev/null +++ b/configs/tags.ini @@ -0,0 +1,4 @@ +parent = /blog +items = this.parent.children.filter(F.tags.contains(tag)).order_by('-publication_date') +template = tag.html +url_path = {{ this.parent.url_path }}tag/{{ tag|trim|lower|replace(" ", "-") }} diff --git a/content/about/contents.lr b/content/about/contents.lr new file mode 100644 index 0000000..9a65a65 --- /dev/null +++ b/content/about/contents.lr @@ -0,0 +1,29 @@ +_model: page +--- +_template: about.html +--- +title: About +--- +body: + +

+Hi, I'm Dennis. Welcome! +

+ +![](me.jpg "class='center w33 round'") + +I am originally from Luxembourg and grew up in the south of the country. During +my formative years, I spent a lot of time in a local scouts group, which was a +fantastic way to develop a deep connection to nature and a desire to learn +through exploration and experimentation. I later got involved in the local +hacker and maker movement, where the development and exploration of technology +often went hand in hand with discussions around privacy, security and the role +of technology in society. I'm a trained educator and now I get to do what I +love most: sharing my passion for science with others through education and +communication. + +This blog is my place to capture my thoughts before they disappear. It'll most +likely include little things from my day-to-day life, things I've been reading +about and recurring ideas, as well as the occasional rabbit hole I've fallen +down. There won't be a theme or a schedule, but it will be a space where I can +collect all the things that I find interesting and share them with you. diff --git a/content/about/me.jpg b/content/about/me.jpg new file mode 100644 index 0000000..300ffd5 Binary files /dev/null and b/content/about/me.jpg differ diff --git a/content/blog/back-to-blogging/contents.lr b/content/blog/back-to-blogging/contents.lr new file mode 100644 index 0000000..a68236a --- /dev/null +++ b/content/blog/back-to-blogging/contents.lr @@ -0,0 +1,43 @@ +title: Back to Blogging +--- +publication_date: 2026-06-17 +--- +render_toc: no +--- +body: + +Welcome back to my blog! Yes, it's time to get back to basics again. But why +now? There are several reasons for this, which I may discuss in more detail in +future posts. For now, let me recap on two things. + +Firstly, the recent rise of the ["Digital Independence Day"](https://di.day/en) +events in Germany, has inspired me to become even more digitally independent. +Since joining the [local Chaos Computer Club](https://c3l.lu) over 15 years +ago, I have been on a mission to leave big tech behind. This has led me to +self-host most of my digital tools and start blogging again. + +The main reason, though, is that the web is fundamentally about sharing +information, not just consuming it. At a time when content generated by LLMs +seems to dominate the web, it's more important than ever to have content +written by humans. This is my attempt to contribute to this. I may blog about +my views on AI in the future. + +What can you find here? As I write on the ["About" page](/about/): + +> This blog is my place to capture my thoughts before they disappear. It'll most +> likely include little things from my day-to-day life, things I've been reading +> about and recurring ideas, as well as the occasional rabbit hole I've fallen +> down. There won't be a theme or a schedule, but it will be a space where I can +> collect all the things that I find interesting and share them with you. + +Thank you for reading my blog, and I hope to see you again soon! You might want to subscribe to the [Atom feed](/blog/latest.xml)! +--- +tags: + +blogging +personal web +digital independence +human-written web +atom feed +--- +category: site-notes diff --git a/content/blog/contents.lr b/content/blog/contents.lr new file mode 100644 index 0000000..8aa3c00 --- /dev/null +++ b/content/blog/contents.lr @@ -0,0 +1,3 @@ +_model: blog +--- +author: Dennis Fink diff --git a/content/categories/contents.lr b/content/categories/contents.lr new file mode 100644 index 0000000..01afcc8 --- /dev/null +++ b/content/categories/contents.lr @@ -0,0 +1 @@ +_model: categories diff --git a/content/categories/site-notes/contents.lr b/content/categories/site-notes/contents.lr new file mode 100644 index 0000000..75971e1 --- /dev/null +++ b/content/categories/site-notes/contents.lr @@ -0,0 +1 @@ +name: Site Notes diff --git a/content/contents.lr b/content/contents.lr new file mode 100644 index 0000000..cd8811e --- /dev/null +++ b/content/contents.lr @@ -0,0 +1,3 @@ +_model: index +--- +title: Index diff --git a/databags/blog.ini b/databags/blog.ini new file mode 100644 index 0000000..fdf0986 --- /dev/null +++ b/databags/blog.ini @@ -0,0 +1,2 @@ +name = Still Thinking About It +tagline = Personal notes on things that linger. diff --git a/justfile b/justfile new file mode 100644 index 0000000..e40389a --- /dev/null +++ b/justfile @@ -0,0 +1,14 @@ +set shell := ["bash", "-uc"] + +serve: + lektor serve -h 0.0.0.0 + +clean: + lektor clean --yes + +build: + lektor build + +deploy: build + rsync --archive --compress --verbose build/ draconis:/home/dennis/blog/ + ssh -t draconis -- /home/dennis/bin/,deploy_blog diff --git a/models/blog-post.ini b/models/blog-post.ini new file mode 100644 index 0000000..4f36c1e --- /dev/null +++ b/models/blog-post.ini @@ -0,0 +1,39 @@ +[model] +name = Blog Post +label = {{ this.title }} + +[fields.title] +label = Title +type = string +size = large + +[fields.publication_date] +label = Publication date +type = date +width = 1/2 + +[fields.updated_date] +label = Updated date +type = date +width = 1/2 + +[fields.category] +label = Category +type = select +source = site.query('/categories') +width = 1/2 + +[fields.render_toc] +label = Render TOC +type = boolean +checkbox_label = If true, then a TOC will be rendered in the webpage. +default = false +width = 1/2 + +[fields.body] +label = Body +type = markdown + +[fields.tags] +label = Tags +type = strings diff --git a/models/blog.ini b/models/blog.ini new file mode 100644 index 0000000..0d612ff --- /dev/null +++ b/models/blog.ini @@ -0,0 +1,17 @@ +[model] +name = Blog +label = Blog +hidden = yes + +[fields.author] +label = Author +type = string + +[children] +model = blog-post +order_by = -publication_date, title +slug_format = {{ (this.publication_date|dateformat('YYYY/MM/') if this.publication_date) ~ this._id }} + +[pagination] +enabled = yes +per_page = 5 diff --git a/models/categories.ini b/models/categories.ini new file mode 100644 index 0000000..2b4c222 --- /dev/null +++ b/models/categories.ini @@ -0,0 +1,9 @@ +[model] +name = Categories +label = Categories +hidden = yes +protected = yes + +[children] +model = category +order_by = name diff --git a/models/category.ini b/models/category.ini new file mode 100644 index 0000000..3a6e10d --- /dev/null +++ b/models/category.ini @@ -0,0 +1,11 @@ +[model] +name = Category +label = {{ this.name }} +hidden = yes + +[children] +replaced_with = site.query('/blog').filter(F.category.contains(this)) + +[fields.name] +label = Name +type = string diff --git a/models/index.ini b/models/index.ini new file mode 100644 index 0000000..475c4a1 --- /dev/null +++ b/models/index.ini @@ -0,0 +1,8 @@ +[model] +name = Index +label = index + +[pagination] +enabled = yes +per_page = 5 +items = site.query('/blog') diff --git a/models/page.ini b/models/page.ini new file mode 100644 index 0000000..dcddfe8 --- /dev/null +++ b/models/page.ini @@ -0,0 +1,11 @@ +[model] +name = Page +label = {{ this.title }} + +[fields.title] +label = Title +type = string + +[fields.body] +label = Body +type = markdown diff --git a/packages/blog-markdown-headings/lektor_blog_markdown_headings.py b/packages/blog-markdown-headings/lektor_blog_markdown_headings.py new file mode 100644 index 0000000..5293676 --- /dev/null +++ b/packages/blog-markdown-headings/lektor_blog_markdown_headings.py @@ -0,0 +1,152 @@ +import uuid +from collections import namedtuple +from html.parser import HTMLParser + +import mistune +from lektor.pluginsystem import Plugin +from lektor.utils import slugify +from markupsafe import Markup, escape + +TocEntry = namedtuple("TocEntry", ["anchor", "title", "children"]) + + +def get_int_config(config, key, default, minimum=None, maximum=None): + value = config.get(key, str(default)) + + try: + value = int(value) + except (TypeError, ValueError): + value = default + + if minimum is not None: + value = max(value, minimum) + + if maximum is not None: + value = min(value, maximum) + + return value + + +class BlogMarkdownHeadingsPlugin(Plugin): + name = "Blog Markdown Headings" + description = ( + "Adds Markdown heading anchors, TOC metadata, and configurable heading offsets." + ) + + def on_markdown_config(self, config, **extra): + plugin_config = self.get_config() + + anchor_type = plugin_config.get("anchors.type", "slug") + + heading_offset = get_int_config( + plugin_config, + "headings.offset", + default=2, + minimum=-5, + maximum=5, + ) + + max_heading_level = get_int_config( + plugin_config, + "headings.max_level", + default=6, + minimum=1, + maximum=6, + ) + + min_heading_level = get_int_config( + plugin_config, + "headings.min_level", + default=1, + minimum=1, + maximum=6, + ) + + def adjusted_level(level): + return min( + max(level + heading_offset, min_heading_level), + max_heading_level, + ) + + config.renderer_mixins.append( + renderer_mixin_factory( + anchor_type=anchor_type, + adjusted_level=adjusted_level, + ) + ) + + def on_markdown_meta_init(self, meta, **extra): + meta["toc"] = [] + + def on_markdown_meta_postprocess(self, meta, **extra): + prev_level = None + toc = [] + stack = [toc] + + for level, anchor, title in meta["toc"]: + if prev_level is None: + prev_level = level + elif prev_level == level - 1: + stack.append(stack[-1][-1].children) + prev_level = level + elif prev_level > level: + while prev_level > level: + if len(stack) > 1: + stack.pop() + prev_level -= 1 + + stack[-1].append(TocEntry(anchor, title, [])) + + meta["toc"] = toc + + +def renderer_mixin_factory(anchor_type, adjusted_level): + def render_heading(text, original_level, meta, raw=None): + level = adjusted_level(original_level) + + if anchor_type == "random": + anchor = uuid.uuid4().hex[:6] + else: + if raw is None: + raw = escape(_strip_tags(text)) + anchor = slugify(raw) + + meta["toc"].append((level, anchor, Markup(text))) + + return '%s' % (level, anchor, text, level) + + class RendererMixinMistune0(object): + def header(self, text, level, raw): + return render_heading(text, level, self.meta, raw) + + class RendererMixinMistune2(object): + def heading(self, text, level): + meta = self.lektor.meta + return render_heading(text, level, meta) + + if mistune.__version__.startswith("0."): + return RendererMixinMistune0 + + if mistune.__version__.startswith("2."): + return RendererMixinMistune2 + + raise RuntimeError("Unsupported mistune version: %s" % mistune.__version__) + + +def _strip_tags(inline_html): + parser = _StripTagsParser() + parser.feed(inline_html) + return parser.close() + + +class _StripTagsParser(HTMLParser): + def reset(self): + super().reset() + self._data = [] + + def handle_data(self, data): + self._data.append(data) + + def close(self): + super().close() + return "".join(self._data) diff --git a/packages/blog-markdown-headings/setup.py b/packages/blog-markdown-headings/setup.py new file mode 100644 index 0000000..59f9895 --- /dev/null +++ b/packages/blog-markdown-headings/setup.py @@ -0,0 +1,13 @@ +from setuptools import setup + +setup( + name="lektor-blog-markdown-headings", + version="0.1", + py_modules=["lektor_blog_markdown_headings"], + entry_points={ + "lektor.plugins": [ + "blog-markdown-headings = lektor_blog_markdown_headings:BlogMarkdownHeadingsPlugin", + ] + }, + install_requires=[], +) diff --git a/templates/.djlintrc b/templates/.djlintrc new file mode 100644 index 0000000..5882faa --- /dev/null +++ b/templates/.djlintrc @@ -0,0 +1,14 @@ +{ +"blank_line_before_tag": "macro", +"blank_line_after_tag": "endmacro,extends,import,from", +"profile": "jinja", +"format_css": true, +"css": { +"indent_size": 2 +}, +"format_js": true, +"js": { +"indent_size": 2 +}, +"indent": "2" +} diff --git a/templates/about.html b/templates/about.html new file mode 100644 index 0000000..55a0778 --- /dev/null +++ b/templates/about.html @@ -0,0 +1,11 @@ +{% extends "base.html" %} + +{% block meta %} + {{ super() }} + + + +{% endblock %} +{% block content %} +
{{ this.body }}
+{% endblock %} diff --git a/templates/base.html b/templates/base.html new file mode 100644 index 0000000..1000014 --- /dev/null +++ b/templates/base.html @@ -0,0 +1,167 @@ + + + + {% block head %} + {% block meta %} + + + + + + + + + + + + + + + {% endblock meta %} + {% block styles %} + + + + {% endblock styles %} + {% block meta_links %} + + + + + {% endblock meta_links %} + + {% block meta_title %} + {%- if (this.title or title) -%}{{ (this.title or title) | safe }} — + {% endif -%} + {{ bag("blog.name") }} — dennisfink.me + {% endblock meta_title %} + + {% endblock head %} + + + {% block body %} + +
+ {% block site_header %} +
+
+

+ {{ bag("blog.name") }} +

+

{{ bag("blog.tagline") }}

+
+ + + +
+
+ +
+ Color theme + + + +
+
+ {% endblock site_header %} +
+
+ {% block content required %} + {% endblock content %} +
+ + {% endblock body %} + + diff --git a/templates/blog-month-index.html b/templates/blog-month-index.html new file mode 100644 index 0000000..d114628 --- /dev/null +++ b/templates/blog-month-index.html @@ -0,0 +1,54 @@ +{% extends "base.html" %} + +{% from "macros/pagination.html" import render_month_archive_navigation %} + +{% block meta %} + {{ super() }} + +{% endblock %} +{% block styles %} + {{ super() }} + +{% endblock %} +{% set title = "Monthly archive: " + this.date | dateformat('MMMM y') %} +{% block content %} + {% set post_count = this.children.count() %} +
+
+
+

Monthly archive: {{ this.date | dateformat("MMMM y") }}

+

+ ({{ post_count }} + {% if post_count > 1 %} + posts) + {% else %} + post) + {% endif %} +

+
+
+ + + + + + + + + {% for post in this.children.order_by('-publication_date') %} + + + + + {% endfor %} + +
DateTitle
+ + + {{ post.title }} +
+ {{ render_month_archive_navigation(this) }} +
+{% endblock %} diff --git a/templates/blog-post.html b/templates/blog-post.html new file mode 100644 index 0000000..6ffb600 --- /dev/null +++ b/templates/blog-post.html @@ -0,0 +1,31 @@ +{% extends "base.html" %} + +{% from "macros/blog.html" import render_blog_post %} + +{% block meta %} + {{ super() }} + + + {% if this.publication_date %} + + {% endif %} + {% if this.updated_date %} + + {% endif %} + {% if this.category %} + {% set category = site.get("/categories/" + this.category) %} + + {% endif %} + {% for t in this.tags %} + + {% endfor %} +{% endblock %} +{% block styles %} + {{ super() }} + +{% endblock %} +{% block content %}{{ render_blog_post(this) }}{% endblock %} diff --git a/templates/blog-year-index.html b/templates/blog-year-index.html new file mode 100644 index 0000000..138cc86 --- /dev/null +++ b/templates/blog-year-index.html @@ -0,0 +1,54 @@ +{% extends "base.html" %} + +{% from "macros/pagination.html" import render_year_archive_navigation %} + +{% block meta %} + {{ super() }} + +{% endblock %} +{% block styles %} + {{ super() }} + +{% endblock %} +{% set title = "Yearly archive: " + this.key %} +{% block content %} + {% set post_count = this.children.count() %} +
+
+
+

Yearly archive: {{ this.key }}

+

+ ({{ post_count }} + {% if post_count > 1 %} + posts) + {% else %} + post) + {% endif %} +

+
+
+ + + + + + + + + {% for post in this.children.order_by('-publication_date') %} + + + + + {% endfor %} + +
DateTitle
+ + + {{ post.title }} +
+ {{ render_year_archive_navigation(this) }} +
+{% endblock %} diff --git a/templates/blog.html b/templates/blog.html new file mode 100644 index 0000000..0ee96c7 --- /dev/null +++ b/templates/blog.html @@ -0,0 +1,15 @@ +{% extends "base.html" %} + +{% from "macros/blog.html" import render_blog_index %} + +{% block meta %} + {{ super() }} + +{% endblock %} +{% block styles %} + {{ super() }} + +{% endblock %} +{% block content %}{{ render_blog_index(this.pagination) }}{% endblock %} diff --git a/templates/categories.html b/templates/categories.html new file mode 100644 index 0000000..4dbdf1a --- /dev/null +++ b/templates/categories.html @@ -0,0 +1,53 @@ +{% extends "base.html" %} + +{% block meta %} + {{ super() }} + +{% endblock %} +{% block styles %} + {{ super() }} + +{% endblock %} +{% set title = "Categories" %} +{% block content %} +
+
+

Categories

+
+ +
+{% endblock %} diff --git a/templates/category.html b/templates/category.html new file mode 100644 index 0000000..9d6b35d --- /dev/null +++ b/templates/category.html @@ -0,0 +1,51 @@ +{% extends "base.html" %} + +{% block meta %} + {{ super() }} + +{% endblock %} +{% block styles %} + {{ super() }} + +{% endblock %} +{% set title = "Category " + this.name %} +{% block content %} + {% set post_count = this.children.count() %} +
+
+
+

Category: {{ this.name }}

+

+ ({{ post_count }} + {% if post_count > 1 %} + posts) + {% else %} + post) + {% endif %} +

+
+
+ + + + + + + + + {% for post in this.children.order_by('-publication_date') %} + + + + + {% endfor %} + +
DateTitle
+ + + {{ post.title }} +
+
+{% endblock %} diff --git a/templates/index.html b/templates/index.html new file mode 100644 index 0000000..0ee96c7 --- /dev/null +++ b/templates/index.html @@ -0,0 +1,15 @@ +{% extends "base.html" %} + +{% from "macros/blog.html" import render_blog_index %} + +{% block meta %} + {{ super() }} + +{% endblock %} +{% block styles %} + {{ super() }} + +{% endblock %} +{% block content %}{{ render_blog_index(this.pagination) }}{% endblock %} diff --git a/templates/macros/blog.html b/templates/macros/blog.html new file mode 100644 index 0000000..b4caafe --- /dev/null +++ b/templates/macros/blog.html @@ -0,0 +1,100 @@ +{% from "macros/pagination.html" import render_pagination %} + +{% macro render_toc(toc, maxdepth=2) %} + +{% endmacro %} + +{% macro render_blog_post(post, show_toc=True) %} +
+
+
+

+ {{ post.title }} +

+
+ {% if post.publication_date %} +
+
Posted:
+
+ + + +
+
+ {% else %} + + {% endif %} + {% if post.updated_date and post.publication_date and (post.updated_date > post.publication_date) %} +
+
Last updated:
+
+ +
+
+ {% endif %} + {% if post.category %} +
+
Category:
+
+ {% set category = site.get('/categories/'+post.category) %} + {{ category.name }} +
+
+ {% endif %} +
+
+ {% if show_toc and post.render_toc %} + {% set toc = post.body.toc|default([]) %} + {{ render_toc(toc) }} + {% endif %} +
+ {{ post.body }} + {% if post.tags %} + + {% endif %} +
+{% endmacro %} + +{% macro render_blog_index(pagination) %} + {% for post in pagination.items %} + {{ render_blog_post(post, False) }} + {% if not loop.last %} +
+ {% endif %} + {% endfor %} + {% if pagination.pages > 1 %} + + {% endif %} +{% endmacro %} diff --git a/templates/macros/pagination.html b/templates/macros/pagination.html new file mode 100644 index 0000000..adb1b5c --- /dev/null +++ b/templates/macros/pagination.html @@ -0,0 +1,221 @@ +{% macro render_pagination(pagination, pagination_window=2) %} + {% set current = pagination.page %} + {% set total = pagination.pages %} + {% set window_start = [current - pagination_window, 1] | max %} + {% set window_end = [current + pagination_window, total] | min %} + +{% endmacro %} + +{% macro render_year_archive_navigation( + current_year_page, + archive_window=2 + ) %} + {% set current_year = current_year_page.year %} + {% set first_year_page = current_year_page.first_year_page %} + {% set last_year_page = current_year_page.last_year_page %} + {% set previous_year_page = current_year_page.previous_year_page %} + {% set next_year_page = current_year_page.next_year_page %} + {% set first_year = first_year_page.year %} + {% set last_year = last_year_page.year %} + {% set window_start = [current_year - archive_window, first_year] | max %} + {% set window_end = [current_year + archive_window, last_year] | min %} + +{% endmacro %} + +{% macro render_month_archive_navigation( + current_month_page, + archive_window=2 + ) %} + {% set year_pages = index_pages('year') | sort(attribute='year') %} + {% set ns = namespace( + month_pages=[], + current_position=none + ) %} + {% for year_page in year_pages %} + {% set ns.month_pages = ns.month_pages + (year_page.subindexes | sort(attribute='month')) %} + {% endfor %} + {% for month_page in ns.month_pages %} + {% if month_page.year == current_month_page.year + and month_page.month == current_month_page.month %} + {% set ns.current_position = loop.index0 %} + {% endif %} + {% endfor %} + {% set total = ns.month_pages | length %} + {% set current_position = ns.current_position %} + {% set window_start = [current_position - archive_window, 0] | max %} + {% set window_end = [current_position + archive_window, total - 1] | min %} + {% set first_month_page = ns.month_pages[0] %} + {% set last_month_page = ns.month_pages[total - 1] %} + {% set previous_month_page = none %} + {% set next_month_page = none %} + {% if current_position > 0 %} + {% set previous_month_page = ns.month_pages[current_position - 1] %} + {% endif %} + {% if current_position < total - 1 %} + {% set next_month_page = ns.month_pages[current_position + 1] %} + {% endif %} + +{% endmacro %} diff --git a/templates/page.html b/templates/page.html new file mode 100644 index 0000000..dd20955 --- /dev/null +++ b/templates/page.html @@ -0,0 +1,3 @@ +{% extends "base.html" %} + +{% block content %}
{{ this.body }}
{% endblock %} diff --git a/templates/tag.html b/templates/tag.html new file mode 100644 index 0000000..b433616 --- /dev/null +++ b/templates/tag.html @@ -0,0 +1,59 @@ +{% extends "base.html" %} + +{% block meta %} + {{ super() }} + +{% endblock %} +{% block styles %} + {{ super() }} + +{% endblock %} +{% set title = "Tag " + this.tag %} +{% block content %} + {% set post_count = this.items.count() %} +
+
+
+

Tag: {{ this.tag }}

+

+ ({{ post_count }} + {% if post_count > 1 %} + posts) + {% else %} + post) + {% endif %} +

+
+
+ + + + + + + + + {% for post in this.items %} + + + + + {% else %} + + + + {% endfor %} + +
DateTitle
+ {% if post.publication_date %} + + {% endif %} + + {{ post.title }} +
+ No posts for this tag. +
+
+{% endblock %} -- cgit v1.3.1