diff --git a/presentation/default.nix b/presentation/default.nix
index 9c78c0d1..7410f3ca 100644
--- a/presentation/default.nix
+++ b/presentation/default.nix
@@ -5,11 +5,13 @@ let
types
;
templates = import ./templates.nix { inherit lib; };
+ # TODO: optionally run the whole thing through the validator
+ # https://github.com/validator/validator
render-html = document:
let
eval = lib.evalModules {
class = "DOM";
- modules = [ document (import ./dom.nix { inherit lib; }).document ];
+ modules = [ document (import ./dom.nix) ];
};
in
toString eval.config;
@@ -53,13 +55,11 @@ in
meta.description = page.description;
link.canonical = lib.head page.locations;
};
- body.content = ''
- ${config.menus.main.outputs.html page}
-
-
${page.title}
-
- ${builtins.readFile (commonmark page.name page.body)}
- '';
+ 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 {
@@ -70,13 +70,11 @@ in
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}
-
- ${page.title}
-
- ${builtins.readFile (commonmark page.name page.body)}
- '';
+ body.content = [
+ (config.menus.main.outputs.html page)
+ { section.heading.content = page.title; }
+ (builtins.readFile (commonmark page.name page.body))
+ ];
};
});
};
diff --git a/presentation/dom.nix b/presentation/dom.nix
index 7e52a819..0f583b7d 100644
--- a/presentation/dom.nix
+++ b/presentation/dom.nix
@@ -5,8 +5,9 @@
Inspired by https://github.com/knupfer/type-of-html by @knupfer (BSD-3-Clause)
Similar work from the OCaml ecosystem: https://github.com/ocsigen/tyxml
*/
-{ lib, ... }:
+{ config, lib, ... }:
let
+ cfg = config;
inherit (lib) mkOption types;
# https://html.spec.whatwg.org/multipage/dom.html#content-models
@@ -25,8 +26,16 @@ let
"scripting" # https://html.spec.whatwg.org/multipage/dom.html#script-supporting-elements
];
+ get-section-depth = content: with lib; foldl'
+ (acc: elem:
+ if isAttrs elem
+ then max acc (lib.head (attrValues elem)).section-depth
+ else acc # TODO: parse with e.g. https://github.com/remarkjs/remark to avoid raw strings
+ ) 0
+ content;
+
# base type for all DOM elements
- element = { name, config, ... }: {
+ element = { ... }: {
# TODO: add fields for upstream documentation references
# TODO: programmatically generate documentation
options = with lib; {
@@ -40,6 +49,15 @@ let
};
};
+ # TODO: rename to something about sectioning
+ content-element = { ... }: {
+ options.section-depth = mkOption {
+ type = types.ints.unsigned;
+ default = 0;
+ internal = true;
+ };
+ };
+
# options with types for all the defined DOM elements
element-types = lib.mapAttrs
(name: value: mkOption { type = types.submodule value; })
@@ -53,7 +71,7 @@ let
(category:
(mapAttrs (_: e: mkOption { type = types.submodule e; })
# HACK: don't evaluate the submodule types, just grab the config directly
- (filterAttrs (_: e: elem category (e { name = "dummy"; }).config.categories) elements))
+ (filterAttrs (_: e: elem category (e { name = "dummy"; config = { }; }).config.categories) elements))
);
global-attrs = lib.mapAttrs (name: value: mkOption value) {
@@ -140,6 +158,7 @@ let
attrs)
);
in
+ if attrs == null then throw "wat" else
optionalString (stringLength result > 0) " " + result
;
@@ -310,7 +329,7 @@ let
link = { name, ... }: {
imports = [ element ];
- options = mkAttrs {
+ options = global-attrs // {
# TODO: more attributes
# https://html.spec.whatwg.org/multipage/semantics.html#the-link-element:concept-element-attributes
inherit (attrs) href;
@@ -349,21 +368,104 @@ let
config.__toString = self: "";
};
- body = { name, ... }: {
- imports = [ element ];
+ body = { config, name, ... }: {
+ imports = [ element content-element ];
options = {
attrs = mkAttrs { };
content = mkOption {
type = with types;
# HACK: bail out for now
# TODO: find a reasonable cut-off for where to place raw content
- either str (listOf (attrTag categories.flow));
+ listOf (either str (attrTag categories.flow));
+ default = [ ];
};
};
+ config.section-depth = get-section-depth config.content;
config.categories = [ ];
config.__toString = self: with lib;
- if isList self.content then join "\n" (toString self.content) else self.content;
+ print-element name self.attrs (
+ join "\n" (map
+ (e:
+ if isAttrs e
+ then toString (lib.head (attrValues e))
+ else e
+ )
+ self.content)
+ );
+ };
+ section = { config, name, ... }: {
+ imports = [ element content-element ];
+ options = {
+ # setting to an attribute set will wrap the section in
+ attrs = mkOption {
+ type = with types; nullOr (submodule { options = global-attrs; });
+ default = null;
+ };
+ # TODO: make `pre-`/`post-heading` wrap the heading in `` if non-empty
+ # https://html.spec.whatwg.org/multipage/sections.html#the-hgroup-element
+ pre-heading = mkOption {
+ type = with types; listOf (attrTag ({ inherit p; } // categories.scripting));
+ default = [ ];
+ };
+ heading = mkOption {
+ type = with types; submodule ({ ... }: {
+ imports = [ element ];
+ options = {
+ attrs = mkAttrs { };
+ content = mkOption {
+ type = with types; either str (listOf (attrTag categories.phrasing));
+ };
+ };
+ });
+ };
+ post-heading = mkOption {
+ type = with types;
+ listOf (attrTag ({ inherit p; } // categories.scripting));
+ default = [ ];
+ };
+ # https://html.spec.whatwg.org/multipage/sections.html#headings-and-outlines
+ content = mkOption {
+ type = with types; listOf (either str (attrTag categories.flow));
+ default = [ ];
+ };
+ };
+ config.section-depth = get-section-depth config.content + 1;
+ options.heading-level = mkOption {
+ # XXX: this will proudly fail if the invariant is violated,
+ # but the error message will be inscrutable
+ type = with types; ints.between 1 6;
+ internal = true;
+ };
+ config.heading-level = cfg.section-depth - config.section-depth + 1;
+ config.categories = [ "flow" "sectioning" "palpable" ];
+ config.__toString = self: with lib;
+ let
+ n = toString config.heading-level;
+ content =
+ "${self.heading.content}" + join "\n" (map
+ (e:
+ if isAttrs e
+ then toString (lib.head (attrValues e))
+ else e
+ )
+ self.content);
+ in
+ if !isNull self.attrs
+ then print-element name self.attrs content
+ else content;
};
};
in
-elements
+{
+ imports = [ element content-element ];
+ options = {
+ inherit (element-types) html;
+ };
+
+ config.section-depth = config.html.body.section-depth;
+ config.categories = [ ];
+ config.__toString = self: ''
+
+ ${self.html}
+ '';
+}