From 1df3f7f89c57610e50c09264e853ad34166b1b1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=93scar=20Fern=C3=A1ndez?= Date: Tue, 14 Nov 2023 23:26:44 +0100 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20feat(sitemap):=20add=20XML=20stylin?= =?UTF-8?q?g=20(#221)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 3 +- content/projects/tabi/index.ca.md | 3 +- content/projects/tabi/index.es.md | 3 +- content/projects/tabi/index.md | 3 +- sass/parts/_misc.scss | 12 ++++ static/js/sortTable.js | 113 ++++++++++++++++++++++++++++++ static/js/sortTable.min.js | 1 + static/sitemap_style.xsl | 44 ++++++++++++ templates/sitemap.xml | 12 ++++ 9 files changed, 190 insertions(+), 4 deletions(-) create mode 100644 static/js/sortTable.js create mode 100644 static/js/sortTable.min.js create mode 100644 static/sitemap_style.xsl create mode 100644 templates/sitemap.xml diff --git a/README.md b/README.md index eb2fd48..46a054a 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,8 @@ tabi has a perfect score on Google's Lighthouse audit: - [X] Support for [comments using giscus, utterances, Hyvor Talk, or Isso](https://welpo.github.io/tabi/blog/comments/). - [X] Code syntax highlighting with colours based on [Catppuccin](https://github.com/catppuccin/catppuccin) Frappé. - [X] [KaTeX](https://katex.org/) support for mathematical notation. +- [X] [Stylized and human readable Atom feed](https://welpo.github.io/tabi/atom.xml). +- [X] [Stylized and human readable sitemap](https://welpo.github.io/tabi/sitemap.xml). - [X] [Mail encoding](https://welpo.github.io/tabi/blog/mastering-tabi-settings/#encoded-email) for spam protection. - [X] All JavaScript can be [fully disabled](https://welpo.github.io/tabi/blog/javascript/). - [X] [Customizable Table of Contents](https://welpo.github.io/tabi/blog/toc/). @@ -40,7 +42,6 @@ tabi has a perfect score on Google's Lighthouse audit: - [X] Responsive design. - [X] [Projects page](https://welpo.github.io/tabi/projects/). - [X] [Archive page](https://welpo.github.io/tabi/archive/). -- [X] [Stylized feed](https://welpo.github.io/tabi/atom.xml). - [X] [Social links](https://welpo.github.io/tabi/blog/mastering-tabi-settings/#social-media-icons). - [X] [Tags](https://welpo.github.io/tabi/blog/mastering-tabi-settings/#tags). diff --git a/content/projects/tabi/index.ca.md b/content/projects/tabi/index.ca.md index d3d7eb3..4f1f29c 100644 --- a/content/projects/tabi/index.ca.md +++ b/content/projects/tabi/index.ca.md @@ -26,7 +26,9 @@ social_media_card = "social_cards/ca_projects_tabi.jpg" - Suport per a [comentaris usant giscus, utterances, Hyvor Talk o Isso](https://welpo.github.io/tabi/ca/blog/comments/). - Tot el JavaScript es pot [deshabilitar completament](https://welpo.github.io/tabi/ca/blog/javascript/). - [Codificació de correu](https://welpo.github.io/tabi/ca/blog/mastering-tabi-settings/#correu-electronic-codificat) per a protecció contra spam. +- [Mapa del lloc estilitzat i llegible per humans](https://welpo.github.io/tabi/sitemap.xml). - [Capçaleres de seguretat personalitzables](https://welpo.github.io/tabi/ca/blog/security/). +- [Feed Atom estilitzat i llegible per humans](https://welpo.github.io/tabi/ca/atom.xml). - [Enllaços de retrocés per a notes al peu](https://welpo.github.io/tabi/ca/blog/mastering-tabi-settings/#enllacos-de-retorn-a-les-notes-a-peu-de-pagina). - [Taula de continguts personalitzable](https://welpo.github.io/tabi/ca/blog/toc/). - [Avís de drets d'autor personalitzat](https://welpo.github.io/tabi/ca/blog/mastering-tabi-settings/#copyright). @@ -41,7 +43,6 @@ social_media_card = "social_cards/ca_projects_tabi.jpg" - Suport de [KaTeX](https://katex.org/). - [Enllaços socials](https://welpo.github.io/tabi/ca/blog/mastering-tabi-settings/#icones-de-xarxes-socials). - [Pàgina d'arxiu](https://welpo.github.io/tabi/ca/archive/). -- [Feed estilitzat](https://welpo.github.io/tabi/ca/atom.xml). - [Etiquetes](https://welpo.github.io/tabi/ca/blog/mastering-tabi-settings/#etiquetes). ## Pràctiques de desenvolupament diff --git a/content/projects/tabi/index.es.md b/content/projects/tabi/index.es.md index 6a10c57..9a3d5a4 100644 --- a/content/projects/tabi/index.es.md +++ b/content/projects/tabi/index.es.md @@ -26,6 +26,8 @@ social_media_card = "social_cards/es_projects_tabi.jpg" - Soporte para [comentarios usando giscus, utterances, Hyvor Talk o Isso](https://welpo.github.io/tabi/es/blog/comments/). - Todo el JavaScript se puede [deshabilitar completamente](https://welpo.github.io/tabi/es/blog/javascript/). - [Codificación de correo](https://welpo.github.io/tabi/es/blog/mastering-tabi-settings/#encoded-email) para protección contra spam. +- [Mapa de sitio web estilizado y legible por humanos](https://welpo.github.io/tabi/sitemap.xml). +- [Feed de Atom estilizado y legible por humanos](https://welpo.github.io/tabi/es/atom.xml). - [Aviso de derechos de autor personalizado](https://welpo.github.io/tabi/es/blog/mastering-tabi-settings/#copyright). - [Cabeceras de seguridad personalizables](https://welpo.github.io/tabi/es/blog/security/). - [Botón de copiar para bloques de código](https://welpo.github.io/tabi/es/blog/mastering-tabi-settings/#boton-de-copiar-en-bloques-de-codigo). @@ -41,7 +43,6 @@ social_media_card = "social_cards/es_projects_tabi.jpg" - Soporte de [KaTeX](https://katex.org/). - [Página de archivo](https://welpo.github.io/tabi/es/archive/). - [Enlaces sociales](https://welpo.github.io/tabi/es/blog/mastering-tabi-settings/#iconos-de-redes-sociales). -- [Feed estilizado](https://welpo.github.io/tabi/es/atom.xml). - [Etiquetas](https://welpo.github.io/tabi/es/blog/mastering-tabi-settings/#tags). ## Prácticas de desarrollo diff --git a/content/projects/tabi/index.md b/content/projects/tabi/index.md index 895ed25..372d237 100644 --- a/content/projects/tabi/index.md +++ b/content/projects/tabi/index.md @@ -26,6 +26,8 @@ social_media_card = "social_cards/projects_tabi.jpg" - Support for [comments using giscus, utterances, Hyvor Talk, or Isso](https://welpo.github.io/tabi/blog/comments/). - Code syntax highlighting with colours based on [Catppuccin](https://github.com/catppuccin/catppuccin) Frappé. - [KaTeX](https://katex.org/) support for mathematical notation. +- [Stylized and human readable Atom feed](https://welpo.github.io/tabi/atom.xml). +- [Stylized and human readable sitemap](https://welpo.github.io/tabi/sitemap.xml). - [Mail encoding](https://welpo.github.io/tabi/blog/mastering-tabi-settings/#encoded-email) for spam protection. - All JavaScript can be [fully disabled](https://welpo.github.io/tabi/blog/javascript/). - [Customizable Table of Contents](https://welpo.github.io/tabi/blog/toc/). @@ -41,7 +43,6 @@ social_media_card = "social_cards/projects_tabi.jpg" - Responsive design. - [Projects page](https://welpo.github.io/tabi/projects/). - [Archive page](https://welpo.github.io/tabi/archive/). -- [Stylized feed](https://welpo.github.io/tabi/atom.xml). - [Social links](https://welpo.github.io/tabi/blog/mastering-tabi-settings/#social-media-icons). - [Tags](https://welpo.github.io/tabi/blog/mastering-tabi-settings/#tags). diff --git a/sass/parts/_misc.scss b/sass/parts/_misc.scss index 44d3744..3531024 100644 --- a/sass/parts/_misc.scss +++ b/sass/parts/_misc.scss @@ -154,3 +154,15 @@ hr { .hidden { display: none; } + +.visually-hidden { + clip: rect(0 0 0 0); + position: absolute; + margin: -1px; + border: 0; + padding: 0; + width: 1px; + height: 1px; + overflow: hidden; + white-space: nowrap; +} diff --git a/static/js/sortTable.js b/static/js/sortTable.js new file mode 100644 index 0000000..a7fab49 --- /dev/null +++ b/static/js/sortTable.js @@ -0,0 +1,113 @@ +// Select the table and table headers. +var table = document.querySelector("#sitemapTable"); +var headers = Array.from(table.querySelectorAll("th")); + +// Create and append the live region for accessibility announcements. +var liveRegion = document.createElement('div'); +liveRegion.setAttribute('aria-live', 'polite'); +liveRegion.setAttribute('aria-atomic', 'true'); +liveRegion.classList.add('visually-hidden'); +document.body.appendChild(liveRegion); + +// Initialise headers with click and keyboard listeners. +initializeHeaders(); +addSortText(); // Add text for screen readers for initial sort direction. +updateSortIndicators(headers[0], 'asc'); // Set initial sort indicators. + +function updateSortIndicators(header, direction) { + removeSortArrows(header); + var arrow = document.createElement('span'); + arrow.classList.add('sort-arrow'); + arrow.textContent = direction === 'asc' ? ' ▲' : ' ▼'; + arrow.setAttribute('aria-hidden', 'true'); + header.appendChild(arrow); +} + +function removeSortArrows(header) { + var arrows = header.querySelectorAll('.sort-arrow'); + arrows.forEach(function (arrow) { + arrow.remove(); + }); +} + +function initializeHeaders() { + headers.forEach(function (header, index) { + header.classList.add('sortable'); + header.setAttribute('tabindex', '0'); + header.sortDirection = 'asc'; // Default sort direction. + var sortAttribute = index === 0 ? 'ascending' : 'none'; + header.setAttribute('aria-sort', sortAttribute); + header.addEventListener("click", function () { + sortTable(index); + }); + header.addEventListener("keydown", function (e) { + if (e.key === "Enter" || e.key === " ") { + e.preventDefault(); + sortTable(index); + } + }); + }); +} + +function announceSort(header, direction) { + var columnTitle = header.querySelector('.columntitle').textContent; + liveRegion.textContent = 'Column ' + columnTitle + ' is now sorted in ' + direction + ' order'; +} + +function sortTable(index) { + var header = headers[index]; + var direction = header.sortDirection === 'asc' ? 'desc' : 'asc'; + var tbody = table.querySelector("tbody"); + var rows = Array.from(tbody.querySelectorAll("tr")); + sortRows(rows, index, direction); + refreshTableBody(tbody, rows); + updateHeaderAttributes(header, direction); + announceSort(header, direction === 'asc' ? 'ascending' : 'descending'); +} + +function sortRows(rows, index, direction) { + rows.sort(function (rowA, rowB) { + var cellA = rowA.querySelectorAll("td")[index].textContent; + var cellB = rowB.querySelectorAll("td")[index].textContent; + return direction === 'asc' ? cellA.localeCompare(cellB) : cellB.localeCompare(cellA); + }); +} + +function refreshTableBody(tbody, rows) { + tbody.innerHTML = ''; // Clear existing rows. + rows.forEach(function (row) { + tbody.appendChild(row); + }); +} + +function updateHeaderAttributes(header, direction) { + headers.forEach(function (otherHeader) { + if (otherHeader !== header) { + otherHeader.setAttribute('aria-sort', 'none'); + removeSortArrows(otherHeader); + } + }); + header.setAttribute('aria-sort', direction === 'asc' ? 'ascending' : 'descending'); + header.sortDirection = direction; + updateSortIndicators(header, direction); + updateAnnounceText(header); +} + +// Update screen reader text for sorting. +function updateAnnounceText(header) { + var span = header.querySelector('.visually-hidden'); + span.textContent = 'Click to sort in ' + (header.sortDirection === 'asc' ? 'descending' : 'ascending') + ' order'; +} + +// Add text for screen readers regarding sort order. +function addSortText() { + headers.forEach(function (header) { + var span = document.createElement('span'); + span.classList.add('visually-hidden'); + span.textContent = 'Click to sort in descending order'; + header.appendChild(span); + }); +} + +headers[0].sortDirection = 'asc'; +headers[0].setAttribute('aria-sort', 'ascending'); diff --git a/static/js/sortTable.min.js b/static/js/sortTable.min.js new file mode 100644 index 0000000..de423a8 --- /dev/null +++ b/static/js/sortTable.min.js @@ -0,0 +1 @@ +var table=document.querySelector("#sitemapTable"),headers=Array.from(table.querySelectorAll("th")),liveRegion=document.createElement("div");function updateSortIndicators(e,t){removeSortArrows(e);var r=document.createElement("span");r.classList.add("sort-arrow"),r.textContent="asc"===t?" ▲":" ▼",r.setAttribute("aria-hidden","true"),e.appendChild(r)}function removeSortArrows(e){e.querySelectorAll(".sort-arrow").forEach(function(e){e.remove()})}function initializeHeaders(){headers.forEach(function(e,t){e.classList.add("sortable"),e.setAttribute("tabindex","0"),e.sortDirection="asc",e.setAttribute("aria-sort",0===t?"ascending":"none"),e.addEventListener("click",function(){sortTable(t)}),e.addEventListener("keydown",function(e){"Enter"!==e.key&&" "!==e.key||(e.preventDefault(),sortTable(t))})})}function announceSort(e,t){e=e.querySelector(".columntitle").textContent;liveRegion.textContent="Column "+e+" is now sorted in "+t+" order"}function sortTable(e){var t=headers[e],r="asc"===t.sortDirection?"desc":"asc",n=table.querySelector("tbody"),o=Array.from(n.querySelectorAll("tr"));sortRows(o,e,r),refreshTableBody(n,o),updateHeaderAttributes(t,r),announceSort(t,"asc"==r?"ascending":"descending")}function sortRows(e,r,n){e.sort(function(e,t){e=e.querySelectorAll("td")[r].textContent,t=t.querySelectorAll("td")[r].textContent;return"asc"===n?e.localeCompare(t):t.localeCompare(e)})}function refreshTableBody(t,e){t.innerHTML="",e.forEach(function(e){t.appendChild(e)})}function updateHeaderAttributes(t,e){headers.forEach(function(e){e!==t&&(e.setAttribute("aria-sort","none"),removeSortArrows(e))}),t.setAttribute("aria-sort","asc"===e?"ascending":"descending"),t.sortDirection=e,updateSortIndicators(t,e),updateAnnounceText(t)}function updateAnnounceText(e){e.querySelector(".visually-hidden").textContent="Click to sort in "+("asc"===e.sortDirection?"descending":"ascending")+" order"}function addSortText(){headers.forEach(function(e){var t=document.createElement("span");t.classList.add("visually-hidden"),t.textContent="Click to sort in descending order",e.appendChild(t)})}liveRegion.setAttribute("aria-live","polite"),liveRegion.setAttribute("aria-atomic","true"),liveRegion.classList.add("visually-hidden"),document.body.appendChild(liveRegion),initializeHeaders(),addSortText(),updateSortIndicators(headers[0],"asc"),headers[0].sortDirection="asc",headers[0].setAttribute("aria-sort","ascending"); diff --git a/static/sitemap_style.xsl b/static/sitemap_style.xsl new file mode 100644 index 0000000..79c083a --- /dev/null +++ b/static/sitemap_style.xsl @@ -0,0 +1,44 @@ + + + + + + + + Sitemap + + + + +
+

Sitemap

+

Number of URLs:

+ + + + + + + + + + + + + + + +
URLLast modification
+ + + + + +
+
+ + +
+
diff --git a/templates/sitemap.xml b/templates/sitemap.xml new file mode 100644 index 0000000..480759f --- /dev/null +++ b/templates/sitemap.xml @@ -0,0 +1,12 @@ + + + + {%- for sitemap_entry in entries %} + + {{ sitemap_entry.permalink | escape_xml | safe }} + {%- if sitemap_entry.updated %} + {{ sitemap_entry.updated }} + {%- endif %} + + {%- endfor %} +