From 4aeb9579d619767c6e13a8979ed1441f0025196f Mon Sep 17 00:00:00 2001
From: valentin gagarin <valentin.gagarin@tweag.io>
Date: Wed, 13 Nov 2024 15:24:41 +0100
Subject: [PATCH] separate DOM mapping and generic templating

the templates collection will soon only be there for reusable snippets,
while the HTML representation of document types will be attached to
those types directly.
---
 website/presentation/default.nix   | 73 ++++++++++++++----------------
 website/presentation/templates.nix | 22 ---------
 website/structure/article.nix      | 24 +++++++++-
 website/structure/document.nix     |  8 ++--
 website/structure/navigation.nix   |  2 +-
 website/structure/page.nix         | 23 +++++++++-
 6 files changed, 83 insertions(+), 69 deletions(-)
 delete mode 100644 website/presentation/templates.nix

diff --git a/website/presentation/default.nix b/website/presentation/default.nix
index 7410f3ca..a3435028 100644
--- a/website/presentation/default.nix
+++ b/website/presentation/default.nix
@@ -37,47 +37,38 @@ in
       type = recursiveAttrs (with types; functionTo (functionTo str));
     };
 
-  config.templates.html =
-    let
-      commonmark = name: markdown: pkgs.runCommand "${name}.html"
-        {
-          buildInputs = [ pkgs.cmark ];
-        } ''
-        cmark ${builtins.toFile "${name}.md" markdown} > $out
+  config.templates.html = {
+    markdown = name: text:
+      let
+        commonmark = pkgs.runCommand "${name}.html"
+          {
+            buildInputs = [ pkgs.cmark ];
+          } ''
+          cmark ${builtins.toFile "${name}.md" text} > $out
+        '';
+      in
+      builtins.readFile commonmark;
+    nav = menu: page:
+      let
+        render-item = item:
+          if item ? menu then ''
+            <li>${item.menu.label}
+            ${lib.indent "  " (item.menu.outputs.html page)}
+            </li>
+          ''
+          else if item ? page then ''<li><a href="${page.link item.page}">${item.page.title}</a></li>''
+          else ''<li><a href="${item.link.url}">${item.link.label}</a></li>''
+        ;
+      in
+      ''
+        <nav>
+          <ul>
+            ${with lib; indent "    " (join "\n" (map render-item menu.items))}
+          </ul>
+        </nav>
       '';
-    in
-    {
-      nav = lib.mkDefault templates.nav;
-      page = lib.mkDefault (config: page: render-html {
-        html = {
-          head = {
-            title.text = page.title;
-            meta.description = page.description;
-            link.canonical = lib.head page.locations;
-          };
-          body.content = [
-            (config.menus.main.outputs.html page)
-            { section.heading.content = page.title; }
-            (builtins.readFile (commonmark page.name page.body))
-          ];
-        };
-      });
-      article = lib.mkDefault (config: page: render-html {
-        html = {
-          head = {
-            title.text = page.title;
-            meta.description = page.description;
-            meta.authors = if lib.isList page.author then page.author else [ page.author ];
-            link.canonical = lib.head page.locations;
-          };
-          body.content = [
-            (config.menus.main.outputs.html page)
-            { section.heading.content = page.title; }
-            (builtins.readFile (commonmark page.name page.body))
-          ];
-        };
-      });
-    };
+
+  };
 
   options.files = mkOption {
     description = ''
@@ -86,6 +77,8 @@ in
       By default, all elements in `option`{pages} are converted to files using their template or the default template.
       Add more files to the output by assigning to this attribute set.
     '';
+    # TODO: this should be attrsOf string-coercible instead.
+    #       we can convert this to file at the very end.
     type = with types; attrsOf path;
   };
 
