Compare commits

...

13 commits

Author SHA1 Message Date
Valentin Gagarin e6ee7149b9 fix <hgroup> rendering 2024-11-10 04:29:16 +01:00
Valentin Gagarin 1fb6b854a0 add stylesheet with fonts 2024-11-10 04:17:18 +01:00
Valentin Gagarin bdf9178fae implement raw assets
this allows adding files to the output as they are
2024-11-10 01:41:34 +01:00
Valentin Gagarin 99ad558f15 move inline recursiveAttrs to lib.types 2024-11-10 01:33:57 +01:00
Valentin Gagarin 43e64b315b implement stylesheet links 2024-11-10 01:32:46 +01:00
Valentin Gagarin 6924b70bef build with full trace 2024-11-10 01:30:29 +01:00
Valentin Gagarin b47d45e67d add .direnv to gitingore 2024-11-10 01:30:19 +01:00
Valentin Gagarin 0cadcb69bb add todo concerning palpable content 2024-11-09 18:33:29 +01:00
Valentin Gagarin 215d531209 list all news articles on a separate page 2024-11-09 18:33:29 +01:00
Valentin Gagarin 738106342c add past events and list them on a page 2024-11-09 04:02:45 +01:00
Valentin Gagarin 2257d5afcb add event content type 2024-11-09 03:45:20 +01:00
Valentin Gagarin 8fc85c9c85 implement definition lists with dl, dt, dd 2024-11-09 03:45:02 +01:00
Valentin Gagarin 59e2e422af make template overrides take final and prev 2024-11-09 01:52:20 +01:00
30 changed files with 585 additions and 150 deletions

1
.gitignore vendored
View file

@ -1 +1,2 @@
result
.direnv

View file

@ -7,16 +7,15 @@ in
imports = lib.nixFiles ./.;
collections.news.type = cfg.content-types.article;
collections.events.type = cfg.content-types.event;
pages.index = { config, link, ... }: {
title = "Fediversity";
title = "Welcome to the Fediversity project";
description = "Fediversity web site";
summary = ''
This web site hosts up-to-date information about the the NGI Zero Fediversity project.
'';
body = ''
# Welcome to the Fediversity project
${pages.fediversity.summary}
[Learn more about Fediversity](${link pages.fediversity})
@ -59,13 +58,20 @@ in
- ${article.date} [${article.title}](${link article})
'') sorted)
}
# Events
${
let
sorted = with lib; reverseList (sortOn (entry: entry.start-date) cfg.collections.events.entry);
in
lib.join "\n" (map (article: ''
- ${article.start-date} [${article.title}](${link article})
'') sorted)
}
'';
outputs.html = (cfg.templates.html.page config).override {
html.body.content = lib.mkForce [
# don't show the page title as a heading
(cfg.menus.main.outputs.html config)
(cfg.templates.html.markdown { inherit (config) name body; })
];
html.head.title.text = "Fediversity";
};
};
}

16
content/events.nix Normal file
View file

@ -0,0 +1,16 @@
{ config, lib, ... }:
{
pages.events = { link, ... }: rec {
title = "Events";
description = "Events related to the Fediverse and NixOS";
summary = description;
body =
with lib;
let
events = map (event: "- [${event.start-date} ${event.title}](${link event})") config.collections.events.entry;
in
''
${join "\n" events}
'';
};
}

View file

