forked from Fediversity/fediversity.eu
Compare commits
13 commits
d0534612f3
...
e6ee7149b9
Author | SHA1 | Date | |
---|---|---|---|
e6ee7149b9 | |||
1fb6b854a0 | |||
bdf9178fae | |||
99ad558f15 | |||
43e64b315b | |||
6924b70bef | |||
b47d45e67d | |||
0cadcb69bb | |||
215d531209 | |||
738106342c | |||
2257d5afcb | |||
8fc85c9c85 | |||
59e2e422af |
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1 +1,2 @@
|
|||
result
|
||||
.direnv
|
||||
|
|
|
@ -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
16
content/events.nix
Normal 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}
|
||||
'';
|
||||
};
|
||||
}
|
24
content/events/owc-annual-conference-2024.nix
Normal file
24
content/events/owc-annual-conference-2024.nix
Normal 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 OW2con’24 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?
|
||||
'';
|
||||
};
|
||||
}
|
18
content/events/publicspaces-conference-2024.nix
Normal file
18
content/events/publicspaces-conference-2024.nix
Normal 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/)
|
||||
'';
|
||||
};
|
||||
}
|
25
content/events/waag-state-internet-2024.nix
Normal file
25
content/events/waag-state-internet-2024.nix
Normal 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/).
|
||||
'';
|
||||
};
|
||||
}
|
|
@ -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
24
content/news.nix
Normal 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}
|
||||
'';
|
||||
};
|
||||
}
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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!).
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
---
|
||||
title: "Events"
|
||||
meta_title: "Events"
|
||||
description: "Events related to the fediverse and NixOS."
|
||||
---
|
|
@ -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 OW2con’24 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?
|
|
@ -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/)
|
||||
|
|
@ -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/)
|
|
@ -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
15
lib.nix
|
@ -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";
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
12
presentation/style.css
Normal 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
53
presentation/style.nix
Normal 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";
|
||||
};
|
||||
};
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
34
structure/assets.nix
Normal 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);
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
81
structure/event.nix
Normal 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;
|
||||
|
||||
}));
|
||||
};
|
||||
}
|
|
@ -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)
|
||||
|
|
Reference in a new issue