From e806dbf1cdd07809803cc80d98733f6aabef4fc9 Mon Sep 17 00:00:00 2001 From: Henk Kalkwater Date: Sun, 27 Jun 2021 12:50:21 +0200 Subject: [PATCH 01/20] Fix NixOS build --- mkDub.nix | 1 + 1 file changed, 1 insertion(+) diff --git a/mkDub.nix b/mkDub.nix index 2600d30..197dcde 100644 --- a/mkDub.nix +++ b/mkDub.nix @@ -1,4 +1,5 @@ { pkgs ? import {}, + lib ? pkgs.lib, stdenv ? pkgs.stdenv, rdmd ? pkgs.rdmd, dmd ? pkgs.dmd, From 75f5d447d6bffd82e5322322b73716afa2f0f8e4 Mon Sep 17 00:00:00 2001 From: Henk Kalkwater Date: Tue, 17 Aug 2021 10:04:36 +0200 Subject: [PATCH 02/20] Update dependencies Nix --- dub.json | 2 +- dub.selections.json | 8 ++++---- dub.selections.nix | 32 ++++++++++++++++---------------- 3 files changed, 21 insertions(+), 21 deletions(-) diff --git a/dub.json b/dub.json index da535bb..e256923 100644 --- a/dub.json +++ b/dub.json @@ -11,5 +11,5 @@ "license": "AGPLv3", "name": "mijnblog", "targetType": "executable", - "stringImportPaths": ["views", "translations"] + "stringImportPaths": ["views", "translations"] } diff --git a/dub.selections.json b/dub.selections.json index 5518a38..4ad848d 100644 --- a/dub.selections.json +++ b/dub.selections.json @@ -3,9 +3,9 @@ "versions": { "botan": "1.12.19", "botan-math": "1.0.3", - "diet-ng": "1.7.5", + "diet-ng": "1.8.0", "dyaml": "0.8.3", - "eventcore": "0.9.13", + "eventcore": "0.9.17", "fswatch": "0.5.0", "libasync": "0.8.6", "libevent": "2.0.2+2.0.16", @@ -13,9 +13,9 @@ "mir-linux-kernel": "1.0.1", "openssl": "1.1.6+1.0.1g", "stdx-allocator": "2.77.5", - "taggedalgebraic": "0.11.19", + "taggedalgebraic": "0.11.22", "tinyendian": "0.2.0", - "vibe-core": "1.13.0", + "vibe-core": "1.19.0", "vibe-d": "0.9.3" } } diff --git a/dub.selections.nix b/dub.selections.nix index 7ec6ca6..f22cf46 100644 --- a/dub.selections.nix +++ b/dub.selections.nix @@ -63,25 +63,25 @@ fetch = { type = "git"; url = "https://github.com/s-ludwig/taggedalgebraic.git"; - rev = "v0.11.19"; - sha256 = "1mb4l9hhkzhwwj2v3m9l4g59q66msy15ky762wk5dv11viyfwrqb"; + rev = "v0.11.22"; + sha256 = "02iy90nwy0zzy25hwdqbcgd0w0lwzramcvi3pgyljhq0w0vl5hkq"; fetchSubmodules = false; - date = "2021-01-13T16:58:20+01:00"; + date = "2021-05-20T21:00:02+02:00"; deepClone = false; leaveDotGit = false; - path = "/nix/store/qlw3bv0xqd0dl01hd9lcyk9cx7v9qhvi-taggedalgebraic"; + path = "/nix/store/p8id0qb13j8pjdczflj2x35w6v63q4cx-taggedalgebraic"; }; } { fetch = { type = "git"; url = "https://github.com/vibe-d/vibe-core.git"; - rev = "v1.13.0"; - sha256 = "1zbx861dwmkp14pbzr4qyq71xsnlksx248x0a1q6afjmvvxiys8w"; + rev = "v1.19.0"; + sha256 = "16xcfihian5sbhy532sfsz9qfaq5fbnlr51w66x1lrk8vwjglln2"; fetchSubmodules = false; - date = "2021-01-15T21:35:13+01:00"; + date = "2021-08-14T15:06:01+02:00"; deepClone = false; leaveDotGit = false; - path = "/nix/store/zngy3z8hmdgjg8wl31d2qy194mhz0i09-vibe-core"; + path = "/nix/store/3a8xazi5rcy1774ayc3iisqgbj1l0ncr-vibe-core"; }; } { fetch = { @@ -123,13 +123,13 @@ fetch = { type = "git"; url = "https://github.com/rejectedsoftware/diet-ng.git"; - rev = "v1.7.5"; - sha256 = "1cymg3v924d499sbjagjf5dqv1pj196c55a2282knxgq18a3gynf"; + rev = "v1.8.0"; + sha256 = "0ghddla911x815dxcqw3zfvd0276hijw5vkgn5g338asxsz5rxnk"; fetchSubmodules = false; - date = "2021-02-07T14:20:30+01:00"; + date = "2021-07-27T22:07:54+02:00"; deepClone = false; leaveDotGit = false; - path = "/nix/store/5r6v9f7y9kwkb90ihgimqqryf6ls3kqq-diet-ng"; + path = "/nix/store/wql4k19cxpdz9sj504hw3sl2rg0vixkw-diet-ng"; }; } { fetch = { @@ -159,13 +159,13 @@ fetch = { type = "git"; url = "https://github.com/vibe-d/eventcore.git"; - rev = "v0.9.13"; - sha256 = "0frxifhjwzyi35cv4pvv8k11a966fg76gqxdpmx9bsqbx750lrvz"; + rev = "v0.9.17"; + sha256 = "0rc4ayi2rg5yyyykmg805knqlyhkbkqprm83wls1bl7gn2661mkm"; fetchSubmodules = false; - date = "2021-01-12T19:20:28+01:00"; + date = "2021-07-05T12:45:15+02:00"; deepClone = false; leaveDotGit = false; - path = "/nix/store/yikgbvj61xhk6r3x8pc8lb7sp5smcn5q-eventcore"; + path = "/nix/store/006a14pi0q1vb699sn3arl5yx76rpfx8-eventcore"; }; } { fetch = { From 48b95c4a130ae0b105076a3bdca27444ab2e540d Mon Sep 17 00:00:00 2001 From: Henk Kalkwater Date: Mon, 11 Oct 2021 15:15:12 +0200 Subject: [PATCH 03/20] Article: parse document separators properly Document separators should start with a newline, followed by three dashes, followed by a newline again. --- source/nl/netsoj/chris/blog/model/page.d | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/nl/netsoj/chris/blog/model/page.d b/source/nl/netsoj/chris/blog/model/page.d index c3e71e5..833cf46 100644 --- a/source/nl/netsoj/chris/blog/model/page.d +++ b/source/nl/netsoj/chris/blog/model/page.d @@ -55,7 +55,7 @@ class Page { this.m_name = file; this.m_contentSource = readText(file); // Find the seperator and split the string in two - const uint seperatorIndex = cast(uint) lastIndexOf(m_contentSource, "---\n"); + const uint seperatorIndex = cast(uint) lastIndexOf(m_contentSource, "\n---\n"); enforce!ArticleParseException(seperatorIndex >= 0); string header = m_contentSource[0..seperatorIndex]; From 1d0d1a54b1e2c6154b12f54f21152a596d7d75b6 Mon Sep 17 00:00:00 2001 From: Henk Kalkwater Date: Wed, 13 Oct 2021 14:07:27 +0200 Subject: [PATCH 04/20] Fix crash caused by lastIndexOf returning negative numbers The ArticleParser would crash when a negative number was returned by lastIndexOf, since it was casted to an unsigned integer. This unsigned integer then was used to allocate memory, which would allocate way to much memory, causing a MemoryException and crashing the process. --- source/nl/netsoj/chris/blog/model/article.d | 4 ++-- source/nl/netsoj/chris/blog/model/page.d | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/source/nl/netsoj/chris/blog/model/article.d b/source/nl/netsoj/chris/blog/model/article.d index ef4b838..a2b10d4 100644 --- a/source/nl/netsoj/chris/blog/model/article.d +++ b/source/nl/netsoj/chris/blog/model/article.d @@ -34,9 +34,9 @@ class Article : Page { // Find the first header and mark everything up to that as if (m_excerpt is null) { // an excerpt, used in search results. - const uint seperatorIndex = cast(uint) indexOf(m_contentSource, "---\n"); + const long seperatorIndex = cast(long) lastIndexOf(m_contentSource, "---\n"); this.m_excerpt = this.m_contentSource[seperatorIndex + 4..$]; - const uint firstHeaderIndex = cast(uint) indexOf(this.m_excerpt, '#'); + const long firstHeaderIndex = indexOf(this.m_excerpt, '#'); if (firstHeaderIndex >= 0) { this.m_excerpt = this.m_excerpt[0..firstHeaderIndex]; } diff --git a/source/nl/netsoj/chris/blog/model/page.d b/source/nl/netsoj/chris/blog/model/page.d index 833cf46..d80faf0 100644 --- a/source/nl/netsoj/chris/blog/model/page.d +++ b/source/nl/netsoj/chris/blog/model/page.d @@ -55,7 +55,7 @@ class Page { this.m_name = file; this.m_contentSource = readText(file); // Find the seperator and split the string in two - const uint seperatorIndex = cast(uint) lastIndexOf(m_contentSource, "\n---\n"); + const long seperatorIndex = lastIndexOf(m_contentSource, "\n---\n"); enforce!ArticleParseException(seperatorIndex >= 0); string header = m_contentSource[0..seperatorIndex]; From f405f997295b0a30c5e79e0ad9eddc509a89469b Mon Sep 17 00:00:00 2001 From: Chris Josten Date: Thu, 18 Nov 2021 19:28:24 +0100 Subject: [PATCH 05/20] WIP --- dub.json | 11 +- dub.selections.json | 2 + source/nl/netsoj/chris/blog/cache.d | 13 +- source/nl/netsoj/chris/blog/constants.d | 8 +- source/nl/netsoj/chris/blog/interfaces/http.d | 72 +++++++---- .../netsoj/chris/blog/interfaces/indieauth.d | 92 ++++++++++++++ .../netsoj/chris/blog/interfaces/micropub.d | 14 +++ source/nl/netsoj/chris/blog/main.d | 15 +-- .../chris/blog/microformats/definitions.d | 12 ++ .../netsoj/chris/blog/microformats/parser.d | 116 ++++++++++++++++++ source/nl/netsoj/chris/blog/model/article.d | 6 +- source/nl/netsoj/chris/blog/model/page.d | 4 +- source/nl/netsoj/chris/blog/model/project.d | 8 +- source/nl/netsoj/chris/blog/package.d | 1 + source/nl/netsoj/chris/blog/staticpaths.d | 2 + source/nl/netsoj/chris/blog/url.d | 27 ++++ source/nl/netsoj/chris/blog/utils.d | 2 + source/nl/netsoj/chris/blog/watcher.d | 12 +- views/pages/article-list.dt | 2 +- views/pages/article.dt | 2 +- views/pages/indieauth.dt | 21 ++++ views/parts/page.dt | 2 +- 22 files changed, 393 insertions(+), 51 deletions(-) create mode 100644 source/nl/netsoj/chris/blog/interfaces/indieauth.d create mode 100644 source/nl/netsoj/chris/blog/interfaces/micropub.d create mode 100644 source/nl/netsoj/chris/blog/microformats/definitions.d create mode 100644 source/nl/netsoj/chris/blog/microformats/parser.d create mode 100644 source/nl/netsoj/chris/blog/package.d create mode 100644 source/nl/netsoj/chris/blog/url.d create mode 100644 views/pages/indieauth.dt diff --git a/dub.json b/dub.json index da535bb..0ff17ad 100644 --- a/dub.json +++ b/dub.json @@ -5,11 +5,16 @@ "copyright": "Copyright © 2019, Chris Josten", "dependencies": { "dyaml": "~>0.8.0", + "htmld": "~>0.3.7", "vibe-d": "~>0.9.0" }, "description": "A blog based on Markdown and JSON", "license": "AGPLv3", + "mainSourceFile": "source/nl/netsoj/chris/blog/main.d", "name": "mijnblog", - "targetType": "executable", - "stringImportPaths": ["views", "translations"] -} + "stringImportPaths": [ + "views", + "translations" + ], + "targetType": "executable" +} \ No newline at end of file diff --git a/dub.selections.json b/dub.selections.json index 5518a38..d511768 100644 --- a/dub.selections.json +++ b/dub.selections.json @@ -7,7 +7,9 @@ "dyaml": "0.8.3", "eventcore": "0.9.13", "fswatch": "0.5.0", + "htmld": "0.3.7", "libasync": "0.8.6", + "libdominator": "1.1.7", "libevent": "2.0.2+2.0.16", "memutils": "1.0.4", "mir-linux-kernel": "1.0.1", diff --git a/source/nl/netsoj/chris/blog/cache.d b/source/nl/netsoj/chris/blog/cache.d index 50e2e87..46d4e3f 100644 --- a/source/nl/netsoj/chris/blog/cache.d +++ b/source/nl/netsoj/chris/blog/cache.d @@ -1,9 +1,14 @@ +/** + * Implements and holds caches for several pages. + */ +module nl.netsoj.chris.blog.cache; + import std.experimental.logger; import std.traits; -import article; -import page; -import project; +import nl.netsoj.chris.blog.model.article; +import nl.netsoj.chris.blog.model.page; +import nl.netsoj.chris.blog.model.project; /** @@ -21,7 +26,7 @@ GenericCache!(Project, "a.title < b.title") projects; * again if needed. */ struct GenericCache(T, string sortOrder) - if (isImplicitlyConvertible!(T, Page)) { + if (is(T : Page)) { public: void addItem(T item) { diff --git a/source/nl/netsoj/chris/blog/constants.d b/source/nl/netsoj/chris/blog/constants.d index 8b665d3..ee4b395 100644 --- a/source/nl/netsoj/chris/blog/constants.d +++ b/source/nl/netsoj/chris/blog/constants.d @@ -1,8 +1,14 @@ +module nl.netsoj.chris.blog.constants; + /** * Constants which are passed to templates while rendering. */ class Constants { public static immutable string SITE_NAME = "Chris Josten's site"; - public static immutable string SITE_URL = "https://chris.netsoj.nl"; + debug { + public static immutable string SITE_URL = "https://kortstondig.chris.netsoj.nl"; + } else { + public static immutable string SITE_URL = "https://chris.netsoj.nl"; + } public static immutable string COPYRIGHT = "© Chris Josten, 2020"; } diff --git a/source/nl/netsoj/chris/blog/interfaces/http.d b/source/nl/netsoj/chris/blog/interfaces/http.d index 0437bcb..dfcb0cd 100644 --- a/source/nl/netsoj/chris/blog/interfaces/http.d +++ b/source/nl/netsoj/chris/blog/interfaces/http.d @@ -1,9 +1,15 @@ +module nl.netsoj.chris.blog.interfaces.http; + +import std.experimental.logger; + import vibe.d; -import cache; -import article; -import page; -import project; +import nl.netsoj.chris.blog.interfaces.indieauth; +import nl.netsoj.chris.blog.model.article; +import nl.netsoj.chris.blog.model.page; +import nl.netsoj.chris.blog.model.project; +import nl.netsoj.chris.blog.cache; +import nl.netsoj.chris.blog.constants; /** * Output types for the content. @@ -69,29 +75,41 @@ struct TranslateContext { * templateName = The name of the template to render. */ string singleResponseMixin(string arrayName, string templateName) { - return `string slug = req.params["slug"]; - OutputType outputType = getOutputType(slug); + return `string slug = req.params["slug"]; + OutputType outputType = getOutputType(slug); - enforceHTTP(slug in ` ~ arrayName ~ `, HTTPStatus.notFound, "Page not found"); - auto content = ` ~ arrayName ~ `[slug]; - switch(outputType) with (OutputType) { - case MARKDOWN: - res.writeBody(content.contentSource, MIME_MARKDOWN); - break; - default: - case HTML: - render!("` ~ templateName ~ `", content); - break; - }`; - } + enforceHTTP(slug in ` ~ arrayName ~ `, HTTPStatus.notFound, "Page not found"); + auto content = ` ~ arrayName ~ `[slug]; + switch(outputType) with (OutputType) { + case MARKDOWN: + res.writeBody(content.contentSource, MIME_MARKDOWN); + break; + default: + case HTML: + render!("` ~ templateName ~ `", content); + break; + }`; +} @translationContext!TranslateContext class MijnBlog { public: + + this() { + m_indieAuth = new IndieAuth(); + } + + /** + * IndieAuth subinterface + */ + @safe + @path("/indieweb") + @property IndieAuth indieweb() { return m_indieAuth; }; + /** - * Generates response for /posts/:slug and /palen/:slug. - */ + * Generates response for /posts/:slug and /palen/:slug. + */ @path("/posts/:slug") void getArticleSingle(string _slug, HTTPServerRequest req, HTTPServerResponse res) { //getSingle!(Article, "pages/article.dt")(articles, req, res); @@ -140,8 +158,13 @@ public: addCachingHeader(res); // If no slug is supplied, it will be adjusted to "index" req.params.addField("slug", "index"); + res.headers.addField("Link", "<" ~ Constants.SITE_URL ~ "/indieweb/auth>; rel=\"authorization_endpoint\""); + res.headers.addField("Link", "<" ~ Constants.SITE_URL ~ "/indieweb/token>; rel=\"token_endpoint\""); + res.headers.addField("Link", "<" ~ Constants.SITE_URL ~ "/indieweb/micropub>; rel=\"micropub\""); mixin(singleResponseMixin("pages", "pages/page.dt")); - } + } +private: + IndieAuth m_indieAuth; } /** @@ -149,7 +172,9 @@ public: */ @safe void errorPage(HTTPServerRequest req, HTTPServerResponse res, HTTPServerErrorInfo error) { - render!("pages/error.dt", error)(res); + //render!("pages/error.dt", error)(res); + import std.conv; + res.writeBody(text("Error ", error.code, ": ", error.message, "\n\n", error.debugMessage), "text/plain"); } @trusted @@ -171,4 +196,7 @@ void startHTTPServer() { router.registerWebInterface(new MijnBlog); listenHTTP(settings, router); + foreach(route; router.getAllRoutes()) { + infof("Path: %s", route); + } } diff --git a/source/nl/netsoj/chris/blog/interfaces/indieauth.d b/source/nl/netsoj/chris/blog/interfaces/indieauth.d new file mode 100644 index 0000000..841f73c --- /dev/null +++ b/source/nl/netsoj/chris/blog/interfaces/indieauth.d @@ -0,0 +1,92 @@ +module nl.netsoj.chris.blog.interfaces.indieauth; + +import nl.netsoj.chris.blog.interfaces.http; +import nl.netsoj.chris.blog.interfaces.micropub; +import nl.netsoj.chris.blog.microformats.parser; + +import mfd = nl.netsoj.chris.blog.microformats.definitions; + +import vibe.d; + +struct App { + string logo;; + string name; + string url; + string clientId; + string redirectUri; + bool richInfo = false; +} + +/** + * An implementation of https://www.w3.org/TR/indieauth/ + */ +@translationContext!TranslateContext +class IndieAuth { +public: + this() { + m_microPub = new MicroPub(); + } + + @queryParam("response_type", "response_type") + @queryParam("me", "me") + @queryParam("client_id", "client_id") + @queryParam("redirect_uri", "redirect_uri") + @queryParam("state", "state") + void getAuth(HTTPServerRequest req, HTTPServerResponse res, + string response_type, string me, string client_id, string redirect_uri, string state) { + enforceHTTP(response_type == "code", HTTPStatus.badRequest); + + URL source = URL(client_id); + HTTPClientResponse response = requestHTTP(source); + mfd.App[] parsedApps = parsePage!(mfd.App)(response.bodyReader.readAllUTF8, source); + + App app; + app.name = client_id; + app.richInfo = false; + if (parsedApps.length > 0) { + auto parsedApp = parsedApps[0]; + app.logo = parsedApp.logo; + app.name = parsedApp.name; + app.richInfo = true; + } + + app.clientId = client_id; + URL redirectUrl = URL(redirect_uri); + redirectUrl.queryString = redirectUrl.queryString ~ "%scode=%s&state=%s".format( + (redirectUrl.queryString.length == 0 ? "" : "&"), + "123456", state); + app.redirectUri = redirectUrl.toString(); + render!("pages/indieauth.dt", app); + } + + @safe + @path("/") + void getIndex(HTTPServerResponse res) { + res.writeBody("IndieAuth root", "text/plain"); + } + + @safe + void postToken(HTTPServerRequest req, HTTPServerResponse res, + string grant_type, string code, string client_id, string redirect_uri, string me) { + + struct OkResponse { + string access_token; + string token_type = "Bearer"; + string me; + string scope_; + } + + OkResponse response; + response.me = me; + response.scope_ = "foo"; + response.access_token = "baz"; + + res.writeJsonBody(response, HTTPStatus.ok); + } + + @safe @property + MicroPub micropub() { return m_microPub; }; + +private: + MicroPub m_microPub; +} diff --git a/source/nl/netsoj/chris/blog/interfaces/micropub.d b/source/nl/netsoj/chris/blog/interfaces/micropub.d new file mode 100644 index 0000000..7e6ece2 --- /dev/null +++ b/source/nl/netsoj/chris/blog/interfaces/micropub.d @@ -0,0 +1,14 @@ +module nl.netsoj.chris.blog.interfaces.micropub; + +import vibe.d; + +import nl.netsoj.chris.blog.interfaces.http; + +@translationContext!TranslateContext +class MicroPub { + + @path("/") + void getIndex(HTTPServerRequest req, HTTPServerResponse res) { + res.writeBody("MicroPub endpoint", "text/plain"); + } +} diff --git a/source/nl/netsoj/chris/blog/main.d b/source/nl/netsoj/chris/blog/main.d index f647a10..5174e36 100644 --- a/source/nl/netsoj/chris/blog/main.d +++ b/source/nl/netsoj/chris/blog/main.d @@ -1,13 +1,14 @@ +module nl.netsoj.chris.blog.main; + import std.experimental.logger; import vibe.d; -import article; -import page; -import project; - -import cache; -import http; -import watcher; +import nl.netsoj.chris.blog.interfaces.http; +import nl.netsoj.chris.blog.model.article; +import nl.netsoj.chris.blog.model.page; +import nl.netsoj.chris.blog.model.project; +import nl.netsoj.chris.blog.cache; +import nl.netsoj.chris.blog.watcher; void main() { diff --git a/source/nl/netsoj/chris/blog/microformats/definitions.d b/source/nl/netsoj/chris/blog/microformats/definitions.d new file mode 100644 index 0000000..66c0417 --- /dev/null +++ b/source/nl/netsoj/chris/blog/microformats/definitions.d @@ -0,0 +1,12 @@ +module nl.netsoj.chris.blog.microformats.definitions; + +import nl.netsoj.chris.blog.microformats.parser; + +struct App { + @MicroFormat(MicroFormat.Type.Url) + string logo; + @MicroFormat(MicroFormat.Type.PlainText) + string name; + + string url; +} diff --git a/source/nl/netsoj/chris/blog/microformats/parser.d b/source/nl/netsoj/chris/blog/microformats/parser.d new file mode 100644 index 0000000..d72279a --- /dev/null +++ b/source/nl/netsoj/chris/blog/microformats/parser.d @@ -0,0 +1,116 @@ +/** + * Parser for microformats. + * Standards: http://microformats.org/wiki/microformats2-parsing + */ +module nl.netsoj.chris.blog.microformats.parser; + +import std.exception; +import std.traits; + +import vibe.inet.url; + +import html; + +import nl.netsoj.chris.blog.url; + +class MicroFormatParseException : Exception { + mixin basicExceptionCtors; +} + + +struct MicroFormatProperty { + enum Type { + RootClass, + PlainText, + Url, + DateTime, + EmbeddedMarkup + }; + + Type type = Type.PlainText; + + string getClassPrefix() pure { + final switch(type) { + case Type.RootClass: + return "h-"; + case Type.PlainText: + return "p-"; + case Type.Url: + return "u-"; + case Type.DateTime: + return "dt-"; + case Type.EmbeddedMarkup: + return "e-"; + } + } +}; + +/** + * Parses a web page to extract to the given microformat model out of it + */ +T[] parsePage(T)(string source, URL url) { + return parsePage!T(createDocument(source), url); +} + +T[] parsePage(T)(Document page, URL url) + if (isAggregateType!T) { + import std.algorithm; + import std.array; + import std.conv; + import std.range; + import std.string; + + string rootClass = "h-" ~ T.stringof.toLower; + + return page.querySelectorAll(".%s".format(rootClass)).map!((node){ + alias PropType = MicroFormatProperty.Type; + T instance = T(); + MicroFormatProperty uda; + string propertyClass; + Node propNode; + static foreach(sym; getSymbolsByUDA!(T, MicroFormatProperty)) { + uda = getUDAs!(sym, MicroFormatProperty)[0]; + propertyClass = uda.getClassPrefix() ~ sym.stringof.toLower; + + propNode = page.querySelector(".%s".format(propertyClass), node); + switch (uda.type) { + case PropType.PlainText: + if (propNode.firstChild && propNode.firstChild.isTextNode()) { + __traits(getMember, instance, sym.stringof) = to!string(propNode.text); + } + break; + case PropType.Url: + if (propNode.tag == "a" && propNode.hasAttr("href")) { + __traits(getMember, instance, sym.stringof) = resolveURL(url, to!string(propNode["href"])).toString; + } else if (propNode.tag == "img" && propNode.hasAttr("src")) { + __traits(getMember, instance, sym.stringof) = resolveURL(url, to!string(propNode["src"])).toString; + } + break; + default: + break; + } + } + + return instance; + }).array; +} + +unittest { + import std.stdio; + string page = q"eos +
+ + Example App +
" +eos"; + + struct App { + @MicroFormatProperty(MicroFormatProperty.Type.Url) + string logo; + @MicroFormatProperty(MicroFormatProperty.Type.PlainText) + string name; + } + + auto ts = parsePage!App(page, URL("https://example.com/")); + assert(ts[0] == App ("https://example.com:443/logo.png", "Example App")); +} diff --git a/source/nl/netsoj/chris/blog/model/article.d b/source/nl/netsoj/chris/blog/model/article.d index ef4b838..b22677b 100644 --- a/source/nl/netsoj/chris/blog/model/article.d +++ b/source/nl/netsoj/chris/blog/model/article.d @@ -1,3 +1,5 @@ +module nl.netsoj.chris.blog.model.article; + import std.file; import std.stdio; import std.string; @@ -7,8 +9,8 @@ import std.experimental.logger; import dyaml; import vibe.d; -import page; -import utils; +import nl.netsoj.chris.blog.model.page; +import nl.netsoj.chris.blog.utils; /** diff --git a/source/nl/netsoj/chris/blog/model/page.d b/source/nl/netsoj/chris/blog/model/page.d index c3e71e5..e0b14d8 100644 --- a/source/nl/netsoj/chris/blog/model/page.d +++ b/source/nl/netsoj/chris/blog/model/page.d @@ -1,3 +1,5 @@ +module nl.netsoj.chris.blog.model.page; + import std.exception; import std.experimental.logger; import std.file; @@ -7,7 +9,7 @@ import std.stdio; import dyaml; import vibe.vibe; -import utils; +import nl.netsoj.chris.blog.utils; /** diff --git a/source/nl/netsoj/chris/blog/model/project.d b/source/nl/netsoj/chris/blog/model/project.d index f96d8d0..1b02a91 100644 --- a/source/nl/netsoj/chris/blog/model/project.d +++ b/source/nl/netsoj/chris/blog/model/project.d @@ -1,3 +1,5 @@ +module nl.netsoj.chris.blog.model.project; + import std.array; import std.algorithm; import std.typecons; @@ -5,9 +7,9 @@ import std.typecons; import dyaml; import vibe.vibe; -import page; -import utils; -import staticpaths; +import nl.netsoj.chris.blog.model.page; +import nl.netsoj.chris.blog.staticpaths; +import nl.netsoj.chris.blog.utils; /** * Represents a project, like an unfinished application diff --git a/source/nl/netsoj/chris/blog/package.d b/source/nl/netsoj/chris/blog/package.d new file mode 100644 index 0000000..2ff5397 --- /dev/null +++ b/source/nl/netsoj/chris/blog/package.d @@ -0,0 +1 @@ +module nl.netsoj.chris.blog; diff --git a/source/nl/netsoj/chris/blog/staticpaths.d b/source/nl/netsoj/chris/blog/staticpaths.d index 663cc5d..36f16b3 100644 --- a/source/nl/netsoj/chris/blog/staticpaths.d +++ b/source/nl/netsoj/chris/blog/staticpaths.d @@ -1,3 +1,5 @@ +module nl.netsoj.chris.blog.staticpaths; + /** * Paths to static data. */ diff --git a/source/nl/netsoj/chris/blog/url.d b/source/nl/netsoj/chris/blog/url.d new file mode 100644 index 0000000..d069c36 --- /dev/null +++ b/source/nl/netsoj/chris/blog/url.d @@ -0,0 +1,27 @@ +module nl.netsoj.chris.blog.url; + +import std.exception; + +import vibe.inet.url; + +URL resolveURL(URL source, string other) { + URL otherUrl = URL(other); + if (otherUrl.schema.length > 0 || otherUrl.host.length > 0) { + return otherUrl; + } + + if (otherUrl.schema.length == 0) { + enforce(source.schema.length > 0, "Source URL must have a scheme to resolve the other URL"); + otherUrl.schema = source.schema; + } + + if (otherUrl.host.length == 0) { + otherUrl.host = source.host; + otherUrl.port = source.port; + if (!otherUrl.path.absolute) { + otherUrl.path = source.path ~ otherUrl.path; + } + } + + return otherUrl; +} diff --git a/source/nl/netsoj/chris/blog/utils.d b/source/nl/netsoj/chris/blog/utils.d index bb1a681..953e9ff 100644 --- a/source/nl/netsoj/chris/blog/utils.d +++ b/source/nl/netsoj/chris/blog/utils.d @@ -1,3 +1,5 @@ +module nl.netsoj.chris.blog.utils; + import std.algorithm; import std.array; import std.conv; diff --git a/source/nl/netsoj/chris/blog/watcher.d b/source/nl/netsoj/chris/blog/watcher.d index 6f0d399..6acf760 100644 --- a/source/nl/netsoj/chris/blog/watcher.d +++ b/source/nl/netsoj/chris/blog/watcher.d @@ -1,3 +1,5 @@ +module nl.netsoj.chris.blog.watcher; + import std.array; import std.algorithm; import std.experimental.logger; @@ -7,14 +9,14 @@ import std.traits; import vibe.d; -import cache; -import page; +import nl.netsoj.chris.blog.cache; +import nl.netsoj.chris.blog.model.page; /** * Loads pages into memory and sets up a "watcher" to watch a directory for file changes. */ void initPages(T, C)(C *cache, const string directory) - if (isImplicitlyConvertible!(T, Page)) { + if (is(T : Page)) { bool addPage(string path) { try { @@ -22,7 +24,7 @@ void initPages(T, C)(C *cache, const string directory) logf("Added %s", newPage.slug); cache.addItem(newPage); return true; - } catch (page.ArticleParseException e) { + } catch (ArticleParseException e) { logf("Could not parse %s: %s", path, e); return false; } catch (Exception e) { @@ -72,7 +74,7 @@ void initPages(T, C)(C *cache, const string directory) try { newPage = new T(change.path.toString()); cache.changeItem(newPage); - } catch(page.ArticleParseException e) { + } catch(ArticleParseException e) { warningf("Could not parse %s", change.path.toString()); } catch (Exception e) { warningf("Error while updating %s: %s", change.path.toString(), e.msg); diff --git a/views/pages/article-list.dt b/views/pages/article-list.dt index 03e6662..4846256 100644 --- a/views/pages/article-list.dt +++ b/views/pages/article-list.dt @@ -12,7 +12,7 @@ block content - if (articleList.length == 0) p No posts found - else - - import utils; + - import nl.netsoj.chris.blog.utils; - foreach(article; articleList) article header diff --git a/views/pages/article.dt b/views/pages/article.dt index 835f174..81d4502 100644 --- a/views/pages/article.dt +++ b/views/pages/article.dt @@ -14,7 +14,7 @@ block extra_meta_data block content article(itemscope, itemtype="https://schema.org/BlogPosting", lang="#{content.language}") - - import utils; + - import nl.netsoj.chris.blog.utils; header h1.title(itemprop="headline") #{content.title} p.subtitle diff --git a/views/pages/indieauth.dt b/views/pages/indieauth.dt new file mode 100644 index 0000000..4e01fc7 --- /dev/null +++ b/views/pages/indieauth.dt @@ -0,0 +1,21 @@ +extends parts/page + +block header + title Aanmelden - Netsoj.nl + +block sidebar + +block content + - if (app.richInfo) + header.project-header + img.project-icon(src="#{app.logo}", alt="Icon of #{app.name}") + div + h1.title Aanmelden met #[a(href="#{app.clientId}") #{app.name}] + span.project-description #{app.clientId} + - else + header + h1.title Aanmelden met #[a(href="#{app.clientId}") #{app.name}] + + p #{app.name} wil uw identiteit bevestigen. + + p #[a(href="#{app.redirectUri}") Aanvaarden en aanmelden]  #[a(href="javascript:history.back();") Weigeren] diff --git a/views/parts/page.dt b/views/parts/page.dt index bf91a60..924b43c 100644 --- a/views/parts/page.dt +++ b/views/parts/page.dt @@ -1,6 +1,6 @@ doctype html html(prefix="og: http://ogp.me/ns#") - - import constants; + - import nl.netsoj.chris.blog.constants; - import vibe.d; head //- Kick off loading the css as fast as possible From 223ca8bc29483cb47b213c0a1ed4d031ca37b539 Mon Sep 17 00:00:00 2001 From: Henk Kalkwater Date: Wed, 25 May 2022 14:46:37 +0200 Subject: [PATCH 06/20] Workaround broken l10n, upgrade deps --- dub.selections.json | 8 ++++---- source/nl/netsoj/chris/blog/interfaces/http.d | 4 ++-- views/parts/footer.dt | 2 +- views/parts/page.dt | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/dub.selections.json b/dub.selections.json index 4ad848d..a642910 100644 --- a/dub.selections.json +++ b/dub.selections.json @@ -4,8 +4,8 @@ "botan": "1.12.19", "botan-math": "1.0.3", "diet-ng": "1.8.0", - "dyaml": "0.8.3", - "eventcore": "0.9.17", + "dyaml": "0.8.6", + "eventcore": "0.9.20", "fswatch": "0.5.0", "libasync": "0.8.6", "libevent": "2.0.2+2.0.16", @@ -15,7 +15,7 @@ "stdx-allocator": "2.77.5", "taggedalgebraic": "0.11.22", "tinyendian": "0.2.0", - "vibe-core": "1.19.0", - "vibe-d": "0.9.3" + "vibe-core": "1.22.3", + "vibe-d": "0.9.4" } } diff --git a/source/nl/netsoj/chris/blog/interfaces/http.d b/source/nl/netsoj/chris/blog/interfaces/http.d index 0437bcb..b5a325e 100644 --- a/source/nl/netsoj/chris/blog/interfaces/http.d +++ b/source/nl/netsoj/chris/blog/interfaces/http.d @@ -58,7 +58,7 @@ struct TranslateContext { mixin translationModule!"mijnblog"; static string determineLanguage(scope HTTPServerRequest req) { if ("lang" !in req.query) return req.determineLanguageByHeader(languages); // default behaviour using "Accept-Language" header - return req.query.get("lang", "en_GB"); + return determineLanguageByHeader(req.query.get("lang", "en_GB"), languages); } } @@ -80,7 +80,7 @@ string singleResponseMixin(string arrayName, string templateName) { break; default: case HTML: - render!("` ~ templateName ~ `", content); + res.render!("` ~ templateName ~ `", content); break; }`; } diff --git a/views/parts/footer.dt b/views/parts/footer.dt index 1385b34..b018fe1 100644 --- a/views/parts/footer.dt +++ b/views/parts/footer.dt @@ -1,2 +1,2 @@ p - small& template.page.copyright + small !{trWeb("template.page.copyright")} diff --git a/views/parts/page.dt b/views/parts/page.dt index bf91a60..513f375 100644 --- a/views/parts/page.dt +++ b/views/parts/page.dt @@ -46,7 +46,7 @@ html(prefix="og: http://ogp.me/ns#") section.header-navigation header img.logo(src="/static/img/logo.png", alt="The logo of the website: the letter C drawn in an unprofessional manner with wobbly eyes on put on top") - p& template.page.name + p #{trWeb("template.page.name")} nav ul - menuItem("template.menu.home", "/"); From 16f25d8297cfbfa1eb5cc93e22f35c30c544356e Mon Sep 17 00:00:00 2001 From: Henk Kalkwater Date: Wed, 25 May 2022 14:49:54 +0200 Subject: [PATCH 07/20] Update nix deps --- dub.selections.nix | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/dub.selections.nix b/dub.selections.nix index f22cf46..ee54309 100644 --- a/dub.selections.nix +++ b/dub.selections.nix @@ -75,25 +75,25 @@ fetch = { type = "git"; url = "https://github.com/vibe-d/vibe-core.git"; - rev = "v1.19.0"; - sha256 = "16xcfihian5sbhy532sfsz9qfaq5fbnlr51w66x1lrk8vwjglln2"; + rev = "v1.22.3"; + sha256 = "198s86ym28nnlyif47fa03ldif7w0qbx7bndgav0n79bdw9mw7i7"; fetchSubmodules = false; - date = "2021-08-14T15:06:01+02:00"; + date = "2022-04-01T21:24:37+02:00"; deepClone = false; leaveDotGit = false; - path = "/nix/store/3a8xazi5rcy1774ayc3iisqgbj1l0ncr-vibe-core"; + path = "/nix/store/y46jzkaqgjgix4df0gws12pvpxlvkx1s-vibe-core"; }; } { fetch = { type = "git"; url = "https://github.com/vibe-d/vibe.d.git"; - rev = "v0.9.3"; - sha256 = "10a5njn2nq1z6gknmkg6m6wrbndzj19mpg5860ky6v12b9ks76z3"; + rev = "v0.9.4"; + sha256 = "0kjnrlz30gvb08v79d7x5laapk13fxyj1cfr31wx0px3xrnjpzfq"; fetchSubmodules = false; - date = "2021-01-29T11:37:00+01:00"; + date = "2021-09-30T14:24:53+02:00"; deepClone = false; leaveDotGit = false; - path = "/nix/store/8r3h7npkhj9wq9lg64i3fih19hibpmg5-vibe.d"; + path = "/nix/store/3x9z474xs6d65w1my3s7kbr45z938rnc-vibe.d"; }; } { fetch = { @@ -159,25 +159,25 @@ fetch = { type = "git"; url = "https://github.com/vibe-d/eventcore.git"; - rev = "v0.9.17"; - sha256 = "0rc4ayi2rg5yyyykmg805knqlyhkbkqprm83wls1bl7gn2661mkm"; + rev = "v0.9.20"; + sha256 = "0dpi01jjz83zv7i2d5vkv0hj6iiqzzh12vvajlh3girarbi72id0"; fetchSubmodules = false; - date = "2021-07-05T12:45:15+02:00"; + date = "2021-12-17T13:21:51+01:00"; deepClone = false; leaveDotGit = false; - path = "/nix/store/006a14pi0q1vb699sn3arl5yx76rpfx8-eventcore"; + path = "/nix/store/hyzyr2j4s8a3dr9rdggm9n09fap8lb0q-eventcore"; }; } { fetch = { type = "git"; url = "https://github.com/kiith-sa/D-YAML.git"; - rev = "v0.8.3"; - sha256 = "13wy304xjbwkpgg7ilql1lkxkm83s87jm59ffnrg26slp7cx149q"; + rev = "v0.8.6"; + sha256 = "1bvidcxp1n65r4wmiqakyl8vjvhqh3gln9wbsmbxrx9mf6k0zv8h"; fetchSubmodules = false; - date = "2020-09-19T23:46:57+02:00"; + date = "2022-05-15T16:18:02-03:00"; deepClone = false; leaveDotGit = false; - path = "/nix/store/3i8i56lkmw2xq3lxr5h66v909waq2mqg-D-YAML"; + path = "/nix/store/zd0yayd1j11809j95sjs4gdqcngvkhbg-D-YAML"; }; } { fetch = { From 40eeb9d25ac2863c8cea2257356fb1867d834180 Mon Sep 17 00:00:00 2001 From: Henk Kalkwater Date: Mon, 30 May 2022 12:48:57 +0200 Subject: [PATCH 08/20] Fix whitespace --- translations/mijnblog.en_GB.po | 2 +- translations/mijnblog.nl_NL.po | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/translations/mijnblog.en_GB.po b/translations/mijnblog.en_GB.po index 8aa177c..b94b9a3 100644 --- a/translations/mijnblog.en_GB.po +++ b/translations/mijnblog.en_GB.po @@ -17,6 +17,6 @@ msgid "template.menu.contact" msgstr "Contact" msgid "template.page.copyright" -msgstr "© Chris Josten, 2021. If not specified otherwise, all content on this" +msgstr "© Chris Josten, 2021. If not specified otherwise, all content on this " "website is " "licensed under the CC-BY 4.0" diff --git a/translations/mijnblog.nl_NL.po b/translations/mijnblog.nl_NL.po index 8271a8a..f8e3f38 100644 --- a/translations/mijnblog.nl_NL.po +++ b/translations/mijnblog.nl_NL.po @@ -30,5 +30,5 @@ msgid "template.menu.contact" msgstr "Contact" msgid "template.page.copyright" -msgstr "© Chris Josten, 2021. Tenzij anders vermeld staat, valt alle inhoud op deze webstek" +msgstr "© Chris Josten, 2021. Tenzij anders vermeld staat, valt alle inhoud op deze webstek " " onder de CC-BY 4.0" From cc526e88f03c9e60c28c2c1c65b620cc7f62399d Mon Sep 17 00:00:00 2001 From: Chris Josten Date: Thu, 15 Sep 2022 17:41:15 +0200 Subject: [PATCH 09/20] Upgrade dependencies --- dub.selections.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/dub.selections.json b/dub.selections.json index a642910..fe10ba0 100644 --- a/dub.selections.json +++ b/dub.selections.json @@ -3,7 +3,7 @@ "versions": { "botan": "1.12.19", "botan-math": "1.0.3", - "diet-ng": "1.8.0", + "diet-ng": "1.8.1", "dyaml": "0.8.6", "eventcore": "0.9.20", "fswatch": "0.5.0", @@ -11,11 +11,11 @@ "libevent": "2.0.2+2.0.16", "memutils": "1.0.4", "mir-linux-kernel": "1.0.1", - "openssl": "1.1.6+1.0.1g", + "openssl": "3.2.2", "stdx-allocator": "2.77.5", "taggedalgebraic": "0.11.22", "tinyendian": "0.2.0", - "vibe-core": "1.22.3", - "vibe-d": "0.9.4" + "vibe-core": "1.22.4", + "vibe-d": "0.9.5" } } From 9a5aeb1526d7c34a105ab53b5b4a00262c6d30c8 Mon Sep 17 00:00:00 2001 From: Chris Josten Date: Thu, 15 Sep 2022 17:43:07 +0200 Subject: [PATCH 10/20] Upgrade version number --- mijnblog.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mijnblog.nix b/mijnblog.nix index 710b373..2a37264 100644 --- a/mijnblog.nix +++ b/mijnblog.nix @@ -6,7 +6,7 @@ mkDubDerivation { src = ./.; dubJSON = ./dub.json; selections = ./dub.selections.nix; - version = "0.0.1"; + version = "0.0.2"; buildInputs = [ pkgs.openssl ]; propagatedBuildInputs = [ pkgs.nix-prefetch-git ]; } From bdb7615044347a86c6d1c6b9792a365f834b5828 Mon Sep 17 00:00:00 2001 From: Chris Josten Date: Thu, 15 Sep 2022 20:29:06 +0200 Subject: [PATCH 11/20] Fix building on NixOS --- dub.json | 3 ++- dub.selections.nix | 32 ++++++++++++++++---------------- mijnblog.nix | 4 ++++ mkDub.nix | 34 ++++++++++++++++++++++++---------- 4 files changed, 46 insertions(+), 27 deletions(-) diff --git a/dub.json b/dub.json index e256923..073325b 100644 --- a/dub.json +++ b/dub.json @@ -11,5 +11,6 @@ "license": "AGPLv3", "name": "mijnblog", "targetType": "executable", - "stringImportPaths": ["views", "translations"] + "stringImportPaths": ["views", "translations"], + "versions": ["DeimosOpenSSL_3_0"] } diff --git a/dub.selections.nix b/dub.selections.nix index ee54309..44bf12c 100644 --- a/dub.selections.nix +++ b/dub.selections.nix @@ -75,25 +75,25 @@ fetch = { type = "git"; url = "https://github.com/vibe-d/vibe-core.git"; - rev = "v1.22.3"; - sha256 = "198s86ym28nnlyif47fa03ldif7w0qbx7bndgav0n79bdw9mw7i7"; + rev = "v1.22.4"; + sha256 = "0cblfy9dyjkz9wyjsi1mq50w09zy1nbvw3y9v9qrzfhp401zccmx"; fetchSubmodules = false; - date = "2022-04-01T21:24:37+02:00"; + date = "2022-05-26T23:00:01+02:00"; deepClone = false; leaveDotGit = false; - path = "/nix/store/y46jzkaqgjgix4df0gws12pvpxlvkx1s-vibe-core"; + path = "/nix/store/7i0n7jf5pl9l4ab294fvjsyx552ph3jg-vibe-core"; }; } { fetch = { type = "git"; url = "https://github.com/vibe-d/vibe.d.git"; - rev = "v0.9.4"; - sha256 = "0kjnrlz30gvb08v79d7x5laapk13fxyj1cfr31wx0px3xrnjpzfq"; + rev = "v0.9.5"; + sha256 = "1cf8psyfxh7053wwcjm3jm9avyph6dkx7c4fahi0gr1lrflpvpcb"; fetchSubmodules = false; - date = "2021-09-30T14:24:53+02:00"; + date = "2022-07-18T17:27:21+02:00"; deepClone = false; leaveDotGit = false; - path = "/nix/store/3x9z474xs6d65w1my3s7kbr45z938rnc-vibe.d"; + path = "/nix/store/dx5wdsw6rrwxrhg1vqx1a1p7jq89rz5g-vibe.d"; }; } { fetch = { @@ -123,13 +123,13 @@ fetch = { type = "git"; url = "https://github.com/rejectedsoftware/diet-ng.git"; - rev = "v1.8.0"; - sha256 = "0ghddla911x815dxcqw3zfvd0276hijw5vkgn5g338asxsz5rxnk"; + rev = "v1.8.1"; + sha256 = "11hrbvsxhipvcz9m1qlq92iaw0h35pzdy5rf7z8c4vx3hwjrnhni"; fetchSubmodules = false; - date = "2021-07-27T22:07:54+02:00"; + date = "2022-04-22T11:38:43+02:00"; deepClone = false; leaveDotGit = false; - path = "/nix/store/wql4k19cxpdz9sj504hw3sl2rg0vixkw-diet-ng"; + path = "/nix/store/xziliy9ah3s3ah8mslnxsw47n4w6wkca-diet-ng"; }; } { fetch = { @@ -147,13 +147,13 @@ fetch = { type = "git"; url = "https://github.com/D-Programming-Deimos/openssl.git"; - rev = "v1.1.6+1.0.1g"; - sha256 = "0ramqjyq4v7xpqwf4nf4ddmsg6yk2fbzn2d1yj4b6dla3q5lv9i3"; + rev = "v3.2.2"; + sha256 = "1j9cnajkz5s7xk8q1sz4gw69w0dazyz57rjvjncnk5ivl3md7mv2"; fetchSubmodules = false; - date = "2017-11-05T20:15:26+01:00"; + date = "2022-07-04T11:40:58+00:00"; deepClone = false; leaveDotGit = false; - path = "/nix/store/jbfb5in6gzqs2byn1hdc1625yh8sx6j2-openssl"; + path = "/nix/store/fdqbs027s89plzmwsjvcg1sr2jajiphv-openssl"; }; } { fetch = { diff --git a/mijnblog.nix b/mijnblog.nix index 2a37264..0d52e13 100644 --- a/mijnblog.nix +++ b/mijnblog.nix @@ -9,4 +9,8 @@ mkDubDerivation { version = "0.0.2"; buildInputs = [ pkgs.openssl ]; propagatedBuildInputs = [ pkgs.nix-prefetch-git ]; + extraDubFlags = "--override-config openssl/library-manual-version"; + preBuild = '' + export DC=${pkgs.dmd}/bin/dmd + ''; } diff --git a/mkDub.nix b/mkDub.nix index 197dcde..bf3b394 100644 --- a/mkDub.nix +++ b/mkDub.nix @@ -1,8 +1,9 @@ { pkgs ? import {}, - lib ? pkgs.lib, stdenv ? pkgs.stdenv, - rdmd ? pkgs.rdmd, + lib ? pkgs.lib, + dtools ? pkgs.dtools or pkgs.rdmd, dmd ? pkgs.dmd, + dcompiler ? dmd, dub ? pkgs.dub }: with stdenv; @@ -21,7 +22,7 @@ let fromDub = dubDep: mkDerivation rec { name = "${src.name}-${version}"; version = rev-to-version dubDep.fetch.rev; - nativeBuildInputs = [ rdmd dmd dub ]; + nativeBuildInputs = [ dcompiler dtools dub ]; src = dep2src dubDep; buildPhase = '' @@ -47,10 +48,18 @@ let targetOf = package: "${package.targetPath or "."}/${package.targetName or package.name}"; # Remove reference to build tools and library sources - disallowedReferences = deps: [ dmd rdmd dub ] ++ builtins.map dep2src deps; + disallowedReferences = deps: [ dcompiler dtools dub ] ++ builtins.map dep2src deps; removeExpr = refs: ''remove-references-to ${lib.concatMapStrings (ref: " -t ${ref}") refs}''; + # Like split, but only keep the matches + matches = regex: str: builtins.filter lib.isList (builtins.split regex str); + + # Very primitive parsing of SDL files, but suffices for name, description, homepage, etc. + importSDL = path: builtins.foldl' (a: l: a // {"${lib.elemAt l 1}"=lib.elemAt l 2;}) {} (matches "(^|\n)([a-z]+) \"([^\"]+)\"" (builtins.readFile path)); + + importPackage = sdl: json: if builtins.pathExists sdl then importSDL sdl else lib.importJSON json; + in { inherit fromDub; @@ -58,20 +67,23 @@ in { src, nativeBuildInputs ? [], dubJSON ? src + "/dub.json", + dubSDL ? src + "/dub.sdl", + buildType ? "release", + extraDubFlags ? "", selections ? src + "/dub.selections.nix", deps ? import selections, + package ? importPackage dubSDL dubJSON, passthru ? {}, - package ? lib.importJSON dubJSON, ... - } @ attrs: stdenv.mkDerivation (attrs // { + } @ attrs: stdenv.mkDerivation ((removeAttrs attrs ["package" "deps" "selections" "dubJSON" "dubSDL"]) // { pname = package.name; - nativeBuildInputs = [ rdmd dmd dub pkgs.removeReferencesTo ] ++ nativeBuildInputs; + nativeBuildInputs = [ dcompiler dtools dub pkgs.removeReferencesTo ] ++ nativeBuildInputs; disallowedReferences = disallowedReferences deps; passthru = passthru // { - inherit dub dmd rdmd pkgs; + inherit dub dcompiler dtools pkgs; }; src = lib.cleanSourceWith { @@ -88,7 +100,7 @@ in { export HOME=$PWD ${lib.concatMapStringsSep "\n" dub-add-local deps} - dub build -b release --combined --skip-registry=all + dub build -b ${buildType} --combined --skip-registry=all ${extraDubFlags} runHook postBuild ''; @@ -98,7 +110,7 @@ in { export HOME=$PWD ${lib.concatMapStringsSep "\n" dub-add-local deps} - dub test --combined --skip-registry=all + dub test --combined --skip-registry=all ${extraDubFlags} runHook postCheck ''; @@ -114,6 +126,8 @@ in { meta = lib.optionalAttrs (package ? description) { description = package.description; + } // lib.optionalAttrs (package ? homepage) { + homepage = package.homepage; } // attrs.meta or {}; } // lib.optionalAttrs (!(attrs ? version)) { # Use name from dub.json, unless pname and version are specified From 651668526d25c52fc13b764e9507e1e08af9134d Mon Sep 17 00:00:00 2001 From: Chris Josten Date: Fri, 8 Dec 2023 22:52:11 +0100 Subject: [PATCH 12/20] Update dmd version + dependencies --- dub.selections.json | 12 ++++--- source/nl/netsoj/chris/blog/cache.d | 1 + source/nl/netsoj/chris/blog/main.d | 16 +++++++-- source/nl/netsoj/chris/blog/model/article.d | 1 + source/nl/netsoj/chris/blog/model/page.d | 3 +- source/nl/netsoj/chris/blog/model/project.d | 2 ++ source/nl/netsoj/chris/blog/watcher.d | 40 ++++++++++++--------- 7 files changed, 50 insertions(+), 25 deletions(-) diff --git a/dub.selections.json b/dub.selections.json index fe10ba0..af8b127 100644 --- a/dub.selections.json +++ b/dub.selections.json @@ -5,17 +5,19 @@ "botan-math": "1.0.3", "diet-ng": "1.8.1", "dyaml": "0.8.6", - "eventcore": "0.9.20", + "eventcore": "0.9.26", "fswatch": "0.5.0", "libasync": "0.8.6", "libevent": "2.0.2+2.0.16", - "memutils": "1.0.4", + "memutils": "1.0.9", "mir-linux-kernel": "1.0.1", - "openssl": "3.2.2", + "openssl": "3.3.3", + "openssl-static": "1.0.2+3.0.8", "stdx-allocator": "2.77.5", "taggedalgebraic": "0.11.22", "tinyendian": "0.2.0", - "vibe-core": "1.22.4", - "vibe-d": "0.9.5" + "vibe-container": "1.0.1", + "vibe-core": "2.5.1", + "vibe-d": "0.9.7" } } diff --git a/source/nl/netsoj/chris/blog/cache.d b/source/nl/netsoj/chris/blog/cache.d index 50e2e87..b11a0d8 100644 --- a/source/nl/netsoj/chris/blog/cache.d +++ b/source/nl/netsoj/chris/blog/cache.d @@ -5,6 +5,7 @@ import article; import page; import project; +@safe: /** * Default ordering and list with pointers to ordered articles. diff --git a/source/nl/netsoj/chris/blog/main.d b/source/nl/netsoj/chris/blog/main.d index f647a10..edce64a 100644 --- a/source/nl/netsoj/chris/blog/main.d +++ b/source/nl/netsoj/chris/blog/main.d @@ -9,19 +9,29 @@ import cache; import http; import watcher; +void watchTask(T, C)(C *cache, string directory) @safe nothrow { + do { + try { + initPages!T(cache, directory); + } catch(Exception e) { + logWarn("Error while watching pages: " ~ e.msg); + } + } while(Task.getThis().running); +} + void main() { startHTTPServer(); // Start indexing pages. runTask({ - initPages!Page(&pages, "pages"); + watchTask!Page(&pages, "pages"); }); runTask({ - initPages!Article(&articles, "articles"); + watchTask!Article(&articles, "articles"); }); runTask({ - initPages!Project(&projects, "projects"); + watchTask!Project(&projects, "projects"); }); runApplication(); } diff --git a/source/nl/netsoj/chris/blog/model/article.d b/source/nl/netsoj/chris/blog/model/article.d index a2b10d4..a1ec65f 100644 --- a/source/nl/netsoj/chris/blog/model/article.d +++ b/source/nl/netsoj/chris/blog/model/article.d @@ -10,6 +10,7 @@ import vibe.d; import page; import utils; +@safe: /** * Represents an article on the blog diff --git a/source/nl/netsoj/chris/blog/model/page.d b/source/nl/netsoj/chris/blog/model/page.d index d80faf0..97f3141 100644 --- a/source/nl/netsoj/chris/blog/model/page.d +++ b/source/nl/netsoj/chris/blog/model/page.d @@ -9,6 +9,7 @@ import vibe.vibe; import utils; +@safe: /** * Exception thrown when a page has syntax errors e.g. @@ -51,7 +52,7 @@ class Page { /** * Creates a page from a file. This will read from the file and parse it. */ - this(string file) { + this(string file) @safe { this.m_name = file; this.m_contentSource = readText(file); // Find the seperator and split the string in two diff --git a/source/nl/netsoj/chris/blog/model/project.d b/source/nl/netsoj/chris/blog/model/project.d index f96d8d0..da7e6b2 100644 --- a/source/nl/netsoj/chris/blog/model/project.d +++ b/source/nl/netsoj/chris/blog/model/project.d @@ -9,6 +9,8 @@ import page; import utils; import staticpaths; +@safe: + /** * Represents a project, like an unfinished application */ diff --git a/source/nl/netsoj/chris/blog/watcher.d b/source/nl/netsoj/chris/blog/watcher.d index 6f0d399..ca05cdc 100644 --- a/source/nl/netsoj/chris/blog/watcher.d +++ b/source/nl/netsoj/chris/blog/watcher.d @@ -1,6 +1,6 @@ import std.array; import std.algorithm; -import std.experimental.logger; +//import std.experimental.logger; import std.file; import std.stdio; import std.traits; @@ -13,27 +13,35 @@ import page; /** * Loads pages into memory and sets up a "watcher" to watch a directory for file changes. */ -void initPages(T, C)(C *cache, const string directory) +void initPages(T, C)(C *cache, const string directory) @trusted if (isImplicitlyConvertible!(T, Page)) { + + NativePath watchingDir; + try { + watchingDir = getWorkingDirectory() ~ directory; + } catch(PathValidationException) { + logError("Cannot watch path " ~ directory); + return; + } bool addPage(string path) { try { T newPage = new T(path); - logf("Added %s", newPage.slug); + logInfo("Added %s", newPage.slug); cache.addItem(newPage); return true; } catch (page.ArticleParseException e) { - logf("Could not parse %s: %s", path, e); + logWarn("Could not parse %s: %s", path, e); return false; } catch (Exception e) { - logf("Other exception while parsing %s: %s", path, e); + logWarn("Other exception while parsing %s: %s", path, e); return false; } } // Initial scan void scan(NativePath path, int level = 0) { - logf("Scanning %s", path.toString()); + logInfo("Scanning %s", path.toString()); foreach(file; iterateDirectory(path)) { if (file.isDirectory) { scan(path ~ file.name, level + 1); @@ -43,11 +51,11 @@ void initPages(T, C)(C *cache, const string directory) } } - if (!existsFile(getWorkingDirectory() ~ directory)) { - createDirectory(getWorkingDirectory() ~ directory); + if (!existsFile(watchingDir)) { + createDirectory(watchingDir); } - scan(getWorkingDirectory() ~ directory); - DirectoryWatcher watcher = watchDirectory(getWorkingDirectory() ~ directory, true); + scan(watchingDir); + DirectoryWatcher watcher = watchDirectory(watchingDir, true); bool shouldStop = false; while (!shouldStop) { @@ -55,16 +63,16 @@ void initPages(T, C)(C *cache, const string directory) DirectoryChange[] changes; shouldStop = !watcher.readChanges(changes); foreach(change; changes) { - logf("=======[New changes]======"); + logInfo("=======[New changes]======"); string[] changeTypes = ["added", "removed", "modified"]; - logf("Path: %s, type: %s", change.path.toString(), changeTypes[change.type]); + logInfo("Path: %s, type: %s", change.path.toString(), changeTypes[change.type]); if (endsWith(change.path.toString(), ".kate-swp")) continue; switch (change.type) with (DirectoryChangeType){ case added: try { addPage(change.path.toString()); } catch(Exception e) { - warningf("Error while updating %s: %s", change.path.toString(), e.msg); + logWarn("Error while updating %s: %s", change.path.toString(), e.msg); } break; case modified: @@ -73,16 +81,16 @@ void initPages(T, C)(C *cache, const string directory) newPage = new T(change.path.toString()); cache.changeItem(newPage); } catch(page.ArticleParseException e) { - warningf("Could not parse %s", change.path.toString()); + logWarn("Could not parse %s", change.path.toString()); } catch (Exception e) { - warningf("Error while updating %s: %s", change.path.toString(), e.msg); + logWarn("Error while updating %s: %s", change.path.toString(), e.msg); } break; case removed: try { cache.removeItemByName(change.path.toString()); } catch(Exception e) { - logf("Error while trying to remove %s: %s", T.stringof, e.msg); + logInfo("Error while trying to remove %s: %s", T.stringof, e.msg); } break; default: break; From 7bb5c65d90c3d80a6cf759694cf7c0409f1f3ea7 Mon Sep 17 00:00:00 2001 From: Chris Josten Date: Fri, 8 Dec 2023 22:57:15 +0100 Subject: [PATCH 13/20] Bump version to 0.0.3 --- mijnblog.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mijnblog.nix b/mijnblog.nix index 0d52e13..8c3dbaf 100644 --- a/mijnblog.nix +++ b/mijnblog.nix @@ -6,7 +6,7 @@ mkDubDerivation { src = ./.; dubJSON = ./dub.json; selections = ./dub.selections.nix; - version = "0.0.2"; + version = "0.0.3"; buildInputs = [ pkgs.openssl ]; propagatedBuildInputs = [ pkgs.nix-prefetch-git ]; extraDubFlags = "--override-config openssl/library-manual-version"; From 0ad53b1a24b2cc211305f1f1f0ccf9cac780e38e Mon Sep 17 00:00:00 2001 From: Chris Josten Date: Fri, 8 Dec 2023 22:59:09 +0100 Subject: [PATCH 14/20] Update dub selections --- dub.selections.nix | 66 +++++++++++++++++++++++++++++++--------------- 1 file changed, 45 insertions(+), 21 deletions(-) diff --git a/dub.selections.nix b/dub.selections.nix index 44bf12c..b4e37e0 100644 --- a/dub.selections.nix +++ b/dub.selections.nix @@ -51,13 +51,13 @@ fetch = { type = "git"; url = "https://github.com/etcimon/memutils.git"; - rev = "v1.0.4"; - sha256 = "1m65iy03yl5km61ijk5ysapxc5mks9b15hmaqxpn111981qrqx9z"; + rev = "v1.0.9"; + sha256 = "08j9grn3l6gr275m392l0dfsx5ma6hs08gjfhh48hpy3v0bvb0ny"; fetchSubmodules = false; - date = "2020-02-02T20:53:12-05:00"; + date = "2023-03-02T15:19:28-05:00"; deepClone = false; leaveDotGit = false; - path = "/nix/store/3pmdm9szxsqfvi2dv8b58vk7856g3gd8-memutils"; + path = "/nix/store/glnf0ljd84mv6gzc9wgs1qq0vqqxvmzh-memutils"; }; } { fetch = { @@ -74,26 +74,38 @@ } { fetch = { type = "git"; - url = "https://github.com/vibe-d/vibe-core.git"; - rev = "v1.22.4"; - sha256 = "0cblfy9dyjkz9wyjsi1mq50w09zy1nbvw3y9v9qrzfhp401zccmx"; + url = "https://github.com/vibe-d/vibe-container.git"; + rev = "v1.0.1"; + sha256 = "0miynjmfzz340z3qw9mclaj6cyirzrpk528idr5yfmvl3jsi6wh8"; fetchSubmodules = false; - date = "2022-05-26T23:00:01+02:00"; + date = "2023-11-27T09:12:47+01:00"; deepClone = false; leaveDotGit = false; - path = "/nix/store/7i0n7jf5pl9l4ab294fvjsyx552ph3jg-vibe-core"; + path = "/nix/store/2vnn20q8k335h5k078yk4lrxkp4y63pd-vibe-container"; }; } { fetch = { type = "git"; url = "https://github.com/vibe-d/vibe.d.git"; - rev = "v0.9.5"; - sha256 = "1cf8psyfxh7053wwcjm3jm9avyph6dkx7c4fahi0gr1lrflpvpcb"; + rev = "v0.9.7"; + sha256 = "1q4yvcaf36lmf29izg01x888v40snpwiph310nrlpr4gyhd834av"; fetchSubmodules = false; - date = "2022-07-18T17:27:21+02:00"; + date = "2023-08-29T13:24:09+02:00"; deepClone = false; leaveDotGit = false; - path = "/nix/store/dx5wdsw6rrwxrhg1vqx1a1p7jq89rz5g-vibe.d"; + path = "/nix/store/7fq089i7zib7m9hxyl75mlfy264d9gnx-vibe.d"; + }; +} { + fetch = { + type = "git"; + url = "https://github.com/vibe-d/vibe-core.git"; + rev = "v2.5.1"; + sha256 = "1g66vyn9hivy8rmsdl1xg1vz41csq5k91c04yvp4wqbil9cwgqdr"; + fetchSubmodules = false; + date = "2023-11-24T17:25:20+01:00"; + deepClone = false; + leaveDotGit = false; + path = "/nix/store/iddq1amvzic8fn4xa2317ykmkrkdr51y-vibe-core"; }; } { fetch = { @@ -147,25 +159,25 @@ fetch = { type = "git"; url = "https://github.com/D-Programming-Deimos/openssl.git"; - rev = "v3.2.2"; - sha256 = "1j9cnajkz5s7xk8q1sz4gw69w0dazyz57rjvjncnk5ivl3md7mv2"; + rev = "v3.3.3"; + sha256 = "1634j4psp3qwgwhk2sa3jj6gvwv3i96hpg2wrdy9ihjhabnszn0f"; fetchSubmodules = false; - date = "2022-07-04T11:40:58+00:00"; + date = "2023-09-14T12:05:32+00:00"; deepClone = false; leaveDotGit = false; - path = "/nix/store/fdqbs027s89plzmwsjvcg1sr2jajiphv-openssl"; + path = "/nix/store/i3pgh154r80xvh4vsnl6srg6khmk5dg7-openssl"; }; } { fetch = { type = "git"; url = "https://github.com/vibe-d/eventcore.git"; - rev = "v0.9.20"; - sha256 = "0dpi01jjz83zv7i2d5vkv0hj6iiqzzh12vvajlh3girarbi72id0"; + rev = "v0.9.26"; + sha256 = "13bjs5v5l1387vi2ss4gvqlslhq33v9sn4pg7nis4r0wdw0zmlk1"; fetchSubmodules = false; - date = "2021-12-17T13:21:51+01:00"; + date = "2023-09-16T09:46:45+02:00"; deepClone = false; leaveDotGit = false; - path = "/nix/store/hyzyr2j4s8a3dr9rdggm9n09fap8lb0q-eventcore"; + path = "/nix/store/zy7ydasg4fwnim2m1mi4gzdg9l087wpb-eventcore"; }; } { fetch = { @@ -191,4 +203,16 @@ leaveDotGit = false; path = "/nix/store/6l82qf5nav5pkbvnrhcs5v9vwj5xycq3-libasync"; }; +} { + fetch = { + type = "git"; + url = "https://github.com/bildhuus/deimos-openssl-static.git"; + rev = "v1.0.2+3.0.8"; + sha256 = "00wllmfrjpq5ln3zs9qcqf4kq2i6bqbjxwj0jnxwwz125kfm55sy"; + fetchSubmodules = false; + date = "2023-02-24T13:23:37+01:00"; + deepClone = false; + leaveDotGit = false; + path = "/nix/store/g9shdkm450yg8c15rvqms8v09vql8z0l-deimos-openssl-static"; + }; } ] \ No newline at end of file From c1dfa1f06572f370e10d2ea2369ddaf9f8b09b45 Mon Sep 17 00:00:00 2001 From: Chris Date: Sat, 12 Oct 2024 14:04:51 +0200 Subject: [PATCH 15/20] Update copyright year --- translations/mijnblog.en_GB.po | 2 +- translations/mijnblog.nl_NL.po | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/translations/mijnblog.en_GB.po b/translations/mijnblog.en_GB.po index b94b9a3..e660461 100644 --- a/translations/mijnblog.en_GB.po +++ b/translations/mijnblog.en_GB.po @@ -17,6 +17,6 @@ msgid "template.menu.contact" msgstr "Contact" msgid "template.page.copyright" -msgstr "© Chris Josten, 2021. If not specified otherwise, all content on this " +msgstr "© Chris Josten, 2024. If not specified otherwise, all content on this " "website is " "licensed under the CC-BY 4.0" diff --git a/translations/mijnblog.nl_NL.po b/translations/mijnblog.nl_NL.po index f8e3f38..3fd9c16 100644 --- a/translations/mijnblog.nl_NL.po +++ b/translations/mijnblog.nl_NL.po @@ -30,5 +30,5 @@ msgid "template.menu.contact" msgstr "Contact" msgid "template.page.copyright" -msgstr "© Chris Josten, 2021. Tenzij anders vermeld staat, valt alle inhoud op deze webstek " +msgstr "© Chris Josten, 2024. Tenzij anders vermeld staat, valt alle inhoud op deze webstek " " onder de CC-BY 4.0" From 6874b149164a6a1ac9ae3d2ec39a729e63d0bb68 Mon Sep 17 00:00:00 2001 From: Chris Date: Sat, 12 Oct 2024 20:37:26 +0200 Subject: [PATCH 16/20] style: move to grid layout from flex This allows to reposition items without hiding/showing classes --- public/static/style/base.css | 125 ++++++++++++++++++++++------------- views/parts/page.dt | 32 ++++----- 2 files changed, 95 insertions(+), 62 deletions(-) diff --git a/public/static/style/base.css b/public/static/style/base.css index 9d12161..6ac448a 100644 --- a/public/static/style/base.css +++ b/public/static/style/base.css @@ -1,32 +1,3 @@ -@media (max-width: 850px) { - body { - flex-wrap: wrap; - } - - body > main { - box-sizing: border-box; - min-width: 100%; - } - - body > section.header-navigation, body > footer { - width: 100%; - flex-grow: 1 !important; - } - - .hide-small { - display: none; - } - - .hide-big { - display: block !important; - } -} - - -.hide-big { - display: none; -} - :root { font-family: "sans-serif"; --colour-bg: #f0f0f0; @@ -62,48 +33,54 @@ body { background-color: #f0f0f0; background-color: var(--colour-bg); margin: 0; - display: flex; + display: grid; + grid-template-columns: 2em 200px max-content min-content; + grid-column-gap: 2em; + grid-template-rows: 2em min-content min-content 1fr; + grid-template-areas: + ". . main . " + ". header main sidebar" + ". menu main sidebar" + ". footer main sidebar"; justify-content: center; min-height: 100vh; color: #000000; color: var(--colour-fg); } -body > section.header-navigation { - flex: 0 0 200px; - padding: 2em; -} - -@media (pointer: coarse) { - body > section.header-navigation li { - padding: 0.25em 0; - } +body > nav { + grid-area: menu; } body > footer { - flex: 0 0 200px; - padding: 2em; - border: none; + grid-area: footer; + margin-top: 0; } -body > section.header-navigation > header { +body > header { + grid-area: header; text-align: center; + margin-bottom: 0; } -body > section.header-navigation > header > img { +body > header > img { width: 100%; max-width: 160px; } +body > section.main-sidebar { + grid-area: sidebar; +} + body > main { - flex: 1 1; + grid-area: main; padding: 2em; /* width: 600px; */ border-left: #7f0602 dotted 1px; border-right: #7f0602 dotted 1px; border-left: var(--colour-fg-highlight) dotted 1px; border-right: var(--colour-fg-highlight) dotted 1px; - max-width: 600px; + max-width: calc(600px); background-color: #ddd; background-color: var(--colour-bg-main); } @@ -274,11 +251,65 @@ a { text-decoration: underline dotted; } -a:hover { +a:hover, a:focus { color: var(--colour-fg); text-decoration: underline solid; } +@media (max-width: calc(800px + 8em)) { + body { + grid-template-columns: 100%; + grid-column-gap: 2em; + grid-template-rows: repeat(4, min-content); + grid-template-areas: + "header" + "menu" + "main" + "footer"; + } + + body > .main-sidebar { + display: none; + } + + body > main { + box-sizing: border-box; + min-width: 100%; + border: none; + border-top: #7f0602 dotted 1px; + border-bottom: #7f0602 dotted 1px; + border-top: var(--colour-fg-highlight) dotted 1px; + border-bottom: var(--colour-fg-highlight) dotted 1px; + } + + body > nav, body > footer { + width: calc(100% - 4em); + padding: 2em; + } +} + +@media (pointer: coarse) { + body > nav li a { + display: inline-block; + padding: 1em 2em; + width: calc(100% - 4em); + } + + body > nav > ul { + width: 100%; + } + + body > nav li:not(:last-child) { + border-bottom: 1px dotted #000000; + border-bottom: 1px dotted var(--colour-fg); + } + + body > nav { + padding: 1em 0em; + width: 100%; + } +} + @media print { body { background-color: initial; diff --git a/views/parts/page.dt b/views/parts/page.dt index 513f375..1c2e1a5 100644 --- a/views/parts/page.dt +++ b/views/parts/page.dt @@ -43,23 +43,25 @@ html(prefix="og: http://ogp.me/ns#") li a(href="#{link}") #{trWeb(text)} - section.header-navigation - header - img.logo(src="/static/img/logo.png", alt="The logo of the website: the letter C drawn in an unprofessional manner with wobbly eyes on put on top") - p #{trWeb("template.page.name")} - nav - ul - - menuItem("template.menu.home", "/"); - - menuItem("template.menu.posts", "/posts/"); - - menuItem("template.menu.projects", "/projects/"); - - menuItem("template.menu.contact", "/contact"); + header + img.logo(src="/static/img/logo.png", alt="The logo of the website: the letter C drawn in an unprofessional manner with wobbly eyes on put on top") + p #{trWeb("template.page.name")} + + section.main-sidebar block sidebar - footer.hide-small - block footer - include parts/footer + + nav + ul + - menuItem("template.menu.home", "/"); + - menuItem("template.menu.posts", "/posts/"); + - menuItem("template.menu.projects", "/projects/"); + - menuItem("template.menu.contact", "/contact"); + main block content - - footer.hide-big + + footer block footer include parts/footer + + From c68094cd11e11524fec1e3dffd7bf1aebdf6a8db Mon Sep 17 00:00:00 2001 From: Chris Date: Sat, 12 Oct 2024 20:51:23 +0200 Subject: [PATCH 17/20] gitignore: ignore result This file is created by running nix build --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 56fa0a7..9d0f300 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,4 @@ mijnblog-test-* /articles/ /pages/ /projects/ +/result From 63f177475bc72d690b79e3d59a7602c214cc22f5 Mon Sep 17 00:00:00 2001 From: Chris Date: Sat, 12 Oct 2024 20:54:03 +0200 Subject: [PATCH 18/20] old style; update for new grid layout It still looks terrible, but I suppose that was the entire idea of this optional style --- public/static/style/old.css | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/public/static/style/old.css b/public/static/style/old.css index 6eda75d..2c7b5db 100644 --- a/public/static/style/old.css +++ b/public/static/style/old.css @@ -2,6 +2,17 @@ body { background-image: url(old/chip.jpg); cursor: url(old/cursor.gif), auto; justify-content: flex-start; + grid-column-gap: 0em; + grid-template-columns: 200px 600px; + grid-template-rows: min-content min-content 1fr; + grid-template-areas: + "header main" + "menu main" + "footer main"; +} + +.main-sidebar { + display: none; } header { @@ -16,14 +27,15 @@ footer { a:hover { cursor: url(old/cursor-over.gif), auto; + color: red; } -.header-navigation { +body > nav, body > header, body > footer { background-color: cyan; color: black; } -.header-navigation> nav ul { +body > nav ul { padding-left: 2em; list-style-image: url(old/bullet.gif); } From 0e7aa0451fb99829d157e05fde86df77c6ade634 Mon Sep 17 00:00:00 2001 From: Chris Date: Sat, 12 Oct 2024 22:31:23 +0200 Subject: [PATCH 19/20] atom: add atom feed to website for posts --- defaultClasses.lua | 10 +++- source/nl/netsoj/chris/blog/constants.d | 4 +- source/nl/netsoj/chris/blog/interfaces/http.d | 59 ++++++++++++++++++- source/nl/netsoj/chris/blog/model/page.d | 5 +- translations/mijnblog.en_GB.po | 6 ++ translations/mijnblog.nl_NL.po | 6 ++ translations/mijnblog.pot | 30 +++++++++- views/parts/page.dt | 1 + 8 files changed, 115 insertions(+), 6 deletions(-) diff --git a/defaultClasses.lua b/defaultClasses.lua index b1ddd6f..2e5dba6 100644 --- a/defaultClasses.lua +++ b/defaultClasses.lua @@ -14,6 +14,14 @@ function get_default_code_class(meta) end end +function make_image_url_absolute (img) + if img.src:sub(1,1) == '/' then + img.src = os.getenv 'WEBROOT' .. img.src + end + return img +end + return {{Meta = get_default_code_class}, {Code = add_default_code_class}, - {CodeBlock = add_default_code_class}} + {CodeBlock = add_default_code_class}, + {Image = make_image_url_absolute}} diff --git a/source/nl/netsoj/chris/blog/constants.d b/source/nl/netsoj/chris/blog/constants.d index 8b665d3..b483152 100644 --- a/source/nl/netsoj/chris/blog/constants.d +++ b/source/nl/netsoj/chris/blog/constants.d @@ -1,8 +1,10 @@ /** * Constants which are passed to templates while rendering. */ -class Constants { +struct Constants { public static immutable string SITE_NAME = "Chris Josten's site"; + public static immutable string SITE_HOST = "chris.netsoj.nl"; public static immutable string SITE_URL = "https://chris.netsoj.nl"; + public static immutable string SITE_AUTHOR = "Chris Josten"; public static immutable string COPYRIGHT = "© Chris Josten, 2020"; } diff --git a/source/nl/netsoj/chris/blog/interfaces/http.d b/source/nl/netsoj/chris/blog/interfaces/http.d index b5a325e..db15abb 100644 --- a/source/nl/netsoj/chris/blog/interfaces/http.d +++ b/source/nl/netsoj/chris/blog/interfaces/http.d @@ -1,6 +1,11 @@ +import std.algorithm : map, maxElement; +import std.format : format; +import std.string: join; + import vibe.d; import cache; +import constants; import article; import page; import project; @@ -141,7 +146,59 @@ public: // If no slug is supplied, it will be adjusted to "index" req.params.addField("slug", "index"); mixin(singleResponseMixin("pages", "pages/page.dt")); - } + } + + @path("/feeds/posts.atom") + void getPostFeed(HTTPServerRequest req, HTTPServerResponse res) { + Article[] articleList = articles.sortedList; + + DateTime lastUpdated = articleList.map!"a.firstPublished()".maxElement; + + string response = q"EOS + + + %s + %s + %s + + + urn:uuid:036f1087-7fcd-466d-866d-78a0c60038cd + %s + %s + +EOS" + .format( + lastUpdated.toISOExtString() ~ "Z", + trWeb("template.feed.title").format(trWeb("template.feed.posts.title")), + Constants.SITE_URL ~ "/static/img/logo.png", + Constants.SITE_URL, + Constants.SITE_URL, + Constants.SITE_AUTHOR, + articleList.map!((article) { + return q"EOS + + %s + + %s + %s + %s + %s + %s + + +EOS" + .format(article.title, + Constants.SITE_URL ~ "/posts/" ~ article.slug, + "tag:" ~ Constants.SITE_HOST ~ ",2024:blog:posts:" ~ article.slug, + article.firstPublished().toISOExtString() ~ "Z", + article.updated().toISOExtString() ~ "Z", + article.excerpt(), + htmlEscape(article.content()) + ); + }).join() + ); + res.writeBody(response, "application/atom+xml"); + } } /** diff --git a/source/nl/netsoj/chris/blog/model/page.d b/source/nl/netsoj/chris/blog/model/page.d index 97f3141..26cc8e2 100644 --- a/source/nl/netsoj/chris/blog/model/page.d +++ b/source/nl/netsoj/chris/blog/model/page.d @@ -7,6 +7,7 @@ import std.stdio; import dyaml; import vibe.vibe; +import constants; import utils; @safe: @@ -107,10 +108,12 @@ class Page { "-f", "markdown", "-t", "html", "--lua-filter", "defaultClasses.lua"]; + string[string] env; + env["WEBROOT"] = Constants.SITE_URL; if (shiftHeader != 0) args ~= "--shift-heading-level-by=" ~ to!string(shiftHeader); - ProcessPipes pandoc = pipeProcess(args); + ProcessPipes pandoc = pipeProcess(args, Redirect.all, env); pandoc.stdin.write(source); pandoc.stdin.writeln(); pandoc.stdin.flush(); diff --git a/translations/mijnblog.en_GB.po b/translations/mijnblog.en_GB.po index e660461..dcf2d5c 100644 --- a/translations/mijnblog.en_GB.po +++ b/translations/mijnblog.en_GB.po @@ -20,3 +20,9 @@ msgid "template.page.copyright" msgstr "© Chris Josten, 2024. If not specified otherwise, all content on this " "website is " "licensed under the CC-BY 4.0" + +msgid "template.feed.title" +msgstr "%s | Chris's website" + +msgid "template.feed.posts.title" +msgstr "Posts" diff --git a/translations/mijnblog.nl_NL.po b/translations/mijnblog.nl_NL.po index 3fd9c16..9eac5b2 100644 --- a/translations/mijnblog.nl_NL.po +++ b/translations/mijnblog.nl_NL.po @@ -32,3 +32,9 @@ msgstr "Contact" msgid "template.page.copyright" msgstr "© Chris Josten, 2024. Tenzij anders vermeld staat, valt alle inhoud op deze webstek " " onder de CC-BY 4.0" + +msgid "template.feed.title" +msgstr "%s | Chris z'n webstekkie" + +msgid "template.feed.posts.title" +msgstr "Berichten" diff --git a/translations/mijnblog.pot b/translations/mijnblog.pot index 4d4a992..dcf2d5c 100644 --- a/translations/mijnblog.pot +++ b/translations/mijnblog.pot @@ -1,2 +1,28 @@ -msgid "page.header-bottom-text" -msgstr "" +msgid "template.page.name" +msgstr "Chris's website" + +msgid "template.page.html_language" +msgstr "en-GB" + +msgid "template.menu.home" +msgstr "Home" + +msgid "template.menu.posts" +msgstr "Posts" + +msgid "template.menu.projects" +msgstr "Projects" + +msgid "template.menu.contact" +msgstr "Contact" + +msgid "template.page.copyright" +msgstr "© Chris Josten, 2024. If not specified otherwise, all content on this " +"website is " +"licensed under the CC-BY 4.0" + +msgid "template.feed.title" +msgstr "%s | Chris's website" + +msgid "template.feed.posts.title" +msgstr "Posts" diff --git a/views/parts/page.dt b/views/parts/page.dt index 1c2e1a5..7990c91 100644 --- a/views/parts/page.dt +++ b/views/parts/page.dt @@ -6,6 +6,7 @@ html(prefix="og: http://ogp.me/ns#") //- Kick off loading the css as fast as possible meta(name="viewport", content="width=device-width; initial-scale=1") link(rel="stylesheet", href="/static/style/base.css") + link(rel="alternate", href="/feeds/posts.atom", type="application/atom+xml", title=trWeb("template.feed.title").format(trWeb("template.feed.posts.title"))) - string page_image = "/static/img/logo.png"; - string page_title; From 7bcc91a2fa68c44e1696c33c1ea9d6275d4c988e Mon Sep 17 00:00:00 2001 From: Chris Date: Sat, 12 Oct 2024 23:11:20 +0200 Subject: [PATCH 20/20] Use fallback date when there are no visible articles This fixes a crash --- public/static/style/base.css | 2 +- source/nl/netsoj/chris/blog/interfaces/http.d | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/public/static/style/base.css b/public/static/style/base.css index 6ac448a..7211e57 100644 --- a/public/static/style/base.css +++ b/public/static/style/base.css @@ -34,7 +34,7 @@ body { background-color: var(--colour-bg); margin: 0; display: grid; - grid-template-columns: 2em 200px max-content min-content; + grid-template-columns: 2em 200px 600px min-content; grid-column-gap: 2em; grid-template-rows: 2em min-content min-content 1fr; grid-template-areas: diff --git a/source/nl/netsoj/chris/blog/interfaces/http.d b/source/nl/netsoj/chris/blog/interfaces/http.d index db15abb..4f592cc 100644 --- a/source/nl/netsoj/chris/blog/interfaces/http.d +++ b/source/nl/netsoj/chris/blog/interfaces/http.d @@ -152,7 +152,9 @@ public: void getPostFeed(HTTPServerRequest req, HTTPServerResponse res) { Article[] articleList = articles.sortedList; - DateTime lastUpdated = articleList.map!"a.firstPublished()".maxElement; + DateTime lastUpdated = articleList.length > 0 + ? articleList.map!"a.firstPublished()".maxElement + : DateTime(2019, 06, 30); string response = q"EOS