forked from Fediversity/fediversity.eu
WIP: implement the DOM
This commit is contained in:
parent
3689353603
commit
9da5bdde9e
178
presentation/dom.nix
Normal file
178
presentation/dom.nix
Normal file
|
@ -0,0 +1,178 @@
|
||||||
|
/**
|
||||||
|
A strongly typed module system implementation of the Document Object Model (DOM)
|
||||||
|
|
||||||
|
Based on the WHATWG's HTML Living Standard https://html.spec.whatwg.org (CC-BY 4.0)
|
||||||
|
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, ... }:
|
||||||
|
let
|
||||||
|
# https://html.spec.whatwg.org/multipage/dom.html#content-models
|
||||||
|
# https://html.spec.whatwg.org/multipage/dom.html#kinds-of-content
|
||||||
|
content-categories = [
|
||||||
|
"none" # https://html.spec.whatwg.org/multipage/dom.html#the-nothing-content-model
|
||||||
|
"text" # https://html.spec.whatwg.org/multipage/dom.html#text-content
|
||||||
|
"metadata" # https://html.spec.whatwg.org/multipage/dom.html#metadata-content
|
||||||
|
"flow" # https://html.spec.whatwg.org/multipage/dom.html#flow-content
|
||||||
|
"sectioning" # https://html.spec.whatwg.org/multipage/dom.html#sectioning-content
|
||||||
|
"heading" # https://html.spec.whatwg.org/multipage/dom.html#heading-content
|
||||||
|
"phrasing" # https://html.spec.whatwg.org/multipage/dom.html#phrasing-content
|
||||||
|
"embedded" # https://html.spec.whatwg.org/multipage/dom.html#embedded-content-2
|
||||||
|
"interactive" # https://html.spec.whatwg.org/multipage/dom.html#interactive-content
|
||||||
|
"palpable" # https://html.spec.whatwg.org/multipage/dom.html#palpable-content
|
||||||
|
"scripting" # https://html.spec.whatwg.org/multipage/dom.html#script-supporting-elements
|
||||||
|
];
|
||||||
|
|
||||||
|
# base type for all DOM elements
|
||||||
|
element = { name, config, ... }: {
|
||||||
|
options = with lib; {
|
||||||
|
attrs = mkAttrs { };
|
||||||
|
categories = mkOption {
|
||||||
|
type = types.listOfUnique (types.enum content-categories);
|
||||||
|
};
|
||||||
|
__toString = mkOption {
|
||||||
|
internal = true;
|
||||||
|
type = with types; functionTo str;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
# options with types for all the defined DOM elements
|
||||||
|
element-types = lib.mapAttrs
|
||||||
|
(name: value: mkOption { type = types.submodule e; })
|
||||||
|
elements;
|
||||||
|
|
||||||
|
# attrset of categories, where values are module options with the type of the
|
||||||
|
# elements that belong to these categories
|
||||||
|
categories = with lib;
|
||||||
|
genAttrs
|
||||||
|
content-categories
|
||||||
|
(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))
|
||||||
|
);
|
||||||
|
|
||||||
|
global-attrs =
|
||||||
|
let
|
||||||
|
inherit (lib) mkOption types;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
class = mkOption {
|
||||||
|
type = with types; listOf nonEmptyStr;
|
||||||
|
};
|
||||||
|
hidden = mkOption {
|
||||||
|
type = types.bool;
|
||||||
|
};
|
||||||
|
id = mkOption {
|
||||||
|
type = types.nonEmptyStr;
|
||||||
|
};
|
||||||
|
lang = mkOption {
|
||||||
|
# TODO: https://www.rfc-editor.org/rfc/rfc5646.html
|
||||||
|
type = types.str;
|
||||||
|
};
|
||||||
|
style = mkOption {
|
||||||
|
# TODO: CSS type ;..)
|
||||||
|
type = types.str;
|
||||||
|
};
|
||||||
|
title = mkOption {
|
||||||
|
type = types.lines;
|
||||||
|
};
|
||||||
|
# TODO: more global attributes
|
||||||
|
# https://html.spec.whatwg.org/#global-attributes
|
||||||
|
# https://html.spec.whatwg.org/#attr-aria-*
|
||||||
|
# https://html.spec.whatwg.org/multipage/microdata.html#encoding-microdata
|
||||||
|
};
|
||||||
|
|
||||||
|
mkAttrs = attrs: with lib; mkOption {
|
||||||
|
type = types.submodule {
|
||||||
|
options = global-attrs // attrs;
|
||||||
|
};
|
||||||
|
default = { };
|
||||||
|
};
|
||||||
|
|
||||||
|
print-element = name: attrs: content:
|
||||||
|
with lib;
|
||||||
|
let
|
||||||
|
attributes = join " " (mapAttrsToList
|
||||||
|
# TODO: this needs to be smarter for boolean attributes
|
||||||
|
# where the value must be written out explicitly.
|
||||||
|
# probably the attribute itself should have its own `__toString`.
|
||||||
|
(name: value: if isBool value then name else "${name}=${value}")
|
||||||
|
attrs);
|
||||||
|
in
|
||||||
|
lib.squash ''
|
||||||
|
<${name}${optionalString (stringLength attributes > 0) " ${attributes}"}>
|
||||||
|
${lib.indent " " content}
|
||||||
|
</${name}>
|
||||||
|
'';
|
||||||
|
|
||||||
|
elements = rec {
|
||||||
|
document = { ... }: {
|
||||||
|
imports = [ element ];
|
||||||
|
options = {
|
||||||
|
inherit (element-types) html;
|
||||||
|
};
|
||||||
|
|
||||||
|
config.categories = [ ];
|
||||||
|
config.__toString = self: ''
|
||||||
|
<!DOCTYPE HTML>
|
||||||
|
${self.html}
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
html = { name, ... }: {
|
||||||
|
imports = [ element ];
|
||||||
|
options = {
|
||||||
|
inherit (element-types) head body;
|
||||||
|
};
|
||||||
|
|
||||||
|
config.categories = [ ];
|
||||||
|
config.__toString = self: print-element name self.attrs ''
|
||||||
|
${self.head}
|
||||||
|
${self.body}
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
head = { name, ... }: {
|
||||||
|
imports = [ element ];
|
||||||
|
options = with lib; {
|
||||||
|
# https://html.spec.whatwg.org/multipage/semantics.html#the-head-element:concept-element-content-model
|
||||||
|
# XXX: this doesn't implement the iframe srcdoc semantics
|
||||||
|
# as those have questionable value and would complicate things a bit.
|
||||||
|
# it should be possible though, by passing a flag via module arguments.
|
||||||
|
inherit (element-types) title;
|
||||||
|
base = mkOption {
|
||||||
|
type = with types; nullOr (submodule base);
|
||||||
|
default = null;
|
||||||
|
};
|
||||||
|
content = with types;
|
||||||
|
listOf (attrTag (
|
||||||
|
removeAttrs categories.metadata [ "title" "base" ]
|
||||||
|
));
|
||||||
|
};
|
||||||
|
|
||||||
|
config.categories = [ ];
|
||||||
|
config.__toString = self: print-element name self.attrs ''
|
||||||
|
${self.title}
|
||||||
|
${with lib; optionalString (!isNull self.base) self.base}
|
||||||
|
${join "\n" (map (s: "${s}") self.content)}
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
title = { name, ... }: {
|
||||||
|
# TODO
|
||||||
|
config.categories = [ "metadata" ];
|
||||||
|
|
||||||
|
};
|
||||||
|
base = { name, ... }: {
|
||||||
|
# TODO
|
||||||
|
config.categories = [ "metadata" ];
|
||||||
|
};
|
||||||
|
|
||||||
|
body = { name, ... }: {
|
||||||
|
# TODO
|
||||||
|
};
|
||||||
|
};
|
||||||
|
in
|
||||||
|
elements
|
Reference in a new issue