forked from Fediversity/fediversity.eu
ensure the section hierarchy is spec-compliant
- automatically assign heading levels - check that the maximum nesting depth is not exceeded
This commit is contained in:
parent
4160b6e976
commit
179482d043
|
@ -117,4 +117,45 @@ in
|
|||
in
|
||||
pkgs.runCommand "source" { } script;
|
||||
};
|
||||
|
||||
# TODO: this is an artefact of exploration; needs to be adapted to actual use
|
||||
config.templates.table-of-contents = { config, ... }:
|
||||
let
|
||||
outline = { ... }: {
|
||||
options = {
|
||||
value = mkOption {
|
||||
# null denotes root
|
||||
type = with types; nullOr (either str (listOf (attrTag categories.phrasing)));
|
||||
subsections = mkOption {
|
||||
type = with types; listOf (submodule outline);
|
||||
default = with lib; map
|
||||
# TODO: go into depth manually here,
|
||||
# we don't want to pollute the DOM implementation
|
||||
(c: (lib.head (attrValues c)).outline)
|
||||
(filter (c: isAttrs c && (lib.head (attrValues c)) ? outline) config.content);
|
||||
};
|
||||
};
|
||||
__toString = mkOption {
|
||||
type = with types; functionTo str;
|
||||
# TODO: convert to HTML
|
||||
default = self: lib.squash ''
|
||||
${if isNull self.value then "root" else self.value}
|
||||
${if self.subsections != [] then
|
||||
" " + lib.indent " " (lib.join "\n" self.subsections) else ""}
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
in
|
||||
{
|
||||
options.outline = mkOption {
|
||||
type = types.submodule outline;
|
||||
default = {
|
||||
value = null;
|
||||
subsections = with lib;
|
||||
map (c: (lib.head (attrValues c)).outline)
|
||||
(filter (c: isAttrs c && (lib.head (attrValues c)) ? outline) config.content);
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
|
@ -26,14 +26,6 @@ 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 = { ... }: {
|
||||
# TODO: add fields for upstream documentation references
|
||||
|
@ -49,15 +41,6 @@ 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; })
|
||||
|
@ -369,18 +352,74 @@ let
|
|||
};
|
||||
|
||||
body = { config, name, ... }: {
|
||||
imports = [ element content-element ];
|
||||
imports = [ element ];
|
||||
options = {
|
||||
attrs = mkAttrs { };
|
||||
content = mkOption {
|
||||
type = with types;
|
||||
let
|
||||
# Type check that ensures spec-compliant section hierarchy
|
||||
# https://html.spec.whatwg.org/multipage/sections.html#headings-and-outlines-2:concept-heading-7
|
||||
with-section-constraints = baseType: baseType // {
|
||||
merge = loc: defs:
|
||||
with lib;
|
||||
let
|
||||
find-and-attach = def:
|
||||
let
|
||||
process-with-depth = depth: content:
|
||||
map
|
||||
(x:
|
||||
if isAttrs x && x ? section && x.section ? heading
|
||||
then x // {
|
||||
section = x.section // {
|
||||
heading-level = depth;
|
||||
content = process-with-depth (depth + 1) (x.section.content or [ ]);
|
||||
};
|
||||
}
|
||||
else x
|
||||
)
|
||||
content;
|
||||
|
||||
find-with-depth = depth: content:
|
||||
let
|
||||
sections = map (v: { inherit (def) file; value = v; depth = depth; })
|
||||
(filter (x: isAttrs x && x ? section && x.section ? heading) content);
|
||||
subsections = concatMap
|
||||
(x:
|
||||
if isAttrs x && x ? section && x.section ? content
|
||||
then find-with-depth (depth + 1) x.section.content
|
||||
else [ ])
|
||||
content;
|
||||
in
|
||||
sections ++ subsections;
|
||||
|
||||
in
|
||||
{
|
||||
inherit def;
|
||||
processed = process-with-depth 1 def.value;
|
||||
validation = find-with-depth 1 def.value;
|
||||
};
|
||||
|
||||
processed = map find-and-attach defs;
|
||||
all-sections = flatten (map (p: p.validation) processed);
|
||||
too-deep = filter (sec: sec.depth > 6) all-sections;
|
||||
in
|
||||
if too-deep != [ ] then
|
||||
throw ''
|
||||
The option `${lib.options.showOption loc}` has sections nested too deeply:
|
||||
${concatMapStrings (sec: " - depth ${toString sec.depth} section in ${toString sec.file}\n") too-deep}
|
||||
Section hierarchy must not be deeper than 6 levels.''
|
||||
else baseType.merge loc (map (p: p.def // { value = p.processed; }) processed);
|
||||
};
|
||||
in
|
||||
# HACK: bail out for now
|
||||
# TODO: find a reasonable cut-off for where to place raw content
|
||||
listOf (either str (attrTag categories.flow));
|
||||
with-section-constraints
|
||||
# TODO: find a reasonable cut-off for where to place raw content
|
||||
(listOf (either str (attrTag categories.flow)));
|
||||
default = [ ];
|
||||
};
|
||||
};
|
||||
config.section-depth = get-section-depth config.content;
|
||||
|
||||
config.categories = [ ];
|
||||
config.__toString = self: with lib;
|
||||
print-element name self.attrs (
|
||||
|
@ -393,8 +432,9 @@ let
|
|||
self.content)
|
||||
);
|
||||
};
|
||||
|
||||
section = { config, name, ... }: {
|
||||
imports = [ element content-element ];
|
||||
imports = [ element ];
|
||||
options = {
|
||||
# setting to an attribute set will wrap the section in <section>
|
||||
attrs = mkOption {
|
||||
|
@ -413,6 +453,7 @@ let
|
|||
options = {
|
||||
attrs = mkAttrs { };
|
||||
content = mkOption {
|
||||
# https://html.spec.whatwg.org/multipage/sections.html#the-h1,-h2,-h3,-h4,-h5,-and-h6-elements
|
||||
type = with types; either str (listOf (attrTag categories.phrasing));
|
||||
};
|
||||
};
|
||||
|
@ -429,14 +470,12 @@ let
|
|||
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
|
||||
|
@ -457,12 +496,11 @@ let
|
|||
};
|
||||
in
|
||||
{
|
||||
imports = [ element content-element ];
|
||||
imports = [ element ];
|
||||
options = {
|
||||
inherit (element-types) html;
|
||||
};
|
||||
|
||||
config.section-depth = config.html.body.section-depth;
|
||||
config.categories = [ ];
|
||||
config.__toString = self: ''
|
||||
<!DOCTYPE HTML >
|
||||
|
|
Reference in a new issue