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
+
+
+
+
+
+
+
+
+
+
+
+