diff --git a/docs/config.md b/docs/config.md index 2dd9107c4..110c99a71 100644 --- a/docs/config.md +++ b/docs/config.md @@ -154,15 +154,21 @@ Whether to show the previous & next links in the footer; defaults to true. The p ## head -An HTML fragment to add to the head. Defaults to the empty string. +An HTML fragment to add to the head. Defaults to the empty string. If specified as a function, receives an object with the page’s `title`, (front-matter) `data`, and `path`, and must return a string. ## header -An HTML fragment to add to the header. Defaults to the empty string. +An HTML fragment to add to the header. Defaults to the empty string. If specified as a function, receives an object with the page’s `title`, (front-matter) `data`, and `path`, and must return a string. ## footer -An HTML fragment to add to the footer. Defaults to “Built with Observable.” +An HTML fragment to add to the footer. Defaults to “Built with Observable.” If specified as a function, receives an object with the page’s `title`, (front-matter) `data`, and `path`, and must return a string. + +For example, the following adds a link to the bottom of each page: + +```js run=false +footer: ({path}) => `view source`, +``` ## base diff --git a/src/config.ts b/src/config.ts index 6b7b7502e..f915a1851 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,19 @@ export interface Script { type: string | null; } +/** + * A function that generates a page fragment such as head, header or footer. + */ +export type PageFragmentFunction = ({ + title, + data, + path +}: { + title: string | null; + data: FrontMatter; + path: string; +}) => string | null; + export interface Config { root: string; // defaults to src output: string; // defaults to dist @@ -52,9 +66,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 +219,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 +261,10 @@ function getPathNormalizer(spec: unknown = true): (path: string) => string { }; } +function pageFragment(spec: unknown): PageFragmentFunction | string | null { + 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) => `` +}; diff --git a/test/output/build/fragments/index.html b/test/output/build/fragments/index.html new file mode 100644 index 000000000..0bf7c1903 --- /dev/null +++ b/test/output/build/fragments/index.html @@ -0,0 +1,34 @@ + + + +Testing fragment functions + + + + + + + + + + + +
+
+ +
+
+

Display title

+

Contents.

+
+ +