@cogenta/theme-docs 0.3.2 → 0.5.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/index.d.ts +2 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +2 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/render/blocks/accordion.d.ts +8 -0
- package/dist/src/render/blocks/accordion.d.ts.map +1 -1
- package/dist/src/render/blocks/accordion.js +10 -10
- package/dist/src/render/blocks/accordion.js.map +1 -1
- package/dist/src/render/blocks/collection-list.d.ts +3 -11
- package/dist/src/render/blocks/collection-list.d.ts.map +1 -1
- package/dist/src/render/blocks/collection-list.js +69 -68
- package/dist/src/render/blocks/collection-list.js.map +1 -1
- package/dist/src/render/blocks/cta.d.ts +6 -0
- package/dist/src/render/blocks/cta.d.ts.map +1 -1
- package/dist/src/render/blocks/cta.js +9 -4
- package/dist/src/render/blocks/cta.js.map +1 -1
- package/dist/src/render/blocks/embed.d.ts.map +1 -1
- package/dist/src/render/blocks/embed.js +14 -13
- package/dist/src/render/blocks/embed.js.map +1 -1
- package/dist/src/render/blocks/faq.d.ts +7 -0
- package/dist/src/render/blocks/faq.d.ts.map +1 -1
- package/dist/src/render/blocks/faq.js +10 -10
- package/dist/src/render/blocks/faq.js.map +1 -1
- package/dist/src/render/blocks/feature-grid.d.ts.map +1 -1
- package/dist/src/render/blocks/feature-grid.js +19 -19
- package/dist/src/render/blocks/feature-grid.js.map +1 -1
- package/dist/src/render/blocks/gallery.d.ts +8 -4
- package/dist/src/render/blocks/gallery.d.ts.map +1 -1
- package/dist/src/render/blocks/gallery.js +19 -13
- package/dist/src/render/blocks/gallery.js.map +1 -1
- package/dist/src/render/blocks/hero.d.ts +12 -9
- package/dist/src/render/blocks/hero.d.ts.map +1 -1
- package/dist/src/render/blocks/hero.js +22 -20
- package/dist/src/render/blocks/hero.js.map +1 -1
- package/dist/src/render/blocks/logo-strip.d.ts +6 -0
- package/dist/src/render/blocks/logo-strip.d.ts.map +1 -1
- package/dist/src/render/blocks/logo-strip.js +6 -10
- package/dist/src/render/blocks/logo-strip.js.map +1 -1
- package/dist/src/render/blocks/logos.d.ts +10 -0
- package/dist/src/render/blocks/logos.d.ts.map +1 -1
- package/dist/src/render/blocks/logos.js +20 -17
- package/dist/src/render/blocks/logos.js.map +1 -1
- package/dist/src/render/blocks/media-figure.d.ts +0 -6
- package/dist/src/render/blocks/media-figure.d.ts.map +1 -1
- package/dist/src/render/blocks/media-figure.js +28 -16
- package/dist/src/render/blocks/media-figure.js.map +1 -1
- package/dist/src/render/blocks/pricing-table.d.ts.map +1 -1
- package/dist/src/render/blocks/pricing-table.js +16 -20
- package/dist/src/render/blocks/pricing-table.js.map +1 -1
- package/dist/src/render/blocks/prose.d.ts +13 -1
- package/dist/src/render/blocks/prose.d.ts.map +1 -1
- package/dist/src/render/blocks/prose.js +12 -31
- package/dist/src/render/blocks/prose.js.map +1 -1
- package/dist/src/render/blocks/quote.d.ts +5 -3
- package/dist/src/render/blocks/quote.d.ts.map +1 -1
- package/dist/src/render/blocks/quote.js +10 -14
- package/dist/src/render/blocks/quote.js.map +1 -1
- package/dist/src/render/blocks/stat-counter.d.ts +5 -0
- package/dist/src/render/blocks/stat-counter.d.ts.map +1 -1
- package/dist/src/render/blocks/stat-counter.js +6 -10
- package/dist/src/render/blocks/stat-counter.js.map +1 -1
- package/dist/src/render/blocks/stats.d.ts +10 -0
- package/dist/src/render/blocks/stats.d.ts.map +1 -1
- package/dist/src/render/blocks/stats.js +13 -8
- package/dist/src/render/blocks/stats.js.map +1 -1
- package/dist/src/render/blocks/testimonial.d.ts +4 -5
- package/dist/src/render/blocks/testimonial.d.ts.map +1 -1
- package/dist/src/render/blocks/testimonial.js +9 -13
- package/dist/src/render/blocks/testimonial.js.map +1 -1
- package/dist/src/render/chrome.d.ts +36 -1
- package/dist/src/render/chrome.d.ts.map +1 -1
- package/dist/src/render/chrome.js +122 -80
- package/dist/src/render/chrome.js.map +1 -1
- package/dist/src/render/doc-index.d.ts +25 -0
- package/dist/src/render/doc-index.d.ts.map +1 -0
- package/dist/src/render/doc-index.js +49 -0
- package/dist/src/render/doc-index.js.map +1 -0
- package/dist/src/render/doc-page.d.ts +6 -0
- package/dist/src/render/doc-page.d.ts.map +1 -0
- package/dist/src/render/doc-page.js +127 -0
- package/dist/src/render/doc-page.js.map +1 -0
- package/dist/src/render/layout.d.ts +23 -0
- package/dist/src/render/layout.d.ts.map +1 -0
- package/dist/src/render/layout.js +40 -0
- package/dist/src/render/layout.js.map +1 -0
- package/dist/src/render/render-block.d.ts +9 -6
- package/dist/src/render/render-block.d.ts.map +1 -1
- package/dist/src/render/render-block.js +47 -100
- package/dist/src/render/render-block.js.map +1 -1
- package/dist/src/render/rich-text.d.ts +26 -0
- package/dist/src/render/rich-text.d.ts.map +1 -0
- package/dist/src/render/rich-text.js +325 -0
- package/dist/src/render/rich-text.js.map +1 -0
- package/dist/src/render/search.d.ts +15 -0
- package/dist/src/render/search.d.ts.map +1 -0
- package/dist/src/render/search.js +31 -0
- package/dist/src/render/search.js.map +1 -0
- package/dist/src/render/strings.d.ts +51 -0
- package/dist/src/render/strings.d.ts.map +1 -0
- package/dist/src/render/strings.js +60 -0
- package/dist/src/render/strings.js.map +1 -0
- package/dist/src/render/term-archive.d.ts +5 -5
- package/dist/src/render/term-archive.d.ts.map +1 -1
- package/dist/src/render/term-archive.js +27 -16
- package/dist/src/render/term-archive.js.map +1 -1
- package/dist/src/render/widget-areas.d.ts +22 -0
- package/dist/src/render/widget-areas.d.ts.map +1 -0
- package/dist/src/render/widget-areas.js +29 -0
- package/dist/src/render/widget-areas.js.map +1 -0
- package/package.json +4 -4
- package/src/index.ts +2 -0
- package/src/render/blocks/accordion.ts +31 -42
- package/src/render/blocks/collection-list.ts +132 -123
- package/src/render/blocks/cta.ts +23 -18
- package/src/render/blocks/embed.ts +37 -28
- package/src/render/blocks/faq.ts +26 -37
- package/src/render/blocks/feature-grid.ts +34 -42
- package/src/render/blocks/gallery.ts +33 -29
- package/src/render/blocks/hero.ts +41 -45
- package/src/render/blocks/logo-strip.ts +21 -21
- package/src/render/blocks/logos.ts +32 -42
- package/src/render/blocks/media-figure.ts +47 -25
- package/src/render/blocks/pricing-table.ts +34 -41
- package/src/render/blocks/prose.ts +25 -40
- package/src/render/blocks/quote.ts +25 -19
- package/src/render/blocks/stat-counter.ts +27 -29
- package/src/render/blocks/stats.ts +35 -32
- package/src/render/blocks/testimonial.ts +18 -19
- package/src/render/chrome.ts +170 -83
- package/src/render/doc-index.ts +65 -0
- package/src/render/doc-page.ts +303 -0
- package/src/render/layout.ts +73 -0
- package/src/render/render-block.ts +67 -191
- package/src/render/rich-text.ts +446 -0
- package/src/render/search.ts +41 -0
- package/src/render/strings.ts +67 -0
- package/src/render/term-archive.ts +62 -38
- package/src/render/widget-areas.ts +33 -0
- package/src/styles/archive.css +21 -27
- package/src/styles/base.css +438 -838
- package/src/styles/blocks.css +658 -916
- package/src/styles/chrome.css +439 -0
- package/src/styles/docs.css +404 -0
- package/src/styles/rich.css +337 -0
- package/src/styles/theme.css +28 -23
- package/src/styles/tokens.css +209 -215
- package/src/styles/widgets.css +890 -0
- package/tokens.json +15 -15
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type Attributes,
|
|
3
|
+
blockHeadingTag,
|
|
4
|
+
type Child,
|
|
5
|
+
type HtmlElement,
|
|
6
|
+
h,
|
|
7
|
+
heading,
|
|
8
|
+
} from '@cogenta/theme-kit'
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* The frame every block of this theme renders into.
|
|
12
|
+
*
|
|
13
|
+
* The outer element carries the one section rhythm (`--cd-section`, the same
|
|
14
|
+
* for every block) and paints edge to edge when a variant asks for a band.
|
|
15
|
+
* The inner element is the twelve-column container, so a section title, a
|
|
16
|
+
* link, a figure and a caption all start on the same left edge. Inside a
|
|
17
|
+
* documentation page the same markup is laid out in the reading column
|
|
18
|
+
* instead (`docs.css`), so a block placed in an article needs nothing extra.
|
|
19
|
+
*
|
|
20
|
+
* `inner` is `figure` for a block whose semantics are a figure (a quotation
|
|
21
|
+
* and its attribution, an image and its caption): `<figcaption>` must be a
|
|
22
|
+
* direct child of its `<figure>`.
|
|
23
|
+
*/
|
|
24
|
+
export function section(
|
|
25
|
+
outer: 'section' | 'div',
|
|
26
|
+
block: string,
|
|
27
|
+
className: string,
|
|
28
|
+
attrs: Attributes,
|
|
29
|
+
inner: 'div' | 'figure',
|
|
30
|
+
...children: readonly Child[]
|
|
31
|
+
): HtmlElement {
|
|
32
|
+
return h(
|
|
33
|
+
outer,
|
|
34
|
+
{ class: `cd-section ${className}`, 'data-block': block, ...attrs },
|
|
35
|
+
h(inner, { class: `cd-container ${className}__inner` }, ...children),
|
|
36
|
+
)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** A titled block's heading, from the first column, left-aligned with the content under it. */
|
|
40
|
+
export function sectionHead(blockName: string, title: string | undefined): HtmlElement | null {
|
|
41
|
+
if (title === undefined || title.trim() === '') return null
|
|
42
|
+
return h(
|
|
43
|
+
'div',
|
|
44
|
+
{ class: 'cd-head' },
|
|
45
|
+
heading(
|
|
46
|
+
blockHeadingTag(blockName) ?? 'h2',
|
|
47
|
+
{ class: 'cd-head__title', 'data-field': 'title' },
|
|
48
|
+
title,
|
|
49
|
+
),
|
|
50
|
+
)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** A paragraph that renders only when the text is there: no empty `<p>` on the page. */
|
|
54
|
+
export function optionalText(
|
|
55
|
+
tag: string,
|
|
56
|
+
className: string,
|
|
57
|
+
value: string | undefined,
|
|
58
|
+
attrs: Attributes = {},
|
|
59
|
+
): HtmlElement | null {
|
|
60
|
+
if (value === undefined || value.trim() === '') return null
|
|
61
|
+
return h(tag, { class: className, ...attrs }, value)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** `Intl.DateTimeFormat`'s long date in the page's locale: "September 14, 2026". */
|
|
65
|
+
export function longDate(iso: string, locale: string): string {
|
|
66
|
+
try {
|
|
67
|
+
return new Intl.DateTimeFormat(locale, { dateStyle: 'long', timeZone: 'UTC' }).format(
|
|
68
|
+
new Date(iso),
|
|
69
|
+
)
|
|
70
|
+
} catch {
|
|
71
|
+
return iso.slice(0, 10)
|
|
72
|
+
}
|
|
73
|
+
}
|
|
@@ -1,15 +1,12 @@
|
|
|
1
1
|
import { type BlockRegistry, VOCABULARY_NAMES, type VocabularyBlock } from '@cogenta/blocks'
|
|
2
2
|
import {
|
|
3
|
-
type ContentEntry,
|
|
4
|
-
entryHref,
|
|
5
|
-
entryTitle,
|
|
6
3
|
type FetchedEntries,
|
|
7
4
|
type HtmlElement,
|
|
8
5
|
h,
|
|
9
6
|
type PageContent,
|
|
10
7
|
pageHasOwnHeading,
|
|
8
|
+
providedBlockNode,
|
|
11
9
|
type RenderContext,
|
|
12
|
-
renderEntryHeader,
|
|
13
10
|
resolveBlockForRender,
|
|
14
11
|
withBlockKey,
|
|
15
12
|
withBlockVariant,
|
|
@@ -31,31 +28,56 @@ import { renderQuote } from './blocks/quote.js'
|
|
|
31
28
|
import { renderStatCounter } from './blocks/stat-counter.js'
|
|
32
29
|
import { renderStats } from './blocks/stats.js'
|
|
33
30
|
import { renderTestimonial } from './blocks/testimonial.js'
|
|
31
|
+
import { isDocIndexBlock } from './doc-index.js'
|
|
32
|
+
import { renderDocPage } from './doc-page.js'
|
|
33
|
+
import { longDate } from './layout.js'
|
|
34
|
+
import { type HeadingAnchors, headingAnchors } from './rich-text.js'
|
|
34
35
|
|
|
35
36
|
export type { FetchedEntries, PageContent }
|
|
36
37
|
|
|
37
|
-
|
|
38
|
+
function renderResolved(
|
|
38
39
|
block: VocabularyBlock,
|
|
39
40
|
ctx: RenderContext,
|
|
40
|
-
entries: FetchedEntries
|
|
41
|
-
registry
|
|
41
|
+
entries: FetchedEntries,
|
|
42
|
+
registry: BlockRegistry | undefined,
|
|
43
|
+
anchors: HeadingAnchors,
|
|
42
44
|
): HtmlElement | null {
|
|
45
|
+
// A stored block is not always literally one of the shared vocabulary:
|
|
46
|
+
// resolving here turns an unimplemented theme-private block into its
|
|
47
|
+
// declared fallback instead of a silently blank slot.
|
|
43
48
|
const resolved = resolveBlockForRender(block, VOCABULARY_NAMES, registry)
|
|
44
49
|
if (resolved === null) return null
|
|
45
50
|
const known = resolved as unknown as VocabularyBlock
|
|
46
|
-
|
|
51
|
+
// `variant` is envelope data every block carries identically, applied once
|
|
52
|
+
// here rather than by each of the seventeen renderers.
|
|
53
|
+
return withBlockVariant(renderKnownBlock(known, ctx, entries, anchors), known.variant)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function renderBlock(
|
|
57
|
+
block: VocabularyBlock,
|
|
58
|
+
ctx: RenderContext,
|
|
59
|
+
entries: FetchedEntries = {},
|
|
60
|
+
registry?: BlockRegistry,
|
|
61
|
+
): HtmlElement | null {
|
|
62
|
+
// A block a plugin provides arrives already rendered by the host, in a
|
|
63
|
+
// process of its own (contract D theme@1.7). Nothing else in this theme
|
|
64
|
+
// needs to know such blocks exist.
|
|
65
|
+
const provided = providedBlockNode(block, ctx)
|
|
66
|
+
if (provided !== null) return withBlockVariant(provided, block.variant)
|
|
67
|
+
return renderResolved(block, ctx, entries, registry, headingAnchors([block]))
|
|
47
68
|
}
|
|
48
69
|
|
|
49
70
|
function renderKnownBlock(
|
|
50
71
|
known: VocabularyBlock,
|
|
51
72
|
ctx: RenderContext,
|
|
52
73
|
entries: FetchedEntries,
|
|
74
|
+
anchors: HeadingAnchors,
|
|
53
75
|
): HtmlElement | null {
|
|
54
76
|
switch (known._type) {
|
|
55
77
|
case 'hero':
|
|
56
78
|
return renderHero(known, ctx)
|
|
57
79
|
case 'prose':
|
|
58
|
-
return renderProse(known, ctx)
|
|
80
|
+
return renderProse(known, ctx, anchors)
|
|
59
81
|
case 'mediaFigure':
|
|
60
82
|
return renderMediaFigure(known, ctx)
|
|
61
83
|
case 'featureGrid':
|
|
@@ -87,6 +109,9 @@ function renderKnownBlock(
|
|
|
87
109
|
case 'logoStrip':
|
|
88
110
|
return renderLogoStrip(known, ctx)
|
|
89
111
|
default: {
|
|
112
|
+
// Exhaustive over contract B: `known` is `never` here, so a block this
|
|
113
|
+
// package does not implement stops it compiling until it is. `null`
|
|
114
|
+
// rather than a throw: choosing a fallback is the render layer's job.
|
|
90
115
|
const unreachable: never = known
|
|
91
116
|
void unreachable
|
|
92
117
|
return null
|
|
@@ -95,193 +120,43 @@ function renderKnownBlock(
|
|
|
95
120
|
}
|
|
96
121
|
|
|
97
122
|
/**
|
|
98
|
-
* The
|
|
99
|
-
*
|
|
100
|
-
* by any flag on the block, because contract B carries no "this is chrome,
|
|
101
|
-
* not content" bit and inventing one would be a contract change for a
|
|
102
|
-
* single theme's layout choice.
|
|
103
|
-
*/
|
|
104
|
-
const DOC_PAGE_COLLECTION = 'doc_page'
|
|
105
|
-
|
|
106
|
-
function isDocSidebarBlock(block: VocabularyBlock): boolean {
|
|
107
|
-
return block._type === 'collectionList' && block.collection === DOC_PAGE_COLLECTION
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
/** The sidebar entry whose own link resolves to the page being rendered — the "current page" the brief asks the sidebar to highlight and the breadcrumb to name. */
|
|
111
|
-
function findCurrentEntry(
|
|
112
|
-
entries: readonly ContentEntry[],
|
|
113
|
-
ctx: RenderContext,
|
|
114
|
-
): ContentEntry | undefined {
|
|
115
|
-
return entries.find((entry) => entryHref(entry, ctx) === ctx.url.pathname)
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
/**
|
|
119
|
-
* The doc-page sidebar — grouped by each entry's own `section` field, in the
|
|
120
|
-
* order the entries were fetched (the blueprint seeds them in `order`
|
|
121
|
-
* within a section already; `collectionList.sort` cannot name `section`/
|
|
122
|
-
* `order`, so an editor who reorders sections relies on the fetch order,
|
|
123
|
-
* exactly like the "All guides" index on the home page does).
|
|
124
|
-
*/
|
|
125
|
-
function renderSidebarPanel(
|
|
126
|
-
entries: readonly ContentEntry[],
|
|
127
|
-
ctx: RenderContext,
|
|
128
|
-
currentHref: string,
|
|
129
|
-
): HtmlElement {
|
|
130
|
-
const groups = new Map<string, ContentEntry[]>()
|
|
131
|
-
for (const entry of entries) {
|
|
132
|
-
const section =
|
|
133
|
-
typeof entry.section === 'string' && entry.section !== ''
|
|
134
|
-
? entry.section
|
|
135
|
-
: ctx.t('entry.untitled')
|
|
136
|
-
const list = groups.get(section) ?? []
|
|
137
|
-
list.push(entry)
|
|
138
|
-
groups.set(section, list)
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
return h(
|
|
142
|
-
'nav',
|
|
143
|
-
{ class: 'cg-docs__nav-panel', 'aria-label': 'Documentation' },
|
|
144
|
-
[...groups.entries()].map(([section, sectionEntries]) =>
|
|
145
|
-
h(
|
|
146
|
-
'div',
|
|
147
|
-
{ class: 'cg-docs__nav-group' },
|
|
148
|
-
h('p', { class: 'cg-docs__nav-heading' }, section),
|
|
149
|
-
h(
|
|
150
|
-
'ul',
|
|
151
|
-
{ class: 'cg-docs__nav-items' },
|
|
152
|
-
sectionEntries.map((entry) => {
|
|
153
|
-
const href = entryHref(entry, ctx)
|
|
154
|
-
const isCurrent = href === currentHref
|
|
155
|
-
return h(
|
|
156
|
-
'li',
|
|
157
|
-
{},
|
|
158
|
-
h(
|
|
159
|
-
'a',
|
|
160
|
-
{
|
|
161
|
-
class: 'cg-docs__nav-link',
|
|
162
|
-
href,
|
|
163
|
-
'aria-current': isCurrent ? 'page' : undefined,
|
|
164
|
-
},
|
|
165
|
-
entryTitle(entry, ctx),
|
|
166
|
-
),
|
|
167
|
-
)
|
|
168
|
-
}),
|
|
169
|
-
),
|
|
170
|
-
),
|
|
171
|
-
),
|
|
172
|
-
)
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
/**
|
|
176
|
-
* The sidebar, in two copies — the same zero-JS technique `chrome.ts` already
|
|
177
|
-
* uses for the header's own mobile menu, and for the same reason: Chrome (and
|
|
178
|
-
* every other engine implementing the current HTML rendering rules) hides a
|
|
179
|
-
* closed `<details>`'s non-summary content through its own internal
|
|
180
|
-
* `::details-content` box, not through the plain CSS `display: none` the spec
|
|
181
|
-
* text describes — so a stylesheet rule that sets `display: block` on that
|
|
182
|
-
* content, however specific, does not reliably bring it back while the
|
|
183
|
-
* element itself stays closed. Verified live: a build with a single
|
|
184
|
-
* `<details>` forced open only above the two-column breakpoint rendered an
|
|
185
|
-
* empty sidebar column at 1280px in a real Chrome tab, despite every
|
|
186
|
-
* computed style reporting `display: block`.
|
|
187
|
-
*
|
|
188
|
-
* The fix is the same one already proven for the header: a plain, always-
|
|
189
|
-
* live `<nav>` for the desktop column (nothing to collapse, so nothing for
|
|
190
|
-
* the browser to hide), and a **separate** `<details>` disclosure — a full
|
|
191
|
-
* second copy of the same panel — for narrow viewports. Exactly one of the
|
|
192
|
-
* two is ever `display: block` at a given viewport width (`base.css`), so a
|
|
193
|
-
* screen reader is never offered two "Documentation" navigations at once,
|
|
194
|
-
* matching the header's own accessibility guarantee.
|
|
195
|
-
*/
|
|
196
|
-
function renderSidebar(
|
|
197
|
-
entries: readonly ContentEntry[],
|
|
198
|
-
ctx: RenderContext,
|
|
199
|
-
currentHref: string,
|
|
200
|
-
): HtmlElement {
|
|
201
|
-
return h('div', { class: 'cg-docs__nav-desktop' }, renderSidebarPanel(entries, ctx, currentHref))
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
function renderSidebarMobile(
|
|
205
|
-
entries: readonly ContentEntry[],
|
|
206
|
-
ctx: RenderContext,
|
|
207
|
-
currentHref: string,
|
|
208
|
-
): HtmlElement {
|
|
209
|
-
return h(
|
|
210
|
-
'details',
|
|
211
|
-
{ class: 'cg-docs__nav-mobile' },
|
|
212
|
-
h('summary', { class: 'cg-docs__nav-toggle' }, 'On this site'),
|
|
213
|
-
renderSidebarPanel(entries, ctx, currentHref),
|
|
214
|
-
)
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
function renderBreadcrumb(section: string | undefined, title: string): HtmlElement {
|
|
218
|
-
return h(
|
|
219
|
-
'nav',
|
|
220
|
-
{ class: 'cg-docs__breadcrumb', 'aria-label': 'Breadcrumb' },
|
|
221
|
-
h(
|
|
222
|
-
'ol',
|
|
223
|
-
{},
|
|
224
|
-
section === undefined ? null : h('li', {}, section),
|
|
225
|
-
h('li', { 'aria-current': 'page' }, title),
|
|
226
|
-
),
|
|
227
|
-
)
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
/**
|
|
231
|
-
* A doc page: two columns, CSS-only. The sidebar comes from the page's own
|
|
232
|
-
* *first* block — a `collectionList` on `doc_page` the blueprint seeds on
|
|
233
|
-
* every doc page for exactly this purpose — rendered as navigation rather
|
|
234
|
-
* than as a card list, and dropped from the ordinary block stream so it
|
|
235
|
-
* never also appears as content. The remaining blocks render in the content
|
|
236
|
-
* column, unchanged.
|
|
123
|
+
* The opening of a page outside the documentation that has no hero: its
|
|
124
|
+
* title, its summary, and the date it was published when it has one.
|
|
237
125
|
*/
|
|
238
|
-
function
|
|
239
|
-
page
|
|
240
|
-
ctx: RenderContext,
|
|
241
|
-
sidebarBlock: VocabularyBlock,
|
|
242
|
-
entries: FetchedEntries,
|
|
243
|
-
registry: BlockRegistry | undefined,
|
|
244
|
-
): HtmlElement {
|
|
245
|
-
const sidebarEntries = entries[sidebarBlock._key] ?? []
|
|
246
|
-
const currentHref = ctx.url.pathname
|
|
247
|
-
const current = findCurrentEntry(sidebarEntries, ctx)
|
|
248
|
-
const section = typeof current?.section === 'string' ? current.section : undefined
|
|
249
|
-
const contentBlocks = page.blocks.slice(1)
|
|
250
|
-
const hasOwnHeading = pageHasOwnHeading(contentBlocks)
|
|
251
|
-
const entryHeader = renderEntryHeader(page, ctx)
|
|
252
|
-
|
|
126
|
+
function renderPageHead(page: PageContent, ctx: RenderContext): HtmlElement {
|
|
127
|
+
const published = page.entry?.publishedAt
|
|
253
128
|
return h(
|
|
254
|
-
'
|
|
255
|
-
{ class: '
|
|
256
|
-
withBlockKey(
|
|
257
|
-
h(
|
|
258
|
-
'div',
|
|
259
|
-
{ class: 'cg-docs__nav' },
|
|
260
|
-
renderSidebar(sidebarEntries, ctx, currentHref),
|
|
261
|
-
renderSidebarMobile(sidebarEntries, ctx, currentHref),
|
|
262
|
-
),
|
|
263
|
-
sidebarBlock._key,
|
|
264
|
-
),
|
|
129
|
+
'header',
|
|
130
|
+
{ class: 'cd-page-head' },
|
|
265
131
|
h(
|
|
266
132
|
'div',
|
|
267
|
-
{ class: '
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
133
|
+
{ class: 'cd-container cd-page-head__inner' },
|
|
134
|
+
h('h1', { class: 'cd-page-head__title' }, page.title),
|
|
135
|
+
page.entry?.excerpt === undefined
|
|
136
|
+
? null
|
|
137
|
+
: h('p', { class: 'cd-page-head__lead' }, page.entry.excerpt),
|
|
138
|
+
published === undefined
|
|
139
|
+
? null
|
|
140
|
+
: h(
|
|
141
|
+
'p',
|
|
142
|
+
{ class: 'cd-page-head__meta' },
|
|
143
|
+
h('time', { datetime: published }, longDate(published, ctx.locale)),
|
|
144
|
+
),
|
|
273
145
|
),
|
|
274
146
|
)
|
|
275
147
|
}
|
|
276
148
|
|
|
277
149
|
/**
|
|
278
|
-
* `<main id="cg-main">` is mandatory: it is the skip
|
|
279
|
-
*
|
|
150
|
+
* `<main id="cg-main">` is mandatory: it is the skip link's target, written
|
|
151
|
+
* by the host outside any theme's control.
|
|
152
|
+
*
|
|
153
|
+
* A page whose first block is the documentation's own index on `doc_page` is
|
|
154
|
+
* a documentation page (`doc-page.ts`); every other page is laid out on the
|
|
155
|
+
* twelve-column grid. Exactly one `h1` on every page: a hero carries it when
|
|
156
|
+
* there is one, the page head otherwise.
|
|
280
157
|
*
|
|
281
|
-
* `withBlockKey` stamps every rendered block with its contract-B `_key`,
|
|
282
|
-
*
|
|
283
|
-
* builder maps a clicked element back to the block that produced it, for
|
|
284
|
-
* this theme exactly as for every other one.
|
|
158
|
+
* `withBlockKey` stamps every rendered block with its contract-B `_key`, so
|
|
159
|
+
* the visual page builder (L16) can map a clicked element back to its block.
|
|
285
160
|
*/
|
|
286
161
|
export function renderPage(
|
|
287
162
|
page: PageContent,
|
|
@@ -290,16 +165,17 @@ export function renderPage(
|
|
|
290
165
|
registry?: BlockRegistry,
|
|
291
166
|
): HtmlElement {
|
|
292
167
|
const first = page.blocks[0]
|
|
293
|
-
if (first !== undefined &&
|
|
294
|
-
return renderDocPage(page, ctx, first, entries, registry)
|
|
168
|
+
if (first !== undefined && isDocIndexBlock(first)) {
|
|
169
|
+
return renderDocPage(page, ctx, first, entries, registry, renderResolved)
|
|
295
170
|
}
|
|
296
171
|
|
|
172
|
+
const anchors = headingAnchors(page.blocks)
|
|
297
173
|
return h(
|
|
298
174
|
'main',
|
|
299
|
-
{ class: 'cg-main', id: 'cg-main' },
|
|
300
|
-
pageHasOwnHeading(page.blocks) ? null :
|
|
175
|
+
{ class: 'cg-main cd-main', id: 'cg-main' },
|
|
176
|
+
pageHasOwnHeading(page.blocks) ? null : renderPageHead(page, ctx),
|
|
301
177
|
page.blocks.map((block) =>
|
|
302
|
-
withBlockKey(
|
|
178
|
+
withBlockKey(renderResolved(block, ctx, entries, registry, anchors), block._key),
|
|
303
179
|
),
|
|
304
180
|
)
|
|
305
181
|
}
|