forked from Fediversity/Fediversity
website: format
This commit is contained in:
parent
92563d387a
commit
10f3d15a98
24 changed files with 1679 additions and 1258 deletions
|
@ -9,124 +9,160 @@ in
|
|||
collections.news.type = cfg.content-types.article;
|
||||
collections.events.type = cfg.content-types.event;
|
||||
|
||||
pages.index = { config, link, ... }: {
|
||||
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 = ''
|
||||
${pages.fediversity.summary}
|
||||
pages.index =
|
||||
{ config, link, ... }:
|
||||
{
|
||||
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 = ''
|
||||
${pages.fediversity.summary}
|
||||
|
||||
[Learn more about Fediversity](${link pages.fediversity})
|
||||
'';
|
||||
outputs.html = (cfg.templates.html.page config).override (final: prev: {
|
||||
html = {
|
||||
head.title.text = "Fediversity";
|
||||
head.link.stylesheets = prev.html.head.link.stylesheets ++ [
|
||||
{ href = "${link cfg.assets."index.css"}"; }
|
||||
];
|
||||
body.content =
|
||||
let
|
||||
to-section = { heading, body, attrs ? { } }: {
|
||||
section = {
|
||||
heading.content = heading;
|
||||
inherit attrs;
|
||||
content = [
|
||||
(cfg.templates.html.markdown {
|
||||
name = "${config.name}-${lib.slug heading}";
|
||||
inherit body;
|
||||
})
|
||||
];
|
||||
};
|
||||
};
|
||||
in
|
||||
[
|
||||
(lib.head prev.html.body.content)
|
||||
{
|
||||
section = {
|
||||
attrs = { };
|
||||
heading.content = config.title;
|
||||
content = [
|
||||
(cfg.templates.html.markdown { inherit (config) name body; })
|
||||
]
|
||||
++
|
||||
(map to-section [
|
||||
[Learn more about Fediversity](${link pages.fediversity})
|
||||
'';
|
||||
outputs.html = (cfg.templates.html.page config).override (
|
||||
final: prev: {
|
||||
html = {
|
||||
head.title.text = "Fediversity";
|
||||
head.link.stylesheets = prev.html.head.link.stylesheets ++ [
|
||||
{ href = "${link cfg.assets."index.css"}"; }
|
||||
];
|
||||
body.content =
|
||||
let
|
||||
to-section =
|
||||
{
|
||||
heading = "Fediversity grants";
|
||||
body = ''
|
||||
${pages.grants.summary}
|
||||
|
||||
[Learn more about Fediversity grants](${link pages.grants})
|
||||
'';
|
||||
}
|
||||
heading,
|
||||
body,
|
||||
attrs ? { },
|
||||
}:
|
||||
{
|
||||
heading = "Consortium";
|
||||
body = ''
|
||||
The Consortium behind the Fediversity project is a cooperation between NLnet, Open Internet Discourse Foundation, NORDUnet and Tweag.
|
||||
section = {
|
||||
heading.content = heading;
|
||||
inherit attrs;
|
||||
content = [
|
||||
(cfg.templates.html.markdown {
|
||||
name = "${config.name}-${lib.slug heading}";
|
||||
inherit body;
|
||||
})
|
||||
];
|
||||
};
|
||||
};
|
||||
in
|
||||
[
|
||||
(lib.head prev.html.body.content)
|
||||
{
|
||||
section = {
|
||||
attrs = { };
|
||||
heading.content = config.title;
|
||||
content =
|
||||
[
|
||||
(cfg.templates.html.markdown { inherit (config) name body; })
|
||||
]
|
||||
++ (map to-section [
|
||||
{
|
||||
heading = "Fediversity grants";
|
||||
body = ''
|
||||
${pages.grants.summary}
|
||||
|
||||
${toString (map (partner: ''
|
||||
### ${partner.title}
|
||||
[Learn more about Fediversity grants](${link pages.grants})
|
||||
'';
|
||||
}
|
||||
{
|
||||
heading = "Consortium";
|
||||
body = ''
|
||||
The Consortium behind the Fediversity project is a cooperation between NLnet, Open Internet Discourse Foundation, NORDUnet and Tweag.
|
||||
|
||||
${partner.summary}
|
||||
${toString (
|
||||
map
|
||||
(partner: ''
|
||||
### ${partner.title}
|
||||
|
||||
[Read more about ${partner.title}](${link partner})
|
||||
'') (with pages; [ nlnet oid tweag nordunet ]))}
|
||||
'';
|
||||
}
|
||||
{
|
||||
heading = "Fediverse explained";
|
||||
body = ''
|
||||
${toString (map (role: ''
|
||||
### ${role.title}
|
||||
${partner.summary}
|
||||
|
||||
${role.summary}
|
||||
[Read more about ${partner.title}](${link partner})
|
||||
'')
|
||||
(
|
||||
with pages;
|
||||
[
|
||||
nlnet
|
||||
oid
|
||||
tweag
|
||||
nordunet
|
||||
]
|
||||
)
|
||||
)}
|
||||
'';
|
||||
}
|
||||
{
|
||||
heading = "Fediverse explained";
|
||||
body = ''
|
||||
${toString (
|
||||
map
|
||||
(role: ''
|
||||
### ${role.title}
|
||||
|
||||
[Read more about ${role.title}](${link role})
|
||||
'') (with pages; [ individuals developers european-commission ]))}
|
||||
'';
|
||||
}
|
||||
]);
|
||||
};
|
||||
}
|
||||
]
|
||||
++
|
||||
(map to-section [
|
||||
{
|
||||
heading = "News";
|
||||
attrs = { class = [ "collection" ]; };
|
||||
body =
|
||||
let
|
||||
sorted = with lib; reverseList (sortOn (entry: entry.date) cfg.collections.news.entry);
|
||||
in
|
||||
lib.join "\n" (map
|
||||
(article: ''
|
||||
- ${article.date} [${article.title}](${link article})
|
||||
'')
|
||||
sorted);
|
||||
}
|
||||
{
|
||||
heading = "Events";
|
||||
attrs = { class = [ "collection" ]; };
|
||||
body =
|
||||
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);
|
||||
}
|
||||
]);
|
||||
};
|
||||
${role.summary}
|
||||
|
||||
});
|
||||
};
|
||||
[Read more about ${role.title}](${link role})
|
||||
'')
|
||||
(
|
||||
with pages;
|
||||
[
|
||||
individuals
|
||||
developers
|
||||
european-commission
|
||||
]
|
||||
)
|
||||
)}
|
||||
'';
|
||||
}
|
||||
]);
|
||||
};
|
||||
}
|
||||
]
|
||||
++ (map to-section [
|
||||
{
|
||||
heading = "News";
|
||||
attrs = {
|
||||
class = [ "collection" ];
|
||||
};
|
||||
body =
|
||||
let
|
||||
sorted = with lib; reverseList (sortOn (entry: entry.date) cfg.collections.news.entry);
|
||||
in
|
||||
lib.join "\n" (
|
||||
map (article: ''
|
||||
- ${article.date} [${article.title}](${link article})
|
||||
'') sorted
|
||||
);
|
||||
}
|
||||
{
|
||||
heading = "Events";
|
||||
attrs = {
|
||||
class = [ "collection" ];
|
||||
};
|
||||
body =
|
||||
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
|
||||
);
|
||||
}
|
||||
]);
|
||||
};
|
||||
|
||||
assets."index.css".path = with lib; builtins.toFile
|
||||
"index.css"
|
||||
''
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
assets."index.css".path =
|
||||
with lib;
|
||||
builtins.toFile "index.css" ''
|
||||
section h1, section h2, section h3
|
||||
{
|
||||
text-align: center;
|
||||
|
|
|
@ -1,22 +1,26 @@
|
|||
{ 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: with lib; ''
|
||||
## [${event.title}](${link event})
|
||||
pages.events =
|
||||
{ link, ... }:
|
||||
rec {
|
||||
title = "Events";
|
||||
description = "Events related to the Fediverse and NixOS";
|
||||
summary = description;
|
||||
body =
|
||||
with lib;
|
||||
let
|
||||
events = map (
|
||||
event: with lib; ''
|
||||
## [${event.title}](${link event})
|
||||
|
||||
${event.start-date} ${optionalString (!isNull event.end-date && event.end-date != event.start-date) "to ${event.end-date}"} in ${event.location}
|
||||
'')
|
||||
config.collections.events.entry;
|
||||
in
|
||||
''
|
||||
${join "\n" events}
|
||||
'';
|
||||
};
|
||||
${event.start-date} ${
|
||||
optionalString (!isNull event.end-date && event.end-date != event.start-date) "to ${event.end-date}"
|
||||
} in ${event.location}
|
||||
''
|
||||
) config.collections.events.entry;
|
||||
in
|
||||
''
|
||||
${join "\n" events}
|
||||
'';
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,23 +1,25 @@
|
|||
{ config, lib, ... }:
|
||||
{
|
||||
collections.events.entry = { link, ... }: {
|
||||
title = "NixOS 24.11 ZHF hackathon";
|
||||
name = "zhf-24-11";
|
||||
description = "NixOS 24.11 ZHF hackathon in Zürich";
|
||||
start-date = "2024-11-23";
|
||||
end-date = "2024-11-24";
|
||||
start-time = "10:00";
|
||||
end-time = "17:00";
|
||||
location = "OST Campus Rapperswil";
|
||||
body = ''
|
||||
The biannual [Zürich NixOS ZHF hackathon](https://zurich.nix.ug/) has become somewhat of an institution for maintaining the tradition of preparing the upcoming NixOS release.
|
||||
collections.events.entry =
|
||||
{ link, ... }:
|
||||
{
|
||||
title = "NixOS 24.11 ZHF hackathon";
|
||||
name = "zhf-24-11";
|
||||
description = "NixOS 24.11 ZHF hackathon in Zürich";
|
||||
start-date = "2024-11-23";
|
||||
end-date = "2024-11-24";
|
||||
start-time = "10:00";
|
||||
end-time = "17:00";
|
||||
location = "OST Campus Rapperswil";
|
||||
body = ''
|
||||
The biannual [Zürich NixOS ZHF hackathon](https://zurich.nix.ug/) has become somewhat of an institution for maintaining the tradition of preparing the upcoming NixOS release.
|
||||
|
||||
The main goal of the two-day gathering is to bring down the number of build failures on the [continuous integration system Hydra](https://status.nixos.org/) before the release: ZHF stands for *Zero Hydra Failures*.
|
||||
It also presents a great opportunity to learn Nix, squash bugs together, get to know each other, discuss current events, and make plans for the future.
|
||||
The main goal of the two-day gathering is to bring down the number of build failures on the [continuous integration system Hydra](https://status.nixos.org/) before the release: ZHF stands for *Zero Hydra Failures*.
|
||||
It also presents a great opportunity to learn Nix, squash bugs together, get to know each other, discuss current events, and make plans for the future.
|
||||
|
||||
This is the greatest event in the series so far, with more than 40 participants from all over Europe, including many high-profile contributors and maintainers in the Nix ecosystem.
|
||||
This is the greatest event in the series so far, with more than 40 participants from all over Europe, including many high-profile contributors and maintainers in the Nix ecosystem.
|
||||
|
||||
[Fediversity engineers attended](${link config.collections.news.by-name.zhf-24-11}) to present prototypes and exchange ideas with other developers.
|
||||
'';
|
||||
};
|
||||
[Fediversity engineers attended](${link config.collections.news.by-name.zhf-24-11}) to present prototypes and exchange ideas with other developers.
|
||||
'';
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,24 +1,26 @@
|
|||
{ ... }:
|
||||
{
|
||||
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.
|
||||
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?
|
||||
'';
|
||||
};
|
||||
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 +1,20 @@
|
|||
{ ... }:
|
||||
{
|
||||
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/)
|
||||
'';
|
||||
};
|
||||
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/)
|
||||
'';
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,25 +1,27 @@
|
|||
{ ... }:
|
||||
{
|
||||
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.
|
||||
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:
|
||||
The event takes place at:
|
||||
|
||||
OBA Oosterdok <br>
|
||||
Oosterdokskade 143 <br>
|
||||
1011 DK Amsterdam
|
||||
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/).
|
||||
'';
|
||||
};
|
||||
Registration available [here](https://waag.org/nl/event/de-staat-van-het-internet-2024-met-kim-van-sparrentak/).
|
||||
'';
|
||||
};
|
||||
}
|
||||
|
|
|
@ -6,16 +6,33 @@ in
|
|||
menus.main = {
|
||||
label = "Main";
|
||||
items = [
|
||||
{ page = pages.index // { title = "Start"; }; }
|
||||
{
|
||||
page = pages.index // {
|
||||
title = "Start";
|
||||
};
|
||||
}
|
||||
{
|
||||
menu.label = "For you";
|
||||
menu.items = map (page: { inherit page; })
|
||||
(with pages; [ individuals developers european-commission ]);
|
||||
menu.items = map (page: { inherit page; }) (
|
||||
with pages;
|
||||
[
|
||||
individuals
|
||||
developers
|
||||
european-commission
|
||||
]
|
||||
);
|
||||
}
|
||||
{
|
||||
menu.label = "Consortium";
|
||||
menu.items = map (page: { inherit page; })
|
||||
(with pages; [ nlnet oid tweag nordunet ]);
|
||||
menu.items = map (page: { inherit page; }) (
|
||||
with pages;
|
||||
[
|
||||
nlnet
|
||||
oid
|
||||
tweag
|
||||
nordunet
|
||||
]
|
||||
);
|
||||
}
|
||||
{ page = pages.fediversity; }
|
||||
{ page = pages.grants; }
|
||||
|
|
|
@ -1,24 +1,24 @@
|
|||
{ config, lib, ... }:
|
||||
{
|
||||
pages.news = { link, ... }: rec {
|
||||
title = "News";
|
||||
description = "News about Fediversity";
|
||||
summary = description;
|
||||
body =
|
||||
with lib;
|
||||
let
|
||||
news = map
|
||||
(article: ''
|
||||
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}
|
||||
'';
|
||||
};
|
||||
'') config.collections.news.entry;
|
||||
in
|
||||
''
|
||||
${join "\n\n" news}
|
||||
'';
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,22 +1,24 @@
|
|||
{ config, lib, ... }:
|
||||
{
|
||||
collections.news.entry = { link, ... }: rec {
|
||||
name = "zhf-24-11";
|
||||
title = "NixOS 24.11 release hackathon and workshop";
|
||||
description = "Fediversity engineers met in Zürich at a NixOS 24.11 ZHF hackathon";
|
||||
date = "2024-11-28";
|
||||
author = "Valentin Gagarin";
|
||||
summary = ''
|
||||
Fediversity engineers met in Zürich at a [NixOS 24.11 ZHF hackathon](${link config.collections.events.by-name.zhf-24-11}) to present prototypes and exchange ideas with the Nix community.
|
||||
'';
|
||||
body = ''
|
||||
${summary}
|
||||
collections.news.entry =
|
||||
{ link, ... }:
|
||||
rec {
|
||||
name = "zhf-24-11";
|
||||
title = "NixOS 24.11 release hackathon and workshop";
|
||||
description = "Fediversity engineers met in Zürich at a NixOS 24.11 ZHF hackathon";
|
||||
date = "2024-11-28";
|
||||
author = "Valentin Gagarin";
|
||||
summary = ''
|
||||
Fediversity engineers met in Zürich at a [NixOS 24.11 ZHF hackathon](${link config.collections.events.by-name.zhf-24-11}) to present prototypes and exchange ideas with the Nix community.
|
||||
'';
|
||||
body = ''
|
||||
${summary}
|
||||
|
||||
Robert held a lightning talk on the design of [NixOps4](https://github.com/nixops4/nixops4), which is currently in the prototype stage of development.
|
||||
Before that, Nicolas had already shown an internal demonstration that NixOps4 is capable of deploying multiple NixOS services in the Fediversity test environment.
|
||||
Robert held a lightning talk on the design of [NixOps4](https://github.com/nixops4/nixops4), which is currently in the prototype stage of development.
|
||||
Before that, Nicolas had already shown an internal demonstration that NixOps4 is capable of deploying multiple NixOS services in the Fediversity test environment.
|
||||
|
||||
In the afternoon, Robert, Valentin, and Koen got together with Eli from [Thymis](https://thymis.io) and Johannes from [Clan](https://clan.lol/) to walk each other through the architecture of their respective systems.
|
||||
This was an extraordinarily fruitful encounter that helped us to identify overlaps and potential for future collaboration!
|
||||
'';
|
||||
};
|
||||
In the afternoon, Robert, Valentin, and Koen got together with Eli from [Thymis](https://thymis.io) and Johannes from [Clan](https://clan.lol/) to walk each other through the architecture of their respective systems.
|
||||
This was an extraordinarily fruitful encounter that helped us to identify overlaps and potential for future collaboration!
|
||||
'';
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,19 +1,21 @@
|
|||
{ config, lib, ... }:
|
||||
{
|
||||
collections.news.entry = { link, ... }: {
|
||||
title = "Fediversity project publicly announced";
|
||||
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.
|
||||
collections.news.entry =
|
||||
{ link, ... }:
|
||||
{
|
||||
title = "Fediversity project publicly announced";
|
||||
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.
|
||||
|
||||
Fediversity is a comprehensive effort to bring easy-to-use, hosted cloud services with service portability and personal freedom at their core to everyone. It wants to provide everyone with high-quality, secure IT systems for everyday use. Without tracking, without exploitation, in a way that runs everywhere and scales effortlessly. Fediversity is based on NixOS, a disruptive Linux distribution with a unique approach to package and configuration management. Built on top of the Nix package manager, NixOS is completely declarative, makes upgrading systems reliable, and has many other advantages. Because it is reproducible, it is ideally suited for complex deployment scenario's where consistent behaviour, stability and configurability matter.
|
||||
Fediversity is a comprehensive effort to bring easy-to-use, hosted cloud services with service portability and personal freedom at their core to everyone. It wants to provide everyone with high-quality, secure IT systems for everyday use. Without tracking, without exploitation, in a way that runs everywhere and scales effortlessly. Fediversity is based on NixOS, a disruptive Linux distribution with a unique approach to package and configuration management. Built on top of the Nix package manager, NixOS is completely declarative, makes upgrading systems reliable, and has many other advantages. Because it is reproducible, it is ideally suited for complex deployment scenario's where consistent behaviour, stability and configurability matter.
|
||||
|
||||
Fediversity has received funding from the European Union’s Horizon Europe research and innovation programme under grant agreement No. 101136078.
|
||||
'';
|
||||
};
|
||||
Fediversity has received funding from the European Union’s Horizon Europe research and innovation programme under grant agreement No. 101136078.
|
||||
'';
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,14 +1,16 @@
|
|||
{ sources ? import ../npins
|
||||
, system ? builtins.currentSystem
|
||||
, pkgs ? import sources.nixpkgs {
|
||||
{
|
||||
sources ? import ../npins,
|
||||
system ? builtins.currentSystem,
|
||||
pkgs ? import sources.nixpkgs {
|
||||
inherit system;
|
||||
config = { };
|
||||
overlays = [ ];
|
||||
}
|
||||
, lib ? import "${sources.nixpkgs}/lib"
|
||||
},
|
||||
lib ? import "${sources.nixpkgs}/lib",
|
||||
}:
|
||||
let
|
||||
lib' = final: prev:
|
||||
lib' =
|
||||
final: prev:
|
||||
let
|
||||
new = import ./lib.nix { lib = final; };
|
||||
in
|
||||
|
@ -37,15 +39,21 @@ rec {
|
|||
let
|
||||
run-tests = pkgs.writeShellApplication {
|
||||
name = "run-tests";
|
||||
text = with pkgs; with lib; ''
|
||||
${getExe nix-unit} ${toString ./tests.nix} "$@"
|
||||
'';
|
||||
text =
|
||||
with pkgs;
|
||||
with lib;
|
||||
''
|
||||
${getExe nix-unit} ${toString ./tests.nix} "$@"
|
||||
'';
|
||||
};
|
||||
test-loop = pkgs.writeShellApplication {
|
||||
name = "test-loop";
|
||||
text = with pkgs; with lib; ''
|
||||
${getExe watchexec} -w ${toString ./.} -- ${getExe nix-unit} ${toString ./tests.nix}
|
||||
'';
|
||||
text =
|
||||
with pkgs;
|
||||
with lib;
|
||||
''
|
||||
${getExe watchexec} -w ${toString ./.} -- ${getExe nix-unit} ${toString ./tests.nix}
|
||||
'';
|
||||
};
|
||||
devmode = pkgs.devmode.override {
|
||||
buildArgs = "${toString ./.} -A build --show-trace";
|
||||
|
@ -62,7 +70,9 @@ rec {
|
|||
};
|
||||
|
||||
inherit sources pkgs;
|
||||
tests = with pkgs; with lib;
|
||||
tests =
|
||||
with pkgs;
|
||||
with lib;
|
||||
let
|
||||
source = fileset.toSource {
|
||||
root = ../.;
|
||||
|
|
171
website/lib.nix
171
website/lib.nix
|
@ -1,21 +1,25 @@
|
|||
{ lib }:
|
||||
rec {
|
||||
template = g: f: x:
|
||||
template =
|
||||
g: f: x:
|
||||
let
|
||||
base = f x;
|
||||
result = g base;
|
||||
in
|
||||
result // {
|
||||
override = new:
|
||||
result
|
||||
// {
|
||||
override =
|
||||
new:
|
||||
let
|
||||
base' =
|
||||
if lib.isFunction new
|
||||
then lib.recursiveUpdate base (new base' base)
|
||||
if lib.isFunction new then
|
||||
lib.recursiveUpdate base (new base' base)
|
||||
else
|
||||
lib.recursiveUpdate base new;
|
||||
result' = g base';
|
||||
in
|
||||
result' // {
|
||||
result'
|
||||
// {
|
||||
override = new: (template g (x': base') x).override new;
|
||||
};
|
||||
};
|
||||
|
@ -28,7 +32,8 @@ rec {
|
|||
replaceStringRec "--" "-" "hello-----world"
|
||||
=> "hello-world"
|
||||
*/
|
||||
replaceStringsRec = from: to: string:
|
||||
replaceStringsRec =
|
||||
from: to: string:
|
||||
let
|
||||
replaced = lib.replaceStrings [ from ] [ to ] string;
|
||||
in
|
||||
|
@ -37,25 +42,24 @@ rec {
|
|||
/**
|
||||
Create a URL-safe slug from any string
|
||||
*/
|
||||
slug = str:
|
||||
slug =
|
||||
str:
|
||||
let
|
||||
# Replace non-alphanumeric characters with hyphens
|
||||
replaced = join ""
|
||||
(
|
||||
builtins.map
|
||||
(c:
|
||||
if (c >= "a" && c <= "z") || (c >= "0" && c <= "9")
|
||||
then c
|
||||
else "-"
|
||||
)
|
||||
(with lib; stringToCharacters (toLower str)));
|
||||
replaced = join "" (
|
||||
builtins.map (c: if (c >= "a" && c <= "z") || (c >= "0" && c <= "9") then c else "-") (
|
||||
with lib; stringToCharacters (toLower str)
|
||||
)
|
||||
);
|
||||
|
||||
# Remove leading and trailing hyphens
|
||||
trimHyphens = s:
|
||||
trimHyphens =
|
||||
s:
|
||||
let
|
||||
matched = builtins.match "(-*)([^-].*[^-]|[^-])(-*)" s;
|
||||
in
|
||||
with lib; optionalString (!isNull matched) (builtins.elemAt matched 1);
|
||||
with lib;
|
||||
optionalString (!isNull matched) (builtins.elemAt matched 1);
|
||||
in
|
||||
trimHyphens (replaceStringsRec "--" "-" replaced);
|
||||
|
||||
|
@ -64,9 +68,11 @@ rec {
|
|||
/**
|
||||
Trim trailing spaces and squash non-leading spaces
|
||||
*/
|
||||
trim = string:
|
||||
trim =
|
||||
string:
|
||||
let
|
||||
trimLine = line:
|
||||
trimLine =
|
||||
line:
|
||||
with lib;
|
||||
let
|
||||
# separate leading spaces from the rest
|
||||
|
@ -76,8 +82,7 @@ rec {
|
|||
# drop trailing spaces
|
||||
body = head (split " *$" rest);
|
||||
in
|
||||
if body == "" then "" else
|
||||
spaces + replaceStringsRec " " " " body;
|
||||
if body == "" then "" else spaces + replaceStringsRec " " " " body;
|
||||
in
|
||||
join "\n" (map trimLine (splitLines string));
|
||||
|
||||
|
@ -85,84 +90,95 @@ rec {
|
|||
|
||||
splitLines = s: with builtins; filter (x: !isList x) (split "\n" s);
|
||||
|
||||
indent = prefix: s:
|
||||
indent =
|
||||
prefix: s:
|
||||
with lib.lists;
|
||||
let
|
||||
lines = splitLines s;
|
||||
in
|
||||
join "\n" (
|
||||
[ (head lines) ]
|
||||
++
|
||||
(map (x: if x == "" then x else "${prefix}${x}") (tail lines))
|
||||
);
|
||||
join "\n" ([ (head lines) ] ++ (map (x: if x == "" then x else "${prefix}${x}") (tail lines)));
|
||||
|
||||
relativePath = path1': path2':
|
||||
relativePath =
|
||||
path1': path2':
|
||||
let
|
||||
inherit (lib.path) subpath;
|
||||
inherit (lib) lists length take drop min max;
|
||||
inherit (lib)
|
||||
lists
|
||||
length
|
||||
take
|
||||
drop
|
||||
min
|
||||
max
|
||||
;
|
||||
|
||||
path1 = subpath.components path1';
|
||||
prefix1 = take (length path1 - 1) path1;
|
||||
path2 = subpath.components path2';
|
||||
prefix2 = take (length path2 - 1) path2;
|
||||
|
||||
commonPrefixLength = with lists;
|
||||
findFirstIndex (i: i.fst != i.snd)
|
||||
(min (length prefix1) (length prefix2))
|
||||
(zipLists prefix1 prefix2);
|
||||
commonPrefixLength =
|
||||
with lists;
|
||||
findFirstIndex (i: i.fst != i.snd) (min (length prefix1) (length prefix2)) (
|
||||
zipLists prefix1 prefix2
|
||||
);
|
||||
|
||||
depth = max 0 (length prefix1 - commonPrefixLength);
|
||||
|
||||
relativeComponents = with lists;
|
||||
relativeComponents =
|
||||
with lists;
|
||||
[ "." ] ++ (replicate depth "..") ++ (drop commonPrefixLength path2);
|
||||
in
|
||||
join "/" relativeComponents;
|
||||
|
||||
/**
|
||||
Recursively list all Nix files from a directory, except the top-level `default.nix`
|
||||
Recursively list all Nix files from a directory, except the top-level `default.nix`
|
||||
|
||||
Useful for module system `imports` from a top-level module.
|
||||
**/
|
||||
nixFiles = dir: with lib.fileset;
|
||||
toList (difference
|
||||
(fileFilter ({ hasExt, ... }: hasExt "nix") dir)
|
||||
(dir + "/default.nix")
|
||||
);
|
||||
Useful for module system `imports` from a top-level module.
|
||||
*
|
||||
*/
|
||||
nixFiles =
|
||||
dir:
|
||||
with lib.fileset;
|
||||
toList (difference (fileFilter ({ hasExt, ... }: hasExt "nix") dir) (dir + "/default.nix"));
|
||||
|
||||
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;
|
||||
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:
|
||||
collection =
|
||||
elemType:
|
||||
let
|
||||
unparenthesize = class: class == "noun";
|
||||
desc = type:
|
||||
types.optionDescriptionPhrase unparenthesize type;
|
||||
desc' = type:
|
||||
desc = type: types.optionDescriptionPhrase unparenthesize type;
|
||||
desc' =
|
||||
type:
|
||||
let
|
||||
typeDesc = lib.types.optionDescriptionPhrase unparenthesize type;
|
||||
in
|
||||
if type.descriptionClass == "noun"
|
||||
then
|
||||
typeDesc + "s"
|
||||
else
|
||||
"many instances of ${typeDesc}";
|
||||
if type.descriptionClass == "noun" then typeDesc + "s" else "many instances of ${typeDesc}";
|
||||
in
|
||||
lib.types.mkOptionType {
|
||||
name = "collection";
|
||||
description = "separately specified ${desc elemType} for a collection of ${desc' elemType}";
|
||||
merge = loc: defs:
|
||||
map
|
||||
(def:
|
||||
elemType.merge (loc ++ [ "[definition ${toString def.file}]" ]) [{ inherit (def) file; value = def.value; }]
|
||||
)
|
||||
defs;
|
||||
merge =
|
||||
loc: defs:
|
||||
map (
|
||||
def:
|
||||
elemType.merge (loc ++ [ "[definition ${toString def.file}]" ]) [
|
||||
{
|
||||
inherit (def) file;
|
||||
value = def.value;
|
||||
}
|
||||
]
|
||||
) defs;
|
||||
check = elemType.check;
|
||||
getSubOptions = elemType.getSubOptions;
|
||||
getSubModules = elemType.getSubModules;
|
||||
|
@ -175,29 +191,34 @@ rec {
|
|||
nestedTypes.elemType = elemType;
|
||||
};
|
||||
|
||||
listOfUnique = elemType:
|
||||
listOfUnique =
|
||||
elemType:
|
||||
let
|
||||
baseType = lib.types.listOf elemType;
|
||||
in
|
||||
baseType // {
|
||||
merge = loc: defs:
|
||||
baseType
|
||||
// {
|
||||
merge =
|
||||
loc: defs:
|
||||
let
|
||||
# Keep track of which definition each value came from
|
||||
defsWithValues = map
|
||||
(def:
|
||||
map (v: { inherit (def) file; value = v; }) def.value
|
||||
)
|
||||
defs;
|
||||
defsWithValues = map (
|
||||
def:
|
||||
map (v: {
|
||||
inherit (def) file;
|
||||
value = v;
|
||||
}) def.value
|
||||
) defs;
|
||||
flatDefs = lib.flatten defsWithValues;
|
||||
|
||||
# Check for duplicates while preserving source info
|
||||
seen = builtins.foldl'
|
||||
(acc: def:
|
||||
if lib.lists.any (v: v.value == def.value) acc
|
||||
then throw "The option `${lib.options.showOption loc}` has duplicate values (${toString def.value}) defined in ${def.file}"
|
||||
else acc ++ [ def ]
|
||||
) [ ]
|
||||
flatDefs;
|
||||
seen = builtins.foldl' (
|
||||
acc: def:
|
||||
if lib.lists.any (v: v.value == def.value) acc then
|
||||
throw "The option `${lib.options.showOption loc}` has duplicate values (${toString def.value}) defined in ${def.file}"
|
||||
else
|
||||
acc ++ [ def ]
|
||||
) [ ] flatDefs;
|
||||
in
|
||||
map (def: def.value) seen;
|
||||
};
|
||||
|
|
|
@ -1,4 +1,10 @@
|
|||
{ config, options, lib, pkgs, ... }:
|
||||
{
|
||||
config,
|
||||
options,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
let
|
||||
inherit (lib)
|
||||
mkOption
|
||||
|
@ -8,13 +14,12 @@ in
|
|||
{
|
||||
imports = lib.nixFiles ./.;
|
||||
|
||||
options.templates =
|
||||
mkOption {
|
||||
description = ''
|
||||
Collection of named helper functions for conversion different structured representations which can be rendered to a string
|
||||
'';
|
||||
type = with types; recursiveAttrs (functionTo (either str attrs));
|
||||
};
|
||||
options.templates = mkOption {
|
||||
description = ''
|
||||
Collection of named helper functions for conversion different structured representations which can be rendered to a string
|
||||
'';
|
||||
type = with types; recursiveAttrs (functionTo (either str attrs));
|
||||
};
|
||||
|
||||
options.files = mkOption {
|
||||
description = ''
|
||||
|
@ -32,58 +37,64 @@ in
|
|||
type = types.package;
|
||||
default =
|
||||
let
|
||||
script = ''
|
||||
mkdir $out
|
||||
'' + lib.join "\n" copy;
|
||||
copy = lib.mapAttrsToList
|
||||
(
|
||||
path: file: ''
|
||||
mkdir -p $out/$(dirname ${path})
|
||||
cp -r ${file} $out/${path}
|
||||
''
|
||||
)
|
||||
config.files;
|
||||
script =
|
||||
''
|
||||
mkdir $out
|
||||
''
|
||||
+ lib.join "\n" copy;
|
||||
copy = lib.mapAttrsToList (path: file: ''
|
||||
mkdir -p $out/$(dirname ${path})
|
||||
cp -r ${file} $out/${path}
|
||||
'') config.files;
|
||||
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, ... }:
|
||||
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);
|
||||
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 ""}
|
||||
'';
|
||||
};
|
||||
};
|
||||
__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);
|
||||
subsections =
|
||||
with lib;
|
||||
map (c: (lib.head (attrValues c)).outline) (
|
||||
filter (c: isAttrs c && (lib.head (attrValues c)) ? outline) config.content
|
||||
);
|
||||
};
|
||||
};
|
||||
};
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,27 +1,53 @@
|
|||
{ config, lib, pkgs, ... }: {
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
{
|
||||
config.assets."style.css".path = ./style.css;
|
||||
config.assets."ngi-fediversity.svg".path = ./ngi-fediversity.svg;
|
||||
# TODO: auto-generate a bunch from SVG
|
||||
config.assets."favicon.png".path = ./favicon.png;
|
||||
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 ] ]
|
||||
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
|
||||
]
|
||||
])
|
||||
)
|
||||
)
|
||||
++
|
||||
(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
|
||||
|
|
|
@ -1,4 +1,10 @@
|
|||
{ config, options, lib, pkgs, ... }:
|
||||
{
|
||||
config,
|
||||
options,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
let
|
||||
inherit (lib)
|
||||
mkOption
|
||||
|
@ -7,11 +13,15 @@ let
|
|||
in
|
||||
{
|
||||
config.templates.html = {
|
||||
dom = document:
|
||||
dom =
|
||||
document:
|
||||
let
|
||||
eval = lib.evalModules {
|
||||
class = "DOM";
|
||||
modules = [ document (import ./dom.nix) ];
|
||||
modules = [
|
||||
document
|
||||
(import ./dom.nix)
|
||||
];
|
||||
};
|
||||
in
|
||||
{
|
||||
|
@ -19,27 +29,34 @@ in
|
|||
value = eval.config;
|
||||
};
|
||||
|
||||
markdown = { name, body }:
|
||||
markdown =
|
||||
{ name, body }:
|
||||
let
|
||||
commonmark = pkgs.runCommand "${name}.html"
|
||||
{
|
||||
buildInputs = [ pkgs.cmark ];
|
||||
} ''
|
||||
cmark ${builtins.toFile "${name}.md" body} > $out
|
||||
'';
|
||||
commonmark =
|
||||
pkgs.runCommand "${name}.html"
|
||||
{
|
||||
buildInputs = [ pkgs.cmark ];
|
||||
}
|
||||
''
|
||||
cmark ${builtins.toFile "${name}.md" body} > $out
|
||||
'';
|
||||
in
|
||||
builtins.readFile commonmark;
|
||||
nav = { menu, page }:
|
||||
nav =
|
||||
{ menu, page }:
|
||||
let
|
||||
render-item = item:
|
||||
if item ? menu then ''
|
||||
<li><details><summary>${item.menu.label}</summary>
|
||||
${lib.indent " " (item.menu.outputs.html page)}
|
||||
</li>
|
||||
''
|
||||
else if item ? page then ''<li><a href="${page.link item.page}">${item.page.title}</a></li>''
|
||||
else ''<li><a href="${item.link.url}">${item.link.label}</a></li>''
|
||||
;
|
||||
render-item =
|
||||
item:
|
||||
if item ? menu then
|
||||
''
|
||||
<li><details><summary>${item.menu.label}</summary>
|
||||
${lib.indent " " (item.menu.outputs.html page)}
|
||||
</li>
|
||||
''
|
||||
else if item ? page then
|
||||
''<li><a href="${page.link item.page}">${item.page.title}</a></li>''
|
||||
else
|
||||
''<li><a href="${item.link.url}">${item.link.label}</a></li>'';
|
||||
in
|
||||
''
|
||||
<nav>
|
||||
|
@ -50,17 +67,27 @@ in
|
|||
'';
|
||||
};
|
||||
|
||||
config.templates.files = fs: with lib;
|
||||
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)
|
||||
(
|
||||
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,51 +1,62 @@
|
|||
{ config, options, lib, ... }:
|
||||
{
|
||||
config,
|
||||
options,
|
||||
lib,
|
||||
...
|
||||
}:
|
||||
let
|
||||
inherit (lib) mkOption
|
||||
inherit (lib)
|
||||
mkOption
|
||||
types
|
||||
;
|
||||
cfg = config;
|
||||
in
|
||||
{
|
||||
content-types.article = { config, collection, ... }: {
|
||||
imports = [ cfg.content-types.page ];
|
||||
options = {
|
||||
collection = mkOption {
|
||||
description = "Collection this article belongs to";
|
||||
type = options.collections.type.nestedTypes.elemType;
|
||||
default = collection;
|
||||
};
|
||||
date = mkOption {
|
||||
description = "Publication date";
|
||||
type = with types; str;
|
||||
default = null;
|
||||
};
|
||||
author = mkOption {
|
||||
description = "Page author";
|
||||
type = with types; either str (nonEmptyListOf str);
|
||||
default = null;
|
||||
};
|
||||
};
|
||||
config.name = with lib; mkDefault (slug config.title);
|
||||
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;
|
||||
content-types.article =
|
||||
{ config, collection, ... }:
|
||||
{
|
||||
imports = [ cfg.content-types.page ];
|
||||
options = {
|
||||
collection = mkOption {
|
||||
description = "Collection this article belongs to";
|
||||
type = options.collections.type.nestedTypes.elemType;
|
||||
default = collection;
|
||||
};
|
||||
}));
|
||||
};
|
||||
date = mkOption {
|
||||
description = "Publication date";
|
||||
type = with types; str;
|
||||
default = null;
|
||||
};
|
||||
author = mkOption {
|
||||
description = "Page author";
|
||||
type = with types; either str (nonEmptyListOf str);
|
||||
default = null;
|
||||
};
|
||||
};
|
||||
config.name = with lib; mkDefault (slug config.title);
|
||||
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;
|
||||
};
|
||||
}
|
||||
)
|
||||
);
|
||||
};
|
||||
}
|
||||
|
|
|
@ -11,24 +11,34 @@ in
|
|||
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}";
|
||||
}));
|
||||
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;
|
||||
config.files =
|
||||
with lib;
|
||||
let
|
||||
flatten = attrs: mapAttrsToList
|
||||
(name: value:
|
||||
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;
|
||||
if value ? outputs then value else mapAttrsToList value
|
||||
) attrs;
|
||||
in
|
||||
cfg.templates.files (flatten cfg.assets);
|
||||
}
|
||||
|
|
|
@ -1,4 +1,10 @@
|
|||
{ config, options, lib, pkgs, ... }:
|
||||
{
|
||||
config,
|
||||
options,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
let
|
||||
inherit (lib)
|
||||
mkOption
|
||||
|
@ -25,46 +31,62 @@ in
|
|||
}
|
||||
```
|
||||
'';
|
||||
type = with types; attrsOf (submodule ({ name, config, ... }: {
|
||||
options = {
|
||||
type = mkOption {
|
||||
description = "Type of entries in the collection";
|
||||
type = types.deferredModule;
|
||||
};
|
||||
name = mkOption {
|
||||
description = "Symbolic name, used as a human-readable identifier";
|
||||
type = types.str;
|
||||
default = name;
|
||||
};
|
||||
prefixes = mkOption {
|
||||
description = ''
|
||||
List of historic output locations for files in the collection
|
||||
type =
|
||||
with types;
|
||||
attrsOf (
|
||||
submodule (
|
||||
{ name, config, ... }:
|
||||
{
|
||||
options = {
|
||||
type = mkOption {
|
||||
description = "Type of entries in the collection";
|
||||
type = types.deferredModule;
|
||||
};
|
||||
name = mkOption {
|
||||
description = "Symbolic name, used as a human-readable identifier";
|
||||
type = types.str;
|
||||
default = name;
|
||||
};
|
||||
prefixes = mkOption {
|
||||
description = ''
|
||||
List of historic output locations for files in the collection
|
||||
|
||||
The first element is the canonical location.
|
||||
All other elements are used to create redirects to the canonical location.
|
||||
The first element is the canonical location.
|
||||
All other elements are used to create redirects to the canonical location.
|
||||
|
||||
The default entry is the symbolic name of the collection.
|
||||
When changing the symbolic name, append the old one to your custom list and use `lib.mkForce` to make sure the default element will be overridden.
|
||||
'';
|
||||
type = with types; nonEmptyListOf str;
|
||||
example = [ "." ];
|
||||
default = [ config.name ];
|
||||
};
|
||||
entry = mkOption {
|
||||
description = "An entry in the collection";
|
||||
type = with types; collection (submodule ({
|
||||
imports = [ config.type ];
|
||||
_module.args.collection = config;
|
||||
process-locations = ls: with lib; concatMap (l: map (p: "${p}/${l}") config.prefixes) ls;
|
||||
}));
|
||||
};
|
||||
by-name = mkOption {
|
||||
description = "Entries accessible by symbolic name";
|
||||
type = with types; attrsOf attrs;
|
||||
default = with lib; listToAttrs (map (e: { name = e.name; value = e; }) config.entry);
|
||||
};
|
||||
};
|
||||
}));
|
||||
The default entry is the symbolic name of the collection.
|
||||
When changing the symbolic name, append the old one to your custom list and use `lib.mkForce` to make sure the default element will be overridden.
|
||||
'';
|
||||
type = with types; nonEmptyListOf str;
|
||||
example = [ "." ];
|
||||
default = [ config.name ];
|
||||
};
|
||||
entry = mkOption {
|
||||
description = "An entry in the collection";
|
||||
type =
|
||||
with types;
|
||||
collection (submodule ({
|
||||
imports = [ config.type ];
|
||||
_module.args.collection = config;
|
||||
process-locations = ls: with lib; concatMap (l: map (p: "${p}/${l}") config.prefixes) ls;
|
||||
}));
|
||||
};
|
||||
by-name = mkOption {
|
||||
description = "Entries accessible by symbolic name";
|
||||
type = with types; attrsOf attrs;
|
||||
default =
|
||||
with lib;
|
||||
listToAttrs (
|
||||
map (e: {
|
||||
name = e.name;
|
||||
value = e;
|
||||
}) config.entry
|
||||
);
|
||||
};
|
||||
};
|
||||
}
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
config.files =
|
||||
|
|
|
@ -1,4 +1,10 @@
|
|||
{ config, options, lib, pkgs, ... }:
|
||||
{
|
||||
config,
|
||||
options,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
let
|
||||
inherit (lib)
|
||||
mkOption
|
||||
|
@ -14,68 +20,81 @@ in
|
|||
type = with types; attrsOf deferredModule;
|
||||
};
|
||||
|
||||
config.content-types.document = { name, config, options, link, ... }: {
|
||||
config._module.args.link = config.link;
|
||||
options = {
|
||||
name = mkOption {
|
||||
description = "Symbolic name, used as a human-readable identifier";
|
||||
type = types.str;
|
||||
default = name;
|
||||
};
|
||||
locations = mkOption {
|
||||
description = ''
|
||||
List of historic output locations for the resulting file
|
||||
config.content-types.document =
|
||||
{
|
||||
name,
|
||||
config,
|
||||
options,
|
||||
link,
|
||||
...
|
||||
}:
|
||||
{
|
||||
config._module.args.link = config.link;
|
||||
options = {
|
||||
name = mkOption {
|
||||
description = "Symbolic name, used as a human-readable identifier";
|
||||
type = types.str;
|
||||
default = name;
|
||||
};
|
||||
locations = mkOption {
|
||||
description = ''
|
||||
List of historic output locations for the resulting file
|
||||
|
||||
Elements are relative paths to output files, without suffix.
|
||||
The suffix will be added depending on output file type.
|
||||
Elements are relative paths to output files, without suffix.
|
||||
The suffix will be added depending on output file type.
|
||||
|
||||
The first element is the canonical location.
|
||||
All other elements are used to create redirects to the canonical location.
|
||||
The first element is the canonical location.
|
||||
All other elements are used to create redirects to the canonical location.
|
||||
|
||||
The default entry is the symbolic name of the document.
|
||||
When changing the symbolic name, append the old one to your custom list and use `lib.mkForce` to make sure the default element will be overridden.
|
||||
'';
|
||||
type = with types; nonEmptyListOf str;
|
||||
apply = config.process-locations;
|
||||
example = [ "about/overview" "index" ];
|
||||
default = [ config.name ];
|
||||
};
|
||||
process-locations = mkOption {
|
||||
description = "Function to post-process the output locations of contained document";
|
||||
type = types.functionTo options.locations.type;
|
||||
default = lib.id;
|
||||
};
|
||||
link = mkOption {
|
||||
description = "Helper function for transparent linking to other pages";
|
||||
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 = with lib; target:
|
||||
let
|
||||
path = relativePath (head config.locations) (head target.locations);
|
||||
links = mapAttrs
|
||||
(type: output:
|
||||
path + optionalString (type != "") ".${type}"
|
||||
The default entry is the symbolic name of the document.
|
||||
When changing the symbolic name, append the old one to your custom list and use `lib.mkForce` to make sure the default element will be overridden.
|
||||
'';
|
||||
type = with types; nonEmptyListOf str;
|
||||
apply = config.process-locations;
|
||||
example = [
|
||||
"about/overview"
|
||||
"index"
|
||||
];
|
||||
default = [ config.name ];
|
||||
};
|
||||
process-locations = mkOption {
|
||||
description = "Function to post-process the output locations of contained document";
|
||||
type = types.functionTo options.locations.type;
|
||||
default = lib.id;
|
||||
};
|
||||
link = mkOption {
|
||||
description = "Helper function for transparent linking to other pages";
|
||||
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 =
|
||||
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 attrs pathInStore);
|
||||
) 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 attrs pathInStore);
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,4 +1,9 @@
|
|||
{ config, options, lib, ... }:
|
||||
{
|
||||
config,
|
||||
options,
|
||||
lib,
|
||||
...
|
||||
}:
|
||||
let
|
||||
inherit (lib)
|
||||
mkOption
|
||||
|
@ -7,75 +12,85 @@ let
|
|||
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;
|
||||
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;
|
||||
};
|
||||
};
|
||||
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 = with lib; mkDefault (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;
|
||||
config.name = with lib; mkDefault (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;
|
||||
|
||||
}));
|
||||
};
|
||||
}
|
||||
)
|
||||
);
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,19 +1,26 @@
|
|||
{ config, options, lib, ... }:
|
||||
{
|
||||
config,
|
||||
options,
|
||||
lib,
|
||||
...
|
||||
}:
|
||||
let
|
||||
inherit (lib)
|
||||
mkOption
|
||||
types
|
||||
;
|
||||
cfg = config;
|
||||
subtype = baseModule: types.submodule [
|
||||
baseModule
|
||||
{
|
||||
_module.freeformType = types.attrs;
|
||||
# XXX: this is supposed to be used with a finished value,
|
||||
# and we don't want to process locations again.
|
||||
process-locations = lib.mkForce lib.id;
|
||||
}
|
||||
];
|
||||
subtype =
|
||||
baseModule:
|
||||
types.submodule [
|
||||
baseModule
|
||||
{
|
||||
_module.freeformType = types.attrs;
|
||||
# XXX: this is supposed to be used with a finished value,
|
||||
# and we don't want to process locations again.
|
||||
process-locations = lib.mkForce lib.id;
|
||||
}
|
||||
];
|
||||
in
|
||||
{
|
||||
options.menus = mkOption {
|
||||
|
@ -23,50 +30,59 @@ in
|
|||
type = with types; attrsOf (submodule config.content-types.navigation);
|
||||
};
|
||||
|
||||
config.content-types.named-link = { ... }: {
|
||||
options = {
|
||||
label = mkOption {
|
||||
description = "Link label";
|
||||
type = types.str;
|
||||
};
|
||||
url = mkOption {
|
||||
description = "Link URL";
|
||||
type = types.str;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config.content-types.navigation = { name, config, ... }: {
|
||||
options = {
|
||||
name = mkOption {
|
||||
description = "Symbolic name, used as a human-readable identifier";
|
||||
type = types.str;
|
||||
default = name;
|
||||
};
|
||||
label = mkOption {
|
||||
description = "Menu label";
|
||||
type = types.str;
|
||||
default = name;
|
||||
};
|
||||
items = mkOption {
|
||||
description = "List of menu items";
|
||||
type = with types; listOf (attrTag {
|
||||
menu = mkOption { type = submodule cfg.content-types.navigation; };
|
||||
page = mkOption { type = subtype cfg.content-types.page; };
|
||||
link = mkOption { type = submodule cfg.content-types.named-link; };
|
||||
});
|
||||
};
|
||||
outputs = mkOption {
|
||||
description = ''
|
||||
Representations of the navigation structure in different formats
|
||||
|
||||
It must be a function that takes the page on which the navigation is to be shown, such that relative links get computed correctly.
|
||||
'';
|
||||
type = with types; attrsOf (functionTo str);
|
||||
default.html = page: cfg.templates.html.nav {
|
||||
menu = config; inherit page;
|
||||
config.content-types.named-link =
|
||||
{ ... }:
|
||||
{
|
||||
options = {
|
||||
label = mkOption {
|
||||
description = "Link label";
|
||||
type = types.str;
|
||||
};
|
||||
url = mkOption {
|
||||
description = "Link URL";
|
||||
type = types.str;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config.content-types.navigation =
|
||||
{ name, config, ... }:
|
||||
{
|
||||
options = {
|
||||
name = mkOption {
|
||||
description = "Symbolic name, used as a human-readable identifier";
|
||||
type = types.str;
|
||||
default = name;
|
||||
};
|
||||
label = mkOption {
|
||||
description = "Menu label";
|
||||
type = types.str;
|
||||
default = name;
|
||||
};
|
||||
items = mkOption {
|
||||
description = "List of menu items";
|
||||
type =
|
||||
with types;
|
||||
listOf (attrTag {
|
||||
menu = mkOption { type = submodule cfg.content-types.navigation; };
|
||||
page = mkOption { type = subtype cfg.content-types.page; };
|
||||
link = mkOption { type = submodule cfg.content-types.named-link; };
|
||||
});
|
||||
};
|
||||
outputs = mkOption {
|
||||
description = ''
|
||||
Representations of the navigation structure in different formats
|
||||
|
||||
It must be a function that takes the page on which the navigation is to be shown, such that relative links get computed correctly.
|
||||
'';
|
||||
type = with types; attrsOf (functionTo str);
|
||||
default.html =
|
||||
page:
|
||||
cfg.templates.html.nav {
|
||||
menu = config;
|
||||
inherit page;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
|
@ -17,36 +17,38 @@ in
|
|||
|
||||
config.files = with lib; cfg.templates.files (attrValues config.pages);
|
||||
|
||||
config.content-types.page = { name, config, ... }: {
|
||||
imports = [ cfg.content-types.document ];
|
||||
options = {
|
||||
title = mkOption {
|
||||
description = "Page title";
|
||||
type = types.str;
|
||||
default = name;
|
||||
config.content-types.page =
|
||||
{ name, config, ... }:
|
||||
{
|
||||
imports = [ cfg.content-types.document ];
|
||||
options = {
|
||||
title = mkOption {
|
||||
description = "Page title";
|
||||
type = types.str;
|
||||
default = name;
|
||||
};
|
||||
description = mkOption {
|
||||
description = ''
|
||||
One-sentence description of page contents
|
||||
'';
|
||||
type = types.str;
|
||||
};
|
||||
summary = mkOption {
|
||||
description = ''
|
||||
One-paragraph summary of page contents
|
||||
'';
|
||||
type = types.str;
|
||||
};
|
||||
body = mkOption {
|
||||
description = ''
|
||||
Page contents in CommonMark
|
||||
'';
|
||||
type = types.str;
|
||||
};
|
||||
};
|
||||
description = mkOption {
|
||||
description = ''
|
||||
One-sentence description of page contents
|
||||
'';
|
||||
type = types.str;
|
||||
};
|
||||
summary = mkOption {
|
||||
description = ''
|
||||
One-paragraph summary of page contents
|
||||
'';
|
||||
type = types.str;
|
||||
};
|
||||
body = mkOption {
|
||||
description = ''
|
||||
Page contents in CommonMark
|
||||
'';
|
||||
type = types.str;
|
||||
};
|
||||
};
|
||||
|
||||
config.outputs.html = cfg.templates.html.page config;
|
||||
};
|
||||
config.outputs.html = cfg.templates.html.page config;
|
||||
};
|
||||
|
||||
config.templates.html.page = lib.template cfg.templates.html.dom (page: {
|
||||
html = {
|
||||
|
|
|
@ -4,14 +4,35 @@ let
|
|||
inherit (import ./. { }) lib;
|
||||
in
|
||||
{
|
||||
test-relativePath = with lib;
|
||||
test-relativePath =
|
||||
with lib;
|
||||
let
|
||||
testData = [
|
||||
{ from = "bar"; to = "baz"; expected = "./baz"; }
|
||||
{ from = "foo/bar"; to = "foo/baz"; expected = "./baz"; }
|
||||
{ from = "foo"; to = "bar/baz"; expected = "./bar/baz"; }
|
||||
{ from = "foo/bar"; to = "baz"; expected = "./../baz"; }
|
||||
{ from = "foo/bar/baz"; to = "foo"; expected = "./../../foo"; }
|
||||
{
|
||||
from = "bar";
|
||||
to = "baz";
|
||||
expected = "./baz";
|
||||
}
|
||||
{
|
||||
from = "foo/bar";
|
||||
to = "foo/baz";
|
||||
expected = "./baz";
|
||||
}
|
||||
{
|
||||
from = "foo";
|
||||
to = "bar/baz";
|
||||
expected = "./bar/baz";
|
||||
}
|
||||
{
|
||||
from = "foo/bar";
|
||||
to = "baz";
|
||||
expected = "./../baz";
|
||||
}
|
||||
{
|
||||
from = "foo/bar/baz";
|
||||
to = "foo";
|
||||
expected = "./../../foo";
|
||||
}
|
||||
];
|
||||
in
|
||||
{
|
||||
|
|
Loading…
Add table
Reference in a new issue