From 3cfb661bf4c457461e1df3990aff28655c2ef4eb Mon Sep 17 00:00:00 2001 From: Helene MJ <20780121+hjonin@users.noreply.github.com> Date: Tue, 20 Jun 2023 15:02:18 +0200 Subject: [PATCH] Eleventy dsfr update (#8) contribute to eleventy-dsfr from codegouv-website * add missing license * add components * add custom markdown containers * add search * some styling * some refactoring * some small fixes --------- Co-authored-by: hjonin Co-authored-by: Bastien --- LICENSE.md | 21 +++ LICENSES/LICENSE.MIT.md | 21 +++ _data/data.js | 3 + _data/i18n/en/index.js | 17 +- _data/i18n/fr/index.js | 21 ++- _data/metadata.js | 45 ++--- _includes/components/accordionsgroup.njk | 25 ++- _includes/components/back_to_top.njk | 3 + _includes/components/breadcrumb.njk | 27 ++- _includes/components/callout.njk | 11 ++ _includes/components/card-horizontal.njk | 22 --- _includes/components/card.njk | 67 ++++++-- _includes/components/display.njk | 144 ++++++++-------- _includes/components/footer.njk | 113 +++++++------ _includes/components/header.njk | 98 ++++++----- _includes/components/imagecontent.njk | 7 - _includes/components/navigation.njk | 60 ++++--- .../components/newsletter_and_follow_us.njk | 129 ++++++++------ _includes/components/pagination.njk | 92 +++++----- _includes/components/quote.njk | 11 ++ _includes/components/share.njk | 17 ++ _includes/components/taggroup-disabled.njk | 9 +- _includes/components/taggroup.njk | 15 +- _includes/components/tile.njk | 18 ++ _includes/components/translate.njk | 53 +++--- _includes/layouts/base.njk | 51 +++--- _includes/layouts/home.njk | 6 + _includes/layouts/page.njk | 14 +- _includes/layouts/post.njk | 75 ++++++--- _includes/postslist-horizontal.njk | 17 -- _includes/postslist.njk | 33 ++-- content/404.md | 41 ----- content/404.njk | 47 ++++++ content/en/about/index.md | 2 + content/en/blog/index.njk | 48 +++--- content/{fr => en}/blog/posts/fifthpost.md | 0 .../blog/posts/fourthpost/fourthpost.md | 0 .../blog/posts/fourthpost/possum.png | Bin content/en/blog/posts/posts.11tydata.js | 22 --- content/{fr => en}/blog/posts/secondpost.md | 4 +- content/{fr => en}/blog/posts/thirdpost.md | 0 content/en/blog/tags.njk | 51 +++--- content/en/blog/tags_index.njk | 6 +- content/en/contact/index.md | 4 - content/en/index.njk | 27 ++- content/en/search-results.njk | 18 ++ content/feed/feed.11tydata.js | 2 +- content/fr/about.md | 6 + content/fr/about/index.md | 11 +- content/fr/accessibility/index.md | 2 +- content/fr/blog/index.njk | 48 +++--- content/fr/blog/posts/firstpost.md | 24 +-- content/fr/blog/posts/posts.11tydata.js | 22 --- content/fr/blog/tags.njk | 51 +++--- content/fr/blog/tags_index.njk | 6 +- content/fr/contact/index.md | 7 +- content/fr/index.11tydata.js | 5 + content/fr/index.njk | 40 +++-- content/fr/legal/index.md | 16 +- content/fr/personal-data/index.md | 6 + content/fr/search-results.njk | 18 ++ content/sitemap/sitemap.njk | 8 +- content/sitemap/sitemap.xml.njk | 2 + eleventy.config.drafts.js | 82 ++++----- eleventy.config.i18n.js | 46 +---- eleventy.config.images.js | 104 +++++++----- eleventy.config.js | 22 ++- eleventy.config.pagination.js | 39 ++++- markdown-custom-containers.js | 45 +++++ package.json | 77 +++++---- public/css/index.css | 158 +++++++++++++----- public/icons/1F3DB.svg | 26 +++ public/icons/logo/peertube.svg | 1 + public/icons/logo/sourcehut.svg | 1 + public/js/matomo.js | 14 ++ public/js/search-results.js | 68 ++++++++ public/js/search.js | 23 +++ 77 files changed, 1509 insertions(+), 986 deletions(-) create mode 100644 LICENSE.md create mode 100644 LICENSES/LICENSE.MIT.md create mode 100644 _data/data.js create mode 100644 _includes/components/back_to_top.njk create mode 100644 _includes/components/callout.njk delete mode 100644 _includes/components/card-horizontal.njk delete mode 100644 _includes/components/imagecontent.njk create mode 100644 _includes/components/quote.njk create mode 100644 _includes/components/share.njk create mode 100644 _includes/components/tile.njk create mode 100644 _includes/layouts/home.njk delete mode 100644 _includes/postslist-horizontal.njk delete mode 100644 content/404.md create mode 100644 content/404.njk rename content/{fr => en}/blog/posts/fifthpost.md (100%) rename content/{fr => en}/blog/posts/fourthpost/fourthpost.md (100%) rename content/{fr => en}/blog/posts/fourthpost/possum.png (100%) rename content/{fr => en}/blog/posts/secondpost.md (92%) rename content/{fr => en}/blog/posts/thirdpost.md (100%) delete mode 100644 content/en/contact/index.md create mode 100644 content/en/search-results.njk create mode 100644 content/fr/about.md create mode 100644 content/fr/index.11tydata.js create mode 100644 content/fr/search-results.njk create mode 100644 markdown-custom-containers.js create mode 100644 public/icons/1F3DB.svg create mode 100644 public/icons/logo/peertube.svg create mode 100644 public/icons/logo/sourcehut.svg create mode 100644 public/js/matomo.js create mode 100644 public/js/search-results.js create mode 100644 public/js/search.js diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 00000000..309b45ca --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 DINUM/Etalab + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/LICENSES/LICENSE.MIT.md b/LICENSES/LICENSE.MIT.md new file mode 100644 index 00000000..309b45ca --- /dev/null +++ b/LICENSES/LICENSE.MIT.md @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 DINUM/Etalab + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/_data/data.js b/_data/data.js new file mode 100644 index 00000000..5b57e332 --- /dev/null +++ b/_data/data.js @@ -0,0 +1,3 @@ +module.exports = { + footer_content_description: `Lorem [...] elit ut.` +} \ No newline at end of file diff --git a/_data/i18n/en/index.js b/_data/i18n/en/index.js index 84c5cc44..f64e861b 100644 --- a/_data/i18n/en/index.js +++ b/_data/i18n/en/index.js @@ -1,13 +1,17 @@ module.exports = { about: "About", accessibility: "Accessibility", + back_to_top: "Back to Top", blog: "Blog", close: "Close", + copy: "Copy", + copied: "Copied!", dark_theme: "Dark Theme", display_params: "Display Parameters", + filter_by_tag: "Filter by Tag:", + filtered_by: "Filtered by", first_page: "First page", - follow_us: "Follow Us", - footer_content_description: "Lorem [...] elit ut.", + follow_us_description: "Follow Us", fully_compliant: "Fully compliant", home: "Home", lang_name: "English", @@ -24,13 +28,14 @@ module.exports = { pick_theme: "Pick a Theme.", previous_page: "Previous page", published_on: "Published on", + results: "results", see_breadcrumb: "See Breadcrumb", + see_more: "See More", select_lang: "Select Language", - service_id: "Intitulé
officiel", - service_name: "Nom de l’entité (ministère, secrétariat d‘état, gouvernement)", - site_description: "baseline - précisions sur l‘organisation", + share: "Share", + share_by_email: "Share by Email", + share_link_on: "Share link on", sitemap: "Sitemap", - site_title: "Nom du site / service", subscribe: "Subscribe", subscribe_newsletter: "Subscribe", system: "System", diff --git a/_data/i18n/fr/index.js b/_data/i18n/fr/index.js index 1b36aada..8a5c8297 100644 --- a/_data/i18n/fr/index.js +++ b/_data/i18n/fr/index.js @@ -1,13 +1,17 @@ module.exports = { about: "À propos", accessibility: "Accessibilité", + back_to_top: "Haut de page", blog: "Blog", close: "Fermer", + copy: "Copier dans le presse-papier", + copied: "Copié !", dark_theme: "Thème sombre", - display_params: "Paramètres d\'affichage", + display_params: "Paramètres d'affichage", + filter_by_tag: "Filtrer par catégorie :", + filtered_by: "Filtré par", first_page: "Première page", - follow_us: "Suivez-nous
sur les réseaux sociaux", - footer_content_description: "Lorem [...] elit ut.", + follow_us_description: "Suivez-nous
sur nos réseaux", fully_compliant: "Totalement conforme", home: "Accueil", lang_name: "Français", @@ -24,14 +28,15 @@ module.exports = { pick_theme: "Choisissez un thème pour personnaliser l’apparence du site.", previous_page: "Page précédente", published_on: "Publié le", + results: "résultats", see_breadcrumb: "Voir le fil d’Ariane", + see_more: "En savoir plus", select_lang: "Sélectionner une langue", - service_id: "Intitulé
officiel", - service_name: "Nom de l’entité (ministère, secrétariat d‘état, gouvernement)", - site_description: "baseline - précisions sur l‘organisation", + share: "Partager", + share_by_email: "Partager par email", + share_link_on: "Partage d'un lien sur", sitemap: "Plan du site", - site_title: "Nom du site / service", - subscribe: "S\'abonner", + subscribe: "S'abonner", subscribe_newsletter: "Abonnez-vous à notre lettre d’information", system: "Système", tags: "Catégories", diff --git a/_data/metadata.js b/_data/metadata.js index 2b044737..b9d5611c 100644 --- a/_data/metadata.js +++ b/_data/metadata.js @@ -1,21 +1,28 @@ module.exports = { - "title": "TITRE DU SITE", - "url": "https://url_du_site.gouv.fr/", - "newsletter": { - "url": "", - "title": "", - "description": "" - }, - "facebook_url": "", - "twitter_url": "", - "instagram_url": "", - "linkedin_url": "", - "youtube_url": "", - "language": "fr", - "description": "DESCRIPTION DU SITE", - "author": { - "name": "NOM DU SERVICE", - "email": "contact@nom_du_service.gouv.fr", - "url": "https://url_du_service.gouv.fr/" - } + title: "Nom du site / service", + url: "https://url_du_site.gouv.fr/", + newsletter: [ + { + url: "", + title: "", + description: "" + } + ], + facebook_url: "", + mastodon_url: "", + twitter_url: "", + instagram_url: "", + linkedin_url: "", + youtube_url: "", + peertube_url: "", + github_url: "", + sourcehut_url: "", + language: "fr", + description: "baseline - précisions sur l‘organisation", + author: { + name: "Nom du service", + email: "contact@nom_du_service.gouv.fr", + url: "https://url_du_service.gouv.fr/" + }, + service: "Nom de l’entité (ministère, secrétariat d‘état, gouvernement)" } diff --git a/_includes/components/accordionsgroup.njk b/_includes/components/accordionsgroup.njk index 927790e8..1ca8c94d 100644 --- a/_includes/components/accordionsgroup.njk +++ b/_includes/components/accordionsgroup.njk @@ -1,15 +1,14 @@ -{# Mandatory variable `accordionItems { title: string, content: string }[]` to make it work #}
- {% for accordionItem in accordionItems %} -
-

- -

-
- {{ accordionItem.content | safe }} -
-
- {% endfor %} + {% for accordionItem in accordionItems %} +
+

+ +

+
+ {{ accordionItem.content | safe }} +
+
+ {% endfor %}
\ No newline at end of file diff --git a/_includes/components/back_to_top.njk b/_includes/components/back_to_top.njk new file mode 100644 index 00000000..144b46e5 --- /dev/null +++ b/_includes/components/back_to_top.njk @@ -0,0 +1,3 @@ + + {{ "back_to_top" | i18n }} + \ No newline at end of file diff --git a/_includes/components/breadcrumb.njk b/_includes/components/breadcrumb.njk index 002ffa8c..a95bd178 100644 --- a/_includes/components/breadcrumb.njk +++ b/_includes/components/breadcrumb.njk @@ -1,18 +1,17 @@ -{# Works with eleventyNavigation or mandatory variable `segments: { url: string, title: string }[]` to make it work #} {% if eleventyNavigation %} - {% set segments = collections.all | eleventyNavigationBreadcrumb(eleventyNavigation.key) %} + {% set segments = collections.all | filterCollectionLang | eleventyNavigationBreadcrumb(eleventyNavigation.key) %} {% endif %} \ No newline at end of file diff --git a/_includes/components/callout.njk b/_includes/components/callout.njk new file mode 100644 index 00000000..4d3d6571 --- /dev/null +++ b/_includes/components/callout.njk @@ -0,0 +1,11 @@ +
+

{{ callout.title }}

+

+ {{ callout.description | safe }} +

+ {% if callout.link %} + + {{ callout.link.title }} + + {% endif %} +
\ No newline at end of file diff --git a/_includes/components/card-horizontal.njk b/_includes/components/card-horizontal.njk deleted file mode 100644 index 1eaeaaed..00000000 --- a/_includes/components/card-horizontal.njk +++ /dev/null @@ -1,22 +0,0 @@ -{# Mandatory variable `card { url: string, title: string, description: string, tags: string[], date: string, imagePath: string, imageAlt: string }` to make it work #} - \ No newline at end of file diff --git a/_includes/components/card.njk b/_includes/components/card.njk index 95adcfa5..bdbaee0a 100644 --- a/_includes/components/card.njk +++ b/_includes/components/card.njk @@ -1,22 +1,53 @@ -{# Mandatory variable `card { url: string, title: string, description: string, tags: string[], date: string, imagePath: string, imageAlt: string }` to make it work #} - diff --git a/_includes/postslist-horizontal.njk b/_includes/postslist-horizontal.njk deleted file mode 100644 index c402c43d..00000000 --- a/_includes/postslist-horizontal.njk +++ /dev/null @@ -1,17 +0,0 @@ -{# Mandatory variable `postslist: CollectionItem[]` to make it work #} -
- {% asyncAll post in postslist %} -
- {% set card = { - url: post.url, - title: post.data.title, - description: post.data.description, - tags: post.data.tags, - date: post.date, - imagePath: post.data.image.path, - imageAlt: post.data.image.alt - } %} - {% include "components/card-horizontal.njk" %} -
- {% endall %} -
diff --git a/_includes/postslist.njk b/_includes/postslist.njk index 06f89be7..d997b758 100644 --- a/_includes/postslist.njk +++ b/_includes/postslist.njk @@ -1,17 +1,18 @@ -{# Mandatory variable `postslist: CollectionItem[]` to make it work #} -
- {% asyncAll post in postslist %} -
- {% set card = { - url: post.url, - title: post.data.title, - description: post.data.description, - tags: post.data.tags, - date: post.date, - imagePath: post.data.image.path, - imageAlt: post.data.image.alt - } %} - {% include "components/card.njk" %} -
- {% endall %} +
+ {% asyncAll post in postslist %} +
+ {% set card = { + url: post.url, + title: post.data.title, + description: post.data.description, + tags: post.data.tags, + date: post.date, + imagePath: post.data.image.path | resolvePath(post), + imageAlt: post.data.image.alt, + badges: post.data.badges, + orientation: "horizontal" + } %} + {% include "components/card.njk" %} +
+ {% endall %}
diff --git a/content/404.md b/content/404.md deleted file mode 100644 index fb6e5445..00000000 --- a/content/404.md +++ /dev/null @@ -1,41 +0,0 @@ ---- -permalink: 404.html -layout: layouts/page.njk -eleventyExcludeFromCollections: true ---- -
-
-
-

Page non trouvée

-

Erreur 404

-

La page que vous cherchez est introuvable. Excusez-nous pour la gène occasionnée.

-

- Si vous avez tapé l'adresse web dans le navigateur, vérifiez qu'elle est correcte. La page n’est peut-être plus disponible. -
Dans ce cas, pour continuer votre visite vous pouvez consulter notre page d’accueil, ou effectuer une recherche avec notre moteur de recherche en haut de page.
Sinon contactez-nous pour que l’on puisse vous rediriger vers la bonne information. -

- -
-
- -
-
-
diff --git a/content/404.njk b/content/404.njk new file mode 100644 index 00000000..96806902 --- /dev/null +++ b/content/404.njk @@ -0,0 +1,47 @@ +--- +permalink: 404.html +layout: layouts/page.njk +eleventyExcludeFromCollections: true +--- +
+
+
+

Page non trouvée

+

Erreur 404

+

La page que vous cherchez est introuvable. Excusez-nous pour la gène + occasionnée.

+

+ Si vous avez tapé l'adresse web dans le navigateur, vérifiez qu'elle est correcte. La page n’est peut-être plus + disponible. +
Dans ce cas, pour continuer votre visite vous pouvez consulter notre page d’accueil, ou effectuer une + recherche avec notre moteur de recherche en haut de page.
Sinon contactez-nous pour que l’on puisse vous + rediriger vers la bonne information. +

+ +
+
+ +
+
+
diff --git a/content/en/about/index.md b/content/en/about/index.md index 9154c5f7..222a8b99 100644 --- a/content/en/about/index.md +++ b/content/en/about/index.md @@ -5,3 +5,5 @@ eleventyNavigation: order: 3 --- # About + +We are the mission logiciels libres. diff --git a/content/en/blog/index.njk b/content/en/blog/index.njk index 49a1b45a..0ea1ec20 100644 --- a/content/en/blog/index.njk +++ b/content/en/blog/index.njk @@ -1,26 +1,30 @@ +---js +{ + title: "Blog", + pagination: { + data: "collections.posts", + size: 10, + reverse: true, + before: function(paginationData, fullData) { + return this.filterCollectionLang(paginationData, fullData.lang); + } + }, + layout: "layouts/page.njk", + permalink: "/{{ lang }}/blog/{% if pagination.pageNumber %}{{ pagePrefix }}{{ pagination.pageNumber + 1 }}/{% endif %}", + eleventyNavigation: { + key: "Blog", + order: 2 + } +} --- -layout: layouts/page.njk -pagination: - data: collections.posts_en - size: 4 - reverse: true - generatePageOnEmptyData: true -permalink: "/{{ lang }}/blog/{% if pagination.pageNumber %}{{ page_prefix }}{{ pagination.pageNumber + 1 }}/{% endif %}" -eleventyNavigation: - key: Blog - order: 2 ---- +

{{ title }}

-
-
-
{{ "tags" | i18n }}
- {% set tags = collections.posts_en | getAllTags %} - {% include "components/taggroup.njk" %} -
-
- {% set postslist = pagination.items %} - {% include "postslist.njk" %} - {% include "components/pagination.njk" %} -
+

{{ "filter_by_tag" | i18n }}

+
+ {% set tags = collections.posts | filterCollectionLang | getAllTags %} + {% include "components/taggroup.njk" %}
+ {% set postslist = pagination.items %} + {% include "postslist.njk" %} + {% include "components/pagination.njk" %}
diff --git a/content/fr/blog/posts/fifthpost.md b/content/en/blog/posts/fifthpost.md similarity index 100% rename from content/fr/blog/posts/fifthpost.md rename to content/en/blog/posts/fifthpost.md diff --git a/content/fr/blog/posts/fourthpost/fourthpost.md b/content/en/blog/posts/fourthpost/fourthpost.md similarity index 100% rename from content/fr/blog/posts/fourthpost/fourthpost.md rename to content/en/blog/posts/fourthpost/fourthpost.md diff --git a/content/fr/blog/posts/fourthpost/possum.png b/content/en/blog/posts/fourthpost/possum.png similarity index 100% rename from content/fr/blog/posts/fourthpost/possum.png rename to content/en/blog/posts/fourthpost/possum.png diff --git a/content/en/blog/posts/posts.11tydata.js b/content/en/blog/posts/posts.11tydata.js index 2bbf91f0..ac7c5d4b 100644 --- a/content/en/blog/posts/posts.11tydata.js +++ b/content/en/blog/posts/posts.11tydata.js @@ -1,6 +1,3 @@ -const path = require("path"); -const chalk = require("chalk"); - module.exports = { tags: [ "posts" @@ -8,24 +5,5 @@ module.exports = { layout: "layouts/post.njk", permalink: function (data) { return `/${data.lang}/blog/${data.page.fileSlug}/`; - }, - eleventyComputed: { - segments: [{ - url: "/blog/", - title: "Blog" - }], - image: data => { - if (data.image.src) { - if (!data.image.alt) { - console.warn(chalk.yellow(`[a11y] Missing alternative text for image source ${data.image.src}.`)); - } - return { - path: path.resolve(`${data.page.inputPath}/..`, data.image.src), - alt: data.image.alt || data.title, - } - } else { - return undefined; - } - } } }; diff --git a/content/fr/blog/posts/secondpost.md b/content/en/blog/posts/secondpost.md similarity index 92% rename from content/fr/blog/posts/secondpost.md rename to content/en/blog/posts/secondpost.md index cc5763d9..f80945e0 100644 --- a/content/fr/blog/posts/secondpost.md +++ b/content/en/blog/posts/secondpost.md @@ -9,8 +9,8 @@ Leverage agile frameworks to provide a robust synopsis for high level overviews. ## Section Header -First post -Third post +First post +Third post Bring to the table win-win survival strategies to ensure proactive domination. At the end of the day, going forward, a new normal that has evolved from generation X is on the runway heading towards a streamlined cloud solution. User generated content in real-time will have multiple touchpoints for offshoring. diff --git a/content/fr/blog/posts/thirdpost.md b/content/en/blog/posts/thirdpost.md similarity index 100% rename from content/fr/blog/posts/thirdpost.md rename to content/en/blog/posts/thirdpost.md diff --git a/content/en/blog/tags.njk b/content/en/blog/tags.njk index d26ac4e7..144c19d7 100644 --- a/content/en/blog/tags.njk +++ b/content/en/blog/tags.njk @@ -1,33 +1,28 @@ +---js +{ + title: "Blog", + pagination: { + data: "collections.posts", + size: 1, + alias: "tag", + before: function(paginationData, fullData) { + return this.paginateCollectionTags(this.filterCollectionLang(paginationData, fullData.lang), 10); + } + }, + layout: "layouts/page.njk", + permalink: "/{{ lang }}/blog/tags/{{ tag.tagName | slugify }}/{% if tag.pageNumber %}{{ pagePrefix }}{{ tag.pageNumber + 1 }}/{% endif %}", +} --- -eleventyComputed: - title: Tagged "{{ tag.tagName }}" -layout: layouts/page.njk -pagination: - data: collections.tags_en_4x4 - size: 1 - alias: tag -permalink: "/{{ lang }}/blog/tags/{{ tag.tagName | slugify }}/{% if tag.pageNumber %}{{ page_prefix }}{{ tag.pageNumber + 1 }}/{% endif %}" -segments: - - url: /blog/ - title: Blog ---- -
-
-
-
{{ "tags" | i18n }}
- {% set tags = collections.posts_en | getAllTags | filterTagList([tag.tagName]) %} - {% include "components/taggroup.njk" %} -
-
-

- {{ tag.tagName }} -

- {% set postslist = tag.pageData %} - {% include "postslist.njk" %} -
+

{{ title }}

+
+

{{ "filtered_by" | i18n }} {{ tag.tagName }}

+
+ {% set tags = collections.posts | filterCollectionLang | getAllTags | filterTagList([tag.tagName]) %} + {% include "components/taggroup.njk" %}
- + {% set postslist = tag.pageData %} + {% include "postslist.njk" %} {% set tagUrl %}{{ "/blog/tags/"| locale_url }}{{ tag.tagName | slugify }}/{% endset %} - {% set pagination = pagination | pagination_object(tagUrl) %} + {% set pagination = pagination | buildPagination(tagUrl) %} {% include "components/pagination.njk" %}
diff --git a/content/en/blog/tags_index.njk b/content/en/blog/tags_index.njk index 537d3284..af8e2e26 100644 --- a/content/en/blog/tags_index.njk +++ b/content/en/blog/tags_index.njk @@ -1,8 +1,8 @@ --- +title: Tags layout: layouts/page.njk permalink: "/{{ lang }}/blog/tags/" --- -

Tags

- -{% set tags = collections.posts_en | getAllTags %} +

{{ title }}

+{% set tags = collections.posts | filterCollectionLang | getAllTags %} {% include "components/taggroup.njk" %} diff --git a/content/en/contact/index.md b/content/en/contact/index.md deleted file mode 100644 index 339ef211..00000000 --- a/content/en/contact/index.md +++ /dev/null @@ -1,4 +0,0 @@ ---- -layout: layouts/page.njk ---- -# Contact \ No newline at end of file diff --git a/content/en/index.njk b/content/en/index.njk index 79cac5f7..b252c90c 100644 --- a/content/en/index.njk +++ b/content/en/index.njk @@ -1,7 +1,30 @@ --- -layout: layouts/page.njk +layout: layouts/home.njk eleventyNavigation: key: Home order: 1 +numberOfLatestPostsToShow: 3 --- -Hello World! \ No newline at end of file +
+ {% set postsCount = collections.posts | length %} + {% set latestPostsCount = postsCount | min(numberOfLatestPostsToShow) %} +

Latest {{ latestPostsCount }} Post{% if latestPostsCount != 1 %}s{% endif %}

+ + {% set postslist = collections.posts | head(-1 * numberOfLatestPostsToShow) %} + {% set postslistCounter = postsCount %} + {% include "postslist.njk" %} + + {% set morePosts = postsCount - numberOfLatestPostsToShow %} + {% if morePosts > 0 %} +

{{ morePosts }} more post{% if morePosts != 1 %}s{% endif %} can be found in the archive.

+ {% endif %} + + {# List every content page in the project #} + {# +
    + {%- for entry in collections.all %} +
  • {{ entry.url }}
  • + {%- endfor %} +
+ #} +
\ No newline at end of file diff --git a/content/en/search-results.njk b/content/en/search-results.njk new file mode 100644 index 00000000..22b05b44 --- /dev/null +++ b/content/en/search-results.njk @@ -0,0 +1,18 @@ +--- +title: Search Results +layout: layouts/base.njk +--- +
+
+
+

{{ title }}

+
+

0 {{ "results" | i18n }}

+
+
+
+
+
+ {% include "components/back_to_top.njk" %} +
+ diff --git a/content/feed/feed.11tydata.js b/content/feed/feed.11tydata.js index ed3fec99..b246adc3 100644 --- a/content/feed/feed.11tydata.js +++ b/content/feed/feed.11tydata.js @@ -1,3 +1,3 @@ module.exports = { - eleventyExcludeFromCollections: true + eleventyExcludeFromCollections: true } diff --git a/content/fr/about.md b/content/fr/about.md new file mode 100644 index 00000000..ee77cb61 --- /dev/null +++ b/content/fr/about.md @@ -0,0 +1,6 @@ +--- +eleventyNavigation: + key: À propos + order: 3 +permalink: false +--- \ No newline at end of file diff --git a/content/fr/about/index.md b/content/fr/about/index.md index c2dc1e89..d5b08e6f 100644 --- a/content/fr/about/index.md +++ b/content/fr/about/index.md @@ -1,9 +1,10 @@ --- layout: layouts/page.njk -slugOverride: a propos eleventyNavigation: - key: À propos - title: Présentation - order: 3 + key: Présentation + parent: À propos + order: 1 --- -# À propos \ No newline at end of file +# À propos + +Nous sommes la mission logiciels libres. \ No newline at end of file diff --git a/content/fr/accessibility/index.md b/content/fr/accessibility/index.md index 3096a351..519ba162 100644 --- a/content/fr/accessibility/index.md +++ b/content/fr/accessibility/index.md @@ -1,6 +1,6 @@ --- title: Exemple de déclaration d’accessibilité -layout: layouts/page.njk +layout: layouts/post.njk description: Déclaration d’accessibilité à compléter slugOverride: accessibilite showBreadcrumb: true diff --git a/content/fr/blog/index.njk b/content/fr/blog/index.njk index bb6008ba..ad661489 100644 --- a/content/fr/blog/index.njk +++ b/content/fr/blog/index.njk @@ -1,26 +1,30 @@ +---js +{ + title: "Actualités", + pagination: { + data: "collections.posts", + size: 10, + reverse: true, + before: function(paginationData, fullData) { + return this.filterCollectionLang(paginationData, fullData.lang); + } + }, + layout: "layouts/page.njk", + permalink: "/{{ lang }}/blog/{% if pagination.pageNumber %}{{ pagePrefix }}{{ pagination.pageNumber + 1 }}/{% endif %}", + eleventyNavigation: { + key: "Blog", + order: 2 + } +} --- -layout: layouts/page.njk -pagination: - data: collections.posts_fr - size: 4 - reverse: true - generatePageOnEmptyData: true -permalink: "/{{ lang }}/blog/{% if pagination.pageNumber %}{{ page_prefix }}{{ pagination.pageNumber + 1 }}/{% endif %}" -eleventyNavigation: - key: Blog - order: 2 ---- +

{{ title }}

-
-
-
{{ "tags" | i18n }}
- {% set tags = collections.posts_fr | getAllTags %} - {% include "components/taggroup.njk" %} -
-
- {% set postslist = pagination.items %} - {% include "postslist-horizontal.njk" %} - {% include "components/pagination.njk" %} -
+

{{ "filter_by_tag" | i18n }}

+
+ {% set tags = collections.posts | filterCollectionLang | getAllTags %} + {% include "components/taggroup.njk" %}
+ {% set postslist = pagination.items %} + {% include "postslist.njk" %} + {% include "components/pagination.njk" %}
diff --git a/content/fr/blog/posts/firstpost.md b/content/fr/blog/posts/firstpost.md index 0557716d..76814cb4 100644 --- a/content/fr/blog/posts/firstpost.md +++ b/content/fr/blog/posts/firstpost.md @@ -1,26 +1,8 @@ --- -title: This is my first post. -description: This is a post on My Blog about agile frameworks. +title: Un premier article. +description: Ceci est le premier article du blog. date: 2018-05-01 tags: - another tag --- -Leverage agile frameworks to provide a robust synopsis for high level overviews. Iterative approaches to corporate strategy foster collaborative thinking to further the overall value proposition. Organically grow the holistic world view of disruptive innovation via workplace diversity and empowerment. - -Bring to the table win-win survival strategies to ensure proactive domination. At the end of the day, going forward, a new normal that has evolved from generation X is on the runway heading towards a streamlined cloud solution. User generated content in real-time will have multiple touchpoints for offshoring. - -## Section Header - -Capitalize on low hanging fruit to identify a ballpark value added activity to beta test. Override the digital divide with additional clickthroughs from DevOps. Nanotechnology immersion along the information highway will close the loop on focusing solely on the bottom line. - -```diff-js - // this is a command - function myCommand() { -+ let counter = 0; -- let counter = 1; - counter++; - } - - // Test with a line break above this line. - console.log('Test'); -``` +Il n'y a rien à voir ici. \ No newline at end of file diff --git a/content/fr/blog/posts/posts.11tydata.js b/content/fr/blog/posts/posts.11tydata.js index 2bbf91f0..ac7c5d4b 100644 --- a/content/fr/blog/posts/posts.11tydata.js +++ b/content/fr/blog/posts/posts.11tydata.js @@ -1,6 +1,3 @@ -const path = require("path"); -const chalk = require("chalk"); - module.exports = { tags: [ "posts" @@ -8,24 +5,5 @@ module.exports = { layout: "layouts/post.njk", permalink: function (data) { return `/${data.lang}/blog/${data.page.fileSlug}/`; - }, - eleventyComputed: { - segments: [{ - url: "/blog/", - title: "Blog" - }], - image: data => { - if (data.image.src) { - if (!data.image.alt) { - console.warn(chalk.yellow(`[a11y] Missing alternative text for image source ${data.image.src}.`)); - } - return { - path: path.resolve(`${data.page.inputPath}/..`, data.image.src), - alt: data.image.alt || data.title, - } - } else { - return undefined; - } - } } }; diff --git a/content/fr/blog/tags.njk b/content/fr/blog/tags.njk index 55f18a07..c8b2bd88 100644 --- a/content/fr/blog/tags.njk +++ b/content/fr/blog/tags.njk @@ -1,33 +1,28 @@ +---js +{ + title: "Actualités", + pagination: { + data: "collections.posts", + size: 1, + alias: "tag", + before: function(paginationData, fullData) { + return this.paginateCollectionTags(this.filterCollectionLang(paginationData, fullData.lang), 10); + } + }, + layout: "layouts/page.njk", + permalink: "/{{ lang }}/blog/tags/{{ tag.tagName | slugify }}/{% if tag.pageNumber %}{{ pagePrefix }}{{ tag.pageNumber + 1 }}/{% endif %}", +} --- -eleventyComputed: - title: Tagged "{{ tag.tagName }}" -layout: layouts/page.njk -pagination: - data: collections.tags_fr_4x4 - size: 1 - alias: tag -permalink: "/{{ lang }}/blog/tags/{{ tag.tagName | slugify }}/{% if tag.pageNumber %}{{ page_prefix }}{{ tag.pageNumber + 1 }}/{% endif %}" -segments: - - url: /blog/ - title: Blog ---- -
-
-
-
{{ "tags" | i18n }}
- {% set tags = collections.posts_fr | getAllTags | filterTagList([tag.tagName]) %} - {% include "components/taggroup.njk" %} -
-
-

- {{ tag.tagName }} -

- {% set postslist = tag.pageData %} - {% include "postslist.njk" %} -
+

{{ title }}

+
+

{{ "filtered_by" | i18n }} {{ tag.tagName }}

+
+ {% set tags = collections.posts | filterCollectionLang | getAllTags | filterTagList([tag.tagName]) %} + {% include "components/taggroup.njk" %}
- + {% set postslist = tag.pageData %} + {% include "postslist.njk" %} {% set tagUrl %}{{ "/blog/tags/"| locale_url }}{{ tag.tagName | slugify }}/{% endset %} - {% set pagination = pagination | pagination_object(tagUrl) %} + {% set pagination = pagination | buildPagination(tagUrl) %} {% include "components/pagination.njk" %}
diff --git a/content/fr/blog/tags_index.njk b/content/fr/blog/tags_index.njk index a4602c80..af8e2e26 100644 --- a/content/fr/blog/tags_index.njk +++ b/content/fr/blog/tags_index.njk @@ -1,8 +1,8 @@ --- +title: Tags layout: layouts/page.njk permalink: "/{{ lang }}/blog/tags/" --- -

Tags

- -{% set tags = collections.posts_fr | getAllTags %} +

{{ title }}

+{% set tags = collections.posts | filterCollectionLang | getAllTags %} {% include "components/taggroup.njk" %} diff --git a/content/fr/contact/index.md b/content/fr/contact/index.md index 799ec4f8..ea085ba3 100644 --- a/content/fr/contact/index.md +++ b/content/fr/contact/index.md @@ -1,8 +1,11 @@ --- layout: layouts/page.njk +title: Contacts eleventyNavigation: key: Contact parent: À propos -showBreadcrumb: true + order: 2 --- -# Contact \ No newline at end of file +# Contact + + diff --git a/content/fr/index.11tydata.js b/content/fr/index.11tydata.js new file mode 100644 index 00000000..9c29a00e --- /dev/null +++ b/content/fr/index.11tydata.js @@ -0,0 +1,5 @@ +module.exports = { + pTitle: "Lorem ipsum", + pDescription: ` +Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec id mi est. Pellentesque ac eros ipsum. Nunc sed ligula a justo vehicula eleifend. Curabitur ut nibh id sapien ullamcorper ornare at sit amet leo. Ut suscipit, erat eget malesuada feugiat, justo leo maximus ipsum, in consectetur neque turpis non mauris. Pellentesque porttitor ut elit eu posuere. Duis vel posuere lacus, nec pellentesque nunc. Vivamus rutrum eros ac nisl sollicitudin, vitae egestas diam interdum. Vivamus a quam eleifend, maximus massa sed, interdum lectus. In placerat porttitor malesuada. Vestibulum at maximus felis, quis eleifend felis. Nullam id tortor sem. Donec eleifend pharetra justo vel ultricies. Cras lobortis, erat non tempus porta, ligula velit laoreet ligula, ac sagittis metus enim vel nisi.` +}; \ No newline at end of file diff --git a/content/fr/index.njk b/content/fr/index.njk index d5e47500..aabef0e7 100644 --- a/content/fr/index.njk +++ b/content/fr/index.njk @@ -1,18 +1,32 @@ --- -layout: layouts/page.njk +title: Accueil +layout: layouts/home.njk eleventyNavigation: key: Accueil order: 1 -numberOfLatestPostsToShow: 3 --- -{% set postsCount = collections.posts | length %} -{% set latestPostsCount = postsCount | min(numberOfLatestPostsToShow) %} -

{{ latestPostsCount }} derniers article{% if latestPostsCount != 1 %}s{% endif %}

- -{% set postslist = collections.posts | head(-1 * numberOfLatestPostsToShow) %} -{% include "postslist.njk" %} - -{% set morePosts = postsCount - numberOfLatestPostsToShow %} -{% if morePosts > 0 %} -

{{ morePosts }} more post{% if morePosts != 1 %}s{% endif %} can be found in the archive.

-{% endif %} + +
+
+
+

{{ pTitle }}

+

+ {{ pDescription | safe }} +

+
+
+
diff --git a/content/fr/legal/index.md b/content/fr/legal/index.md index b5ba2e35..b8a4c2e0 100644 --- a/content/fr/legal/index.md +++ b/content/fr/legal/index.md @@ -19,6 +19,20 @@ showBreadcrumb: true [ A COMPLETER ] +## Crédits + +- [1F3DB.svg](https://openmoji.org/data/color/svg/1F3DB.svg) par [Martin Wehl](https://openmoji.org/library/#author=Martin%20Wehl) sous licence [CC BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0>). + +[ A COMPLETER ] + +## Code source du site + +[ A COMPLETER ] + +## Accessibilité + +[Voir la déclaration d'accessibilité](/fr/accessibilite/). + ## Traitement des données à caractère personnel -[ A COMPLETER ] \ No newline at end of file +[Voir les informations concernant les cookies utilisées sur ce site](/fr/donnees-personnelles/). \ No newline at end of file diff --git a/content/fr/personal-data/index.md b/content/fr/personal-data/index.md index ac9f5609..eef5a704 100644 --- a/content/fr/personal-data/index.md +++ b/content/fr/personal-data/index.md @@ -7,4 +7,10 @@ showBreadcrumb: true --- # Données personnelles +Ce site ne prélève aucune donnée à caractère personnel. L’outil de mesure d’audience que nous utilisons est paramétré de façon à ce que les informations recueillies soient anonymisées. + +Les _cookies_ déposées sur votre ordinateur lorsque vous le consultez nous permet de connaître le nombre de visites et les pages les plus consultées, sans pour autant permettre de vous identifier personnellement. + +Nous sommes ainsi en conformité avec la [réglementation de la CNIL](https://www.cnil.fr/fr/solutions-pour-les-cookies-de-mesure-daudience) et exemptés d’autorisation préalable. + [ A COMPLETER ] \ No newline at end of file diff --git a/content/fr/search-results.njk b/content/fr/search-results.njk new file mode 100644 index 00000000..5cce38e8 --- /dev/null +++ b/content/fr/search-results.njk @@ -0,0 +1,18 @@ +--- +title: Résultats de la recherche +layout: layouts/base.njk +--- +
+
+
+

{{ title }}

+
+

0 résultats

+
+
+
+
+
+ {% include "components/back_to_top.njk" %} +
+ diff --git a/content/sitemap/sitemap.njk b/content/sitemap/sitemap.njk index ff3a73d1..0bad0e54 100644 --- a/content/sitemap/sitemap.njk +++ b/content/sitemap/sitemap.njk @@ -3,7 +3,9 @@ layout: layouts/page.njk --- {# List every content page in the project #}
    - {% for entry in collections.all %} -
  • {{ entry.url }}
  • - {% endfor %} + {% for entry in collections.all %} + {% if entry.url %} +
  • {{ entry.url }}
  • + {% endif %} + {% endfor %}
diff --git a/content/sitemap/sitemap.xml.njk b/content/sitemap/sitemap.xml.njk index 011b1a68..094d8ead 100644 --- a/content/sitemap/sitemap.xml.njk +++ b/content/sitemap/sitemap.xml.njk @@ -5,10 +5,12 @@ eleventyExcludeFromCollections: true {% for page in collections.all %} + {% if page.url %} {% set absoluteUrl %}{{ page.url | htmlBaseUrl(metadata.url) }}{% endset %} {{ absoluteUrl }} {{ page.date | htmlDateString }} + {% endif %} {% endfor %} diff --git a/eleventy.config.drafts.js b/eleventy.config.drafts.js index 8eb92dc3..277f83cf 100644 --- a/eleventy.config.drafts.js +++ b/eleventy.config.drafts.js @@ -1,50 +1,50 @@ function eleventyComputedPermalink() { - // When using `addGlobalData` and you *want* to return a function, you must nest functions like this. - // `addGlobalData` acts like a global data file and runs the top level function it receives. - return (data) => { - // Always skip during non-watch/serve builds - if(data.draft && !process.env.BUILD_DRAFTS) { - return false; - } - - return data.permalink; - } -}; + // When using `addGlobalData` and you *want* to return a function, you must nest functions like this. + // `addGlobalData` acts like a global data file and runs the top level function it receives. + return (data) => { + // Always skip during non-watch/serve builds + if (data.draft && !process.env.BUILD_DRAFTS) { + return false; + } + + return data.permalink; + } +} function eleventyComputedExcludeFromCollections() { - // When using `addGlobalData` and you *want* to return a function, you must nest functions like this. - // `addGlobalData` acts like a global data file and runs the top level function it receives. - return (data) => { - // Always exclude from non-watch/serve builds - if(data.draft && !process.env.BUILD_DRAFTS) { - return true; - } - - return data.eleventyExcludeFromCollections; - } -}; + // When using `addGlobalData` and you *want* to return a function, you must nest functions like this. + // `addGlobalData` acts like a global data file and runs the top level function it receives. + return (data) => { + // Always exclude from non-watch/serve builds + if (data.draft && !process.env.BUILD_DRAFTS) { + return true; + } + + return data.eleventyExcludeFromCollections; + } +} module.exports.eleventyComputedPermalink = eleventyComputedPermalink; module.exports.eleventyComputedExcludeFromCollections = eleventyComputedExcludeFromCollections; module.exports = eleventyConfig => { - eleventyConfig.addGlobalData("eleventyComputed.permalink", eleventyComputedPermalink); - eleventyConfig.addGlobalData("eleventyComputed.eleventyExcludeFromCollections", eleventyComputedExcludeFromCollections); - - let logged = false; - eleventyConfig.on("eleventy.before", ({runMode}) => { - let text = "Excluding"; - // Only show drafts in serve/watch modes - if(runMode === "serve" || runMode === "watch") { - process.env.BUILD_DRAFTS = true; - text = "Including"; - } - - // Only log once. - if(!logged) { - console.log( `[11ty/eleventy-base-blog] ${text} drafts.` ); - } - - logged = true; - }); + eleventyConfig.addGlobalData("eleventyComputed.permalink", eleventyComputedPermalink); + eleventyConfig.addGlobalData("eleventyComputed.eleventyExcludeFromCollections", eleventyComputedExcludeFromCollections); + + let logged = false; + eleventyConfig.on("eleventy.before", ({runMode}) => { + let text = "Excluding"; + // Only show drafts in serve/watch modes + if (runMode === "serve" || runMode === "watch") { + process.env.BUILD_DRAFTS = true; + text = "Including"; + } + + // Only log once. + if (!logged) { + console.log(`[11ty/eleventy-base-blog] ${text} drafts.`); + } + + logged = true; + }); } diff --git a/eleventy.config.i18n.js b/eleventy.config.i18n.js index 70451eff..81f4c077 100644 --- a/eleventy.config.i18n.js +++ b/eleventy.config.i18n.js @@ -2,15 +2,6 @@ const chalk = require("chalk") const translations = require("./_data/i18n"); -const getLocalisedPosts = (collectionApi, lang) => - collectionApi.getFilteredByTag("posts") - .filter(page => page.data.lang === lang); - -const chunk = (arr, size) => - Array.from({length: Math.ceil(arr.length / size)}, (v, i) => - arr.slice(i * size, i * size + size) - ); - module.exports = eleventyConfig => { eleventyConfig.addFilter("i18n", function i18n(key, langOverride) { const lang = langOverride || this.page.lang; @@ -21,39 +12,10 @@ module.exports = eleventyConfig => { return translations[lang][key]; }); - eleventyConfig.addFilter("filterCollectionLang", (collection, lang) => { - return collection.filter(item => item.data.lang === lang); + eleventyConfig.addFilter("filterCollectionLang", function filterCollectionLang(collection, langOverride) { + const lang = langOverride || this.page.lang; + return collection.filter(entry => entry.data.lang === lang); }); - eleventyConfig.addGlobalData("available_langs", Object.keys(translations)); - - // Localised collections - Object.keys(translations).forEach(lang => { - eleventyConfig.addCollection(`posts_${lang}`, collectionApi => { - return getLocalisedPosts(collectionApi, lang); - }); - - // Localised and paginated collection - eleventyConfig.addCollection(`tags_${lang}_4x4`, collectionApi => { - const paginationSize = 4; - let tagMap = []; - const localisedPosts = getLocalisedPosts(collectionApi, lang); - const postTags = localisedPosts.map(post => post.data.tags) - .flat(); - eleventyConfig.getFilter("filterTagList")(postTags) - .filter((tag, index, tags) => tags.indexOf(tag) === index) - .forEach(tag => { - const tagPosts = localisedPosts.filter(post => post.data.tags.includes(tag)); - const pagedTagPosts = chunk(tagPosts, paginationSize) - pagedTagPosts.forEach((pagedItem, pageNumber) => { - tagMap.push({ - tagName: tag, - pageNumber: pageNumber, - pageData: pagedTagPosts[pageNumber] - }); - }); - }); - return tagMap; - }); - }); + eleventyConfig.addGlobalData("availableLangs", Object.keys(translations)); } diff --git a/eleventy.config.images.js b/eleventy.config.images.js index 80ab2302..8a96ea97 100644 --- a/eleventy.config.images.js +++ b/eleventy.config.images.js @@ -1,54 +1,72 @@ const path = require("path"); const eleventyImage = require("@11ty/eleventy-img"); +// Full list of formats here: https://www.11ty.dev/docs/plugins/image/#output-formats +// Warning: Avif can be resource-intensive so take care! const getOptions = widths => { - return { - widths: widths || ["auto"], - formats: ["avif", "webp", "auto"], - } + return { + widths: widths || ["auto"], + formats: ["avif", "webp", "auto"], + } }; const getImageAttributes = (cls, alt, sizes) => { - return { - class: `fr-responsive-img ${cls}`, - alt, - sizes, - loading: "lazy", - decoding: "async", - }; + return { + class: `fr-responsive-img fr-ratio-auto ${cls}`, + alt, + sizes, + loading: "lazy", + decoding: "async", + }; }; +const relativeToInputPath = (inputPath, relativeFilePath) => { + let split = inputPath.split("/"); + split.pop(); + return path.resolve(split.join(path.sep), relativeFilePath); +} + module.exports = eleventyConfig => { - function relativeToInputPath(inputPath, relativeFilePath) { - let split = inputPath.split("/"); - split.pop(); - - return path.resolve(split.join(path.sep), relativeFilePath); - } - - // Eleventy Image shortcode - // https://www.11ty.dev/docs/plugins/image/ - eleventyConfig.addAsyncShortcode("image", async function imageShortcode(src, alt, widths, sizes, cls) { - // Full list of formats here: https://www.11ty.dev/docs/plugins/image/#output-formats - // Warning: Avif can be resource-intensive so take care! - let file = relativeToInputPath(this.page.inputPath, src); - const options = getOptions(widths); - options["outputDir"] = path.join(eleventyConfig.dir.output, "img"); // Advanced usage note: `eleventyConfig.dir` works here because we’re using addPlugin. - let metadata = await eleventyImage(file, options); - - // TODO loading=eager and fetchpriority=high - return eleventyImage.generateHTML(metadata, getImageAttributes(cls, alt, sizes)); - }); - - // Synchronous method for Nunjucks macros - eleventyConfig.addNunjucksShortcode("imageSync", function imageShortcode(src, alt, widths, sizes, cls) { - const options = getOptions(widths); - options["outputDir"] = path.join(eleventyConfig.dir.output, "img"); // Advanced usage note: `eleventyConfig.dir` works here because we’re using addPlugin. - // generate images, while this is async we don’t wait - eleventyImage(src, options); - - // get metadata even if the images are not fully generated yet - let metadata = eleventyImage.statsSync(src, options); - return eleventyImage.generateHTML(metadata, getImageAttributes(cls, alt, sizes)); - }); + // Eleventy Image shortcodes + // https://www.11ty.dev/docs/plugins/image/ + eleventyConfig.addAsyncShortcode("image", async function imageShortcode(src, alt, widths, sizes, cls = "") { + let file = relativeToInputPath(this.page.inputPath, src); + const options = getOptions(widths); + options["outputDir"] = path.join(eleventyConfig.dir.output, "img"); // Advanced usage note: `eleventyConfig.dir` works here because we’re using addPlugin. + let metadata = await eleventyImage(file, options); + + // TODO loading=eager and fetchpriority=high + return eleventyImage.generateHTML(metadata, getImageAttributes(cls, alt, sizes)); + }); + + eleventyConfig.addAsyncShortcode("imageContent", async function imageContentShortcode(src, alt, widths, sizes, cls = "") { + let file = relativeToInputPath(this.page.inputPath, src); + const options = getOptions(widths); + options["outputDir"] = path.join(eleventyConfig.dir.output, "img"); // Advanced usage note: `eleventyConfig.dir` works here because we’re using addPlugin. + let metadata = await eleventyImage(file, options); + + return ` +
+
+ ${eleventyImage.generateHTML(metadata, getImageAttributes(cls, alt, sizes))} +
+
${alt}
+
\n`; + }); + + // Synchronous method for Nunjucks macros + eleventyConfig.addNunjucksShortcode("imageSync", function imageShortcode(src, alt, widths, sizes, cls = "") { + const options = getOptions(widths); + options["outputDir"] = path.join(eleventyConfig.dir.output, "img"); // Advanced usage note: `eleventyConfig.dir` works here because we’re using addPlugin. + // generate images, while this is async we don’t wait + eleventyImage(src, options); + + // get metadata even if the images are not fully generated yet + let metadata = eleventyImage.statsSync(src, options); + return eleventyImage.generateHTML(metadata, getImageAttributes(cls, alt, sizes)); + }); + + eleventyConfig.addFilter("resolvePath", (imagePath, page) => { + return imagePath ? path.resolve(`${page.inputPath}/..`, imagePath) : undefined; + }); }; diff --git a/eleventy.config.js b/eleventy.config.js index ea9563b7..0999baab 100644 --- a/eleventy.config.js +++ b/eleventy.config.js @@ -1,5 +1,7 @@ const {DateTime} = require("luxon"); const markdownItAnchor = require("markdown-it-anchor"); +const markdownItAttrs = require("markdown-it-attrs"); +const markdownItContainer = require("markdown-it-container"); const pluginRss = require("@11ty/eleventy-plugin-rss"); const pluginSyntaxHighlight = require("@11ty/eleventy-plugin-syntaxhighlight"); @@ -8,6 +10,8 @@ const pluginNavigation = require("@11ty/eleventy-navigation"); const {EleventyHtmlBasePlugin} = require("@11ty/eleventy"); const {EleventyI18nPlugin} = require("@11ty/eleventy"); +const customMarkdownContainers = require("./markdown-custom-containers"); + module.exports = function (eleventyConfig) { // Copy the contents of the `public` folder to the output folder // For example, `./public/css/` ends up in `_site/css/` @@ -89,7 +93,11 @@ module.exports = function (eleventyConfig) { }); eleventyConfig.addFilter("filterTagList", function filterTagList(tags, addTags = []) { - return (tags || []).filter(tag => ["all", "nav", "post", "posts"].concat(addTags).indexOf(tag) === -1); + return (tags || []).filter(tag => ["all", "nav", "post", "posts", "bluehats_post", "bluehats_posts"].concat(addTags).indexOf(tag) === -1); + }); + + eleventyConfig.addFilter("stripTags", str => { + return (str || "").replace(/<[^>]*>/g, ''); }); // Customize Markdown library settings: @@ -106,6 +114,18 @@ module.exports = function (eleventyConfig) { }); }); + eleventyConfig.amendLibrary("md", mdLib => { + mdLib.use(markdownItAttrs); + }); + + eleventyConfig.amendLibrary("md", mdLib => { + mdLib.use(markdownItContainer, 'callout', customMarkdownContainers.callout(mdLib)); + }); + + eleventyConfig.amendLibrary("md", mdLib => { + mdLib.use(markdownItContainer, 'quote', customMarkdownContainers.quote(mdLib)); + }); + // Automatically strip all leading or trailing whitespace // to prevent Markdown lib from rendering with wrapping into paragraphs // instead of using Nunjucks special syntax. Learn more: diff --git a/eleventy.config.pagination.js b/eleventy.config.pagination.js index a17d17e9..8413159d 100644 --- a/eleventy.config.pagination.js +++ b/eleventy.config.pagination.js @@ -1,20 +1,28 @@ const PAGE_PREFIX = "page-"; +const chunk = (arr, size) => + Array.from({length: Math.ceil(arr.length / size)}, (v, i) => + arr.slice(i * size, i * size + size) + ); + const paginatedUrl = (url, pageNumber) => { + if (pageNumber === 1) { + return url; + } const trimmedUrl = url.endsWith("/") ? url.slice(0, -1) : url; return `${trimmedUrl}/page-${pageNumber}/` } module.exports = eleventyConfig => { - eleventyConfig.addFilter("paginated_url", (url, pageNumber) => { + eleventyConfig.addFilter("paginatedUrl", (url, pageNumber) => { return paginatedUrl(url, pageNumber); }); - eleventyConfig.addFilter("pagination_object", (pagination, url) => { + eleventyConfig.addFilter("buildPagination", (pagination, url) => { const filteredHrefs = pagination.hrefs.filter(href => href.startsWith(url)); const pageCount = filteredHrefs.length; - const pageNumber = pagination.pageNumber % pageCount; + const pageNumber = filteredHrefs.findIndex(href => href === pagination.hrefs[pagination.pageNumber]); const currentPageNumber = pageNumber + 1; return { pageNumber: pageNumber, @@ -28,5 +36,28 @@ module.exports = eleventyConfig => { }; }); - eleventyConfig.addGlobalData("page_prefix", PAGE_PREFIX); + eleventyConfig.addFilter("paginateCollectionTags", function paginateCollection(collection, paginationSize) { + collection.sort(function (a, b) { + return b.date - a.date; // sort by date - descending + }); + const entryTags = collection.map(entry => entry.data.tags) + .flat(); + let tagMap = []; + eleventyConfig.getFilter("filterTagList")(entryTags) + .filter((tag, index, tags) => tags.indexOf(tag) === index) + .forEach(tag => { + const tagEntries = collection.filter(entry => entry.data.tags.includes(tag)); + const pagedTagEntries = chunk(tagEntries, paginationSize) + pagedTagEntries.forEach((pagedItem, pageNumber) => { + tagMap.push({ + tagName: tag, + pageNumber: pageNumber, + pageData: pagedTagEntries[pageNumber] + }); + }); + }); + return tagMap; + }); + + eleventyConfig.addGlobalData("pagePrefix", PAGE_PREFIX); } diff --git a/markdown-custom-containers.js b/markdown-custom-containers.js new file mode 100644 index 00000000..73ee0cf4 --- /dev/null +++ b/markdown-custom-containers.js @@ -0,0 +1,45 @@ +module.exports = { + callout: md => { + return { + validate: (params) => { + return params.trim().match(/^callout(\s+.*)?$/); + }, + + render: (tokens, idx) => { + const m = tokens[idx].info.trim().match(/^callout(\s+.*)?$/); + + if (tokens[idx].nesting === 1) { + // opening tag + return ` +
+

${md.utils.escapeHtml(m[1]) || ""}

+
+`; + } else { + // closing tag + return '
\n'; + } + } + } + }, + quote: md => { + return { + validate: (params) => { + return params.trim().match(/^quote\s*$/); + }, + + render: (tokens, idx) => { + if (tokens[idx].nesting === 1) { + // opening tag + return ` +
+
+`; + } else { + // closing tag + return '
\n'; + } + } + } + } +} diff --git a/package.json b/package.json index 7726fb39..8dba48aa 100644 --- a/package.json +++ b/package.json @@ -1,39 +1,42 @@ { - "name": "eleventy-dsfr", - "version": "1.0.0", - "description": "Une base de code pour déployer un site/blog statique avec Eleventy et le DSFR.", - "scripts": { - "build": "npx @11ty/eleventy", - "build-ghpages": "npx @11ty/eleventy --pathprefix=/eleventy-dsfr/", - "start": "npx @11ty/eleventy --serve --quiet", - "debug": "DEBUG=Eleventy* npx @11ty/eleventy", - "debugstart": "DEBUG=Eleventy* npx @11ty/eleventy --serve --quiet", - "benchmark": "DEBUG=Eleventy:Benchmark* npx @11ty/eleventy" - }, - "repository": { - "type": "git", - "url": "git@github.com:codegouvfr/eleventy-dsfr.git" - }, - "author": { - "name": "", - "email": "", - "url": "" - }, - "license": "etalab-2.0", - "engines": { - "node": ">=14" - }, - "homepage": "TODO github page", - "devDependencies": { - "@11ty/eleventy": "^2.0.0", - "@11ty/eleventy-img": "^3.0.0", - "@11ty/eleventy-navigation": "^0.3.5", - "@11ty/eleventy-plugin-bundle": "^1.0.3", - "@11ty/eleventy-plugin-rss": "^1.2.0", - "@11ty/eleventy-plugin-syntaxhighlight": "^4.2.0", - "@gouvfr/dsfr": "^1.9.0", - "chalk": "^4.1.2", - "luxon": "^3.2.1", - "markdown-it-anchor": "^8.6.6" - } + "name": "eleventy-dsfr", + "version": "1.1.0", + "description": "Une base de code pour déployer un site/blog statique avec Eleventy et le DSFR.", + "scripts": { + "build": "npx @11ty/eleventy", + "postbuild": "npx -y pagefind --source _site/", + "start": "npx @11ty/eleventy --serve --quiet", + "debug": "DEBUG=Eleventy* npx @11ty/eleventy", + "debugstart": "DEBUG=Eleventy* npx @11ty/eleventy --serve --quiet", + "benchmark": "DEBUG=Eleventy:Benchmark* npx @11ty/eleventy" + }, + "repository": { + "type": "git", + "url": "git@github.com:codegouvfr/eleventy-dsfr.git" + }, + "author": { + "name": "Mission logiciels libres (DINUM)", + "email": "contact@code.gouv.fr", + "url": "https://code.gouv.fr/mission/" + }, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "homepage": "https://codegouvfr.github.io/eleventy-dsfr/", + "devDependencies": { + "@11ty/eleventy": "^2.0.0", + "@11ty/eleventy-img": "^3.0.0", + "@11ty/eleventy-navigation": "^0.3.5", + "@11ty/eleventy-plugin-bundle": "^1.0.3", + "@11ty/eleventy-plugin-rss": "^1.2.0", + "@11ty/eleventy-plugin-syntaxhighlight": "^4.2.0", + "chalk": "^4.1.2", + "luxon": "^3.2.1", + "markdown-it-anchor": "^8.6.6", + "markdown-it-attrs": "^4.1.6", + "markdown-it-container": "^3.0.0", + "pagefind": "^0.12.0", + "@gouvfr/dsfr": "^1.9.3" + } } diff --git a/public/css/index.css b/public/css/index.css index d2055645..ae150b9c 100644 --- a/public/css/index.css +++ b/public/css/index.css @@ -1,84 +1,164 @@ /* Defaults */ :root { - --font-family-monospace: Consolas, Menlo, Monaco, Andale Mono WT, Andale Mono, Lucida Console, Lucida Sans Typewriter, DejaVu Sans Mono, Bitstream Vera Sans Mono, Liberation Mono, Nimbus Mono L, Courier New, Courier, monospace; - --syntax-tab-size: 2; + --font-family-monospace: Consolas, Menlo, Monaco, Andale Mono WT, Andale Mono, Lucida Console, Lucida Sans Typewriter, DejaVu Sans Mono, Bitstream Vera Sans Mono, Liberation Mono, Nimbus Mono L, Courier New, Courier, monospace; + --syntax-tab-size: 2; } /* Global stylesheet */ /* https://www.a11yproject.com/posts/how-to-hide-content/ */ .visually-hidden { - clip: rect(0 0 0 0); - clip-path: inset(50%); - height: 1px; - overflow: hidden; - position: absolute; - white-space: nowrap; - width: 1px; + clip: rect(0 0 0 0); + clip-path: inset(50%); + height: 1px; + overflow: hidden; + position: absolute; + white-space: nowrap; + width: 1px; } .links-nextprev { - box-shadow: inset 0 1px 0 0 var(--background-contrast-grey); - display: flex; - justify-content: space-between; - list-style: none; + box-shadow: inset 0 1px 0 0 var(--background-contrast-grey); + display: flex; + justify-content: space-between; + list-style: none; + width: 100%; } table { - margin: 1em 0; + margin: 1em 0; } + table td, table th { - padding-right: 1em; + padding-right: 1em; } pre, code { - font-family: var(--font-family-monospace); + font-family: var(--font-family-monospace); } + pre:not([class*="language-"]) { - margin: .5em 0; - line-height: 1.375; /* 22px /16 */ - -moz-tab-size: var(--syntax-tab-size); - -o-tab-size: var(--syntax-tab-size); - tab-size: var(--syntax-tab-size); - -webkit-hyphens: none; - -ms-hyphens: none; - hyphens: none; - direction: ltr; - text-align: left; - white-space: pre; - word-spacing: normal; - word-break: normal; + margin: .5em 0; + line-height: 1.375; /* 22px /16 */ + -moz-tab-size: var(--syntax-tab-size); + -o-tab-size: var(--syntax-tab-size); + tab-size: var(--syntax-tab-size); + -webkit-hyphens: none; + -ms-hyphens: none; + hyphens: none; + direction: ltr; + text-align: left; + white-space: pre; + word-spacing: normal; + word-break: normal; } + code { - word-break: break-all; + word-break: break-all; } /* Direct Links / Markdown Headers */ .header-anchor { - text-decoration: none; - font-style: normal; - font-size: 1em; - margin-left: .1em; + text-decoration: none; + font-style: normal; + font-size: 1em; + margin-left: .1em; } + a[href].header-anchor, a[href].header-anchor:visited { - color: transparent; + color: transparent; } + a[href].header-anchor:focus, a[href].header-anchor:hover { - text-decoration: underline; + text-decoration: underline; } + a[href].header-anchor:focus, :hover > a[href].header-anchor { - color: #aaa; + color: #aaa; } h2 + .header-anchor { - font-size: 1.5em; + font-size: 1.5em; } /* Custom DSFR */ .fr-pagination__list { - justify-content: flex-end !important; + justify-content: flex-end !important; +} + +@media (min-width: 48em) { + .fr-share__group { + justify-content: flex-end !important; + } +} + +.fr-responsive-img.fr-ratio-auto { + object-fit: contain; +} + +/** Properly render text in Markdown callout containers **/ +div.fr-callout__text p { + font-size: inherit; + line-height: inherit; +} + +/** Missing icons **/ +.fr-follow .fr-link--mastodon:before, +.fr-follow .fr-link--peertube:before, +.fr-follow .fr-link--github:before, +.fr-follow .fr-link--sourcehut:before { + background-color: currentColor !important; + content: "" !important; + display: inline-block !important; + flex: 0 0 auto !important; + height: var(--icon-size) !important; + -webkit-mask-size: 100% 100% !important; + mask-size: 100% 100% !important; + vertical-align: calc((.75em - var(--icon-size)) * .5) !important; + width: var(--icon-size) !important; +} + +.fr-follow .fr-link--mastodon:before { + -webkit-mask-image: url(icons/logo/mastodon-fill.svg) !important; + mask-image: url(icons/logo/mastodon-fill.svg) !important; +} + +.fr-follow .fr-link--peertube:before { + -webkit-mask-image: url(../icons/logo/peertube.svg) !important; + mask-image: url(../icons/logo/peertube.svg) !important; +} + +.fr-follow .fr-link--github:before { + -webkit-mask-image: url(icons/logo/github-fill.svg) !important; + mask-image: url(icons/logo/github-fill.svg) !important; +} + +.fr-follow .fr-link--sourcehut:before { + -webkit-mask-image: url(../icons/logo/sourcehut.svg) !important; + mask-image: url(../icons/logo/sourcehut.svg) !important; +} + +.fr-tile__link:after { + content: none !important; +} + +/* Custom */ +.banner-primary { + background: var(--background-alt-green-archipel); +} + +.banner-secondary { + background: var(--background-alt-grey); +} + +.text-center { + text-align: center; +} + +.col-center { + justify-content: center; } diff --git a/public/icons/1F3DB.svg b/public/icons/1F3DB.svg new file mode 100644 index 00000000..f57946b8 --- /dev/null +++ b/public/icons/1F3DB.svg @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/icons/logo/peertube.svg b/public/icons/logo/peertube.svg new file mode 100644 index 00000000..2b086031 --- /dev/null +++ b/public/icons/logo/peertube.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/icons/logo/sourcehut.svg b/public/icons/logo/sourcehut.svg new file mode 100644 index 00000000..1a77f5a1 --- /dev/null +++ b/public/icons/logo/sourcehut.svg @@ -0,0 +1 @@ + diff --git a/public/js/matomo.js b/public/js/matomo.js new file mode 100644 index 00000000..625f7ecf --- /dev/null +++ b/public/js/matomo.js @@ -0,0 +1,14 @@ +var _paq = window._paq = window._paq || []; +/* tracker methods like "setCustomDimension" should be called before "trackPageView" */ +_paq.push(["setExcludedQueryParams", ["simulationId", "_csrf"]]); +_paq.push(['trackPageView']); +_paq.push(['enableLinkTracking']); +(function () { + var u = "https://stats.data.gouv.fr/"; + _paq.push(['setTrackerUrl', u + 'matomo.php']); + _paq.push(['setSiteId', '95']); + var d = document, g = d.createElement('script'), s = d.getElementsByTagName('script')[0]; + g.async = true; + g.src = u + 'matomo.js'; + s.parentNode.insertBefore(g, s); +})(); \ No newline at end of file diff --git a/public/js/search-results.js b/public/js/search-results.js new file mode 100644 index 00000000..c034f3ac --- /dev/null +++ b/public/js/search-results.js @@ -0,0 +1,68 @@ +(() => { + const RESULTS_PER_PAGE = 10; + + const SEARCH_RESULTS_SELECTOR = "#search-results"; + const RESULT_COUNT_SELECTOR = "#result-count"; + + const FULL_WIDTH_COL_CLASS = "fr-col-12"; + + const searchResultList = document.querySelector(SEARCH_RESULTS_SELECTOR); + const resultCounter = document.querySelector(RESULT_COUNT_SELECTOR); + + const getSearchResults = async () => { + const pagefind = await import("/_pagefind/pagefind.js"); + const queryParams = new URLSearchParams(window.location.search); + const search = await pagefind.search(queryParams.get(SEARCH_PARAM)); + return search.results; + } + + const getCardHtml = (title, excerpt, url) => { + return ` +`; + } + + const populateSearchResults = async (paginatedResults) => { + paginatedResults.forEach(result => { + const cardCol = document.createElement("div"); + cardCol.className = FULL_WIDTH_COL_CLASS; + cardCol.innerHTML = getCardHtml(result.meta.title, result.excerpt, result.url) + searchResultList.appendChild(cardCol); + }); + } + + const bottomIsReached = () => { + return window.scrollY + window.innerHeight >= document.documentElement.scrollHeight; + } + + getSearchResults().then(async searchResults => { + resultCounter.innerText = searchResults.length; + + let start = 0; + let paginatedResults = await Promise.all(searchResults + .slice(start, start + RESULTS_PER_PAGE) + .map(r => r.data()) + ); + + await populateSearchResults(paginatedResults); + + window.addEventListener('scroll', async () => { + if (bottomIsReached()) { + start += RESULTS_PER_PAGE; + paginatedResults = await Promise.all(searchResults + .slice(start, start + RESULTS_PER_PAGE) + .map(r => r.data()) + ); + await populateSearchResults(paginatedResults); + } + }) + }); +})(); diff --git a/public/js/search.js b/public/js/search.js new file mode 100644 index 00000000..de2e5e4d --- /dev/null +++ b/public/js/search.js @@ -0,0 +1,23 @@ +const SEARCH_RESULTS_URL = `/${document.documentElement.lang}/search-results/`; +const SEARCH_PARAM = "term"; + +(() => { + const SEARCH_SELECTOR = "#header-search"; + + const searchBox = document.querySelector(SEARCH_SELECTOR); + const searchInput = searchBox.querySelector("input"); + const searchBtn = searchBox.querySelector("button"); + + const search = () => { + location.href = `${SEARCH_RESULTS_URL}?${SEARCH_PARAM}=${searchInput.value}`; + } + + searchInput.addEventListener("keydown", async (event) => { + if (event.code === "Enter") { + await search(); + } + }); + searchBtn.addEventListener("click", async () => { + await search(); + }); +})();