override page template for articles

This commit is contained in:
Valentin Gagarin 2024-11-13 15:24:41 +01:00 committed by Valentin Gagarin
parent 00e3cfcb52
commit 9b74458a8c
2 changed files with 57 additions and 35 deletions

View file

@ -9,6 +9,7 @@
let let
cfg = config; cfg = config;
inherit (lib) mkOption types; inherit (lib) mkOption types;
inherit (types) submodule;
# https://html.spec.whatwg.org/multipage/dom.html#content-models # https://html.spec.whatwg.org/multipage/dom.html#content-models
# https://html.spec.whatwg.org/multipage/dom.html#kinds-of-content # https://html.spec.whatwg.org/multipage/dom.html#kinds-of-content
@ -43,7 +44,7 @@ let
# options with types for all the defined DOM elements # options with types for all the defined DOM elements
element-types = lib.mapAttrs element-types = lib.mapAttrs
(name: value: mkOption { type = types.submodule value; }) (name: value: mkOption { type = submodule value; })
elements; elements;
# attrset of categories, where values are module options with the type of the # attrset of categories, where values are module options with the type of the
@ -52,7 +53,7 @@ let
genAttrs genAttrs
content-categories content-categories
(category: (category:
(mapAttrs (_: e: mkOption { type = types.submodule e; }) (mapAttrs (_: e: mkOption { type = submodule e; })
# HACK: don't evaluate the submodule types, just grab the config directly # HACK: don't evaluate the submodule types, just grab the config directly
(filterAttrs (_: e: elem category (e { name = "dummy"; config = { }; }).config.categories) elements)) (filterAttrs (_: e: elem category (e { name = "dummy"; config = { }; }).config.categories) elements))
); );
@ -118,7 +119,7 @@ let
mkAttrs = attrs: with lib; mkAttrs = attrs: with lib;
mkOption { mkOption {
type = types.submodule { type = submodule {
options = global-attrs // attrs; options = global-attrs // attrs;
}; };
default = { }; default = { };
@ -147,12 +148,21 @@ let
print-element = name: attrs: content: print-element = name: attrs: content:
with lib; with lib;
# TODO: be smarter about content to save some space and repetition at the call sites
squash (trim '' squash (trim ''
<${name}${print-attrs attrs}> <${name}${print-attrs attrs}>
${lib.indent " " content} ${lib.indent " " content}
</${name}> </${name}>
''); '');
toString-unwrap = e:
with lib;
if isAttrs e
then toString (head (attrValues e))
else if isList e
then toString (map toString-unwrap e)
else e;
elements = rec { elements = rec {
document = { ... }: { document = { ... }: {
imports = [ element ]; imports = [ element ];
@ -206,7 +216,7 @@ let
# https://developer.mozilla.org/en-US/docs/Web/HTML/Viewport_meta_tag#viewport_width_and_screen_width # https://developer.mozilla.org/en-US/docs/Web/HTML/Viewport_meta_tag#viewport_width_and_screen_width
# this should not exist and no one should ever have to think about it # this should not exist and no one should ever have to think about it
meta.viewport = mkOption { meta.viewport = mkOption {
type = types.submodule ({ ... }: { type = submodule ({ ... }: {
# TODO: figure out how to render only non-default values # TODO: figure out how to render only non-default values
options = { options = {
width = mkOption { width = mkOption {
@ -422,21 +432,13 @@ let
config.categories = [ ]; config.categories = [ ];
config.__toString = self: with lib; config.__toString = self: with lib;
print-element name self.attrs ( print-element name self.attrs (join "\n" (map toString-unwrap self.content));
join "\n" (map
(e:
if isAttrs e
then toString (lib.head (attrValues e))
else e
)
self.content)
);
}; };
section = { config, name, ... }: { section = { config, name, ... }: {
imports = [ element ]; imports = [ element ];
options = { options = {
# setting to an attribute set will wrap the section in <section> # setting to an attribute set will wrap the section in `<section>`
attrs = mkOption { attrs = mkOption {
type = with types; nullOr (submodule { options = global-attrs; }); type = with types; nullOr (submodule { options = global-attrs; });
default = null; default = null;
@ -450,14 +452,18 @@ let
# such an outline is rather meaningless without headings for navigation, # such an outline is rather meaningless without headings for navigation,
# which is why we enforce headings in sections. # which is why we enforce headings in sections.
# arguably, and this is encoded here, a section *is defined* by its heading. # arguably, and this is encoded here, a section *is defined* by its heading.
type = with types; submodule ({ ... }: { type = with types; submodule ({ config, ... }: {
imports = [ element ]; imports = [ element ];
options = { options = {
attrs = mkAttrs { }; attrs = mkAttrs { };
# TODO: make `before`/`after` wrap the heading in `<hgroup>` if non-empty # setting to an attribute set will wrap the section in `<hgroup>`
hgroup.attrs = mkOption {
type = with types; nullOr (submodule { options = global-attrs; });
default = with lib; mkIf (!isNull config.before || !isNull config.after) { };
};
# https://html.spec.whatwg.org/multipage/sections.html#the-hgroup-element # https://html.spec.whatwg.org/multipage/sections.html#the-hgroup-element
before = mkOption { before = mkOption {
type = with types; listOf (attrTag ({ inherit p; } // categories.scripting)); type = with types; listOf (attrTag ({ inherit (element-types) p; } // categories.scripting));
default = [ ]; default = [ ];
}; };
content = mkOption { content = mkOption {
@ -466,7 +472,7 @@ let
}; };
after = mkOption { after = mkOption {
type = with types; type = with types;
listOf (attrTag ({ inherit p; } // categories.scripting)); listOf (attrTag ({ inherit (element-types) p; } // categories.scripting));
default = [ ]; default = [ ];
}; };
}; };
@ -489,20 +495,32 @@ let
__toString = self: with lib; __toString = self: with lib;
let let
n = toString config.heading-level; n = toString config.heading-level;
content = ''<h${n}${print-attrs self.heading.attrs}>${self.heading.content}</h${n}> heading = ''<h${n}${print-attrs self.heading.attrs}>${self.heading.content}</h${n}>'';
'' + join "\n" (map hgroup = with lib; print-element "hgroup" self.heading.hgroup.attrs (squash ''
(e: ${optionalString (!isNull self.heading.before) (toString-unwrap self.heading.before)}
if isAttrs e ${heading}
then toString (lib.head (attrValues e)) ${optionalString (!isNull self.heading.after) (toString-unwrap self.heading.after)}
else e '');
) content = if isNull self.heading.hgroup.attrs then heading else hgroup
self.content); + join "\n" (map toString-unwrap self.content);
in in
if !isNull self.attrs if !isNull self.attrs
then print-element name self.attrs content then print-element name self.attrs content
else content; else content;
}; };
}; };
p = { name, ... }: {
imports = [ element ];
options = {
attrs = mkAttrs { };
content = mkOption {
type = with types; either str (listOf (attrTag categories.phrasing));
};
};
config.categories = [ "flow" "palpable" ];
config.__toString = self: print-element name self.attrs (toString self.content);
};
}; };
in in
{ {

View file

@ -27,17 +27,21 @@ in
}; };
}; };
config.name = lib.slug config.title; config.name = lib.slug config.title;
config.outputs.html = lib.mkForce (cfg.templates.html.dom { config.outputs.html = lib.mkForce ((cfg.templates.html.page config).override {
html = { html = {
head = { # TODO: make authors always a list
title.text = config.title; head.meta.authors = if lib.isList config.author then config.author else [ config.author ];
meta.description = config.description; body.content = lib.mkForce [
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) (cfg.menus.main.outputs.html config)
{ section.heading.content = config.title; } {
section.heading = {
# TODO: i18n support
# TODO: structured dates
before = [{ p.content = "Published ${config.date}"; }];
content = config.title;
after = [{ p.content = "Written by ${config.author}"; }];
};
}
(cfg.templates.html.markdown { inherit (config) name body; }) (cfg.templates.html.markdown { inherit (config) name body; })
]; ];
}; };