@ -0,0 +1,24 @@
{ ... }:
{
collections.events.entry = { ... }: {
title = "OW2con 2024";
description = "OW2con is the annual European open source conference in Paris";
start-date = "2024-06-11";
end-date = "2024-06-12";
start-time = "09:00";
end-time = "18:00";
location = "Paris-Chatillon";
body = ''
OW2con is the European open source conference organized by OW2.
An international meeting of developpers, IT companies, academics and non-profit organizations, OW2con brings together the entire open source community, during two days of presentations ranging from tech topics to business and ethical issues of open source.
It also offers a unique opportunity to establish contact with peers through friendly networking sessions.
OW2con is [open](https://www.ngi.eu/event/open-source-community-annual-conference-2024/) to all, the event is free and all sessions are held in English.
The OW2con24 call for presentations is open.
This year we are giving the highlight on the theme of open source funding:
What are the current solutions for innovators, start-ups or ISVs to finance their development?
Private or public financing?
Are national and European public policies up to the challenges?
'';
};
}

View file

@ -0,0 +1,18 @@
{ ... }:
{
collections.events.entry = { ... }: {
title = "PublicSpaces Conference 2024";
description = "A conference by PublicSpaces, Taking Back the Internet.";
start-date = "2024-06-06";
end-date = "2024-06-07";
start-time = "09:00";
end-time = "18:00";
location = "Pakhuis de Zwijger - Amsterdam";
body = ''
On June 6th and 7th, PublicSpaces and Waag Futurelab proudly present the fourth edition of the PublicSpaces conference under the theme 'Empowering the Internet'.
Held at Pakhuis de Zwijger, this two-day event will feature panels, keynotes, roundtable discussions, lectures, as well as art and cultural showcases, all aimed at collectively shaping the rules for a more inclusive internet.
Join us as we navigate towards a digital landscape where everyone has a voice.
For more information, check out the [website](https://publicspaces.net/2024/02/01/save-the-date-publicspaces-conferentie-2024/)
'';
};
}

View file

@ -0,0 +1,25 @@
{ ... }:
{
collections.events.entry = { ... }: {
title = "State of the Internet 2024";
description = "The State of the Internet 2024 by Waag";
start-date = "2024-05-16";
end-date = "2024-05-16";
start-time = "18:00";
end-time = "20:00";
location = "OBA Oosterdok - Amsterdam";
body = ''
Join us at the State of the Internet 2024, where Waag Futurelab, alongside the Municipality of Amsterdam and the OBA, delves into the depths of the online realm.
Featuring Kim van Sparrentak, Member of the European Parliament, discussing Europe's efforts to regulate Big Tech and enhance digital rights.
Explore the impact of pivotal European laws like the GDPR and AI Act while celebrating 30 years of Waag Futurelab's dedication to democratizing technology access for all.
The event takes place at:
OBA Oosterdok <br>
Oosterdokskade 143 <br>
1011 DK Amsterdam
Registration available [here](https://waag.org/nl/event/de-staat-van-het-internet-2024-met-kim-van-sparrentak/).
'';
};
}

View file

@ -19,18 +19,8 @@ in
}
{ page = pages.fediversity; }
{ page = pages.grants; }
{
menu.label = "News";
menu.items =
let
sorted = with lib; reverseList (sortOn (entry: entry.date) config.collections.news.entry);
in
map
(page: {
page = lib.recursiveUpdate page { title = "${page.date}: ${page.title}"; };
})
(lib.take 3 sorted);
}
{ page = pages.news; }
{ page = pages.events; }
];
};
}

24
content/news.nix Normal file
View file

@ -0,0 +1,24 @@
{ config, lib, ... }:
{
pages.news = { link, ... }: rec {
title = "News";
description = "News about Fediversity";
summary = description;
body =
with lib;
let
news = map
(article: ''
## [${article.title}](${link article})
${article.date} by ${article.author}
${article.summary}
'')
config.collections.news.entry;
in
''
${join "\n\n" news}
'';
};
}

View file

@ -5,6 +5,9 @@
description = "Report from the NORDUnet Conference 2024";
date = "2024-09-17";
author = "Laurens Hof";
summary = ''
Fediversity was represented in Bergen at the Nordunet Conference for 2024, with both the Internet Discourse Foundation and Nordunet themselves being present.
'';
body = ''
Fediversity was represented in Bergen at the Nordunet Conference for 2024, with both the Internet Discourse Foundation and Nordunet themselves being present. This was a great opportunity for the different organisations in the consortium to meet with each other and exchange ideas.

View file

@ -5,6 +5,9 @@
description = "The Fediversity project has officially been announced";
date = "2024-01-01";
author = "Laurens Hof";
summary = ''
We are pleased to introduce the launch of our new website dedicated to the Fediversity project.
'';
body = ''
The Consortium behind the Fediversity project announces that the project has officially been started. NLnet, Tweag, NorduNet and the Open Internet Discourse Foundation are working together to build a new service for cloud hosters.

View file

