From 07d3669eeade1fd18c072c4a28dba4ebc22e61dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Rivi=C3=A8re?= Date: Wed, 15 May 2024 16:06:14 +0200 Subject: [PATCH 1/9] rebase & simplify --- docs/config.md | 2 ++ src/config.ts | 22 ++++++++---- src/markdown.ts | 24 +++++++------ test/input/build/fragments/index.md | 10 ++++++ .../build/fragments/observablehq.config.js | 5 +++ test/output/build/fragments/index.html | 34 +++++++++++++++++++ 6 files changed, 81 insertions(+), 16 deletions(-) create mode 100644 test/input/build/fragments/index.md create mode 100644 test/input/build/fragments/observablehq.config.js create mode 100644 test/output/build/fragments/index.html diff --git a/docs/config.md b/docs/config.md index 2dd9107c4..5989baae5 100644 --- a/docs/config.md +++ b/docs/config.md @@ -164,6 +164,8 @@ An HTML fragment to add to the header. Defaults to the empty string. An HTML fragment to add to the footer. Defaults to “Built with Observable.” +head, header and footer can be specified as strings, or as functions that receive as arguments the page’s title, front matter, and path, and return a string. + ## base The base path when serving the site. Currently this only affects the custom 404 page, if any. diff --git a/src/config.ts b/src/config.ts index 6b7b7502e..a1af0d5d7 100644 --- a/src/config.ts +++ b/src/config.ts @@ -10,6 +10,7 @@ import wrapAnsi from "wrap-ansi"; import {LoaderResolver} from "./dataloader.js"; import {visitMarkdownFiles} from "./files.js"; import {formatIsoDate, formatLocaleDate} from "./format.js"; +import type {FrontMatter} from "./frontMatter.js"; import {createMarkdownIt, parseMarkdownMetadata} from "./markdown.js"; import {isAssetPath, parseRelativeUrl, resolvePath} from "./path.js"; import {resolveTheme} from "./theme.js"; @@ -43,6 +44,11 @@ export interface Script { type: string | null; } +/** + * A function that generates a page fragment such as head, header or footer. + */ +type PageFragmentFunction = (title: string | null, data: FrontMatter, path: string) => string; + export interface Config { root: string; // defaults to src output: string; // defaults to dist @@ -52,9 +58,9 @@ export interface Config { pages: (Page | Section)[]; pager: boolean; // defaults to true scripts: Script[]; // deprecated; defaults to empty array - head: string | null; // defaults to null - header: string | null; // defaults to null - footer: string | null; // defaults to “Built with Observable on [date].” + head: PageFragmentFunction | string | null; // defaults to null + header: PageFragmentFunction | string | null; // defaults to null + footer: PageFragmentFunction | string | null; // defaults to “Built with Observable on [date].” toc: TableOfContents; style: null | Style; // defaults to {theme: ["light", "dark"]} search: boolean; // default to false @@ -205,9 +211,9 @@ export function normalizeConfig(spec: ConfigSpec = {}, defaultRoot?: string, wat const toc = normalizeToc(spec.toc as any); const sidebar = spec.sidebar === undefined ? undefined : Boolean(spec.sidebar); const scripts = spec.scripts === undefined ? [] : normalizeScripts(spec.scripts); - const head = spec.head === undefined ? "" : stringOrNull(spec.head); - const header = spec.header === undefined ? "" : stringOrNull(spec.header); - const footer = spec.footer === undefined ? defaultFooter() : stringOrNull(spec.footer); + const head = pageFragment(spec.head === undefined ? "" : spec.head); + const header = pageFragment(spec.header === undefined ? "" : spec.header); + const footer = pageFragment(spec.footer === undefined ? defaultFooter() : spec.footer); const search = Boolean(spec.search); const interpreters = normalizeInterpreters(spec.interpreters as any); const config: Config = { @@ -247,6 +253,10 @@ function getPathNormalizer(spec: unknown = true): (path: string) => string { }; } +function pageFragment(spec: unknown) { + return typeof spec === "function" ? (spec as PageFragmentFunction) : stringOrNull(spec); +} + function defaultFooter(): string { const date = currentDate ?? new Date(); return `Built with Observable on ``, + header: (data) => ``, + footer: (data) => ``, +}; \ No newline at end of file diff --git a/test/output/build/fragments/index.html b/test/output/build/fragments/index.html new file mode 100644 index 000000000..b86ee585e --- /dev/null +++ b/test/output/build/fragments/index.html @@ -0,0 +1,34 @@ + + + +Testing fragment functions + + + + + + + + + + + +
+
+ +
+
+

Display title

+

Contents.

