Skip to content

Commit 2566e07

Browse files
committed
PageToc generation now covers regor markup on build time and runtime.
1 parent 550c204 commit 2566e07

50 files changed

Lines changed: 1517 additions & 485 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎frontend/docs/documentationLinks.ts‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ export function addDocumentationLinks(document: Document) {
1414
link.setAttribute('aria-label', `Link to ${heading.textContent?.trim()}`)
1515
link.setAttribute('title', 'Link to this section')
1616
link.setAttribute('data-pagefind-ignore', '')
17-
link.textContent = '#'
17+
// The decorative mark comes from CSS so client TOC refresh reads only
18+
// the heading's actual title. The link keeps its accessible name above.
1819
heading.appendChild(link)
1920
}
2021
}

‎frontend/docs/pageTocStyles.ts‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { styleBuilder, themes } from '@purestack/ts-style'
2+
3+
export function registerTocPlaygroundStyles() {
4+
themes.forEach((theme) => {
5+
styleBuilder.select('.toc-playground__frame', theme).height('42rem')
6+
})
7+
}

‎frontend/plugin.ts‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { registerApiReferenceStyles } from './docs/apiReferenceStyles'
44
import { registerComponentGuideStyles } from './docs/componentGuide'
55
import { defineDocumentationComponents } from './docs/docsComponents'
66
import { addDocumentationLinks } from './docs/documentationLinks'
7+
import { registerTocPlaygroundStyles } from './docs/pageTocStyles'
78
import { previewTemplate } from './docs/previewTemplate'
89
import { sitePreviewPages } from './docs/sitePreviews'
910
import { ensureLegalContent } from './ensureLegalContent'
@@ -38,6 +39,7 @@ export const frontendPlugin = definePlugin({
3839
registerLandingStyles()
3940
registerApiReferenceStyles()
4041
registerComponentGuideStyles()
42+
registerTocPlaygroundStyles()
4143
registerDocumentationStyles()
4244
registerPaletteStyles()
4345
registerPalettePickerStyles()

‎frontend/purestack.studio/components/site/index.mdx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ Twelve guides connect branding, navigation, search, theme, accounts, and privacy
115115
</div>
116116
<h3>Page<wbr/>Toc</h3>
117117
<Badge tone="neutral">Document outline</Badge>
118-
<p>Nested and flat headings, empty state, and layout controls.</p>
118+
<p>Rendered headings, client refresh, and an empty-page playground.</p>
119119
<a href="/components/site/page-toc/">Explore PageToc ↗</a>
120120
</article>
121121
<article class="component-guide-card">
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<RegorApp
2+
id="page-toc-playground"
3+
class="d-block min-w-0"
4+
data-toc-playground
5+
src="./app.ts"/>
6+
<noscript
7+
>Enable JavaScript to try client refresh. The rest of this guide works without
8+
it.</noscript>
9+
<span class="consent-settings-teleport-area hidden"></span>
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<h3 id="toc-html-heading">A heading written as HTML</h3>
2+
<p>This h3 has an authored ID. Its TOC link points to #toc-html-heading.</p>
3+
4+
<SectionHeader
5+
title="A heading rendered by a component"
6+
titleTag="h3"
7+
titleId="toc-component-heading"
8+
titleClass="fs-lg"/>
9+
<p>
10+
SectionHeader emits the heading during SSG. It participates in the same
11+
outline.
12+
</p>
13+
14+
<section r-for="section in pageContext.pageInfo.frontmatter.tocExamples">
15+
<h3 :id="section.id">{{ section.title }}</h3>
16+
<p>This section comes from a Regor loop over the page's frontmatter.</p>
17+
</section>
18+
19+
### {{ pageContext.pageInfo.frontmatter.tocEvaluatedTitle }}
20+
21+
The label and generated ID use the evaluated title above.
22+
23+
<h3 r-if="false">This conditional heading is never rendered</h3>
24+
25+
<div class="p-3 b-1 rounded-md tone-border-surface" data-toc-ignore>
26+
<h3 class="mt-0">An example deliberately outside the TOC</h3>
27+
<p class="mb-0">
28+
This heading is visible, but its containing element has data-toc-ignore. It
29+
has no entry in the sidebar.
30+
</p>
31+
</div>
32+
33+
<h4>A detail below the TOC's heading range</h4>
34+
<p>h4, h5 and h6 remain ordinary content. They do not create TOC entries.</p>
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import {
2+
defineButtonComponents,
3+
defineFlexComponents,
4+
defineFormCheckComponent,
5+
defineFormInputField,
6+
definePanelComponents,
7+
} from '@purestack/ts-components'
8+
import { createApp, html } from 'regor'
9+
import { defineTocPlaygroundControls } from './controls'
10+
import { defineTocPlayground } from './playground'
11+
import { defineTocPlaygroundSection } from './section'
12+
13+
const components = {
14+
TocPlayground: defineTocPlayground(),
15+
TocPlaygroundControls: defineTocPlaygroundControls(),
16+
TocPlaygroundSection: defineTocPlaygroundSection(),
17+
FormCheck: defineFormCheckComponent(),
18+
...defineButtonComponents(),
19+
...defineFlexComponents(),
20+
...defineFormInputField(),
21+
...definePanelComponents(),
22+
}
23+
24+
for (const mount of document.querySelectorAll<HTMLElement>(
25+
'app[data-toc-playground]',
26+
)) {
27+
createApp(
28+
{ components, prefix: mount.id },
29+
{
30+
element: mount,
31+
template: html`<TocPlayground :prefix="prefix"/>`,
32+
},
33+
)
34+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
---
2+
title: Client-only TOC playground
3+
description: Start without server headings, then create and manage a table of contents from browser content.
4+
template: doc
5+
index: false
6+
nav:
7+
hidden: true
8+
layout:
9+
showNav: false
10+
showToc: always
11+
showFooter: false
12+
---
13+
14+
<p class="text-eyebrow">PageToc live example</p>
15+
<p class="text-title">An empty page, ready for client headings</p>
16+
17+
This page has no server-rendered h1, h2 or h3. Its frontmatter sets `layout.showToc: always`, so the table of contents starts with “No sections yet.” Open **on this page** if the panel is collapsed.
18+
19+
Add a section and follow its TOC link. The section uses the textbox value, then the textbox advances to the next name by adding or incrementing its trailing number. Rename, reorder, exclude or remove sections. Turn off automatic refresh to see that the TOC changes only when you explicitly refresh it. Clear everything to return to the empty state; the textbox keeps its current value.
20+
21+
<import-content src="./_playground.mdx"/>
22+
23+
[Return to the PageToc guide](./page-toc.mdx#try-client-updates).
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import { defineComponent, html } from 'regor'
2+
import { TocPlayground } from './playground'
3+
4+
export interface TocPlaygroundControls {
5+
playground: TocPlayground
6+
}
7+
8+
const controlsTemplate = html`<Panel variant="surface" bodyClass="p-4" data-toc-ignore>
9+
<Flex direction="column" class="gap-4">
10+
<FormInputField
11+
:id="playground.prefix + '-title'"
12+
label="Heading text"
13+
:model="playground.title"/>
14+
<Flex wrap="true" class="gap-2">
15+
<Btn @click="playground.add" data-toc-action="add" tone="accent"
16+
>Add section</Btn>
17+
<Btn
18+
@click="playground.rename"
19+
:disabled="!playground.hasSections"
20+
data-toc-action="rename"
21+
variant="surface"
22+
>Rename last</Btn>
23+
<Btn
24+
@click="playground.reorder"
25+
:disabled="playground.sections.length < 2"
26+
data-toc-action="reorder"
27+
variant="surface"
28+
>Move last to first</Btn>
29+
<Btn
30+
@click="playground.toggleExcluded"
31+
:disabled="!playground.hasSections"
32+
data-toc-action="exclude"
33+
variant="surface"
34+
>{{ playground.lastExcluded ? 'Include last' : 'Exclude last' }}</Btn>
35+
<Btn
36+
@click="playground.remove"
37+
:disabled="!playground.hasSections"
38+
data-toc-action="remove"
39+
variant="surface"
40+
>Remove last</Btn>
41+
<Btn
42+
@click="playground.clear"
43+
:disabled="!playground.hasSections"
44+
data-toc-action="clear"
45+
variant="subtle"
46+
>Clear sections</Btn>
47+
</Flex>
48+
<Flex wrap="true" align="center" class="gap-3">
49+
<FormCheck
50+
:id="playground.prefix + '-automatic'"
51+
label="Refresh after every change"
52+
:checked="playground.automatic"
53+
data-toc-auto-refresh/>
54+
<Btn
55+
@click="playground.refresh"
56+
data-toc-action="refresh"
57+
variant="outlineFill"
58+
>Refresh TOC</Btn>
59+
</Flex>
60+
<p
61+
class="text-muted fs-sm m-0"
62+
role="status"
63+
aria-live="polite"
64+
aria-atomic="true"
65+
data-toc-status
66+
>
67+
{{ playground.status }}
68+
</p>
69+
</Flex>
70+
</Panel>`
71+
72+
export function defineTocPlaygroundControls() {
73+
return defineComponent<TocPlaygroundControls>(controlsTemplate, {
74+
context: (head) => ({ playground: head.requireContext(TocPlayground) }),
75+
})
76+
}

0 commit comments

Comments
 (0)