@ -5,6 +5,9 @@
description = "Report from the PublicSpaces Conference 2024 - 'Take Back the Internet'";
date = "2024-07-30";
author = "Laurens Hof";
summary = ''
PublicSpaces and Waag Futurelabs recently held their yearly conference in Amsterdam, titled Taking Back the Internet
'';
body = ''
PublicSpaces and Waag Futurelabs recently held their yearly conference in Amsterdam, titled 'Taking Back the Internet'. PublicSpaces is a network of public organisations fighting for an internet based on public values. The Fediversity Project attended, to share ideas, and learn more about how people and organisations think about an ethical internet. If you are interested, you can view all sessions [here](https://conference.publicspaces.net/en/archive/pubconf2024) (hosted on PeerTube!).

View file

@ -5,6 +5,9 @@
description = "Fediversity tech session - NixOS and Kubernetes";
date = "2024-08-05";
author = "Laurens Hof";
summary = ''
Recently Fediversity hosted a tech session on NixOS and Kubernetes. We invited people within the community to discuss some design considerations of the Fediversity project with us.
'';
body = ''
Recently Fediversity hosted a tech session on NixOS and Kubernetes. We invited people within the community to discuss some design considerations of the Fediversity project with us.

View file

@ -5,6 +5,9 @@
description = "Announcing our new website for the Fediversity project";
date = "2024-05-15";
author = "Laurens Hof";
summary = ''
We are pleased to introduce the launch of our new website dedicated to the Fediversity project.
'';
body = ''
We are pleased to introduce the launch of our new website dedicated to the Fediversity project.

View file

@ -1,5 +0,0 @@
---
title: "Events"
meta_title: "Events"
description: "Events related to the fediverse and NixOS."
---

View file

@ -1,18 +0,0 @@
---
title: "OW2con 2024"
meta_title: ""
description: "OW2con is the annual European open source conference in Paris"
date: 2024-05-11T09:00:00Z
categories: ["Event", "Conference"]
image: "/images/image-placeholder.png"
author: "Laurens Hof"
draft: false
date_start: 2024-06-11
date_end: 2024-06-12
datetime_start: 2024-06-11 09:00
datetime_end: 2024-06-12 18:00
location: Paris-Chatillon
---
OW2con is the European open source conference organized by OW2. An international meeting of developpers, IT companies, academics and non-profit organizations, OW2con brings together the entire open source community, during two days of presentations ranging from tech topics to business and ethical issues of open source. It also offers a unique opportunity to establish contact with peers through friendly networking sessions. OW2con is [open](https://www.ngi.eu/event/open-source-community-annual-conference-2024/) to all, the event is free and all sessions are held in English.
The OW2con24 call for presentations is open. This year we are giving the highlight on the theme of open source funding: what are the current solutions for innovators, start-ups or ISVs to finance their development? private or public financing? Are national and European public policies up to the challenges?

View file

@ -1,18 +0,0 @@
---
title: "PublicSpaces Conference 2024"
meta_title: ""
date: 2024-05-11T14:00:00+02:00
description: "A conference by PublicSpaces, Taking Back the Internet."
categories: ["Event", "Conference"]
image: "/images/image-placeholder.png"
author: "Laurens Hof"
draft: false
date_start: 2024-06-06
date_end: 2024-06-07
datetime_start: 2024-06-06 09:00
datetime_end: 2024-06-07 18:00
location: Pakhuis de Zwijger - Amsterdam
---
w
On June 6th and 7th, PublicSpaces and Waag Futurelab proudly present the fourth edition of the PublicSpaces conference under the theme 'Empowering the Internet'. Held at Pakhuis de Zwijger, this two-day event will feature panels, keynotes, roundtable discussions, lectures, as well as art and cultural showcases, all aimed at collectively shaping the rules for a more inclusive internet. Join us as we navigate towards a digital landscape where everyone has a voice. For more information, check out the [website](https://publicspaces.net/2024/02/01/save-the-date-publicspaces-conferentie-2024/)

View file

@ -1,25 +0,0 @@
---
title: "State of the Internet 2024"
meta_title: ""
description: "The State of the Internet 2024 by Waag"
date: 2024-04-10T16:00:00Z
image: "/images/image-placeholder.png"
categories: ["Event", "Webinar"]
author: "Laurens Hof"
draft: false
date_start: 2024-05-16
date_end: 2024-05-16
datetime_start: 2024-05-16 18:00
datetime_end: 2024-05-16 20:00
location: OBA Oosterdok - Amsterdam
---
Join us at the State of the Internet 2024, where Waag Futurelab, alongside the Municipality of Amsterdam and the OBA, delves into the depths of the online realm. Featuring Kim van Sparrentak, Member of the European Parliament, discussing Europe's efforts to regulate Big Tech and enhance digital rights. Explore the impact of pivotal European laws like the GDPR and AI Act while celebrating 30 years of Waag Futurelab's dedication to democratizing technology access for all.
The event takes place at:
OBA Oosterdok <br>
Oosterdokskade 143 <br>
1011 DK Amsterdam
Registration available [here](https://waag.org/nl/event/de-staat-van-het-internet-2024-met-kim-van-sparrentak/)

View file

@ -17,7 +17,7 @@ let
# TODO: update when the PR to expose `pkgs.devmode` is merged
# https://github.com/NixOS/nixpkgs/pull/354556
devmode = pkgs.callPackage "${sources.devmode-reusable}/pkgs/by-name/de/devmode/package.nix" {
buildArgs = "${toString ./.} -A build";
buildArgs = "${toString ./.} -A build --show-trace";
open = "/index.html";
};
in

15
lib.nix
View file

@ -8,7 +8,11 @@ rec {
result // {
override = new:
let
base' = lib.recursiveUpdate base new;
base' =
if lib.isFunction new
then lib.recursiveUpdate base (new base' base)
else
lib.recursiveUpdate base new;
result' = g base';
in
result' // {
@ -124,6 +128,15 @@ rec {
);
types = rec {
# arbitrarily nested attribute set where the leaves are of type `type`
# NOTE: this works for anything but attribute sets!
recursiveAttrs = type: with lib.types;
# NOTE: due to how `either` works, the first match is significant,
# so if `type` happens to be an attrset, the typecheck will consider
# `type`, not `attrsOf`
attrsOf (either type (recursiveAttrs type));
# collection of unnamed items that can be added to item-wise, i.e. without wrapping the item in a list
collection = elemType:
let
unparenthesize = class: class == "noun";

View file

@ -9,19 +9,11 @@ in
imports = lib.nixFiles ./.;
options.templates =
let
# arbitrarily nested attribute set where the leaves are of type `type`
recursiveAttrs = type: with types;
# NOTE: due to how `either` works, the first match is significant,
# so if `type` happens to be an attrset, the typecheck will consider
# `type`, not `attrsOf`
attrsOf (either type (recursiveAttrs type));
in
mkOption {
description = ''
Collection of named helper functions for conversion different structured representations which can be rendered to a string
'';
type = recursiveAttrs (with types; functionTo (either str attrs));
type = with types; recursiveAttrs (functionTo (either str attrs));
};
options.files = mkOption {

View file

@ -55,6 +55,7 @@ let
(category:
(mapAttrs (_: e: mkOption { type = submodule e; })
# HACK: don't evaluate the submodule types, just grab the config directly
# TODO: we may want to do this properly and loop `categories` through the top-level `config`
(filterAttrs (_: e: elem category (e { name = "dummy"; config = { }; }).config.categories) elements))
);
@ -68,6 +69,7 @@ let
default = false;
};
id = {
# TODO: would be cool if we could enforce unique IDs per document
type = with types; nullOr nonEmptyStr;
default = null;
};
@ -91,7 +93,35 @@ let
# https://html.spec.whatwg.org/multipage/microdata.html#encoding-microdata
};
# all possible attributes to `<link>` elements.
# since not all of them apply to each `rel=` type, the separate implementations can pick from this collection
link-attrs = lib.mapAttrs (name: value: mkOption value) {
href = {
# TODO: implement https://html.spec.whatwg.org/multipage/semantics.html#the-link-element:attr-link-href-3
# TODO: https://url.spec.whatwg.org/#valid-url-string
type = types.nonEmptyStr;
};
media = {
# TODO: https://drafts.csswg.org/mediaqueries/#media
# it's awsome we have that standard, but ugh so much work
# ;..S
# Clay seems to do it right: https://github.com/sebastiaanvisser/clay
type = with types; nullOr str;
default = null;
};
integrity = {
# TODO: implement https://w3c.github.io/webappsec-subresource-integrity/
type = with types; nullOr str;
default = null;
};
# TODO: more attributes
# https://html.spec.whatwg.org/multipage/semantics.html#the-link-element:concept-element-attributes
};
# TODO: not sure where to put these, since so far they apply to multiple elements,
# but have the same properties for all of them
attrs = lib.mapAttrs (name: value: mkOption value) {
# TODO: investigate: `href` may be coupled with other attributes such as `target` or `hreflang`, this could simplify things
href = {
# TODO: https://url.spec.whatwg.org/#valid-url-string
# ;..O
@ -155,6 +185,8 @@ let
</${name}>
'');
print-element' = name: attrs: "<${name}${print-attrs attrs}>";
toString-unwrap = e:
with lib;
if isAttrs e
@ -269,10 +301,16 @@ let
type = with types; nullOr str;
default = null;
};
# TODO: this one has more internal structure, e.g with hreflang
# TODO: print in output
link.canonical = mkOption {
type = with types; nullOr str;
default = null;
};
link.stylesheets = mkOption {
type = types.listOf (submodule stylesheet);
default = [ ];
};
# TODO: figure out `meta` elements
# https://html.spec.whatwg.org/multipage/semantics.html#the-meta-element:concept-element-attributes
@ -290,14 +328,23 @@ let
${/* https://html.spec.whatwg.org/multipage/semantics.html#attr-meta-http-equiv-x-ua-compatible */
""}<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="${join ", " (mapAttrsToList
(name: value: "${name}=${toString value}") self.meta.viewport)
}" />
${print-element' "meta" {
name = "viewport";
content = "${join ", " (mapAttrsToList (name: value: "${name}=${toString value}") self.meta.viewport) }";
}}
${join "\n" (map
(author: ''<meta name="author" content="${author}" />'')
(author: print-element' "meta" {
name = "author";
content = "${author}";
})
self.meta.authors)
}
${join "\n" (map
(stylesheet: print-element' "link" ({ rel = "stylesheet"; } // (removeAttrs stylesheet [ "categories" "__toString" ])))
self.link.stylesheets)
}
'';
};
@ -349,7 +396,6 @@ let
"prev"
"privacy-policy"
"search"
"stylesheet"
"terms-of-service"
]
);
@ -361,6 +407,37 @@ let
config.__toString = self: "<link${print-attrs self}>";
};
# <link rel="stylesheet"> is implemented separately because it can be used both in `<head>` and `<body>`
# semantically it's a standalone thing but syntactically happens to be subsumed under `<link>`
stylesheet = { config, name, ... }: {
imports = [ element ];
options = global-attrs // {
type = mkOption {
# TODO: this must be a valid MIME type string, which is a bit involved.
# the syntax is explicated here: https://mimesniff.spec.whatwg.org/#mime-type-writing
# but the spec refers to RFC9110: https://www.rfc-editor.org/rfc/rfc9110#name-media-type
# all registered MIME types: https://www.iana.org/assignments/top-level-media-types/top-level-media-types.xhtml
# XXX: if nothing is specified, "text/css" is assumed.
# https://html.spec.whatwg.org/multipage/links.html#link-type-stylesheet:link-type-stylesheet-2
# there's no specification on what else could be there, and it's questionable whether setting anything else even makes sense.
# in practice, browsers seem to ignore anything but "text/css", so we may as well not care at all.
type = with types; nullOr str;
default = null;
};
# https://html.spec.whatwg.org/multipage/semantics.html#attr-link-disabled
disabled = mkOption {
type = types.bool;
default = false;
};
# TODO: implement the rest of the stylesheet attributes
# https://html.spec.whatwg.org/#link-type-stylesheet
inherit (link-attrs) href media integrity;
};
# https://html.spec.whatwg.org/multipage/links.html#link-type-stylesheet:body-ok
config.categories = [ "metadata" "phrasing" ];
config.__toString = self: print-attrs (removeAttrs self [ "categories" "__toString" ]);
};
body = { config, name, ... }: {
imports = [ element ];
options = {
@ -459,7 +536,7 @@ let
# 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) { };
default = with lib; if (config.before == [ ] && config.after == [ ]) then null else { };
};
# https://html.spec.whatwg.org/multipage/sections.html#the-hgroup-element
before = mkOption {
@ -501,8 +578,10 @@ let
${heading}
${optionalString (!isNull self.heading.after) (toString-unwrap self.heading.after)}
'');
content = if isNull self.heading.hgroup.attrs then heading else hgroup
+ join "\n" (map toString-unwrap self.content);
content =
(if isNull self.heading.hgroup.attrs then heading else hgroup)
+
join "\n" (map toString-unwrap self.content);
in
if !isNull self.attrs
then print-element name self.attrs content
@ -521,6 +600,103 @@ let
config.categories = [ "flow" "palpable" ];
config.__toString = self: print-element name self.attrs (toString self.content);
};
dl = { config, name, ... }: {
imports = [ element ];
options = {
attrs = mkAttrs { };
content = mkOption {
type = with types; listOf (submodule ({ ... }: {
options = {
# TODO: wrap in `<div>` if set
div.attrs = mkOption {
type = with types; nullOr (submodule { options = global-attrs; });
default = null;
};
before = mkOption {
type = with types; listOf (attrTag categories.scripting);
default = [ ];
};
terms = mkOption {
type = with types; nonEmptyListOf (submodule dt);
};
between = mkOption {
type = with types; listOf (attrTag categories.scripting);
default = [ ];
};
descriptions = mkOption {
type = with types; nonEmptyListOf (submodule dd);
};
after = mkOption {
type = with types; listOf (attrTag categories.scripting);
default = [ ];
};
};
}));
};
};
# XXX: here we can't express the spec requirement that `dl` is palpable if the list of term-description-pairs is nonempty.
# the reason is that we have to specify a child's *type* in the parent, but being palpable is a property of the value in this case.
# and while the module system does have some dependent typing capabilities, we can't say "the type is X but only if its value has property Y".
# but since the "palpable" category isn't used in any structural requirement in the spec, this is not a loss of fidelity on our side.
# TODO: the whole notion of content categories may be a red herring for this implementation after all, reconsider it.
# it does help to concisely express type constraints on an element's children, but it seems that most of the categories in the spec can be ignored entirely in this implementation.
# the cleanup task would be to identify which categories are really helpful, and document the rationale for using that mechanism as well as the specific choice of categories to keep.
config.categories = [ "flow" ];
config.__toString = self:
with lib;
let
content = map
(entry:
let
list = squash ''
${join "\n" entry.before}
${join "\n" entry.terms}
${join "\n" entry.between}
${join "\n" entry.descriptions}
${join "\n" entry.after}
'';
in
if !isNull entry.div.attrs
then print-element "div" entry.div.attrs list
else list
)
self.content;
in
print-element name self.attrs (join "\n" content);
};
dt = { config, ... }: {
imports = [ element ];
options = {
attrs = mkAttrs { };
dt = mkOption {
type = with types; either str (submodule (attrTag (
# TODO: test
with lib; removeAttrs
(filterAttrs
(name: value: ! any (c: elem c [ "sectioning" "heading" ]) value.categories)
categories.flow
)
[ "header" "footer" ]
)));
};
};
config.categories = [ ];
config.__toString = self: print-element "dt" self.attrs self.dt;
};
dd = { config, ... }: {
imports = [ element ];
options = {
attrs = mkAttrs { };
dd = mkOption {
type = with types; either str (submodule (attrTag categories.flow));
};
};
config.categories = [ ];
config.__toString = self: print-element "dd" self.attrs self.dd;
};
};
in
{

12
presentation/style.css Normal file
View file

@ -0,0 +1,12 @@
body {
font-family: Heebo, sans-serif;
padding: 1em;
max-width: 50em;
margin: auto;
}
h1, h2, h3, h4, h5, h6
{
font-family: Signika, sans-serif;
}

53
presentation/style.nix Normal file
View file

@ -0,0 +1,53 @@
{ config, lib, pkgs, ... }: {
config.assets."style.css".path = ./style.css;
config.assets."fonts.css".path = with lib; builtins.toFile "fonts.css" (join "\n" (map
(font: ''
@font-face {
font-family: '${font.name}';
font-style: normal;
font-weight: ${toString font.weight};
src: url(/${head config.assets.${font.file}.locations}) format('woff2');
}
'')
(
(crossLists (name: file: weight: { inherit name file weight; })
[ [ "Signika " ] [ "signika-extended.woff2" "signika.woff2" ] [ 500 700 ] ]
)
++
(crossLists (name: file: weight: { inherit name file weight; })
[ [ "Heebo " ] [ "heebo-extended.woff2" "heebo.woff2" ] [ 400 600 ] ]
)
)
));
# TODO: get directly from https://github.com/google/fonts
# and compress with https://github.com/fonttools/fonttools
config.assets."signika-extended.woff2" = {
path = pkgs.fetchurl {
url = "https://fonts.gstatic.com/s/signika/v25/vEFO2_JTCgwQ5ejvMV0Ox_Kg1UwJ0tKfX6bOjM7sfA.woff2";
hash = "sha256-6xM7cHYlTKNf1b0gpqhPJjwOoZfxx9+u1e4JPYG2lKk=";
name = "signika-extended.woff2";
};
};
config.assets."signika.woff2" = {
path = pkgs.fetchurl {
url = "https://fonts.gstatic.com/s/signika/v25/vEFO2_JTCgwQ5ejvMV0Ox_Kg1UwJ0tKfX6bBjM4.woff2";
hash = "sha256-Yu0kGT3seb8Qtulu84wvY6nLyPXsRBO/JvTD2BQBtHg=";
name = "signika.woff2";
};
};
config.assets."heebo-extended.woff2" = {
path = pkgs.fetchurl {
url = "https://fonts.gstatic.com/s/heebo/v26/NGS6v5_NC0k9P9H2TbE.woff2";
hash = "sha256-lk3+fFEqYWbHHGyXkdhKnOOMGS9m5ZbbxQcRQCSlxDE=";
name = "heebo-extended.woff2";
};
};
config.assets."heebo.woff2" = {
path = pkgs.fetchurl {
url = "https://fonts.gstatic.com/s/heebo/v26/NGS6v5_NC0k9P9H4TbFzsQ.woff2";
hash = "sha256-JWnjYlbcNsg6KCJnRRjECL2HnZGJOBTMtdutWBNza4Q=";
name = "heebo.woff2";
};
};
}

View file

@ -49,4 +49,18 @@ in
</nav>
'';
};
config.templates.files = fs: with lib;
foldl'
# TODO: create static redirects from `tail <collection>.locations`
(acc: elem: acc // (mapAttrs' (type: value: {
name = head elem.locations + optionalString (type != "") ".${type}";
value = if isStorePath value then value else
builtins.toFile
(elem.name + optionalString (type != "") ".${type}")
(toString value);
}))
elem.outputs)
{ }
fs;
}