+
+
+
+
+
From 3bd3dfa5c3036f7495ee9891e8687124004d6a7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Rivi=C3=A8re?= Date: Wed, 15 May 2024 17:08:26 +0200 Subject: [PATCH 2/9] Update src/markdown.ts Co-authored-by: Mike Bostock --- src/markdown.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/markdown.ts b/src/markdown.ts index 64c18a2e6..1268cc18c 100644 --- a/src/markdown.ts +++ b/src/markdown.ts @@ -377,10 +377,8 @@ function getHtml( ? data[key] ? String(data[key]) : null - : typeof defaultValue === "function" - ? rewriteHtmlPaths(defaultValue(title, data, path), path) : defaultValue != null - ? rewriteHtmlPaths(defaultValue, path) + ? rewriteHtmlPaths(typeof defaultValue === "function" ? defaultValue(title, data, path) : defaultValue, path) : null; } From c5c15128c9189b6bbc6466911326928900bba94e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Rivi=C3=A8re?= Date: Wed, 15 May 2024 17:09:02 +0200 Subject: [PATCH 3/9] Update src/config.ts Co-authored-by: Mike Bostock --- src/config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/config.ts b/src/config.ts index a1af0d5d7..59d61629c 100644 --- a/src/config.ts +++ b/src/config.ts @@ -253,7 +253,7 @@ function getPathNormalizer(spec: unknown = true): (path: string) => string { }; } -function pageFragment(spec: unknown) { +function pageFragment(spec: unknown): PageFragmentFunction | string | null { return typeof spec === "function" ? (spec as PageFragmentFunction) : stringOrNull(spec); } From 67155ce5153f85a303617742811b65e01b377f63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Rivi=C3=A8re?= Date: Wed, 15 May 2024 17:16:22 +0200 Subject: [PATCH 4/9] no quotes necessary --- test/input/build/fragments/index.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/input/build/fragments/index.md b/test/input/build/fragments/index.md index ffb5e833e..ce2c909ae 100644 --- a/test/input/build/fragments/index.md +++ b/test/input/build/fragments/index.md @@ -1,8 +1,8 @@ --- -author: "Ignored Anonymous" +author: Ignored Anonymous title: Testing fragment functions date: 2024-04-18 -keywords: ["very", "much"] +keywords: [very, much] --- # Display title From 7585484dbcea4f73af812c90f1f4199884dd613a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Rivi=C3=A8re?= Date: Wed, 15 May 2024 17:16:54 +0200 Subject: [PATCH 5/9] prettier --- test/input/build/fragments/observablehq.config.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/input/build/fragments/observablehq.config.js b/test/input/build/fragments/observablehq.config.js index 2915a7037..c99b7ba06 100644 --- a/test/input/build/fragments/observablehq.config.js +++ b/test/input/build/fragments/observablehq.config.js @@ -1,5 +1,5 @@ export default { head: (data) => ``, header: (data) => ``, - footer: (data) => ``, -}; \ No newline at end of file + footer: (data) => `` +}; From ec10c569e1d3b6921dd0e7dd52be6e028a754eaa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Rivi=C3=A8re?= Date: Wed, 15 May 2024 17:17:22 +0200 Subject: [PATCH 6/9] combine arguments --- src/config.ts | 10 +++++++++- src/markdown.ts | 2 +- test/output/build/fragments/index.html | 6 +++--- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/src/config.ts b/src/config.ts index 59d61629c..23768862e 100644 --- a/src/config.ts +++ b/src/config.ts @@ -47,7 +47,15 @@ export interface Script { /** * A function that generates a page fragment such as head, header or footer. */ -type PageFragmentFunction = (title: string | null, data: FrontMatter, path: string) => string; +export type PageFragmentFunction = ({ + title, + data, + path +}: { + title: string | null; + data: FrontMatter; + path: string; +}) => string; export interface Config { root: string; // defaults to src diff --git a/src/markdown.ts b/src/markdown.ts index 1268cc18c..7b19dcb3f 100644 --- a/src/markdown.ts +++ b/src/markdown.ts @@ -378,7 +378,7 @@ function getHtml( ? String(data[key]) : null : defaultValue != null - ? rewriteHtmlPaths(typeof defaultValue === "function" ? defaultValue(title, data, path) : defaultValue, path) + ? rewriteHtmlPaths(typeof defaultValue === "function" ? defaultValue({title, data, path}) : defaultValue, path) : null; } diff --git a/test/output/build/fragments/index.html b/test/output/build/fragments/index.html index b86ee585e..0bf7c1903 100644 --- a/test/output/build/fragments/index.html +++ b/test/output/build/fragments/index.html @@ -10,7 +10,7 @@ - +