From cd39169fd73f22b2a67f4a98cc34a131044ae2b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micka=C3=ABl=20Canouil?= <8896044+mcanouil@users.noreply.github.com> Date: Sun, 26 Jul 2026 20:54:06 +0200 Subject: [PATCH 1/6] feat(docs): add website icons and social preview metadata The documentation site shipped no icon assets and only three Open Graph tags, because the atelier project type sets `open-graph: true` and Quarto emits nothing beyond title, description, and site name from that. Add an icon set derived from one hand-authored SVG, a social preview card rendered from a committed Typst template, a web app manifest, and a filter covering the head tags Quarto has no configuration for: `og:type`, a per-page `og:url` and canonical link, `` from the pandoc `description-meta` variable Quarto never populates, and the icon, manifest, and theme-color links. The Twitter card and `og:locale` are configuration rather than markup: naming `twitter-card` at all is what switches Quarto's provider on. `website.site-url` is anchored and republished as top-level metadata because Quarto keeps the `website` block out of what it hands to Lua filters, so the filter cannot read it to build the canonical URL. `project.resources` is required because Quarto copies nothing to the output directory by convention, and a manifest names its icons in JSON, which is not scanned for resource references. 404.qmd gains a description; it was the only page without one, since every other page carries a subtitle that Quarto already uses as the fallback. --- .gitignore | 2 + docs/404.qmd | 1 + docs/_quarto.yml | 31 ++++- docs/assets/filters/social-metadata.lua | 176 ++++++++++++++++++++++++ docs/assets/icons/README.md | 45 ++++++ docs/assets/icons/apple-touch-icon.png | Bin 0 -> 1114 bytes docs/assets/icons/icon-192.png | Bin 0 -> 1280 bytes docs/assets/icons/icon-512.png | Bin 0 -> 3646 bytes docs/assets/icons/icon.svg | 18 +++ docs/assets/icons/raster-dark.css | 15 ++ docs/assets/social/og-image.png | Bin 0 -> 40357 bytes docs/assets/social/og-image.typ | 40 ++++++ docs/favicon.ico | Bin 0 -> 4286 bytes docs/site.webmanifest | 22 +++ 14 files changed, 349 insertions(+), 1 deletion(-) create mode 100644 docs/assets/filters/social-metadata.lua create mode 100644 docs/assets/icons/README.md create mode 100644 docs/assets/icons/apple-touch-icon.png create mode 100644 docs/assets/icons/icon-192.png create mode 100644 docs/assets/icons/icon-512.png create mode 100644 docs/assets/icons/icon.svg create mode 100644 docs/assets/icons/raster-dark.css create mode 100644 docs/assets/social/og-image.png create mode 100644 docs/assets/social/og-image.typ create mode 100644 docs/favicon.ico create mode 100644 docs/site.webmanifest diff --git a/.gitignore b/.gitignore index 9a1776a..fdcca3b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ .quarto/ _site/ + +/.luarc.json diff --git a/docs/404.qmd b/docs/404.qmd index d9effd3..6433a50 100644 --- a/docs/404.qmd +++ b/docs/404.qmd @@ -1,5 +1,6 @@ --- title: "Page not found" +description: "This page does not exist or has moved." toc: false sidebar: false --- diff --git a/docs/_quarto.yml b/docs/_quarto.yml index 66cd0f8..0c17437 100644 --- a/docs/_quarto.yml +++ b/docs/_quarto.yml @@ -6,12 +6,33 @@ project: - reference/ - contributing.qmd - 404.qmd + # Quarto copies nothing to the output directory by convention: only these + # globs, plus files it finds behind an `href` or `src` in the rendered HTML. + # The manifest names its icons in JSON, which is not scanned, so they are + # listed here. + resources: + - favicon.ico + - site.webmanifest + - assets/icons/*.png + - assets/social/og-image.png website: title: "Quarto Codespaces" description: "GitHub Codespaces and Dev Containers with Quarto, R, Python, and Julia, ready to render." - site-url: "https://m.canouil.dev/quarto-codespaces" + site-url: &site-url "https://m.canouil.dev/quarto-codespaces" repo-url: https://github.com/mcanouil/quarto-codespaces + favicon: favicon.ico + # The leading slash marks a project-relative path, which Quarto turns into + # `/assets/social/og-image.png` and measures to fill the image + # width and height tags. Setting `twitter-card` at all is what switches the + # Twitter provider on; it then inherits title, description, image, and image + # alt from each page. No `creator` or `site`: there is no X account to name. + image: /assets/social/og-image.png + image-alt: "The Quarto Codespaces mark, a document outline with an amber chevron, beside the title Quarto Codespaces on a dark navy background." + open-graph: + locale: en_GB + twitter-card: + card-style: summary_large_image page-footer: right: | Licence: [MIT](https://github.com/mcanouil/quarto-codespaces?tab=MIT-1-ov-file#readme). @@ -31,6 +52,13 @@ metadata-files: - _sidebar-getting-started.yml - _sidebar-reference.yml +# Quarto keeps the `website` block out of the metadata it hands to Lua filters, +# so `assets/filters/social-metadata.lua` cannot read `website.site-url` to +# build the canonical URL. The anchor above republishes the same scalar as +# plain document metadata, which does reach the filter, without a second copy +# of the string. +site-url: *site-url + brand: _brand.yml # The `atelier` project type sets `format: atelier-html`. Declaring `html` @@ -51,6 +79,7 @@ format: filters: - assets/filters/devcontainer-configurations.lua + - assets/filters/social-metadata.lua - at: post-quarto path: gitlink diff --git a/docs/assets/filters/social-metadata.lua b/docs/assets/filters/social-metadata.lua new file mode 100644 index 0000000..2c6684d --- /dev/null +++ b/docs/assets/filters/social-metadata.lua @@ -0,0 +1,176 @@ +--- @module social-metadata +--- @license MIT +--- @copyright 2026 Mickaël Canouil +--- @author Mickaël Canouil +--- +--- Emit the head tags Quarto's website machinery does not. +--- +--- `website.open-graph` covers title, description, image, image dimensions, +--- image alt, locale, and site name, and `website.twitter-card` covers the +--- Twitter equivalents. Neither emits `og:type` or `og:url`, there is no +--- canonical link, and `` needs the pandoc +--- `description-meta` variable, which Quarto never populates. The icon and +--- manifest links beyond `rel="icon"` have no configuration key either. +--- +--- This filter fills exactly those gaps: +--- +--- - `description-meta`, from `description` or `subtitle`, so every page +--- gets a plain description tag; +--- - `og:type`, `og:url`, and ``, built from the +--- top-level `site-url`, in the same form as the generated sitemap; +--- - the SVG icon, Apple touch icon, and manifest links, plus `theme-color`. +--- +--- Paths are written relative to the page, so they resolve under +--- `quarto preview` at the server root as well as under the deployed +--- `/quarto-codespaces/` prefix. On `404.html` Quarto rewrites them to +--- site-absolute paths itself, since a 404 is served from any depth. + +--- Icon and manifest links, in head order. `href` is relative to the site root. +--- @type table> +local ICON_LINKS = { + { rel = "icon", type = "image/svg+xml", href = "assets/icons/icon.svg" }, + { rel = "apple-touch-icon", sizes = "180x180", href = "assets/icons/apple-touch-icon.png" }, + { rel = "manifest", href = "site.webmanifest" }, +} + +--- Browser chrome colours, from the brand palette: frost and midnight. +--- @type table> +local THEME_COLOURS = { + { colour = "#F5F7FA", scheme = "light" }, + { colour = "#0B1220", scheme = "dark" }, +} + +--- The 404 page is served for any missing path, so it must not claim a +--- canonical URL of its own. +--- @type table +local NO_CANONICAL = { ["404.qmd"] = true } + +--- Escape a value for use inside a double-quoted HTML attribute. +--- @param value string The raw attribute value +--- @return string +local function escape_attribute(value) + return value + :gsub("&", "&") + :gsub("<", "<") + :gsub(">", ">") + :gsub('"', """) +end + +--- The input file, relative to the project root, with forward slashes. +--- @return string|nil The relative path, or nil outside a project context +local function project_relative_input() + local project = quarto.project.directory + local input = quarto.doc.input_file + if not project or not input then + return nil + end + return (pandoc.path.make_relative(input, project):gsub("\\", "/")) +end + +--- The `../` steps that separate the page from the site root. +--- Empty at the root, so hrefs stay free of a redundant `./`: Quarto prefixes +--- the site path on `404.html`, and `/quarto-codespaces/./icon.svg` is ugly. +--- @param relative_input string The input path relative to the project root +--- @return string The offset, with a trailing slash, or an empty string +local function offset_to_root(relative_input) + local steps = {} + for _ in relative_input:gmatch("/") do + table.insert(steps, "..") + end + if #steps == 0 then + return "" + end + return table.concat(steps, "/") .. "/" +end + +--- Build the canonical URL for the page. +--- Uses the top-level `site-url`, which `_quarto.yml` anchors to +--- `website.site-url`; the `website` block itself never reaches Lua. +--- @param meta table The document metadata +--- @param relative_input string The input path relative to the project root +--- @return string|nil The absolute URL, or nil when `site-url` is unset +local function canonical_url(meta, relative_input) + if not meta["site-url"] then + return nil + end + local site_url = pandoc.utils.stringify(meta["site-url"]):gsub("/+$", "") + local page = relative_input:gsub("%.%w+$", ".html") + return site_url .. "/" .. page +end + +--- Render one `` tag. +--- @param link table One entry of `ICON_LINKS` +--- @param offset string The offset to the site root, with a trailing slash +--- @return string +local function link_tag(link, offset) + local attributes = { string.format('rel="%s"', link.rel) } + if link.type then + table.insert(attributes, string.format('type="%s"', link.type)) + end + if link.sizes then + table.insert(attributes, string.format('sizes="%s"', link.sizes)) + end + table.insert(attributes, string.format('href="%s%s"', offset, link.href)) + return "" +end + +--- Give pandoc a `description-meta` so it emits ``. +--- Every page here carries a subtitle; `description` wins when both are set. +--- @param meta table The document metadata, modified in place +--- @return nil +local function set_description_meta(meta) + if meta["description-meta"] then + return + end + local source = meta.description or meta.subtitle + if source then + meta["description-meta"] = pandoc.MetaString(pandoc.utils.stringify(source)) + end +end + +--- @param meta table The document metadata +--- @return table|nil +function Meta(meta) + if not quarto.doc.is_format("html:js") then + return nil + end + + set_description_meta(meta) + + local relative_input = project_relative_input() + if not relative_input then + quarto.log.warning("[social-metadata] no project context; skipping the head tags") + return meta + end + + local offset = offset_to_root(relative_input) + local tags = { '' } + + if not NO_CANONICAL[relative_input] then + local url = canonical_url(meta, relative_input) + if url then + local escaped = escape_attribute(url) + table.insert(tags, string.format('', escaped)) + table.insert(tags, string.format('', escaped)) + end + end + + for _, theme in ipairs(THEME_COLOURS) do + table.insert( + tags, + string.format( + '', + theme.colour, + theme.scheme + ) + ) + end + + for _, link in ipairs(ICON_LINKS) do + table.insert(tags, link_tag(link, offset)) + end + + quarto.doc.include_text("in-header", table.concat(tags, "\n")) + + return meta +end diff --git a/docs/assets/icons/README.md b/docs/assets/icons/README.md new file mode 100644 index 0000000..649f475 --- /dev/null +++ b/docs/assets/icons/README.md @@ -0,0 +1,45 @@ +# Icons + +Everything here derives from `icon.svg`, the hand-authored master. +Nothing is traced, upscaled, or model-generated, so the whole set is reproducible from source. + +`icon.svg` carries an inline `@media (prefers-color-scheme: dark)` rule, so browsers that load it directly flip it with the theme: an ink outline with an amber-deep chevron in the light scheme, mist and amber in the dark one. +The colours come from `../../_brand.yml`. + +## Rasters + +The rasters sit on the midnight background, matching the navbar, which is dark in both schemes. +librsvg does not evaluate `prefers-color-scheme`, so `raster-dark.css` forces the dark variant. + +Tools: `rsvg-convert` (librsvg) for the vector to raster step, `magick` (ImageMagick 7) for padding, flattening, and `.ico` assembly. +Neither is a build dependency; the commands are run by hand when the master changes. + +Run from this directory. + +```sh +for size in 32 144 154 410; do + rsvg-convert -s raster-dark.css -w "${size}" -h "${size}" icon.svg -o "/tmp/icon-${size}.png" +done + +magick /tmp/icon-32.png -background '#0B1220' -flatten ../../favicon.ico +magick /tmp/icon-144.png -background '#0B1220' -gravity center -extent 180x180 -flatten apple-touch-icon.png +magick /tmp/icon-154.png -background '#0B1220' -gravity center -extent 192x192 -flatten icon-192.png +magick /tmp/icon-410.png -background '#0B1220' -gravity center -extent 512x512 -flatten icon-512.png +``` + +| File | Size | Purpose | +| ---------------------- | ------- | --------------------------------------------------- | +| `icon.svg` | vector | `rel="icon"`, and the source for everything below | +| `../../favicon.ico` | 32x32 | site root, for clients that ignore the SVG | +| `apple-touch-icon.png` | 180x180 | iOS home screen, opaque, roughly 10% padding | +| `icon-192.png` | 192x192 | `site.webmanifest` | +| `icon-512.png` | 512x512 | `site.webmanifest`, and the mark on the social card | + +There is no maskable icon: this is a documentation site, not an installable application. + +`icon-512.png` is also what `../social/og-image.typ` places on the social card, since Typst renders SVG through resvg, which ignores the media query. + +## After a change + +Regenerate the rasters, then regenerate the social card, which embeds `icon-512.png`. +See the header of `../social/og-image.typ`. diff --git a/docs/assets/icons/apple-touch-icon.png b/docs/assets/icons/apple-touch-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..d98126f432a1c4a4cd0e3a9f38f7c19fe92e01bd GIT binary patch literal 1114 zcmeAS@N?(olHy`uVBq!ia0vp^TR@nD8Ax&oe*=;X3dtTpz6=aiY77hwEes65fIlj zw<9LGfL}}{yQuc&gO^+P98=S=I)3iDmZ6=FvEz;VFZLZhfBoL`lNWF4nmDc5w5O`E zx461Z)4=xF*=y3W%ISW(a|(^u)|#gI>fD)a{bI3gRg_+HqJg8C`twD$8){9}^{gdj zl%6fJwK7y|OE$1HP&;?^-t9-P>*DmQWAy9e^)2+(T9XWxtlpMeQg3Y${2u69#w2fd z7lsa2Sq~tGy~NYkmHidFl#l{Xsr)kwppa^bYeY#(Vo9o1a#1RfVlXl=GSf9M*EKQ= zF|@EUHnK7_(l#)#GBDV5;dU2_hTQy=%(P0}8V>g+n*lXwz-=hW%uOvWNz5(4t!K&% zrqP;Ng(^?)iE)VVsKm^Q(|Gtdo_EzMp-$#+J>}ne zF8-@?|2V0%Z>`4S%P%#SP6_hTgb;C=OLy>r#9r!^D->-S|p{Nus$PT@KpyB|6Z5N zR{~7DdtE1ANicEmbql_-^}^xFm-CmaA2xYq^9{&T|=Z!_ysRU)8)2R?4>}a4t)3aJu8{E14I>O69gV&gHnfQ)hMka(DfJ z{&klYi?2BT^J41Px4v~Hk+zwPzv|@vs@w1CJ-xVBdSh?@9ZIRs-9?OIsi?&{U$8~KDA{||+ be#QBd!IG(XV)u1mf@bh^^>bP0l+XkKy7%ND literal 0 HcmV?d00001 diff --git a/docs/assets/icons/icon-192.png b/docs/assets/icons/icon-192.png new file mode 100644 index 0000000000000000000000000000000000000000..e2e96919470b9362216ec478c5c2c88e381cc6db GIT binary patch literal 1280 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 literal 0 HcmV?d00001 diff --git a/docs/assets/icons/icon.svg b/docs/assets/icons/icon.svg new file mode 100644 index 0000000..db237c4 --- /dev/null +++ b/docs/assets/icons/icon.svg @@ -0,0 +1,18 @@ + + Quarto Codespaces + + + + diff --git a/docs/assets/icons/raster-dark.css b/docs/assets/icons/raster-dark.css new file mode 100644 index 0000000..489831c --- /dev/null +++ b/docs/assets/icons/raster-dark.css @@ -0,0 +1,15 @@ +/* Force the dark-scheme colours when rasterising icon.svg. + + librsvg does not evaluate `prefers-color-scheme`, so it would render the + light variant, an ink outline that disappears once flattened onto the + midnight background the PNG and ICO files use. `rsvg-convert --stylesheet` + applies this as a user stylesheet, hence `!important` to beat the author + rules inside the SVG. */ + +.page { + stroke: #DCE3EC !important; +} + +.caret { + stroke: #E8A33D !important; +} diff --git a/docs/assets/social/og-image.png b/docs/assets/social/og-image.png new file mode 100644 index 0000000000000000000000000000000000000000..d613bdb439c06b9b877e0262f4789e9a3f5c5bb1 GIT binary patch literal 40357 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 literal 0 HcmV?d00001 diff --git a/docs/assets/social/og-image.typ b/docs/assets/social/og-image.typ new file mode 100644 index 0000000..c7ca97d --- /dev/null +++ b/docs/assets/social/og-image.typ @@ -0,0 +1,40 @@ +// Social preview card, 1200x630, rendered to og-image.png from `docs/` with: +// +// typst compile --root . --format png --ppi 72 \ +// assets/social/og-image.typ assets/social/og-image.png +// +// `--root .` is what lets the template reach the mark in assets/icons/; Typst +// otherwise sandboxes it to its own directory. +// +// The page is 1200pt by 630pt and the render is 72 ppi, so one point is one +// pixel and the output is exactly 1200x630. +// +// The mark comes in as icon-512.png rather than icon.svg because Typst renders +// SVG through resvg, which does not evaluate `prefers-color-scheme` and would +// draw the light variant: an ink outline, invisible on midnight. That PNG is +// already the dark variant on an opaque midnight square, so it sits seamlessly +// on the page fill. +// +// Colours and fonts come from ../../_brand.yml. Space Grotesk ships no static +// 600 face, so the title uses Bold, the closest heavier match for display type. + +#let midnight = rgb("#0B1220") +#let mist = rgb("#DCE3EC") +#let mist-muted = rgb("#8EA0B8") + +#set page(width: 1200pt, height: 630pt, margin: 80pt, fill: midnight) +#set text(fill: mist) + +#align(horizon)[ + #stack( + dir: ttb, + spacing: 44pt, + image("/assets/icons/icon-512.png", width: 128pt), + text(font: "Space Grotesk", weight: 700, size: 92pt, tracking: -1.5pt)[ + Quarto Codespaces + ], + text(font: "IBM Plex Sans", weight: 300, size: 34pt, fill: mist-muted)[ + Quarto, R, Python, and Julia, ready to render. + ], + ) +] diff --git a/docs/favicon.ico b/docs/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..989dfd5fa4efad58bbe4f120065659a9fe1ab3a1 GIT binary patch literal 4286 zcmeHLu}T9$5FKj`h!N}6O44Z`u**m22MGEDR)Q9yF;&jOCXJ1MB!Y-wp@RRg&MZt^ zS2DYkofuFrOy=%(^4^=7-Ayh43;1#l=pAmC0oDLu6Af>{4jSDL2actOTUqq~_F04e z@sWRfy7%wT52i+#gq^GP%j2CXZ=45V=lJ9JS)6P5bFro4aUQwPXkv3%Jg&ji#4>)) zYcR#w_LA@Pc7r)Q&++oOh?(p^Qf#NU=eswr!L|>E-nCmr8YE2C$X<$#`AJ8q1__gS z*^9B5FL81WN;Qa>iuwnQ*!Qjd!B-83jo1rQe$uPdV9p;jl=osL{1Y_L`sBI9{#WuV z)`EHd^PGQ5ZkhvT`73J9b8lm{WyY`Fe~EiE?2q}izkfIf_A!5M?@zg@2hI7(x3$UG z?8RPT?@PXa*D>yTa5m1HFh|X$Cr!*V$&387=VYvgo3Ly6Yq@LqEx1$uxE9)Tn)fCB jPWx%`{4p&zX{mYsqGznDi_@v{PV4YjuG9Yme){?Y5Jbl} literal 0 HcmV?d00001 diff --git a/docs/site.webmanifest b/docs/site.webmanifest new file mode 100644 index 0000000..fc53bc2 --- /dev/null +++ b/docs/site.webmanifest @@ -0,0 +1,22 @@ +{ + "name": "Quarto Codespaces", + "short_name": "Codespaces", + "description": "GitHub Codespaces and Dev Containers with Quarto, R, Python, and Julia, ready to render.", + "lang": "en-GB", + "icons": [ + { + "src": "/quarto-codespaces/assets/icons/icon-192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "/quarto-codespaces/assets/icons/icon-512.png", + "sizes": "512x512", + "type": "image/png" + } + ], + "theme_color": "#0B1220", + "background_color": "#0B1220", + "display": "standalone", + "start_url": "/quarto-codespaces/" +} From 46c74ba8014d2abe5d8463d82809290af3c291ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micka=C3=ABl=20Canouil?= <8896044+mcanouil@users.noreply.github.com> Date: Sun, 26 Jul 2026 20:56:05 +0200 Subject: [PATCH 2/6] style(docs): adopt the LuaDoc header convention in the filter The bundled extensions carry a `--- @module` header with licence, copyright, and author, plus LuaDoc annotations on each function. Bring the configuration-reference filter to the same shape so both filters in the project read alike. --- .../filters/devcontainer-configurations.lua | 40 +++++++++++-------- 1 file changed, 24 insertions(+), 16 deletions(-) diff --git a/docs/assets/filters/devcontainer-configurations.lua b/docs/assets/filters/devcontainer-configurations.lua index 9614f83..0982ec0 100644 --- a/docs/assets/filters/devcontainer-configurations.lua +++ b/docs/assets/filters/devcontainer-configurations.lua @@ -1,22 +1,30 @@ ---[[ -Generate the configuration reference from the repository itself. - -The `.devcontainer/` directory gains a new `quarto-` configuration -every time Quarto releases a minor version, and every place that lists those -versions by hand drifts the moment one is added. This filter reads the -directory at render time and fills in: - - - `::: {#devcontainer-configurations}`, replaced by the configuration table; - - `[]{.version-range}`, replaced by "`` … ``"; - - `[]{.version-latest}`, replaced by the highest version. - -Both spans accept a `suffix` attribute, appended inside the code spans, so -`[]{.version-range suffix="-noble"}` renders "`1.0-noble` … `1.10-noble`". -]] - +--- @module devcontainer-configurations +--- @license MIT +--- @copyright 2026 Mickaël Canouil +--- @author Mickaël Canouil +--- +--- Generate the configuration reference from the repository itself. +--- +--- The `.devcontainer/` directory gains a new `quarto-` +--- configuration every time Quarto releases a minor version, and every place +--- that lists those versions by hand drifts the moment one is added. This +--- filter reads the directory at render time and fills in: +--- +--- - `::: {#devcontainer-configurations}`, replaced by the configuration +--- table; +--- - `[]{.version-range}`, replaced by "`` … ``"; +--- - `[]{.version-latest}`, replaced by the highest version. +--- +--- Both spans accept a `suffix` attribute, appended inside the code spans, so +--- `[]{.version-range suffix="-noble"}` renders "`1.0-noble` … `1.10-noble`". + +--- @type string The image referenced by every shipped configuration local IMAGE = "`quarto-codespaces:latest`" --- Compare two dotted version strings numerically, so 1.10 sorts after 1.9. +--- @param a string The left version, as `.` +--- @param b string The right version, as `.` +--- @return boolean local function version_less_than(a, b) local a_major, a_minor = a:match("^(%d+)%.(%d+)$") local b_major, b_minor = b:match("^(%d+)%.(%d+)$") From f3b0a495a6993cae8f5ece2265e5c860c29a5527 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micka=C3=ABl=20Canouil?= <8896044+mcanouil@users.noreply.github.com> Date: Sun, 26 Jul 2026 21:14:36 +0200 Subject: [PATCH 3/6] refactor(docs): use quarto.project.offset and shrink the raster assets The filter counted path separators to rebuild the offset back to the site root. Quarto already computes that and exposes it as `quarto.project.offset`, which is how it builds its own `website.favicon` href, so the hand-rolled arithmetic was a second implementation of the same thing. Output is identical at the root, in a subdirectory, and on the 404 page. Writing favicon.ico straight from the transparent render made ImageMagick store an uncompressed 32-bit bitmap. Flattening to an opaque PNG first lets it pick a palette: 4,286 bytes down to 2,238, byte-for-byte identical pixels. Typst writes an alpha channel that is opaque everywhere, since the page has a solid fill. Dropping it is lossless and takes the card from 40 KB to 39 KB. Both regeneration recipes are updated so they reproduce what is committed. --- docs/assets/filters/social-metadata.lua | 17 +++++++---------- docs/assets/icons/README.md | 6 +++++- docs/assets/social/og-image.png | Bin 40357 -> 38881 bytes docs/assets/social/og-image.typ | 5 +++++ docs/favicon.ico | Bin 4286 -> 2238 bytes 5 files changed, 17 insertions(+), 11 deletions(-) diff --git a/docs/assets/filters/social-metadata.lua b/docs/assets/filters/social-metadata.lua index 2c6684d..71906c8 100644 --- a/docs/assets/filters/social-metadata.lua +++ b/docs/assets/filters/social-metadata.lua @@ -67,20 +67,17 @@ local function project_relative_input() return (pandoc.path.make_relative(input, project):gsub("\\", "/")) end ---- The `../` steps that separate the page from the site root. +--- Prefix that walks from the page back to the site root, with a trailing +--- slash. Quarto builds its own `website.favicon` href from the same offset. --- Empty at the root, so hrefs stay free of a redundant `./`: Quarto prefixes --- the site path on `404.html`, and `/quarto-codespaces/./icon.svg` is ugly. ---- @param relative_input string The input path relative to the project root --- @return string The offset, with a trailing slash, or an empty string -local function offset_to_root(relative_input) - local steps = {} - for _ in relative_input:gmatch("/") do - table.insert(steps, "..") - end - if #steps == 0 then +local function site_root_prefix() + local offset = quarto.project.offset + if not offset or offset == "." then return "" end - return table.concat(steps, "/") .. "/" + return offset .. "/" end --- Build the canonical URL for the page. @@ -143,7 +140,7 @@ function Meta(meta) return meta end - local offset = offset_to_root(relative_input) + local offset = site_root_prefix() local tags = { '' } if not NO_CANONICAL[relative_input] then diff --git a/docs/assets/icons/README.md b/docs/assets/icons/README.md index 649f475..40eb0c8 100644 --- a/docs/assets/icons/README.md +++ b/docs/assets/icons/README.md @@ -21,12 +21,16 @@ for size in 32 144 154 410; do rsvg-convert -s raster-dark.css -w "${size}" -h "${size}" icon.svg -o "/tmp/icon-${size}.png" done -magick /tmp/icon-32.png -background '#0B1220' -flatten ../../favicon.ico +magick /tmp/icon-32.png -background '#0B1220' -flatten -strip /tmp/icon-32-flat.png +magick /tmp/icon-32-flat.png -define icon:format=png ../../favicon.ico magick /tmp/icon-144.png -background '#0B1220' -gravity center -extent 180x180 -flatten apple-touch-icon.png magick /tmp/icon-154.png -background '#0B1220' -gravity center -extent 192x192 -flatten icon-192.png magick /tmp/icon-410.png -background '#0B1220' -gravity center -extent 512x512 -flatten icon-512.png ``` +The favicon is flattened to an opaque PNG before the `.ico` is written. +Writing the `.ico` straight from the transparent render makes ImageMagick store an uncompressed 32-bit bitmap, 4,286 bytes for the same 32x32 pixels; going through the flat PNG lets it pick a palette instead, at 2,238 bytes and byte-for-byte identical output. + | File | Size | Purpose | | ---------------------- | ------- | --------------------------------------------------- | | `icon.svg` | vector | `rel="icon"`, and the source for everything below | diff --git a/docs/assets/social/og-image.png b/docs/assets/social/og-image.png index d613bdb439c06b9b877e0262f4789e9a3f5c5bb1..6283bcde7a3c1be41ab7a47663dc545895fbd95f 100644 GIT binary patch literal 38881 zcmd42bySqy+XiZj2vRE2NGc#mH%JIbcZYNgA>GU?As{6n&CuQ53`*yaLx*&C$H3Y6 z{=T!;x7IoToORCmzHzMqoM)cBpL^f?j_bO1puDU&CORSdojZ3hKS_ux-nsLjpzN$N%(4Xuh|zxO4CI?fX9; z&~AT_W8WI;&j0jB{~OKq-&XB^m%0Ais{OZl-ud5X$^U8Z`fuj?@BaV4(Omy+LH|E@ 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 diff --git a/docs/assets/social/og-image.typ b/docs/assets/social/og-image.typ index c7ca97d..68ff8da 100644 --- a/docs/assets/social/og-image.typ +++ b/docs/assets/social/og-image.typ @@ -2,6 +2,11 @@ // // typst compile --root . --format png --ppi 72 \ // assets/social/og-image.typ assets/social/og-image.png +// magick assets/social/og-image.png -alpha off -strip \ +// -define png:compression-level=9 assets/social/og-image.png +// +// Typst writes an alpha channel that is opaque everywhere, since the page has +// a solid fill. Dropping it is lossless and takes the file from 40 KB to 39 KB. // // `--root .` is what lets the template reach the mark in assets/icons/; Typst // otherwise sandboxes it to its own directory. diff --git a/docs/favicon.ico b/docs/favicon.ico index 989dfd5fa4efad58bbe4f120065659a9fe1ab3a1..7f7b6070d889ae9ada9e096e6a7e6381f4d31c5f 100644 GIT binary patch literal 2238 zcmeH{u};G<5QhIGO&dtkmKHGwCd5W}BsLxb9smhmfC;G#2ui1hfu#!zZ-kH#Lh1lW zeF*N7IK9|bk-DMgBHQQt&Uf)S4&cy}B%nUPO91Qu*r!G&98+U^CXIZ^dWU*yNiW3c z`U*dvZC@pylOz3muZAFr5BM%e8~cpBegnm!;Q{ov%3+8K_XzcIL&kS#`fH^kxj z7=z1HB&gN80(Aw}p#qNUS;XZ(OeReWB~w#zJN7)mmAV*dk(vlqnE+uF>M|ZiQMio9 z?RKYII*6c!N?bUYyxWluCP&yKhklX5!Yd9o4w1sjbBBmGu@e6yzTAK%zQmUNA~@ii z>t%iYR`vVg9f|W{>}|1;r3ke+-(Z7Gn|@A9uBOniTCcc{HEv+$nzYK?9MrfURSfNZ NxsA$d^;-XP`~{9zB>eyY literal 4286 zcmeHLu}T9$5FKj`h!N}6O44Z`u**m22MGEDR)Q9yF;&jOCXJ1MB!Y-wp@RRg&MZt^ zS2DYkofuFrOy=%(^4^=7-Ayh43;1#l=pAmC0oDLu6Af>{4jSDL2actOTUqq~_F04e z@sWRfy7%wT52i+#gq^GP%j2CXZ=45V=lJ9JS)6P5bFro4aUQwPXkv3%Jg&ji#4>)) zYcR#w_LA@Pc7r)Q&++oOh?(p^Qf#NU=eswr!L|>E-nCmr8YE2C$X<$#`AJ8q1__gS z*^9B5FL81WN;Qa>iuwnQ*!Qjd!B-83jo1rQe$uPdV9p;jl=osL{1Y_L`sBI9{#WuV z)`EHd^PGQ5ZkhvT`73J9b8lm{WyY`Fe~EiE?2q}izkfIf_A!5M?@zg@2hI7(x3$UG z?8RPT?@PXa*D>yTa5m1HFh|X$Cr!*V$&387=VYvgo3Ly6Yq@LqEx1$uxE9)Tn)fCB jPWx%`{4p&zX{mYsqGznDi_@v{PV4YjuG9Yme){?Y5Jbl} From f037b5fd25ca968ad64c21c112f3afaf17237894 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micka=C3=ABl=20Canouil?= <8896044+mcanouil@users.noreply.github.com> Date: Sun, 26 Jul 2026 21:15:23 +0200 Subject: [PATCH 4/6] docs: note the Typst card is this site's own, not a prescribed approach --- docs/assets/icons/README.md | 2 +- docs/assets/social/og-image.typ | 9 ++++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/docs/assets/icons/README.md b/docs/assets/icons/README.md index 40eb0c8..9d393c7 100644 --- a/docs/assets/icons/README.md +++ b/docs/assets/icons/README.md @@ -46,4 +46,4 @@ There is no maskable icon: this is a documentation site, not an installable appl ## After a change Regenerate the rasters, then regenerate the social card, which embeds `icon-512.png`. -See the header of `../social/og-image.typ`. +See the header of `../social/og-image.typ`, which is how this site's card happens to be built rather than a prescribed approach. diff --git a/docs/assets/social/og-image.typ b/docs/assets/social/og-image.typ index 68ff8da..fb0bc84 100644 --- a/docs/assets/social/og-image.typ +++ b/docs/assets/social/og-image.typ @@ -1,4 +1,11 @@ -// Social preview card, 1200x630, rendered to og-image.png from `docs/` with: +// Social preview card for this site, 1200x630. +// +// This is one way to produce a card, not a house style or a template to copy. +// It is committed so that this site's own card can be regenerated from source +// rather than being an orphaned binary; anything that emits a 1200x630 PNG +// would do just as well. +// +// Rendered to og-image.png from `docs/` with: // // typst compile --root . --format png --ppi 72 \ // assets/social/og-image.typ assets/social/og-image.png From a883dadae1e8c5e53756eac7e3b54dc4c957c0f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micka=C3=ABl=20Canouil?= <8896044+mcanouil@users.noreply.github.com> Date: Sun, 26 Jul 2026 21:57:24 +0200 Subject: [PATCH 5/6] refactor(docs): take the social metadata from quarto-atelier 0.5.0 The filter added here has shipped in the extension, so the local copy is removed rather than left alongside it. Its configuration moves under `extensions.atelier`, where the anchored `site-url` now lives with the icon, touch icon, manifest, and theme colours. `open-graph.locale` and `twitter-card.card-style` are dropped: the extension sets both, the locale alongside the `lang: en-GB` it already contributes. The site-specific parts stay where they are, since the extension has no opinion on them: the icon artwork, the social card, the manifest contents, `website.image`, `website.image-alt`, `website.favicon`, and the `project.resources` globs that get the files into the output directory. Verified across a full render: 13 pages, no warnings, 13 distinct descriptions, 12 distinct canonical URLs for the 12 non-404 pages, all 52 icon and manifest references and both manifest icons resolving, and every absolute URL carrying the site prefix. --- .../mcanouil/atelier/_extension.yml | 76 +++--- .../mcanouil/atelier/_modules/logging.lua | 62 +++++ .../mcanouil/atelier/_modules/metadata.lua | 182 +++++++++++++++ .../mcanouil/atelier/_modules/string.lua | 198 ++++++++++++++++ .../mcanouil/atelier/social-metadata.lua | 219 ++++++++++++++++++ docs/_quarto.yml | 30 +-- docs/assets/filters/social-metadata.lua | 173 -------------- 7 files changed, 716 insertions(+), 224 deletions(-) create mode 100644 docs/_extensions/mcanouil/atelier/_modules/logging.lua create mode 100644 docs/_extensions/mcanouil/atelier/_modules/metadata.lua create mode 100644 docs/_extensions/mcanouil/atelier/_modules/string.lua create mode 100644 docs/_extensions/mcanouil/atelier/social-metadata.lua delete mode 100644 docs/assets/filters/social-metadata.lua diff --git a/docs/_extensions/mcanouil/atelier/_extension.yml b/docs/_extensions/mcanouil/atelier/_extension.yml index f69aabb..a2dae67 100644 --- a/docs/_extensions/mcanouil/atelier/_extension.yml +++ b/docs/_extensions/mcanouil/atelier/_extension.yml @@ -1,14 +1,47 @@ title: Atelier author: Mickaël Canouil -version: 0.3.0 -quarto-required: '>=1.9.36' -source: mcanouil/quarto-atelier@0.3.0 -source-type: github +version: 0.5.0 +quarto-required: ">=1.9.36" contributes: + project: + project: + type: website + output-dir: _site + website: + repo-actions: [edit, issue] + repo-link-target: _blank + repo-link-rel: noopener noreferrer + # `locale` matches `lang: en-GB` below; override the two together. + # Naming `twitter-card` at all is what switches Quarto's Twitter + # provider on, after which it inherits title, description, image, and + # image alt from each page. + open-graph: + locale: en_GB + twitter-card: + card-style: summary_large_image + page-navigation: true + back-to-top-navigation: true + llms-txt: true + search: + location: navbar + type: overlay + navbar: + background: dark + foreground: light + search: true + page-footer: + border: true + background: dark + foreground: light + left: | + Powered by [Quarto](https://quarto.org){target="_blank" rel="noopener noreferrer"}. + center: | + © []{#current-year} [Mickaël CANOUIL](https://mickael.canouil.fr){target="_blank" rel="noopener noreferrer"}. + format: atelier-html formats: common: lang: en-GB - date-format: dddd[, the] Do [of] MMMM, YYYY + date-format: "dddd[, the] Do [of] MMMM, YYYY" code-copy: true code-overflow: wrap code-link: false @@ -32,6 +65,8 @@ contributes: sidebar-width: 300px body-width: 1000px margin-width: 300px + filters: + - social-metadata.lua include-before-body: - file: html/scripts/skip-link.html include-after-body: @@ -40,34 +75,3 @@ contributes: - file: html/scripts/ordinal-dates.html - file: html/scripts/a11y-fixes.html - file: html/scripts/navbar-tooltips.html - project: - project: - type: website - output-dir: _site - website: - repo-actions: - - edit - - issue - repo-link-target: _blank - repo-link-rel: noopener noreferrer - open-graph: true - page-navigation: true - back-to-top-navigation: true - llms-txt: true - search: - location: navbar - type: overlay - navbar: - background: dark - foreground: light - search: true - page-footer: - border: true - background: dark - foreground: light - left: | - Powered by [Quarto](https://quarto.org){target="_blank" rel="noopener noreferrer"}. - center: > - © []{#current-year} [Mickaël CANOUIL](https://mickael.canouil.fr){target="_blank" rel="noopener - noreferrer"}. - format: atelier-html diff --git a/docs/_extensions/mcanouil/atelier/_modules/logging.lua b/docs/_extensions/mcanouil/atelier/_modules/logging.lua new file mode 100644 index 0000000..a538809 --- /dev/null +++ b/docs/_extensions/mcanouil/atelier/_modules/logging.lua @@ -0,0 +1,62 @@ +--- MC Logging - Formatted log output for Quarto Lua filters and shortcodes +--- @module "logging" +--- @license MIT +--- @copyright 2026 Mickaël Canouil +--- @author Mickaël Canouil +--- @version 1.0.0 + +local M = {} + +-- ============================================================================ +-- LOGGING UTILITIES +-- ============================================================================ + +--- Format and log an error message with extension prefix. +--- Provides standardised error messages with consistent formatting across extensions. +--- Format: [extension-name] Message with details. +--- +--- @param extension_name string The name of the extension (e.g., "external", "lua-env") +--- @param message string The error message to display +--- @usage M.log_error("external", "Could not open file 'example.md'.") +function M.log_error(extension_name, message) + quarto.log.error('[' .. extension_name .. '] ' .. message) +end + +--- Format and log a warning message with extension prefix. +--- Provides standardised warning messages with consistent formatting across extensions. +--- Format: [extension-name] Message with details. +--- +--- @param extension_name string The name of the extension (e.g., "external", "lua-env") +--- @param message string The warning message to display +--- @usage M.log_warning("lua-env", "No variable name provided.") +function M.log_warning(extension_name, message) + quarto.log.warning('[' .. extension_name .. '] ' .. message) +end + +--- Format and log an output message with extension prefix. +--- Provides standardised informational messages with consistent formatting across extensions. +--- Format: [extension-name] Message with details. +--- +--- @param extension_name string The name of the extension (e.g., "lua-env") +--- @param message string The informational message to display +--- @usage M.log_output("lua-env", "Exported metadata to: output.json") +function M.log_output(extension_name, message) + quarto.log.output('[' .. extension_name .. '] ' .. message) +end + +--- Format and log a debug message with extension prefix. +--- Provides standardised debug messages with consistent formatting across extensions. +--- Format: [extension-name] Message with details. +--- +--- @param extension_name string The name of the extension (e.g., "lua-env") +--- @param message string The debug message to display +--- @usage M.log_debug("lua-env", "Variable 'x' has value: 42") +function M.log_debug(extension_name, message) + quarto.log.debug('[' .. extension_name .. '] ' .. message) +end + +-- ============================================================================ +-- MODULE EXPORT +-- ============================================================================ + +return M diff --git a/docs/_extensions/mcanouil/atelier/_modules/metadata.lua b/docs/_extensions/mcanouil/atelier/_modules/metadata.lua new file mode 100644 index 0000000..bab2087 --- /dev/null +++ b/docs/_extensions/mcanouil/atelier/_modules/metadata.lua @@ -0,0 +1,182 @@ +--- MC Metadata - Extension configuration and metadata access for Quarto Lua filters and shortcodes +--- @module "metadata" +--- @license MIT +--- @copyright 2026 Mickaël Canouil +--- @author Mickaël Canouil +--- @version 1.0.0 + +local M = {} + +--- Load a sibling module from the same directory as this file. +--- @param filename string The sibling module filename (e.g., 'string.lua') +--- @return table The loaded module +local function load_sibling(filename) + local source = debug.getinfo(1, 'S').source:sub(2) + local dir = source:match('(.*[/\\])') or '' + return require((dir .. filename):gsub('%.lua$', '')) +end + +--- Load required modules +local str = load_sibling('string.lua') +local log = load_sibling('logging.lua') + +-- ============================================================================ +-- METADATA UTILITIES +-- ============================================================================ + +--- Get configuration from extensions.name namespace. +--- @param meta table Document metadata +--- @param extension_name string The extension name (e.g., "github", "iconify") +--- @return any The value/table or nil +--- @usage local value = M.get_extension_config(meta, 'section-outline') +function M.get_extension_config(meta, extension_name) + local config_ext = meta.extensions and meta.extensions[extension_name] + if not config_ext then return nil end + return config_ext +end + +--- Extract metadata value from document meta using nested structure. +--- Supports the extensions.{extension-name}.{key} pattern. +--- @param meta table The document metadata table +--- @param extension_name string The extension name (e.g., "github", "iconify") +--- @param key string The metadata key to retrieve +--- @return string|nil The metadata value as a string, or nil if not found +--- @usage local repo = M.get_metadata_value(meta, "github", "repository-name") +function M.get_metadata_value(meta, extension_name, key) + if meta['extensions'] and meta['extensions'][extension_name] and meta['extensions'][extension_name][key] then + return str.stringify(meta['extensions'][extension_name][key]) + end + return nil +end + +--- Check for deprecated top-level configuration and emit warning +--- @param meta table The document metadata table +--- @param extension_name string The extension name +--- @param key string|nil The configuration key being accessed (nil to check entire extension config) +--- @param deprecation_warning_shown boolean Flag to track if warning has been shown +--- @return any|nil The value from deprecated config, or nil if not found +--- @return boolean Updated deprecation warning flag +function M.check_deprecated_config(meta, extension_name, key, deprecation_warning_shown) + -- Handle array-based configuration (when key is nil) + if key == nil then + if not str.is_empty(meta[extension_name]) then + if not deprecation_warning_shown then + log.log_warning( + extension_name, + 'Top-level "' .. extension_name .. '" configuration is deprecated. ' .. + 'Please use:\n' .. + 'extensions:\n' .. + ' ' .. extension_name .. ':\n' .. + ' - (configuration array)' + ) + deprecation_warning_shown = true + end + return meta[extension_name], deprecation_warning_shown + end + return nil, deprecation_warning_shown + end + + -- Handle key-value configuration (original behaviour) + if not str.is_empty(meta[extension_name]) and not str.is_empty(meta[extension_name][key]) then + if not deprecation_warning_shown then + log.log_warning( + extension_name, + 'Top-level "' .. extension_name .. '" configuration is deprecated. ' .. + 'Please use:\n' .. + 'extensions:\n' .. + ' ' .. extension_name .. ':\n' .. + ' ' .. key .. ': value' + ) + deprecation_warning_shown = true + end + return str.stringify(meta[extension_name][key]), deprecation_warning_shown + end + return nil, deprecation_warning_shown +end + +-- ============================================================================ +-- ENHANCED METADATA/CONFIGURATION UTILITIES +-- ============================================================================ + +--- Get option value with fallback hierarchy: args -> extensions.{extension}.{key} -> defaults. +--- Provides a standardised way to read configuration values with multiple fallback levels. +--- Priority: 1. Named arguments (kwargs), 2. Document metadata, 3. Default values. +--- +--- @param spec table Configuration spec with fields: extension (string), key (string), args (table|nil), meta (table|nil), default (any|nil) +--- @return any The resolved option value (type depends on what's stored in config) +--- @usage local duration = M.get_option_with_fallbacks({extension = 'animate', key = 'duration', args = kwargs, meta = meta, default = '3s'}) +function M.get_option_with_fallbacks(spec) + -- Validate required fields + if not spec.extension or not spec.key then + error("Configuration spec must include 'extension' and 'key' fields") + end + + --- @type string The extension name + local extension = spec.extension + --- @type string The configuration key + local key = spec.key + --- @type table|nil Named arguments table + local args = spec.args + --- @type table|nil Document metadata + local meta = spec.meta + --- @type any Default value if not found elsewhere + local default = spec.default + + -- Priority 1: Check named arguments (kwargs) + if args and args[key] then + local arg_value = str.stringify(args[key]) + if not str.is_empty(arg_value) then + return arg_value + end + end + + -- Priority 2: Check metadata extensions.{extension}.{key} + if meta then + local meta_value = M.get_metadata_value(meta, extension, key) + if not str.is_empty(meta_value) then + return meta_value + end + end + + -- Priority 3: Return default value + return default +end + +--- Get multiple option values at once with fallback hierarchy. +--- Batch version of get_option_with_fallbacks for retrieving multiple configuration values. +--- Returns a table mapping each key to its resolved value. +--- +--- @param spec table Configuration spec with fields: extension (string), keys (table), args (table|nil), meta (table|nil), defaults (table|nil) +--- @return table Table mapping each key to its resolved value +--- @usage local opts = M.get_options({extension = 'animate', keys = {'duration', 'delay'}, args = kwargs, meta = meta, defaults = {duration = '3s', delay = '2s'}}) +function M.get_options(spec) + -- Validate required fields + if not spec.extension or not spec.keys then + error("Configuration spec must include 'extension' and 'keys' fields") + end + + --- @type table Result table + local result = {} + + --- @type table Default values table + local defaults = spec.defaults or {} + + -- Get each key using the single-option fallback logic + for _, key in ipairs(spec.keys) do + result[key] = M.get_option_with_fallbacks({ + extension = spec.extension, + key = key, + args = spec.args, + meta = spec.meta, + default = defaults[key] + }) + end + + return result +end + +-- ============================================================================ +-- MODULE EXPORT +-- ============================================================================ + +return M diff --git a/docs/_extensions/mcanouil/atelier/_modules/string.lua b/docs/_extensions/mcanouil/atelier/_modules/string.lua new file mode 100644 index 0000000..8f89e0b --- /dev/null +++ b/docs/_extensions/mcanouil/atelier/_modules/string.lua @@ -0,0 +1,198 @@ +--- MC String - String manipulation and escaping for Quarto Lua filters and shortcodes +--- @module "string" +--- @license MIT +--- @copyright 2026 Mickaël Canouil +--- @author Mickaël Canouil +--- @version 1.0.0 + +local M = {} + +-- ============================================================================ +-- STRING UTILITIES +-- ============================================================================ + +--- Pandoc utility function for converting values to strings +--- @type function +M.stringify = pandoc.utils.stringify + +--- Check if a string is empty or nil. +--- Utility function to determine if a value is empty or nil, +--- which is useful for parameter validation throughout the module. +--- @param s string|nil|table The value to check for emptiness +--- @return boolean True if the value is nil or empty, false otherwise +--- @usage local result = M.is_empty("") -- returns true +--- @usage local result = M.is_empty(nil) -- returns true +--- @usage local result = M.is_empty("hello") -- returns false +function M.is_empty(s) + return s == nil or s == '' +end + +--- Escape special pattern characters in a string for Lua pattern matching +--- @param s string The string to escape +--- @return string The escaped string +--- @usage local escaped = M.escape_pattern("user/repo#123") +function M.escape_pattern(s) + local escaped = s:gsub('([%^%$%(%)%%%.%[%]%*%+%-%?])', '%%%1') + return escaped +end + +--- Split a string by a separator +--- @param str string The string to split +--- @param sep string The separator pattern +--- @return table Array of string fields +--- @usage local parts = M.split("a.b.c", ".") +function M.split(str, sep) + local fields = {} + local pattern = string.format('([^%s]+)', sep) + str:gsub(pattern, function(c) fields[#fields + 1] = c end) + return fields +end + +--- Trim leading and trailing whitespace from a string +--- @param str string The string to trim +--- @return string The trimmed string +--- @usage local trimmed = M.trim(" hello world ") -- returns "hello world" +function M.trim(str) + if str == nil then return '' end + return str:match('^%s*(.-)%s*$') +end + +--- Convert any value to a string, handling Pandoc objects and empty values. +--- Returns nil for empty or nil values, otherwise returns a string representation. +--- @param val any The value to convert +--- @return string|nil The string value or nil if empty +--- @usage local str = M.to_string(kwargs.value) +function M.to_string(val) + if not val then return nil end + if type(val) == 'string' then + return val ~= '' and val or nil + end + -- Handle Pandoc objects + if pandoc and pandoc.utils and pandoc.utils.stringify then + local str = pandoc.utils.stringify(val) + return str ~= '' and str or nil + end + local str = tostring(val) + return str ~= '' and str or nil +end + +-- ============================================================================ +-- ESCAPE UTILITIES +-- ============================================================================ + +--- Escape special LaTeX characters in text. +--- @param text string The text to escape +--- @return string The escaped text safe for LaTeX +function M.escape_latex(text) + text = string.gsub(text, '\\', '\\textbackslash{}') + text = string.gsub(text, '%{', '\\{') + text = string.gsub(text, '%}', '\\}') + text = string.gsub(text, '%$', '\\$') + text = string.gsub(text, '%&', '\\&') + text = string.gsub(text, '%%', '\\%%') + text = string.gsub(text, '%#', '\\#') + text = string.gsub(text, '%^', '\\textasciicircum{}') + text = string.gsub(text, '%_', '\\_') + text = string.gsub(text, '~', '\\textasciitilde{}') + return text +end + +--- Escape special Typst characters in text. +--- @param text string The text to escape +--- @return string The escaped text safe for Typst +function M.escape_typst(text) + text = string.gsub(text, '%#', '\\#') + return text +end + +--- Escape characters for Typst string literals (inside `"..."`). +--- @param text string The text to escape +--- @return string The escaped text safe for Typst string literals +function M.escape_typst_string(text) + return text:gsub('\\', '\\\\'):gsub('"', '\\"') +end + +--- Escape special Lua pattern characters for use in string.gsub. +--- @param text string The text containing characters to escape +--- @return string The escaped text safe for Lua patterns +function M.escape_lua_pattern(text) + text = string.gsub(text, '%%', '%%%%') + text = string.gsub(text, '%^', '%%^') + text = string.gsub(text, '%$', '%%$') + text = string.gsub(text, '%(', '%%(') + text = string.gsub(text, '%)', '%%)') + text = string.gsub(text, '%.', '%%.') + text = string.gsub(text, '%[', '%%[') + text = string.gsub(text, '%]', '%%]') + text = string.gsub(text, '%*', '%%*') + text = string.gsub(text, '%+', '%%+') + text = string.gsub(text, '%-', '%%-') + text = string.gsub(text, '%?', '%%?') + return text +end + +--- Escape special HTML characters in text. +--- Escapes &, <, >, ", and ' to prevent XSS and ensure valid HTML. +--- @param text string The text to escape +--- @return string Escaped text safe for use in HTML +--- @usage local escaped = M.escape_html('Hello ') +function M.escape_html(text) + if text == nil then return '' end + if type(text) ~= 'string' then text = tostring(text) end + local result = text + :gsub('&', '&') + :gsub('<', '<') + :gsub('>', '>') + :gsub('"', '"') + :gsub("'", ''') + return result +end + +--- Escape special HTML attribute characters. +--- Escapes characters that could break attribute values. +--- @param value string The attribute value to escape +--- @return string Escaped value safe for use in HTML attributes +--- @usage local escaped = M.escape_attribute('Hello "World"') +function M.escape_attribute(value) + if value == nil then return '' end + if type(value) ~= 'string' then value = tostring(value) end + local result = value + :gsub('&', '&') + :gsub('"', '"') + :gsub('<', '<') + :gsub('>', '>') + return result +end + +--- Escape text for different formats. +--- @param text string The text to escape +--- @param format string The format to escape for (e.g., "latex", "typst", "lua") +--- @return string The escaped text +function M.escape_text(text, format) + local escape_functions = { + latex = M.escape_latex, + typst = M.escape_typst, + lua = M.escape_lua_pattern + } + + local escape = escape_functions[format] + if escape then + return escape(text) + else + error('Unsupported escape format: ' .. format) + end +end + +--- Converts a string to a valid HTML id by lowercasing and replacing spaces. +--- @param text string The text to convert +--- @return string The HTML id +function M.ascii_id(text) + local id = text:lower():gsub('[^a-z0-9 ]', ''):gsub(' +', '-') + return id +end + +-- ============================================================================ +-- MODULE EXPORT +-- ============================================================================ + +return M diff --git a/docs/_extensions/mcanouil/atelier/social-metadata.lua b/docs/_extensions/mcanouil/atelier/social-metadata.lua new file mode 100644 index 0000000..dedb521 --- /dev/null +++ b/docs/_extensions/mcanouil/atelier/social-metadata.lua @@ -0,0 +1,219 @@ +--- Atelier - Social Metadata Filter +--- @module "social-metadata" +--- @license MIT +--- @copyright 2026 Mickaël Canouil +--- @author Mickaël Canouil +--- @brief Emit the head tags Quarto's website machinery leaves out. +--- @description Quarto's `website.open-graph` covers title, description, +--- image, image dimensions, image alt, locale, and site name, and +--- `website.twitter-card` covers the Twitter equivalents. Neither emits +--- `og:type` or `og:url`, there is no canonical link, `` +--- needs the pandoc `description-meta` variable that Quarto never populates, +--- and the icon and manifest links beyond `rel="icon"` have no configuration +--- key. This filter fills those gaps and nothing else. + +--- Extension name constant +local EXTENSION_NAME = 'atelier' + +--- Load modules +local str = require(quarto.utils.resolve_path('_modules/string.lua'):gsub('%.lua$', '')) +local log = require(quarto.utils.resolve_path('_modules/logging.lua'):gsub('%.lua$', '')) +local meta_mod = require(quarto.utils.resolve_path('_modules/metadata.lua'):gsub('%.lua$', '')) + +--- Link tags built from the configured paths, in head order. +--- `option` names the `extensions.atelier` key that supplies the `href`; +--- `type` and `sizes` are optional and emitted only when present. +--- @type table +local ICON_LINKS = { + { option = 'icon', rel = 'icon', type = 'image/svg+xml' }, + { option = 'apple-touch-icon', rel = 'apple-touch-icon', sizes = '180x180' }, + { option = 'manifest', rel = 'manifest' }, +} + +--- Colour schemes read from the `theme-color` option, in head order. +--- @type string[] +local THEME_COLOUR_SCHEMES = { 'light', 'dark' } + +--- Stem of the page Quarto renders for missing paths. It is served from any +--- URL depth, so it must not claim a canonical URL of its own. +--- @type string +local NOT_FOUND_STEM = '404' + +--- Stem of a directory index, served from its directory rather than from the +--- file itself. +--- @type string +local INDEX_STEM = 'index' + +--- The input file, relative to the project root, with forward slashes. +--- @return string|nil The relative path, or nil outside a project context +local function project_relative_input() + local project = quarto.project.directory + local input = quarto.doc.input_file + if not project or not input then + return nil + end + return (pandoc.path.make_relative(input, project):gsub('\\', '/')) +end + +--- Whether the page is the project's 404 page. +--- @param relative_input string The input path relative to the project root +--- @return boolean +local function is_not_found_page(relative_input) + return relative_input:match('^' .. NOT_FOUND_STEM .. '%.%w+$') ~= nil +end + +--- The page path as the site serves it, relative to the site root. +--- A directory index is served from its directory rather than from +--- `index.html`, and that is the URL a visitor lands on and the one a scraper +--- de-duplicates against, so `index.qmd` maps to the site root and +--- `/index.qmd` to `/`. +--- @param relative_input string The input path relative to the project root +--- @return string The served path, empty at the site root +local function served_path(relative_input) + local stem = relative_input:gsub('%.%w+$', '') + if stem == INDEX_STEM then + return '' + end + local directory = stem:match('^(.*)/' .. INDEX_STEM .. '$') + if directory then + return directory .. '/' + end + return stem .. '.html' +end + +--- Build the canonical URL for the page. +--- Uses `extensions.atelier.site-url`, because Quarto keeps the `website` +--- block out of the metadata it hands to Lua filters. Anchor the two together +--- in `_quarto.yml` so the URL is written once. +--- @param meta table The document metadata +--- @param relative_input string The input path relative to the project root +--- @return string|nil The absolute URL, or nil when `site-url` is unset +local function canonical_url(meta, relative_input) + local site_url = meta_mod.get_metadata_value(meta, EXTENSION_NAME, 'site-url') + if str.is_empty(site_url) then + return nil + end + return (site_url:gsub('/+$', '')) .. '/' .. served_path(relative_input) +end + +--- Render one `` tag. +--- The `href` is written exactly as configured, relative to the site root. +--- Quarto's website resource resolver rewrites every `link[href]` it finds in +--- the rendered page, prefixing the page's own offset to the project root, so +--- adding one here too would double it on any page below the root. The 404 +--- page is rewritten to a site-absolute path by the same pass. +--- @param link table One entry of `ICON_LINKS` +--- @param href string The configured path, relative to the site root +--- @return string +local function link_tag(link, href) + local attributes = { string.format('rel="%s"', link.rel) } + if link.type then + table.insert(attributes, string.format('type="%s"', link.type)) + end + if link.sizes then + table.insert(attributes, string.format('sizes="%s"', link.sizes)) + end + table.insert(attributes, string.format('href="%s"', str.escape_attribute(href))) + return '' +end + +--- Give pandoc a `description-meta` so it emits ``. +--- Falls back to `subtitle`, which most pages carry; `description` wins when +--- both are set. Left alone when the document sets it itself. +--- @param meta table The document metadata, modified in place +--- @return nil +local function set_description_meta(meta) + if meta['description-meta'] then + return + end + local source = meta.description or meta.subtitle + if source then + meta['description-meta'] = pandoc.MetaString(str.stringify(source)) + end +end + +--- Collect the icon and manifest link tags for the page. +--- @param config table|nil The `extensions.atelier` configuration table +--- @return table +local function icon_tags(config) + local tags = {} + if not config then + return tags + end + for _, link in ipairs(ICON_LINKS) do + local href = config[link.option] and str.stringify(config[link.option]) + if not str.is_empty(href) then + table.insert(tags, link_tag(link, href)) + end + end + return tags +end + +--- Collect the `theme-color` tags for the page. +--- @param config table|nil The `extensions.atelier` configuration table +--- @return table +local function theme_colour_tags(config) + local tags = {} + local colours = config and config['theme-color'] + if not colours then + return tags + end + for _, scheme in ipairs(THEME_COLOUR_SCHEMES) do + local colour = colours[scheme] and str.stringify(colours[scheme]) + if not str.is_empty(colour) then + table.insert( + tags, + string.format( + '', + str.escape_attribute(colour), + scheme + ) + ) + end + end + return tags +end + +--- @param meta table The document metadata +--- @return table|nil +local function social_metadata(meta) + if not quarto.doc.is_format('html:js') then + return nil + end + + set_description_meta(meta) + + local relative_input = project_relative_input() + if not relative_input then + log.log_warning(EXTENSION_NAME, 'No project context; skipping the social metadata head tags.') + return meta + end + + local config = meta_mod.get_extension_config(meta, EXTENSION_NAME) + local tags = { '' } + + if not is_not_found_page(relative_input) then + local url = canonical_url(meta, relative_input) + if url then + local escaped = str.escape_attribute(url) + table.insert(tags, string.format('', escaped)) + table.insert(tags, string.format('', escaped)) + end + end + + for _, tag in ipairs(theme_colour_tags(config)) do + table.insert(tags, tag) + end + + for _, tag in ipairs(icon_tags(config)) do + table.insert(tags, tag) + end + + quarto.doc.include_text('in-header', table.concat(tags, '\n')) + + return meta +end + +return { + { Meta = social_metadata } +} diff --git a/docs/_quarto.yml b/docs/_quarto.yml index 0c17437..caf060d 100644 --- a/docs/_quarto.yml +++ b/docs/_quarto.yml @@ -24,15 +24,11 @@ website: favicon: favicon.ico # The leading slash marks a project-relative path, which Quarto turns into # `/assets/social/og-image.png` and measures to fill the image - # width and height tags. Setting `twitter-card` at all is what switches the - # Twitter provider on; it then inherits title, description, image, and image - # alt from each page. No `creator` or `site`: there is no X account to name. + # width and height tags. The card inherits into both Open Graph and the + # Twitter card, which atelier switches on; no `creator` or `site` handles, + # since there is no X account to name. image: /assets/social/og-image.png image-alt: "The Quarto Codespaces mark, a document outline with an amber chevron, beside the title Quarto Codespaces on a dark navy background." - open-graph: - locale: en_GB - twitter-card: - card-style: summary_large_image page-footer: right: | Licence: [MIT](https://github.com/mcanouil/quarto-codespaces?tab=MIT-1-ov-file#readme). @@ -52,13 +48,6 @@ metadata-files: - _sidebar-getting-started.yml - _sidebar-reference.yml -# Quarto keeps the `website` block out of the metadata it hands to Lua filters, -# so `assets/filters/social-metadata.lua` cannot read `website.site-url` to -# build the canonical URL. The anchor above republishes the same scalar as -# plain document metadata, which does reach the filter, without a second copy -# of the string. -site-url: *site-url - brand: _brand.yml # The `atelier` project type sets `format: atelier-html`. Declaring `html` @@ -79,11 +68,22 @@ format: filters: - assets/filters/devcontainer-configurations.lua - - assets/filters/social-metadata.lua - at: post-quarto path: gitlink extensions: + atelier: + # Quarto keeps the `website` block out of the metadata it hands to Lua + # filters, so atelier's social-metadata filter cannot read + # `website.site-url` to build the canonical URL. The anchor above + # republishes the same scalar here, without a second copy of the string. + site-url: *site-url + icon: assets/icons/icon.svg + apple-touch-icon: assets/icons/apple-touch-icon.png + manifest: site.webmanifest + theme-color: + light: "#F5F7FA" + dark: "#0B1220" gitlink: # Widget only: the platform and repository come from `website.repo-url`, # and in-text reference rewriting stays off. diff --git a/docs/assets/filters/social-metadata.lua b/docs/assets/filters/social-metadata.lua deleted file mode 100644 index 71906c8..0000000 --- a/docs/assets/filters/social-metadata.lua +++ /dev/null @@ -1,173 +0,0 @@ ---- @module social-metadata ---- @license MIT ---- @copyright 2026 Mickaël Canouil ---- @author Mickaël Canouil ---- ---- Emit the head tags Quarto's website machinery does not. ---- ---- `website.open-graph` covers title, description, image, image dimensions, ---- image alt, locale, and site name, and `website.twitter-card` covers the ---- Twitter equivalents. Neither emits `og:type` or `og:url`, there is no ---- canonical link, and `` needs the pandoc ---- `description-meta` variable, which Quarto never populates. The icon and ---- manifest links beyond `rel="icon"` have no configuration key either. ---- ---- This filter fills exactly those gaps: ---- ---- - `description-meta`, from `description` or `subtitle`, so every page ---- gets a plain description tag; ---- - `og:type`, `og:url`, and ``, built from the ---- top-level `site-url`, in the same form as the generated sitemap; ---- - the SVG icon, Apple touch icon, and manifest links, plus `theme-color`. ---- ---- Paths are written relative to the page, so they resolve under ---- `quarto preview` at the server root as well as under the deployed ---- `/quarto-codespaces/` prefix. On `404.html` Quarto rewrites them to ---- site-absolute paths itself, since a 404 is served from any depth. - ---- Icon and manifest links, in head order. `href` is relative to the site root. ---- @type table> -local ICON_LINKS = { - { rel = "icon", type = "image/svg+xml", href = "assets/icons/icon.svg" }, - { rel = "apple-touch-icon", sizes = "180x180", href = "assets/icons/apple-touch-icon.png" }, - { rel = "manifest", href = "site.webmanifest" }, -} - ---- Browser chrome colours, from the brand palette: frost and midnight. ---- @type table> -local THEME_COLOURS = { - { colour = "#F5F7FA", scheme = "light" }, - { colour = "#0B1220", scheme = "dark" }, -} - ---- The 404 page is served for any missing path, so it must not claim a ---- canonical URL of its own. ---- @type table -local NO_CANONICAL = { ["404.qmd"] = true } - ---- Escape a value for use inside a double-quoted HTML attribute. ---- @param value string The raw attribute value ---- @return string -local function escape_attribute(value) - return value - :gsub("&", "&") - :gsub("<", "<") - :gsub(">", ">") - :gsub('"', """) -end - ---- The input file, relative to the project root, with forward slashes. ---- @return string|nil The relative path, or nil outside a project context -local function project_relative_input() - local project = quarto.project.directory - local input = quarto.doc.input_file - if not project or not input then - return nil - end - return (pandoc.path.make_relative(input, project):gsub("\\", "/")) -end - ---- Prefix that walks from the page back to the site root, with a trailing ---- slash. Quarto builds its own `website.favicon` href from the same offset. ---- Empty at the root, so hrefs stay free of a redundant `./`: Quarto prefixes ---- the site path on `404.html`, and `/quarto-codespaces/./icon.svg` is ugly. ---- @return string The offset, with a trailing slash, or an empty string -local function site_root_prefix() - local offset = quarto.project.offset - if not offset or offset == "." then - return "" - end - return offset .. "/" -end - ---- Build the canonical URL for the page. ---- Uses the top-level `site-url`, which `_quarto.yml` anchors to ---- `website.site-url`; the `website` block itself never reaches Lua. ---- @param meta table The document metadata ---- @param relative_input string The input path relative to the project root ---- @return string|nil The absolute URL, or nil when `site-url` is unset -local function canonical_url(meta, relative_input) - if not meta["site-url"] then - return nil - end - local site_url = pandoc.utils.stringify(meta["site-url"]):gsub("/+$", "") - local page = relative_input:gsub("%.%w+$", ".html") - return site_url .. "/" .. page -end - ---- Render one `` tag. ---- @param link table One entry of `ICON_LINKS` ---- @param offset string The offset to the site root, with a trailing slash ---- @return string -local function link_tag(link, offset) - local attributes = { string.format('rel="%s"', link.rel) } - if link.type then - table.insert(attributes, string.format('type="%s"', link.type)) - end - if link.sizes then - table.insert(attributes, string.format('sizes="%s"', link.sizes)) - end - table.insert(attributes, string.format('href="%s%s"', offset, link.href)) - return "" -end - ---- Give pandoc a `description-meta` so it emits ``. ---- Every page here carries a subtitle; `description` wins when both are set. ---- @param meta table The document metadata, modified in place ---- @return nil -local function set_description_meta(meta) - if meta["description-meta"] then - return - end - local source = meta.description or meta.subtitle - if source then - meta["description-meta"] = pandoc.MetaString(pandoc.utils.stringify(source)) - end -end - ---- @param meta table The document metadata ---- @return table|nil -function Meta(meta) - if not quarto.doc.is_format("html:js") then - return nil - end - - set_description_meta(meta) - - local relative_input = project_relative_input() - if not relative_input then - quarto.log.warning("[social-metadata] no project context; skipping the head tags") - return meta - end - - local offset = site_root_prefix() - local tags = { '' } - - if not NO_CANONICAL[relative_input] then - local url = canonical_url(meta, relative_input) - if url then - local escaped = escape_attribute(url) - table.insert(tags, string.format('', escaped)) - table.insert(tags, string.format('', escaped)) - end - end - - for _, theme in ipairs(THEME_COLOURS) do - table.insert( - tags, - string.format( - '', - theme.colour, - theme.scheme - ) - ) - end - - for _, link in ipairs(ICON_LINKS) do - table.insert(tags, link_tag(link, offset)) - end - - quarto.doc.include_text("in-header", table.concat(tags, "\n")) - - return meta -end From 25ed4d3433da8d9178646ae905eb0e07e49ab320 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micka=C3=ABl=20Canouil?= <8896044+mcanouil@users.noreply.github.com> Date: Sun, 26 Jul 2026 22:02:41 +0200 Subject: [PATCH 6/6] chore(deps): fix source --- .../mcanouil/atelier/_extension.yml | 75 ++++++++++--------- 1 file changed, 38 insertions(+), 37 deletions(-) diff --git a/docs/_extensions/mcanouil/atelier/_extension.yml b/docs/_extensions/mcanouil/atelier/_extension.yml index a2dae67..7a7d88e 100644 --- a/docs/_extensions/mcanouil/atelier/_extension.yml +++ b/docs/_extensions/mcanouil/atelier/_extension.yml @@ -1,47 +1,14 @@ title: Atelier author: Mickaël Canouil version: 0.5.0 -quarto-required: ">=1.9.36" +quarto-required: '>=1.9.36' +source: mcanouil/quarto-atelier@0.5.0 +source-type: github contributes: - project: - project: - type: website - output-dir: _site - website: - repo-actions: [edit, issue] - repo-link-target: _blank - repo-link-rel: noopener noreferrer - # `locale` matches `lang: en-GB` below; override the two together. - # Naming `twitter-card` at all is what switches Quarto's Twitter - # provider on, after which it inherits title, description, image, and - # image alt from each page. - open-graph: - locale: en_GB - twitter-card: - card-style: summary_large_image - page-navigation: true - back-to-top-navigation: true - llms-txt: true - search: - location: navbar - type: overlay - navbar: - background: dark - foreground: light - search: true - page-footer: - border: true - background: dark - foreground: light - left: | - Powered by [Quarto](https://quarto.org){target="_blank" rel="noopener noreferrer"}. - center: | - © []{#current-year} [Mickaël CANOUIL](https://mickael.canouil.fr){target="_blank" rel="noopener noreferrer"}. - format: atelier-html formats: common: lang: en-GB - date-format: "dddd[, the] Do [of] MMMM, YYYY" + date-format: dddd[, the] Do [of] MMMM, YYYY code-copy: true code-overflow: wrap code-link: false @@ -75,3 +42,37 @@ contributes: - file: html/scripts/ordinal-dates.html - file: html/scripts/a11y-fixes.html - file: html/scripts/navbar-tooltips.html + project: + project: + type: website + output-dir: _site + website: + repo-actions: + - edit + - issue + repo-link-target: _blank + repo-link-rel: noopener noreferrer + open-graph: + locale: en_GB + twitter-card: + card-style: summary_large_image + page-navigation: true + back-to-top-navigation: true + llms-txt: true + search: + location: navbar + type: overlay + navbar: + background: dark + foreground: light + search: true + page-footer: + border: true + background: dark + foreground: light + left: | + Powered by [Quarto](https://quarto.org){target="_blank" rel="noopener noreferrer"}. + center: > + © []{#current-year} [Mickaël CANOUIL](https://mickael.canouil.fr){target="_blank" rel="noopener + noreferrer"}. + format: atelier-html