View file

@ -1,7 +1,6 @@
{ config, options, lib, ... }:
let
inherit (lib)
mkOption
inherit (lib) mkOption
types
;
cfg = config;
@ -27,28 +26,26 @@ in
};
};
config.name = lib.slug config.title;
config.outputs.html = lib.mkForce ((cfg.templates.html.page config).override {
html = {
# TODO: make authors always a list
head.meta.authors = if lib.isList config.author then config.author else [ config.author ];
body.content = lib.mkForce [
(cfg.menus.main.outputs.html config)
{
section = {
heading = {
# TODO: i18n support
# TODO: structured dates
before = [{ p.content = "Published ${config.date}"; }];
content = config.title;
after = [{ p.content = "Written by ${config.author}"; }];
};
content = [
(cfg.templates.html.markdown { inherit (config) name body; })
];
};
}
];
};
});
config.outputs.html = lib.mkForce
((cfg.templates.html.page config).override (final: prev: {
html = {
# TODO: make authors always a list
head.meta.authors = if lib.isList config.author then config.author else [ config.author ];
body.content = with lib; map
(e:
if isAttrs e && e ? section
then
recursiveUpdate e
{
section.heading = {
before = [{ p.content = "Published ${config.date}"; }];
after = [{ p.content = "Written by ${config.author}"; }];
};
}
else e
)
prev.html.body.content;
};
}));
};
}