diff --git a/website/presentation/templates.nix b/website/presentation/templates.nix
deleted file mode 100644
index c0d7f652..00000000
--- a/website/presentation/templates.nix
+++ /dev/null
@@ -1,22 +0,0 @@
-{ lib }:
-rec {
-  nav = menu: page:
-    let
-      render-item = item:
-        if item ? menu then ''
-          <li>${item.menu.label}
-          ${lib.indent "  " (item.menu.outputs.html page)}
-          </li>
-        ''
-        else if item ? page then ''<li><a href="${page.link item.page}">${item.page.title}</a></li>''
-        else ''<li><a href="${item.link.url}">${item.link.label}</a></li>''
-      ;
-    in
-    ''
-      <nav>
-        <ul>
-          ${with lib; indent "    " (join "\n" (map render-item menu.menu.items))}
-        </ul>
-      </nav>
-    '';
-}
diff --git a/website/structure/article.nix b/website/structure/article.nix
index ca7495df..aac3a398 100644
--- a/website/structure/article.nix
+++ b/website/structure/article.nix
@@ -5,6 +5,14 @@ let
     types
     ;
   cfg = config;
+  render-html = document:
+    let
+      eval = lib.evalModules {
+        class = "DOM";
+        modules = [ document (import ../presentation/dom.nix) ];
+      };
+    in
+    toString eval.config;
 in
 {
   content-types.article = { config, collection, ... }: {
@@ -27,6 +35,20 @@ in
       };
     };
     config.name = lib.slug config.title;
-    config.outputs.html = lib.mkForce (cfg.templates.html.article cfg config);
+    config.outputs.html = lib.mkForce (render-html {
+      html = {
+        head = {
+          title.text = config.title;
+          meta.description = config.description;
+          meta.authors = if lib.isList config.author then config.author else [ config.author ];
+          link.canonical = lib.head config.locations;
+        };
+        body.content = [
+          (cfg.menus.main.outputs.html config)
+          { section.heading.content = config.title; }
+          (cfg.templates.html.markdown config.name config.body)
+        ];
+      };
+    });
   };
 }
diff --git a/website/structure/document.nix b/website/structure/document.nix
index 8af04c36..dd3718d5 100644
--- a/website/structure/document.nix
+++ b/website/structure/document.nix
@@ -45,13 +45,13 @@ in
         #       names is soft.
         default = target: with lib; "${relativePath (head config.locations) (head target.locations)}.html";
       };
-      outputs = mkOption {
-        # TODO: figure out how to make this overridable at any granularity.
-        #       it should be possible with the DOM module now.
+      outputs.html = mkOption {
+        # TODO: make this of type DOM and convert to string at the output.
+        #       the output aggregator then only needs something string-coercible
         description = ''
           Representations of the document in different formats
         '';
-        type = with types; attrsOf str;
+        type = with types; str;
       };
     };
   };
diff --git a/website/structure/navigation.nix b/website/structure/navigation.nix
index a3d78360..97ae9480 100644
--- a/website/structure/navigation.nix
+++ b/website/structure/navigation.nix
@@ -56,7 +56,7 @@ in
           It must be a function that takes the page on which the navigation is to be shown, such that relative links get computed correctly.
         '';
         type = with types; attrsOf (functionTo str);
-        default.html = cfg.templates.html.nav { menu = config; };
+        default.html = cfg.templates.html.nav config;
       };
     };
   };
diff --git a/website/structure/page.nix b/website/structure/page.nix
index d76954e1..a4352f12 100644
--- a/website/structure/page.nix
+++ b/website/structure/page.nix
@@ -5,6 +5,14 @@ let
     types
     ;
   cfg = config;
+  render-html = document:
+    let
+      eval = lib.evalModules {
+        class = "DOM";
+        modules = [ document (import ../presentation/dom.nix) ];
+      };
+    in
+    toString eval.config;
 in
 {
   content-types.page = { name, config, ... }: {
@@ -34,6 +42,19 @@ in
         type = types.str;
       };
     };
-    config.outputs.html = cfg.templates.html.page cfg config;
+    config.outputs.html = render-html {
+      html = {
+        head = {
+          title.text = config.title;
+          meta.description = config.description;
+          link.canonical = lib.head config.locations;
+        };
+        body.content = [
+          (cfg.menus.main.outputs.html config)
+          { section.heading.content = config.title; }
+          (cfg.templates.html.markdown config.name config.body)
+        ];
+      };
+    };
   };
 }