From dca7e6234dc2e54bdff213b0dc172b889e7e48aa Mon Sep 17 00:00:00 2001 From: Cindy <37343722+cinxmo@users.noreply.github.com> Date: Mon, 20 Nov 2023 17:39:28 -0500 Subject: [PATCH 01/11] toc without highlighting --- docs/.observablehq/config.ts | 3 +- docs/getting-started.md | 43 +++++++++++++++ docs/loaders.md | 5 ++ public/client.js | 14 +++++ public/style.css | 48 +++++++++++++++-- src/build.ts | 5 +- src/config.ts | 6 +++ src/preview.ts | 4 +- src/render.ts | 44 ++++++++++++++- .../build/config/.observablehq/config.ts | 5 +- test/input/build/config/toc-override.md | 15 ++++++ test/input/build/config/toc.md | 14 +++++ test/output/build/config/toc-override.html | 54 +++++++++++++++++++ test/output/build/config/toc.html | 54 +++++++++++++++++++ 14 files changed, 304 insertions(+), 10 deletions(-) create mode 100644 test/input/build/config/toc-override.md create mode 100644 test/input/build/config/toc.md create mode 100644 test/output/build/config/toc-override.html create mode 100644 test/output/build/config/toc.html diff --git a/docs/.observablehq/config.ts b/docs/.observablehq/config.ts index f74bd135c..16efa72a1 100644 --- a/docs/.observablehq/config.ts +++ b/docs/.observablehq/config.ts @@ -28,5 +28,6 @@ export default { ] }, {name: "Contributing", path: "/contributing"} - ] + ], + toc: {label: "Contents"} }; diff --git a/docs/getting-started.md b/docs/getting-started.md index 3a971a6e6..1806620e7 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -1,3 +1,8 @@ +--- +toc: + show: true +--- + # Getting started The Observable CLI is a Node.js application and is published to npm as [`@observablehq/cli`](https://www.npmjs.com/package/@observablehq/cli). As the name suggests, the CLI lives on the command line; the instructions below are intended to run in your [terminal](https://support.apple.com/guide/terminal/open-or-quit-terminal-apd5265185d-f365-44cb-8b09-71a064a42125/mac). You’ll need to install [Node.js 18 or later](https://nodejs.org/) before you can install the CLI. @@ -88,3 +93,41 @@ observable build Creates `dist`. You can use `npx http-server dist` to preview your built site. + +## Configuration + +Add a `config.js` or `config.ts` file under the `.observablehq` directory. Example config: + +``` +{ + title: "Hello World", + pages: [ + {name: "Getting started", path: "/getting-started"}, + { + name: "JavaScript", + pages: [ + {name: "Reactivity", path: "/javascript/reactivity"}, + {name: "Display", path: "/javascript/display"}, + ] + } + ], + toc: { + label: "Contents" + show: true + } +} +``` + +You can configure: + +### `title` + +Customize the title on the left sidebar. + +### `pages` + +A page has a name and path. The page path corresponds to the path of the `.md` file from your root directory. For example, if `docs` is your root directory, the `docs/javascript.md` file corresponds to the `/javascript` path. + +### `table of contents on a page` + +`label` is the name of the TOC (table of contents) section. Setting `show` to `true` renders the TOC globally with `h2` tags. \ No newline at end of file diff --git a/docs/loaders.md b/docs/loaders.md index 3f06fcc59..93760c865 100644 --- a/docs/loaders.md +++ b/docs/loaders.md @@ -1,3 +1,8 @@ +--- +toc: + show: true +--- + # Data loaders **Data loaders** generate files — typically static snapshots of data — at build time. For example, a data loader might query a database and output a CSV or Parquet file, or server-side render a chart and output a PNG image. diff --git a/public/client.js b/public/client.js index f9b452f16..7b9d85e0b 100644 --- a/public/client.js +++ b/public/client.js @@ -352,3 +352,17 @@ function enableCopyButtons() { async function copy({currentTarget}) { await navigator.clipboard.writeText(currentTarget.parentElement.textContent.trimEnd()); } + +document.addEventListener("DOMContentLoaded", () => { + if (location.hash) highlight(location.hash); +}); + +window.addEventListener("hashchange", () => { + highlight(location.hash); +}); + +export function highlight(hash) { + const currentSelected = document.querySelector("li.observablehq-secondary-link-active"); + if (currentSelected) currentSelected.classList.remove("observablehq-secondary-link-active"); + document.querySelector(`li a[href="${hash}"]`).parentElement.classList.add("observablehq-secondary-link-active"); +} diff --git a/public/style.css b/public/style.css index 7cc6ec280..71786e2a4 100644 --- a/public/style.css +++ b/public/style.css @@ -229,11 +229,52 @@ body { color: var(--theme-foreground-focus); } -.observablehq-link a[href] { + +#observablehq-toc { + display: none; + position: fixed; + top: 1rem; + right: 1rem; + padding-left: 2rem; + padding-right: 2rem; + border-left: solid 1px var(--theme-foreground-faintest); +} + +@media (min-width: calc(1152px + 240px)) { + /** TODO - mobile view **/ + #observablehq-toc { + display: block; + } +} + +#observablehq-toc nav ol { + margin: 0; + padding-inline-start: 5px; +} + +#observablehq-toc div { + font: 14px var(--sans-serif); + color: var(--theme-foreground-mute); + margin-bottom: 8px; +} + +#observablehq-toc nav ol li { + font: 14px var(--sans-serif); + font-weight: 350; + line-height: 1.5; + list-style-type: none; + color: var(--theme-foreground-faint); +} + +.observablehq-link a[href], +.observablehq-secondary-link a[href] + { color: inherit; } -.observablehq-link-active { +.observablehq-link-active, +.observablehq-secondary-link-active + { position: relative; } @@ -248,7 +289,8 @@ body { } a[href], -.observablehq-link-active a[href] { +.observablehq-link-active a[href], +.observablehq-secondary-link-active a[href] { color: var(--theme-foreground-focus); } diff --git a/src/build.ts b/src/build.ts index e33fce372..5beb66499 100644 --- a/src/build.ts +++ b/src/build.ts @@ -31,7 +31,7 @@ export async function build(context: CommandContext = makeCommandContext()) { // Render .md files, building a list of file attachments as we go. const pages = await readPages(sourceRoot); - const title = (await readConfig(sourceRoot))?.title; + const config = await readConfig(sourceRoot); const files: string[] = []; const imports: string[] = []; const resolver = await makeCLIResolver(); @@ -44,7 +44,8 @@ export async function build(context: CommandContext = makeCommandContext()) { root: sourceRoot, path, pages, - title, + title: config?.title, + toc: config?.toc, resolver }); const resolveFile = ({name}) => join(name.startsWith("/") ? "." : dirname(sourceFile), name); diff --git a/src/config.ts b/src/config.ts index 2821afb39..933cc6de0 100644 --- a/src/config.ts +++ b/src/config.ts @@ -12,9 +12,15 @@ export interface Section { pages: Page[]; } +export interface TableOfContents { + label?: string; + show?: boolean; +} + export interface Config { title?: string; pages?: (Page | Section)[]; // TODO rename to sidebar? + toc?: TableOfContents; } export async function readConfig(root: string): Promise { diff --git a/src/preview.ts b/src/preview.ts index 1b5e2c30b..9f54d40e2 100644 --- a/src/preview.ts +++ b/src/preview.ts @@ -127,11 +127,13 @@ class Server { // Anything else should 404; static files should be matched above. try { pages = await readPages(this.root); // TODO cache? watcher? + const config = await readConfig(this.root); const {html} = await renderPreview(await readFile(path + ".md", "utf-8"), { root: this.root, path: pathname, pages, - title: (await readConfig(this.root))?.title, + title: config?.title, + toc: config?.toc, resolver: this._resolver! }); end(req, res, html, "text/html"); diff --git a/src/render.ts b/src/render.ts index ad09fa625..a52734b42 100644 --- a/src/render.ts +++ b/src/render.ts @@ -1,4 +1,5 @@ import {dirname, join} from "node:path"; +import {parseHTML} from "linkedom"; import {type Config, type Page, type Section} from "./config.js"; import {computeHash} from "./hash.js"; import {resolveImport} from "./javascript/imports.js"; @@ -52,7 +53,7 @@ type RenderInternalOptions = function render( parseResult: ParseResult, - {path, pages, title, preview, hash, resolver}: RenderOptions & RenderInternalOptions + {path, pages, title, toc: tocConfig, preview, hash, resolver}: RenderOptions & RenderInternalOptions ): string { return ` ${path === "/404" ? `\n` : ""} @@ -86,7 +87,7 @@ ${JSON.stringify(parseResult.data)} ` : "" } -${pages.length > 0 ? sidebar(title, pages, path) : ""} +${pages.length > 0 ? sidebar(title, pages, path) : ""}${tableOfContents(parseResult, tocConfig)}
${parseResult.html}
@@ -144,6 +145,45 @@ function sidebar(title: string | undefined, pages: (Page | Section)[], path: str }`; } +function tableOfContentsSections( + parseResult: ParseResult, + globalConfig?: Config["toc"] +): {label: string; headers: string[]} { + const pageConfig = parseResult.data?.toc; + const pageShow = pageConfig?.show; + const globalShow = globalConfig?.show; + const headers: string[] = []; + if (pageShow || globalShow) { + headers.push("h2"); + } + return {label: pageConfig?.label ?? globalConfig?.label ?? "Sections", headers}; +} + +function tableOfContents(parseResult: ParseResult, tocConfig: RenderOptions["toc"]) { + const toc = tableOfContentsSections(parseResult, tocConfig); + let showToc = toc.headers.length > 0; + let headers; + if (showToc) { + headers = Array.from(parseHTML(parseResult.html).document.querySelectorAll(toc.headers.join(", "))).map((node) => ({ + label: node.firstElementChild?.textContent, + href: node.firstElementChild?.getAttribute("href") + })); + } + showToc = showToc && headers?.length > 0; + return showToc + ? `
+
${toc.label}
+
` + : ""; +} + function renderListItem(p: Page, path: string): string { return `
  • + + +H1: Section + + + + + + + + +
    +
    TOC
    +
    +
    +
    +

    H1: Section

    +

    H2: Section 1

    +

    Some text here

    +

    H2: Section 2

    +

    Some text here again

    +

    H3: Section 1

    +
    +
    +
    © 2023 Observable, Inc.
    +
    +
    diff --git a/test/output/build/config/toc.html b/test/output/build/config/toc.html new file mode 100644 index 000000000..0b99ff118 --- /dev/null +++ b/test/output/build/config/toc.html @@ -0,0 +1,54 @@ + + + +H1: Section + + + + + + + + +
    +
    Contents
    +
    +
    +
    +

    H1: Section

    +

    H2: Section 1

    +

    Some text here

    +

    H2: Section 2

    +

    Some text here again

    +

    H3: Section 1

    +
    +
    +
    © 2023 Observable, Inc.
    +
    +
    From 6eb7b0d6e65e59df517175fa59e07a8d97709f7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Rivi=C3=A8re?= Date: Tue, 21 Nov 2023 11:21:17 +0100 Subject: [PATCH 02/11] no export; avoid an error when the hash is not managed by the toc --- public/client.js | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/public/client.js b/public/client.js index 7b9d85e0b..89195af3f 100644 --- a/public/client.js +++ b/public/client.js @@ -354,15 +354,13 @@ async function copy({currentTarget}) { } document.addEventListener("DOMContentLoaded", () => { - if (location.hash) highlight(location.hash); -}); - -window.addEventListener("hashchange", () => { - highlight(location.hash); + if (location.hash) highlightToc(location.hash); + window.addEventListener("hashchange", () => { + highlightToc(location.hash); + }); + function highlightToc(hash) { + const currentSelected = document.querySelector("li.observablehq-secondary-link-active"); + if (currentSelected) currentSelected.classList.remove("observablehq-secondary-link-active"); + document.querySelector(`li a[href="${hash}"]`)?.parentElement.classList.add("observablehq-secondary-link-active"); + } }); - -export function highlight(hash) { - const currentSelected = document.querySelector("li.observablehq-secondary-link-active"); - if (currentSelected) currentSelected.classList.remove("observablehq-secondary-link-active"); - document.querySelector(`li a[href="${hash}"]`).parentElement.classList.add("observablehq-secondary-link-active"); -} From 42ba187219756d7fadb4609f4e3f4262914fed7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Rivi=C3=A8re?= Date: Tue, 21 Nov 2023 11:23:59 +0100 Subject: [PATCH 03/11] =?UTF-8?q?Adopt=20defaults=20=E2=80=94=20only=20ski?= =?UTF-8?q?p=20the=20toc=20for=20the=20(hero)=20home=20page?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/.observablehq/config.ts | 4 +++- docs/getting-started.md | 5 ----- docs/index.md | 5 +++++ docs/loaders.md | 5 ----- src/render.ts | 2 +- 5 files changed, 9 insertions(+), 12 deletions(-) diff --git a/docs/.observablehq/config.ts b/docs/.observablehq/config.ts index 16efa72a1..be13066c7 100644 --- a/docs/.observablehq/config.ts +++ b/docs/.observablehq/config.ts @@ -29,5 +29,7 @@ export default { }, {name: "Contributing", path: "/contributing"} ], - toc: {label: "Contents"} + toc: { + show: true + } }; diff --git a/docs/getting-started.md b/docs/getting-started.md index 1806620e7..7378cdef6 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -1,8 +1,3 @@ ---- -toc: - show: true ---- - # Getting started The Observable CLI is a Node.js application and is published to npm as [`@observablehq/cli`](https://www.npmjs.com/package/@observablehq/cli). As the name suggests, the CLI lives on the command line; the instructions below are intended to run in your [terminal](https://support.apple.com/guide/terminal/open-or-quit-terminal-apd5265185d-f365-44cb-8b09-71a064a42125/mac). You’ll need to install [Node.js 18 or later](https://nodejs.org/) before you can install the CLI. diff --git a/docs/index.md b/docs/index.md index 2fde9455a..d56f2a903 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,3 +1,8 @@ +--- +toc: + show: false +--- + diff --git a/docs/loaders.md b/docs/loaders.md index 93760c865..3f06fcc59 100644 --- a/docs/loaders.md +++ b/docs/loaders.md @@ -1,8 +1,3 @@ ---- -toc: - show: true ---- - # Data loaders **Data loaders** generate files — typically static snapshots of data — at build time. For example, a data loader might query a database and output a CSV or Parquet file, or server-side render a chart and output a PNG image. diff --git a/src/render.ts b/src/render.ts index a52734b42..bb2f454d1 100644 --- a/src/render.ts +++ b/src/render.ts @@ -156,7 +156,7 @@ function tableOfContentsSections( if (pageShow || globalShow) { headers.push("h2"); } - return {label: pageConfig?.label ?? globalConfig?.label ?? "Sections", headers}; + return {label: pageConfig?.label ?? globalConfig?.label ?? "Contents", headers}; } function tableOfContents(parseResult: ParseResult, tocConfig: RenderOptions["toc"]) { From 763e1ca36537c32a46b77417378d7a9f27b41d5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Rivi=C3=A8re?= Date: Tue, 21 Nov 2023 11:25:46 +0100 Subject: [PATCH 04/11] escape strings --- src/render.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/render.ts b/src/render.ts index bb2f454d1..998a0618b 100644 --- a/src/render.ts +++ b/src/render.ts @@ -176,7 +176,7 @@ function tableOfContents(parseResult: ParseResult, tocConfig: RenderOptions["toc
      ${headers .map( ({label, href}) => `` ) .join("")}
    From 4c3bb58d9b9c27972ce7c9cf3c6fe1d3655361bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Rivi=C3=A8re?= Date: Tue, 21 Nov 2023 11:30:16 +0100 Subject: [PATCH 05/11] move the toc to the observablehq-center --- src/render.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/render.ts b/src/render.ts index 998a0618b..2ff95c207 100644 --- a/src/render.ts +++ b/src/render.ts @@ -53,8 +53,9 @@ type RenderInternalOptions = function render( parseResult: ParseResult, - {path, pages, title, toc: tocConfig, preview, hash, resolver}: RenderOptions & RenderInternalOptions + {path, pages, title, toc, preview, hash, resolver}: RenderOptions & RenderInternalOptions ): string { + const table = tableOfContents(parseResult, toc); return ` ${path === "/404" ? `\n` : ""} @@ -87,10 +88,12 @@ ${JSON.stringify(parseResult.data)} ` : "" } -${pages.length > 0 ? sidebar(title, pages, path) : ""}${tableOfContents(parseResult, tocConfig)} +${pages.length > 0 ? sidebar(title, pages, path) : ""}
    +${table}
    -${parseResult.html}
    +${parseResult.html} + ${footer(path, {pages, title})}
    `; From 80e7ebfce5586c4486709f86896c98c28da76263 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Rivi=C3=A8re?= Date: Tue, 21 Nov 2023 11:31:26 +0100 Subject: [PATCH 06/11] refactor toc generation --- src/render.ts | 57 +++++++++++++++++++-------------------------------- 1 file changed, 21 insertions(+), 36 deletions(-) diff --git a/src/render.ts b/src/render.ts index 2ff95c207..fa70bfbe3 100644 --- a/src/render.ts +++ b/src/render.ts @@ -148,42 +148,27 @@ function sidebar(title: string | undefined, pages: (Page | Section)[], path: str }`; } -function tableOfContentsSections( - parseResult: ParseResult, - globalConfig?: Config["toc"] -): {label: string; headers: string[]} { - const pageConfig = parseResult.data?.toc; - const pageShow = pageConfig?.show; - const globalShow = globalConfig?.show; - const headers: string[] = []; - if (pageShow || globalShow) { - headers.push("h2"); - } - return {label: pageConfig?.label ?? globalConfig?.label ?? "Contents", headers}; -} - -function tableOfContents(parseResult: ParseResult, tocConfig: RenderOptions["toc"]) { - const toc = tableOfContentsSections(parseResult, tocConfig); - let showToc = toc.headers.length > 0; - let headers; - if (showToc) { - headers = Array.from(parseHTML(parseResult.html).document.querySelectorAll(toc.headers.join(", "))).map((node) => ({ - label: node.firstElementChild?.textContent, - href: node.firstElementChild?.getAttribute("href") - })); - } - showToc = showToc && headers?.length > 0; - return showToc - ? `
    -
    ${toc.label}
    -
    ` +function tableOfContents(parseResult: ParseResult, toc: RenderOptions["toc"]) { + const pageTocConfig = parseResult.data?.toc; + const headers = + (pageTocConfig?.show ?? toc?.show) && + Array.from(parseHTML(parseResult.html).document.querySelectorAll("h2")) + .map((node) => ({ + label: node.textContent, + href: node.firstElementChild?.getAttribute("href") + })) + .filter((d) => d.label && d.href); + return headers?.length + ? `
    +${pageTocConfig?.label ?? toc?.label ?? "Contents"} + +
    ` : ""; } From 013d6e0e6358466a96ef3acc101fbed101a22224 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Rivi=C3=A8re?= Date: Tue, 21 Nov 2023 11:32:56 +0100 Subject: [PATCH 07/11] position & details>summary --- public/style.css | 37 ++++++++++++++++++++++++++++--------- src/render.ts | 2 +- 2 files changed, 29 insertions(+), 10 deletions(-) diff --git a/public/style.css b/public/style.css index 71786e2a4..cda395d0a 100644 --- a/public/style.css +++ b/public/style.css @@ -231,19 +231,38 @@ body { #observablehq-toc { - display: none; - position: fixed; - top: 1rem; + display: none; /** TODO - mobile view **/ + width: 6em; + position: absolute; right: 1rem; + background: var(--theme-background); padding-left: 2rem; padding-right: 2rem; - border-left: solid 1px var(--theme-foreground-faintest); + user-select: none; +} +#observablehq-toc:not([open]) summary span { + display: none; +} + +#observablehq-toc summary::marker { + content: ''; +} +#observablehq-toc ::-webkit-details-marker { + display: none; } -@media (min-width: calc(1152px + 240px)) { - /** TODO - mobile view **/ +@media (min-width: calc(1152px + 6em)) { #observablehq-toc { display: block; + position: fixed; + top: 1rem; + border-left: solid 1px var(--theme-foreground-faintest); + } + #observablehq-toc summary { + pointer-events: none; + } + #observablehq-main.has-toc { + padding-right: 11em; } } @@ -252,14 +271,14 @@ body { padding-inline-start: 5px; } -#observablehq-toc div { - font: 14px var(--sans-serif); +#observablehq-toc>* { color: var(--theme-foreground-mute); margin-bottom: 8px; + font: 14px var(--sans-serif); } #observablehq-toc nav ol li { - font: 14px var(--sans-serif); + font: 12px var(--sans-serif); font-weight: 350; line-height: 1.5; list-style-type: none; diff --git a/src/render.ts b/src/render.ts index fa70bfbe3..2db0a52d1 100644 --- a/src/render.ts +++ b/src/render.ts @@ -91,7 +91,7 @@ ${JSON.stringify(parseResult.data)} ${pages.length > 0 ? sidebar(title, pages, path) : ""}
    ${table} -
    +
    ${parseResult.html}
    ${footer(path, {pages, title})} From 304855d47ecdf29d09efef6c880d893b09994185 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Rivi=C3=A8re?= Date: Tue, 21 Nov 2023 11:33:19 +0100 Subject: [PATCH 08/11] nit: space --- public/style.css | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/public/style.css b/public/style.css index cda395d0a..3a82b19fa 100644 --- a/public/style.css +++ b/public/style.css @@ -286,14 +286,12 @@ body { } .observablehq-link a[href], -.observablehq-secondary-link a[href] - { +.observablehq-secondary-link a[href] { color: inherit; } .observablehq-link-active, -.observablehq-secondary-link-active - { +.observablehq-secondary-link-active { position: relative; } From d2b36602307ee9f82ca5d9af55c469b160074ce0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Rivi=C3=A8re?= Date: Tue, 21 Nov 2023 11:39:46 +0100 Subject: [PATCH 09/11] fix tests, minimize diff, test escaping --- src/render.ts | 13 ++++++------- test/input/build/config/toc.md | 5 ++++- test/output/build/config/toc-override.html | 18 +++++++++--------- test/output/build/config/toc.html | 20 +++++++++++--------- 4 files changed, 30 insertions(+), 26 deletions(-) diff --git a/src/render.ts b/src/render.ts index 2db0a52d1..d8d38f349 100644 --- a/src/render.ts +++ b/src/render.ts @@ -90,10 +90,8 @@ ${JSON.stringify(parseResult.data)} } ${pages.length > 0 ? sidebar(title, pages, path) : ""}
    -${table} -
    -${parseResult.html} -
    +${table}
    +${parseResult.html}
    ${footer(path, {pages, title})}
    `; @@ -164,11 +162,12 @@ function tableOfContents(parseResult: ParseResult, toc: RenderOptions["toc"]) { -` +\n` : ""; } diff --git a/test/input/build/config/toc.md b/test/input/build/config/toc.md index da1d1e6b8..ac5b5b6f1 100644 --- a/test/input/build/config/toc.md +++ b/test/input/build/config/toc.md @@ -11,4 +11,7 @@ Some text here ## H2: Section 2 Some text here again -### H3: Section 1 \ No newline at end of file +### H3: Section 1 + +## H2 <script>alert(1)</script> not nice + diff --git a/test/output/build/config/toc-override.html b/test/output/build/config/toc-override.html index 8154586ee..ab3f81a12 100644 --- a/test/output/build/config/toc-override.html +++ b/test/output/build/config/toc-override.html @@ -31,16 +31,16 @@ const initialState = localStorage.getItem("observablehq-sidebar"); if (initialState) toggle.checked = initialState === "true"; else toggle.indeterminate = true; -}
    -
    TOC
    -
    +}
    -
    +
    +TOC + +
    +

    H1: Section

    H2: Section 1

    Some text here

    diff --git a/test/output/build/config/toc.html b/test/output/build/config/toc.html index 0b99ff118..f327c6534 100644 --- a/test/output/build/config/toc.html +++ b/test/output/build/config/toc.html @@ -31,22 +31,24 @@ const initialState = localStorage.getItem("observablehq-sidebar"); if (initialState) toggle.checked = initialState === "true"; else toggle.indeterminate = true; -}
    -
    Contents
    -
    +}
    -
    +
    +Contents + +
    +

    H1: Section

    H2: Section 1

    Some text here

    H2: Section 2

    Some text here again

    H3: Section 1

    +

    H2 <script>alert(1)</script> not nice