34
structure/assets.nix Normal file
View file

@ -0,0 +1,34 @@
{ config, lib, ... }:
let
inherit (lib)
mkOption
types
;
cfg = config;
in
{
options.assets = mkOption {
description = ''
Collection of assets, i.e. static files that can be linked to from within documents
'';
type = with types; attrsOf (submodule ({ config, ... }: {
imports = [ cfg.content-types.document ];
options.path = mkOption {
type = types.path;
};
config.outputs."" = if lib.isStorePath config.path then config.path else "${config.path}";
}));
default = { };
};
config.files = with lib;
let
flatten = attrs: mapAttrsToList
(name: value:
# HACK: we somehow have to distinguish a module value from regular attributes.
# arbitrary choice: the outputs attribute
if value ? outputs then value else mapAttrsToList value)
attrs;
in
cfg.templates.files (flatten cfg.assets);
}

View file

@ -61,15 +61,11 @@ in
};
}));
};
config.files =
# TODO: create static redirects from `tail <collection>.locations`
with lib;
let
collections = with lib; concatMap (collection: collection.entry) (attrValues config.collections);
collections = concatMap (collection: collection.entry) (attrValues config.collections);
in
with lib; foldl
(acc: elem: acc // {
"${head elem.locations}.html" = builtins.toFile "${elem.name}.html" "${elem.outputs.html}";
})
{ }
collections;
cfg.templates.files collections;
}

View file

@ -47,17 +47,34 @@ in
};
link = mkOption {
description = "Helper function for transparent linking to other pages";
type = with types; functionTo str;
type = with types; functionTo attrs;
# TODO: we may want links to other representations,
# and currently the mapping of output types to output file
# names is soft.
default = target: with lib; "${relativePath (head config.locations) (head target.locations)}.html";
default = with lib; target:
let
path = relativePath (head config.locations) (head target.locations);
links = mapAttrs
(type: output:
path + optionalString (type != "") ".${type}"
# ^^^^^^^^^^^^
# convention for raw files
)
target.outputs;
in
if length (attrValues links) == 0
then throw "no output to link to for '${target.name}'"
else if length (attrValues links) == 1
then links // {
__toString = _: head (attrValues links);
}
else links;
};
outputs = mkOption {
description = ''
Representations of the document in different formats
'';
type = with types; attrsOf (either str attrs);
type = with types; attrsOf (either attrs pathInStore);
};
};
};

81
structure/event.nix Normal file
View file

@ -0,0 +1,81 @@
{ config, options, lib, ... }:
let
inherit (lib)
mkOption
types
;
cfg = config;
in
{
content-types.event = { config, collection, ... }: {
imports = [ cfg.content-types.page ];
options = {
collection = mkOption {
description = "Collection this event belongs to";
type = options.collections.type.nestedTypes.elemType;
default = collection;
};
start-date = mkOption {
description = "Start date of the event";
type = with types; str;
};
start-time = mkOption {
description = "Start time of the event";
type = with types; str;
default = null;
};
end-date = mkOption {
description = "End date of the event";
type = with types; str;
default = null;
};
end-time = mkOption {
description = "End time of the event";
type = with types; str;
default = null;
};
location = mkOption {
description = "Location of the event";
type = with types; str;
};
};
config.name = lib.slug config.title;
config.summary = lib.mkDefault config.description;
config.outputs.html = lib.mkForce
((cfg.templates.html.page config).override (final: prev: {
html.body.content = with lib; map
(e:
if isAttrs e && e ? section
then
recursiveUpdate e
{
section.content = [
{
dl.content = [
{
terms = [{ dt = "Location"; }];
descriptions = [{ dd = config.location; }];
}
{
terms = [{ dt = "Start"; }];
descriptions = [{
dd = config.start-date + lib.optionalString (!isNull config.start-time) " ${config.start-time}";
}];
}
] ++ lib.optional (!isNull config.end-date) {
terms = [{ dt = "End"; }];
descriptions = [{
dd = config.end-date + lib.optionalString (!isNull config.end-time) " ${config.end-time}";
}];
};
}
]
++ e.section.content;
}
else e
)
prev.html.body.content;
}));
};
}

View file

@ -14,15 +14,8 @@ in
'';
type = with types; attrsOf (submodule config.content-types.page);
};
config.files = with lib;
foldl'
(acc: elem: acc // {
# TODO: create static redirects from `tail page.locations`
# TODO: the file name could correspond to the canonical location in the HTML representation
"${head elem.locations}.html" = builtins.toFile "${elem.name}.html" "${elem.outputs.html}";
})
{ }
(attrValues config.pages);
config.files = with lib; cfg.templates.files (attrValues config.pages);
config.content-types.page = { name, config, ... }: {
imports = [ cfg.content-types.document ];
@ -61,6 +54,10 @@ in
title.text = page.title;
meta.description = page.description;
link.canonical = lib.head page.locations;
link.stylesheets = [
{ href = "${page.link cfg.assets."style.css"}"; }
{ href = "${page.link cfg.assets."fonts.css"}"; }
];
};
body.content = [
(cfg.menus.main.outputs.html page)