@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.
Files changed (148) hide show
  1. package/dist/src/index.d.ts +2 -0
  2. package/dist/src/index.d.ts.map +1 -1
  3. package/dist/src/index.js +2 -0
  4. package/dist/src/index.js.map +1 -1
  5. package/dist/src/render/blocks/accordion.d.ts +8 -0
  6. package/dist/src/render/blocks/accordion.d.ts.map +1 -1
  7. package/dist/src/render/blocks/accordion.js +10 -10
  8. package/dist/src/render/blocks/accordion.js.map +1 -1
  9. package/dist/src/render/blocks/collection-list.d.ts +3 -11
  10. package/dist/src/render/blocks/collection-list.d.ts.map +1 -1
  11. package/dist/src/render/blocks/collection-list.js +69 -68
  12. package/dist/src/render/blocks/collection-list.js.map +1 -1
  13. package/dist/src/render/blocks/cta.d.ts +6 -0
  14. package/dist/src/render/blocks/cta.d.ts.map +1 -1
  15. package/dist/src/render/blocks/cta.js +9 -4
  16. package/dist/src/render/blocks/cta.js.map +1 -1
  17. package/dist/src/render/blocks/embed.d.ts.map +1 -1
  18. package/dist/src/render/blocks/embed.js +14 -13
  19. package/dist/src/render/blocks/embed.js.map +1 -1
  20. package/dist/src/render/blocks/faq.d.ts +7 -0
  21. package/dist/src/render/blocks/faq.d.ts.map +1 -1
  22. package/dist/src/render/blocks/faq.js +10 -10
  23. package/dist/src/render/blocks/faq.js.map +1 -1
  24. package/dist/src/render/blocks/feature-grid.d.ts.map +1 -1
  25. package/dist/src/render/blocks/feature-grid.js +19 -19
  26. package/dist/src/render/blocks/feature-grid.js.map +1 -1
  27. package/dist/src/render/blocks/gallery.d.ts +8 -4
  28. package/dist/src/render/blocks/gallery.d.ts.map +1 -1
  29. package/dist/src/render/blocks/gallery.js +19 -13
  30. package/dist/src/render/blocks/gallery.js.map +1 -1
  31. package/dist/src/render/blocks/hero.d.ts +12 -9
  32. package/dist/src/render/blocks/hero.d.ts.map +1 -1
  33. package/dist/src/render/blocks/hero.js +22 -20
  34. package/dist/src/render/blocks/hero.js.map +1 -1
  35. package/dist/src/render/blocks/logo-strip.d.ts +6 -0
  36. package/dist/src/render/blocks/logo-strip.d.ts.map +1 -1
  37. package/dist/src/render/blocks/logo-strip.js +6 -10
  38. package/dist/src/render/blocks/logo-strip.js.map +1 -1
  39. package/dist/src/render/blocks/logos.d.ts +10 -0
  40. package/dist/src/render/blocks/logos.d.ts.map +1 -1
  41. package/dist/src/render/blocks/logos.js +20 -17
  42. package/dist/src/render/blocks/logos.js.map +1 -1
  43. package/dist/src/render/blocks/media-figure.d.ts +0 -6
  44. package/dist/src/render/blocks/media-figure.d.ts.map +1 -1
  45. package/dist/src/render/blocks/media-figure.js +28 -16
  46. package/dist/src/render/blocks/media-figure.js.map +1 -1
  47. package/dist/src/render/blocks/pricing-table.d.ts.map +1 -1
  48. package/dist/src/render/blocks/pricing-table.js +16 -20
  49. package/dist/src/render/blocks/pricing-table.js.map +1 -1
  50. package/dist/src/render/blocks/prose.d.ts +13 -1
  51. package/dist/src/render/blocks/prose.d.ts.map +1 -1
  52. package/dist/src/render/blocks/prose.js +12 -31
  53. package/dist/src/render/blocks/prose.js.map +1 -1
  54. package/dist/src/render/blocks/quote.d.ts +5 -3
  55. package/dist/src/render/blocks/quote.d.ts.map +1 -1
  56. package/dist/src/render/blocks/quote.js +10 -14
  57. package/dist/src/render/blocks/quote.js.map +1 -1
  58. package/dist/src/render/blocks/stat-counter.d.ts +5 -0
  59. package/dist/src/render/blocks/stat-counter.d.ts.map +1 -1
  60. package/dist/src/render/blocks/stat-counter.js +6 -10
  61. package/dist/src/render/blocks/stat-counter.js.map +1 -1
  62. package/dist/src/render/blocks/stats.d.ts +10 -0
  63. package/dist/src/render/blocks/stats.d.ts.map +1 -1
  64. package/dist/src/render/blocks/stats.js +13 -8
  65. package/dist/src/render/blocks/stats.js.map +1 -1
  66. package/dist/src/render/blocks/testimonial.d.ts +4 -5
  67. package/dist/src/render/blocks/testimonial.d.ts.map +1 -1
  68. package/dist/src/render/blocks/testimonial.js +9 -13
  69. package/dist/src/render/blocks/testimonial.js.map +1 -1
  70. package/dist/src/render/chrome.d.ts +36 -1
  71. package/dist/src/render/chrome.d.ts.map +1 -1
  72. package/dist/src/render/chrome.js +122 -80
  73. package/dist/src/render/chrome.js.map +1 -1
  74. package/dist/src/render/doc-index.d.ts +25 -0
  75. package/dist/src/render/doc-index.d.ts.map +1 -0
  76. package/dist/src/render/doc-index.js +49 -0
  77. package/dist/src/render/doc-index.js.map +1 -0
  78. package/dist/src/render/doc-page.d.ts +6 -0
  79. package/dist/src/render/doc-page.d.ts.map +1 -0
  80. package/dist/src/render/doc-page.js +127 -0
  81. package/dist/src/render/doc-page.js.map +1 -0
  82. package/dist/src/render/layout.d.ts +23 -0
  83. package/dist/src/render/layout.d.ts.map +1 -0
  84. package/dist/src/render/layout.js +40 -0
  85. package/dist/src/render/layout.js.map +1 -0
  86. package/dist/src/render/render-block.d.ts +9 -6
  87. package/dist/src/render/render-block.d.ts.map +1 -1
  88. package/dist/src/render/render-block.js +47 -100
  89. package/dist/src/render/render-block.js.map +1 -1
  90. package/dist/src/render/rich-text.d.ts +26 -0
  91. package/dist/src/render/rich-text.d.ts.map +1 -0
  92. package/dist/src/render/rich-text.js +325 -0
  93. package/dist/src/render/rich-text.js.map +1 -0
  94. package/dist/src/render/search.d.ts +15 -0
  95. package/dist/src/render/search.d.ts.map +1 -0
  96. package/dist/src/render/search.js +31 -0
  97. package/dist/src/render/search.js.map +1 -0
  98. package/dist/src/render/strings.d.ts +51 -0
  99. package/dist/src/render/strings.d.ts.map +1 -0
  100. package/dist/src/render/strings.js +60 -0
  101. package/dist/src/render/strings.js.map +1 -0
  102. package/dist/src/render/term-archive.d.ts +5 -5
  103. package/dist/src/render/term-archive.d.ts.map +1 -1
  104. package/dist/src/render/term-archive.js +27 -16
  105. package/dist/src/render/term-archive.js.map +1 -1
  106. package/dist/src/render/widget-areas.d.ts +22 -0
  107. package/dist/src/render/widget-areas.d.ts.map +1 -0
  108. package/dist/src/render/widget-areas.js +29 -0
  109. package/dist/src/render/widget-areas.js.map +1 -0
  110. package/package.json +4 -4
  111. package/src/index.ts +2 -0
  112. package/src/render/blocks/accordion.ts +31 -42
  113. package/src/render/blocks/collection-list.ts +132 -123
  114. package/src/render/blocks/cta.ts +23 -18
  115. package/src/render/blocks/embed.ts +37 -28
  116. package/src/render/blocks/faq.ts +26 -37
  117. package/src/render/blocks/feature-grid.ts +34 -42
  118. package/src/render/blocks/gallery.ts +33 -29
  119. package/src/render/blocks/hero.ts +41 -45
  120. package/src/render/blocks/logo-strip.ts +21 -21
  121. package/src/render/blocks/logos.ts +32 -42
  122. package/src/render/blocks/media-figure.ts +47 -25
  123. package/src/render/blocks/pricing-table.ts +34 -41
  124. package/src/render/blocks/prose.ts +25 -40
  125. package/src/render/blocks/quote.ts +25 -19
  126. package/src/render/blocks/stat-counter.ts +27 -29
  127. package/src/render/blocks/stats.ts +35 -32
  128. package/src/render/blocks/testimonial.ts +18 -19
  129. package/src/render/chrome.ts +170 -83
  130. package/src/render/doc-index.ts +65 -0
  131. package/src/render/doc-page.ts +303 -0
  132. package/src/render/layout.ts +73 -0
  133. package/src/render/render-block.ts +67 -191
  134. package/src/render/rich-text.ts +446 -0
  135. package/src/render/search.ts +41 -0
  136. package/src/render/strings.ts +67 -0
  137. package/src/render/term-archive.ts +62 -38
  138. package/src/render/widget-areas.ts +33 -0
  139. package/src/styles/archive.css +21 -27
  140. package/src/styles/base.css +438 -838
  141. package/src/styles/blocks.css +658 -916
  142. package/src/styles/chrome.css +439 -0
  143. package/src/styles/docs.css +404 -0
  144. package/src/styles/rich.css +337 -0
  145. package/src/styles/theme.css +28 -23
  146. package/src/styles/tokens.css +209 -215
  147. package/src/styles/widgets.css +890 -0
  148. package/tokens.json +15 -15
@@ -0,0 +1,22 @@
1
+ /**
2
+ * The widget areas this theme names (contract D `theme@1.6`). Declaring them
3
+ * is what tells the host this theme draws the footer columns inside its own
4
+ * footer; the page areas are placed by the host in the shared
5
+ * `cg-sidebar-layout` markup, which `styles/widgets.css` sets on this theme's
6
+ * own documentation grid.
7
+ *
8
+ * A documentation page already has two columns of its own beside the
9
+ * article: the navigation on the left and "On this page" on the right. The
10
+ * sidebar never becomes a fourth column next to them. It is the right-hand
11
+ * rail, shared with "On this page": the contents stay in view while the page
12
+ * is read, and the widgets sit at the foot of the rail, level with the end of
13
+ * the article, where a reader who finished the page looks for help and for
14
+ * what comes next. On a page without its own contents, and beside search
15
+ * results or an archive, the widgets are the rail.
16
+ */
17
+ export declare const widgetAreas: readonly {
18
+ readonly id: string;
19
+ readonly label: string;
20
+ readonly description?: string;
21
+ }[];
22
+ //# sourceMappingURL=widget-areas.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"widget-areas.d.ts","sourceRoot":"","sources":["../../../src/render/widget-areas.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,WAAW,EAAE,SAAS;IACjC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;IACnB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAA;CAC9B,EAYA,CAAA"}
@@ -0,0 +1,29 @@
1
+ /**
2
+ * The widget areas this theme names (contract D `theme@1.6`). Declaring them
3
+ * is what tells the host this theme draws the footer columns inside its own
4
+ * footer; the page areas are placed by the host in the shared
5
+ * `cg-sidebar-layout` markup, which `styles/widgets.css` sets on this theme's
6
+ * own documentation grid.
7
+ *
8
+ * A documentation page already has two columns of its own beside the
9
+ * article: the navigation on the left and "On this page" on the right. The
10
+ * sidebar never becomes a fourth column next to them. It is the right-hand
11
+ * rail, shared with "On this page": the contents stay in view while the page
12
+ * is read, and the widgets sit at the foot of the rail, level with the end of
13
+ * the article, where a reader who finished the page looks for help and for
14
+ * what comes next. On a page without its own contents, and beside search
15
+ * results or an archive, the widgets are the rail.
16
+ */
17
+ export const widgetAreas = [
18
+ {
19
+ id: 'sidebar',
20
+ label: 'Page rail',
21
+ description: 'The right-hand rail: under “On this page” and level with the end of a documentation page, beside search results and archives.',
22
+ },
23
+ {
24
+ id: 'content-after',
25
+ label: 'After the page',
26
+ description: 'In the reading column, after a page and its previous and next links.',
27
+ },
28
+ ];
29
+ //# sourceMappingURL=widget-areas.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"widget-areas.js","sourceRoot":"","sources":["../../../src/render/widget-areas.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,MAAM,CAAC,MAAM,WAAW,GAIlB;IACJ;QACE,EAAE,EAAE,SAAS;QACb,KAAK,EAAE,WAAW;QAClB,WAAW,EACT,+HAA+H;KAClI;IACD;QACE,EAAE,EAAE,eAAe;QACnB,KAAK,EAAE,gBAAgB;QACvB,WAAW,EAAE,sEAAsE;KACpF;CACF,CAAA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cogenta/theme-docs",
3
- "version": "0.3.2",
3
+ "version": "0.5.3",
4
4
  "description": "A dense, reference-first documentation theme for Cogenta: a sticky sidebar-navigated doc layout, real code blocks, and a genuine dark mode — zero client JavaScript.",
5
5
  "license": "MPL-2.0",
6
6
  "type": "module",
@@ -36,9 +36,9 @@
36
36
  "directory": "packages/theme-docs"
37
37
  },
38
38
  "dependencies": {
39
- "@cogenta/theme-kit": "0.3.2",
40
- "@cogenta/blocks": "1.0.3",
41
- "@cogenta/render": "0.2.3"
39
+ "@cogenta/blocks": "1.1.3",
40
+ "@cogenta/theme-kit": "0.5.3",
41
+ "@cogenta/render": "0.3.5"
42
42
  },
43
43
  "peerDependencies": {
44
44
  "astro": "^7.2.1"
package/src/index.ts CHANGED
@@ -17,4 +17,6 @@ export * from './render/blocks/stats.js'
17
17
  export * from './render/blocks/testimonial.js'
18
18
  export * from './render/chrome.js'
19
19
  export * from './render/render-block.js'
20
+ export { headingAnchors, keyChord, slugify } from './render/rich-text.js'
20
21
  export * from './render/term-archive.js'
22
+ export * from './render/widget-areas.js'
@@ -1,51 +1,40 @@
1
- import type { AccordionBlock, AccordionItem } from '@cogenta/blocks'
2
- import {
3
- blockHeadingTag,
4
- type HtmlElement,
5
- h,
6
- heading,
7
- type RenderContext,
8
- renderRichText,
9
- } from '@cogenta/theme-kit'
1
+ import type { AccordionBlock } from '@cogenta/blocks'
2
+ import { type HtmlElement, h, type RenderContext } from '@cogenta/theme-kit'
3
+ import { section, sectionHead } from '../layout.js'
4
+ import { renderDocsRichText } from '../rich-text.js'
10
5
 
11
6
  /**
12
- * `blocks@2.0` (RFC 0001). Reuses `faq`'s zero-JS `<details>`/`<summary>`
13
- * mechanics with a markup vocabulary of its own (`cg-panels`, not
14
- * `cg-faq`), a rotating chevron mark rather than a numbered row.
7
+ * Detail a reader opens when they need it (every error code, the fields of a
8
+ * response) as ruled rows on the reading measure.
9
+ *
10
+ * Every row is a `<details>`: it opens without a script, and the browser's
11
+ * own find-in-page still reaches a closed answer. The plus that turns into a
12
+ * minus is two hairlines drawn by the stylesheet, not a glyph.
15
13
  */
16
- function renderItem(item: AccordionItem, ctx: RenderContext): HtmlElement {
17
- return h(
18
- 'li',
19
- { class: 'cg-panels__item' },
20
- h(
21
- 'details',
22
- { class: 'cg-panels__details' },
23
- h(
24
- 'summary',
25
- { class: 'cg-panels__question' },
26
- h('span', { class: 'cg-panels__question-text' }, item.question),
27
- h('span', { class: 'cg-panels__chevron', 'aria-hidden': 'true' }),
28
- ),
29
- h('div', { class: 'cg-panels__answer' }, renderRichText(ctx, item.answer)),
30
- ),
31
- )
32
- }
33
-
34
14
  export function renderAccordion(block: AccordionBlock, ctx: RenderContext): HtmlElement {
35
- return h(
15
+ return section(
36
16
  'section',
37
- { class: 'cg-block cg-panels', 'data-block': 'accordion' },
38
- block.title === undefined
39
- ? null
40
- : heading(
41
- blockHeadingTag('accordion') ?? 'h2',
42
- { class: 'cg-panels__title', 'data-field': 'title' },
43
- block.title,
44
- ),
17
+ 'accordion',
18
+ 'cd-accordion',
19
+ {},
20
+ 'div',
21
+ sectionHead('accordion', block.title),
45
22
  h(
46
- 'ul',
47
- { class: 'cg-panels__items' },
48
- block.items.map((item) => renderItem(item, ctx)),
23
+ 'div',
24
+ { class: 'cd-accordion__items' },
25
+ block.items.map((item) =>
26
+ h(
27
+ 'details',
28
+ { class: 'cd-accordion__item' },
29
+ h(
30
+ 'summary',
31
+ { class: 'cd-accordion__summary' },
32
+ h('span', { class: 'cd-accordion__question' }, item.question),
33
+ h('span', { class: 'cd-accordion__sign', 'aria-hidden': 'true' }),
34
+ ),
35
+ h('div', { class: 'cd-accordion__answer cd-rich' }, renderDocsRichText(ctx, item.answer)),
36
+ ),
37
+ ),
49
38
  ),
50
39
  )
51
40
  }
@@ -1,6 +1,5 @@
1
1
  import type { CollectionListBlock } from '@cogenta/blocks'
2
2
  import {
3
- blockHeadingTag,
4
3
  buildCollectionListQuery,
5
4
  type ContentEntry,
6
5
  entryDate,
@@ -8,119 +7,122 @@ import {
8
7
  entryHref,
9
8
  entryImage,
10
9
  entryTitle,
11
- type HeadingTag,
12
10
  type HtmlElement,
13
11
  h,
14
- heading,
15
12
  nestedHeadingTag,
13
+ type QueryRequest,
16
14
  type RenderContext,
17
15
  renderImageSource,
18
16
  } from '@cogenta/theme-kit'
17
+ import { DOC_PAGE_COLLECTION, groupDocPages } from '../doc-index.js'
18
+ import { longDate, section, sectionHead } from '../layout.js'
19
19
 
20
20
  /**
21
- * The only block of the seventeen that reads data at render time — contract
22
- * B marks it `runtime: 'server'` for that reason. The read is *not* done
23
- * here: `query` builds the request from the block's own fields
24
- * (`@cogenta/theme-kit`'s `buildCollectionListQuery`, shared across every
25
- * theme), the caller awaits `ctx.content.list(...)` before rendering
26
- * starts, and this function stays a pure function of the entries handed to
27
- * it.
21
+ * A list of entries, read by what they are as much as by the layout chosen.
22
+ *
23
+ * - **Doc pages: the documentation, by section.** The home page's "Browse"
24
+ * block. One column per section (sections in the order the documentation
25
+ * reads, pages by their own `order`), the section named in a small heading
26
+ * over a hairline, its pages as a plain column of links. Up to four columns
27
+ * on a desk, two on a tablet, one on a phone. The layout an editor picked
28
+ * is ignored on purpose: a table of contents is not a carousel.
29
+ * - **`list`: an index.** Ruled rows: the title, the summary under it, and a
30
+ * date on the right when the entry has one, in tabular numerals.
31
+ * - **`grid`**: three columns without cards. A picture, when there is one,
32
+ * in the hairline frame at 16:10, then the title, the summary and the date.
33
+ * - **`carousel`**: the same items in one row that scrolls sideways.
34
+ *
35
+ * Each picture repeats the link of its title, so it is taken out of the tab
36
+ * order and hidden from assistive technology: one link per entry.
28
37
  */
29
- export { buildCollectionListQuery as query }
30
-
31
- /**
32
- * The general-purpose row: a cover image (`entryImage`, contract D
33
- * `theme@1.4`) when the collection carries one, title, excerpt and date —
34
- * this is what a non-docs collection placed in this block gets.
35
- */
36
- function renderRow(entry: ContentEntry, ctx: RenderContext, tag: HeadingTag): HtmlElement {
37
- const date = entryDate(entry)
38
- const excerpt = entryExcerpt(entry)
39
- const image = entryImage(entry, ctx)
40
- return h(
41
- 'li',
42
- { class: 'cg-list__row' },
43
- image === undefined
44
- ? null
45
- : h(
46
- 'div',
47
- { class: 'cg-list__thumb' },
48
- renderImageSource(image, { loading: 'lazy', className: 'cg-list__thumb-image' }),
49
- ),
50
- h(
51
- 'div',
52
- { class: 'cg-list__body' },
53
- heading(
54
- tag,
55
- { class: 'cg-list__title' },
56
- h('a', { class: 'cg-list__link', href: entryHref(entry, ctx) }, entryTitle(entry, ctx)),
57
- ),
58
- date === undefined
59
- ? null
60
- : h(
61
- 'time',
62
- { class: 'cg-list__date', datetime: date },
63
- new Intl.DateTimeFormat(ctx.locale, { dateStyle: 'long' }).format(new Date(date)),
64
- ),
65
- excerpt === undefined ? null : h('p', { class: 'cg-list__excerpt' }, excerpt),
66
- ),
67
- )
68
- }
69
-
70
- /**
71
- * `doc_page`'s own layout: a compact, multi-column index grouped by the
72
- * entry's own `section` field and ordered by its own `order` field within
73
- * each group — neither is a valid `collectionList.sort.field` (contract B
74
- * fixes that union to `id`/`createdAt`/`updatedAt`), so the theme re-groups
75
- * and re-sorts the already-fetched slice itself rather than asking for a
76
- * sort the contract does not offer. Sections are ordered alphabetically for
77
- * a deterministic page; entries within a section fall back to the order
78
- * they were fetched in when `order` is absent or not a number.
79
- */
80
- function renderGuidesIndex(entries: readonly ContentEntry[], ctx: RenderContext): HtmlElement {
81
- const groups = new Map<string, ContentEntry[]>()
82
- entries.forEach((entry) => {
83
- const section =
84
- typeof entry.section === 'string' && entry.section !== ''
85
- ? entry.section
86
- : ctx.t('entry.untitled')
87
- const list = groups.get(section) ?? []
88
- list.push(entry)
89
- groups.set(section, list)
90
- })
91
-
92
- const sortedSections = [...groups.entries()].sort(([a], [b]) => a.localeCompare(b))
93
38
 
39
+ function renderDocIndex(
40
+ entries: readonly ContentEntry[],
41
+ ctx: RenderContext,
42
+ tag: string,
43
+ ): HtmlElement {
44
+ const groups = groupDocPages(entries, ctx)
94
45
  return h(
95
46
  'div',
96
- { class: 'cg-guides' },
97
- sortedSections.map(([section, sectionEntries]) => {
98
- const ordered = [...sectionEntries].sort((a, b) => {
99
- const orderA = typeof a.order === 'number' ? a.order : Number.POSITIVE_INFINITY
100
- const orderB = typeof b.order === 'number' ? b.order : Number.POSITIVE_INFINITY
101
- return orderA - orderB
102
- })
103
- return h(
47
+ { class: 'cd-browse', 'data-count': String(Math.min(groups.length, 4)) },
48
+ groups.map((group) =>
49
+ h(
104
50
  'div',
105
- { class: 'cg-guides__section' },
106
- h('h3', { class: 'cg-guides__heading' }, section),
51
+ { class: 'cd-browse__group' },
52
+ h(tag, { class: 'cd-browse__heading' }, group.section),
107
53
  h(
108
54
  'ul',
109
- { class: 'cg-guides__items' },
110
- ordered.map((entry) =>
55
+ { class: 'cd-browse__links' },
56
+ group.entries.map((entry) =>
111
57
  h(
112
58
  'li',
113
- { class: 'cg-guides__item' },
59
+ { class: 'cd-browse__item' },
114
60
  h(
115
61
  'a',
116
- { class: 'cg-guides__link', href: entryHref(entry, ctx) },
62
+ { class: 'cd-browse__link', href: entryHref(entry, ctx) },
117
63
  entryTitle(entry, ctx),
118
64
  ),
119
65
  ),
120
66
  ),
121
67
  ),
122
- )
123
- }),
68
+ ),
69
+ ),
70
+ )
71
+ }
72
+
73
+ function dateOf(entry: ContentEntry, ctx: RenderContext, className: string): HtmlElement | null {
74
+ const date = entryDate(entry)
75
+ if (date === undefined) return null
76
+ return h('time', { class: className, datetime: date }, longDate(date, ctx.locale))
77
+ }
78
+
79
+ function indexRow(entry: ContentEntry, ctx: RenderContext, tag: string): HtmlElement {
80
+ const excerpt = entryExcerpt(entry)
81
+ return h(
82
+ 'li',
83
+ { class: 'cd-index__item' },
84
+ h(
85
+ 'div',
86
+ { class: 'cd-index__words' },
87
+ h(
88
+ tag,
89
+ { class: 'cd-index__title' },
90
+ h('a', { class: 'cd-index__link', href: entryHref(entry, ctx) }, entryTitle(entry, ctx)),
91
+ ),
92
+ excerpt === undefined ? null : h('p', { class: 'cd-index__text' }, excerpt),
93
+ ),
94
+ dateOf(entry, ctx, 'cd-index__date'),
95
+ )
96
+ }
97
+
98
+ function card(entry: ContentEntry, ctx: RenderContext, tag: string): HtmlElement {
99
+ const excerpt = entryExcerpt(entry)
100
+ const source = entryImage(entry, ctx)
101
+ return h(
102
+ 'li',
103
+ { class: 'cd-cards__item' },
104
+ source === undefined
105
+ ? null
106
+ : h(
107
+ 'a',
108
+ {
109
+ class: 'cd-cards__media cd-frame',
110
+ href: entryHref(entry, ctx),
111
+ tabindex: -1,
112
+ 'aria-hidden': 'true',
113
+ },
114
+ renderImageSource(source, {
115
+ className: 'cd-frame__image',
116
+ sizes: '(min-width: 64rem) 24rem, (min-width: 40rem) 50vw, 100vw',
117
+ }),
118
+ ),
119
+ h(
120
+ tag,
121
+ { class: 'cd-cards__title' },
122
+ h('a', { class: 'cd-cards__link', href: entryHref(entry, ctx) }, entryTitle(entry, ctx)),
123
+ ),
124
+ excerpt === undefined ? null : h('p', { class: 'cd-cards__text' }, excerpt),
125
+ dateOf(entry, ctx, 'cd-cards__date'),
124
126
  )
125
127
  }
126
128
 
@@ -129,42 +131,49 @@ export function renderCollectionList(
129
131
  ctx: RenderContext,
130
132
  entries: readonly ContentEntry[],
131
133
  ): HtmlElement {
132
- const hasTitle = block.title !== undefined
133
- const entryTag = nestedHeadingTag('collectionList', hasTitle)
134
- const isGuidesIndex = block.collection === 'doc_page'
134
+ const tag = nestedHeadingTag('collectionList', block.title !== undefined)
135
+ const docs = block.collection === DOC_PAGE_COLLECTION
136
+ const shape = entries.length === 0 ? 'empty' : docs ? 'docs' : block.layout
135
137
 
136
138
  const body =
137
- entries.length === 0
138
- ? h('p', { class: 'cg-list__empty' }, ctx.t('collection.empty'))
139
- : isGuidesIndex
140
- ? renderGuidesIndex(entries, ctx)
141
- : h(
142
- 'ul',
143
- { class: 'cg-list__items' },
144
- entries.map((entry) => renderRow(entry, ctx, entryTag)),
145
- )
139
+ shape === 'empty'
140
+ ? h('p', { class: 'cd-empty' }, ctx.t('collection.empty'))
141
+ : shape === 'docs'
142
+ ? renderDocIndex(entries, ctx, tag)
143
+ : shape === 'list'
144
+ ? h(
145
+ 'ul',
146
+ { class: 'cd-index' },
147
+ entries.map((entry) => indexRow(entry, ctx, tag)),
148
+ )
149
+ : h(
150
+ 'ul',
151
+ {
152
+ class: 'cd-cards',
153
+ 'data-carousel': shape === 'carousel' ? 'true' : 'false',
154
+ ...(shape === 'carousel'
155
+ ? {
156
+ role: 'region',
157
+ 'aria-label': block.title ?? ctx.t('collection.carousel'),
158
+ tabindex: 0,
159
+ }
160
+ : {}),
161
+ },
162
+ entries.map((entry) => card(entry, ctx, tag)),
163
+ )
146
164
 
147
- return h(
165
+ return section(
148
166
  'section',
149
- { class: 'cg-block cg-list', 'data-block': 'collectionList', 'data-layout': block.layout },
150
- hasTitle
151
- ? heading(
152
- blockHeadingTag('collectionList') ?? 'h2',
153
- { class: 'cg-list__title-heading', 'data-field': 'title' },
154
- block.title ?? '',
155
- )
156
- : null,
157
- block.layout === 'carousel' && !isGuidesIndex
158
- ? h(
159
- 'div',
160
- {
161
- class: 'cg-list__viewport',
162
- role: 'region',
163
- 'aria-label': block.title ?? ctx.t('collection.carousel'),
164
- tabindex: '0',
165
- },
166
- body,
167
- )
168
- : body,
167
+ 'collectionList',
168
+ 'cd-list',
169
+ { 'data-layout': block.layout, 'data-shape': shape },
170
+ 'div',
171
+ sectionHead('collectionList', block.title),
172
+ body,
169
173
  )
170
174
  }
175
+
176
+ /** The query this block needs, fetched by the host before any markup is built. */
177
+ export function query(block: CollectionListBlock): QueryRequest {
178
+ return buildCollectionListQuery(block)
179
+ }
@@ -1,25 +1,30 @@
1
1
  import type { CtaBlock } from '@cogenta/blocks'
2
- import {
3
- actionList,
4
- blockHeadingTag,
5
- type HtmlElement,
6
- h,
7
- heading,
8
- type RenderContext,
9
- } from '@cogenta/theme-kit'
2
+ import { actionList, type HtmlElement, h, type RenderContext } from '@cogenta/theme-kit'
3
+ import { optionalText, section } from '../layout.js'
10
4
 
5
+ /**
6
+ * A closing line: "Stuck on something", "Upgrading from an older version".
7
+ * Between two hairlines held to the width of the content (never a coloured
8
+ * band, never a rule across the gutters): the title and a sentence on the
9
+ * first seven columns, the actions after them on a wide screen.
10
+ */
11
11
  export function renderCta(block: CtaBlock, ctx: RenderContext): HtmlElement {
12
- return h(
12
+ return section(
13
13
  'section',
14
- { class: 'cg-block cg-cta', 'data-block': 'cta' },
15
- heading(
16
- blockHeadingTag('cta') ?? 'h2',
17
- { class: 'cg-cta__title', 'data-field': 'title' },
18
- block.title,
14
+ 'cta',
15
+ 'cd-cta',
16
+ {},
17
+ 'div',
18
+ h(
19
+ 'div',
20
+ { class: 'cd-cta__panel' },
21
+ h(
22
+ 'div',
23
+ { class: 'cd-cta__words' },
24
+ h('h2', { class: 'cd-cta__title', 'data-field': 'title' }, block.title),
25
+ optionalText('p', 'cd-cta__text', block.text, { 'data-field': 'text' }),
26
+ ),
27
+ h('div', { class: 'cd-cta__actions' }, actionList(ctx, block.actions, block.title)),
19
28
  ),
20
- block.text === undefined
21
- ? null
22
- : h('p', { class: 'cg-cta__text', 'data-field': 'text' }, block.text),
23
- actionList(ctx, block.actions, block.title),
24
29
  )
25
30
  }
@@ -2,11 +2,13 @@ import type { EmbedBlock } from '@cogenta/blocks'
2
2
  import { aspectRatio, type HtmlElement, h, type RenderContext } from '@cogenta/theme-kit'
3
3
 
4
4
  /**
5
- * Nothing here contacts a third party before the visitor has consented.
5
+ * A recorded walkthrough, a talk, a post: embedded in the hairline frame.
6
6
  *
7
- * When `consentRequired` is true the block renders a self-contained card and
8
- * an outbound link — no `<iframe>`, no `<script>`, no preconnect, no poster
9
- * image fetched from the provider.
7
+ * Nothing here contacts a third party before the visitor has consented. When
8
+ * `consentRequired` is true (and for a provider this theme cannot embed) the
9
+ * frame holds a short notice and a link to the original instead: no
10
+ * `<iframe>`, no `<script>`, no preconnect, no poster fetched from the
11
+ * provider.
10
12
  */
11
13
 
12
14
  function frameSource(provider: EmbedBlock['provider'], rawUrl: string): string | null {
@@ -42,16 +44,15 @@ function frameSource(provider: EmbedBlock['provider'], rawUrl: string): string |
42
44
  }
43
45
  }
44
46
 
45
- function consentCard(block: EmbedBlock, ctx: RenderContext, reason: string): HtmlElement {
47
+ function notice(block: EmbedBlock, ctx: RenderContext, reason: string): HtmlElement {
46
48
  return h(
47
49
  'div',
48
- { class: 'cg-embed__placeholder' },
49
- h('p', { class: 'cg-embed__provider', 'aria-hidden': 'true' }, block.provider),
50
- h('p', { class: 'cg-embed__notice' }, reason),
50
+ { class: 'cd-embed__notice' },
51
+ h('p', { class: 'cd-embed__reason' }, reason),
51
52
  h(
52
53
  'a',
53
54
  {
54
- class: 'cg-embed__link',
55
+ class: 'cd-arrow-link cd-embed__link',
55
56
  href: ctx.link(block.url),
56
57
  rel: 'noopener noreferrer nofollow',
57
58
  },
@@ -67,28 +68,36 @@ export function renderEmbed(block: EmbedBlock, ctx: RenderContext): HtmlElement
67
68
  return h(
68
69
  'div',
69
70
  {
70
- class: 'cg-block cg-embed',
71
+ class: 'cd-section cd-embed',
71
72
  'data-block': 'embed',
72
73
  'data-provider': block.provider,
73
74
  'data-consent': block.consentRequired ? 'required' : 'not-required',
74
- style: `--cg-ratio:${ratio}`,
75
75
  },
76
- source === null
77
- ? consentCard(
78
- block,
79
- ctx,
80
- block.consentRequired
81
- ? ctx.t('embed.consentRequired', { provider: block.provider })
82
- : ctx.t('embed.unsupported', { provider: block.provider }),
83
- )
84
- : h('iframe', {
85
- class: 'cg-embed__frame',
86
- src: source,
87
- title: ctx.t('embed.title', { provider: block.provider }),
88
- loading: 'lazy',
89
- referrerpolicy: 'strict-origin-when-cross-origin',
90
- allow: 'accelerometer; clipboard-write; encrypted-media; picture-in-picture; fullscreen',
91
- allowfullscreen: true,
92
- }),
76
+ h(
77
+ 'div',
78
+ { class: 'cd-container cd-embed__inner' },
79
+ h(
80
+ 'div',
81
+ { class: 'cd-embed__frame cd-frame', style: `aspect-ratio:${ratio}` },
82
+ source === null
83
+ ? notice(
84
+ block,
85
+ ctx,
86
+ block.consentRequired
87
+ ? ctx.t('embed.consentRequired', { provider: block.provider })
88
+ : ctx.t('embed.unsupported', { provider: block.provider }),
89
+ )
90
+ : h('iframe', {
91
+ class: 'cd-embed__iframe',
92
+ src: source,
93
+ title: ctx.t('embed.title', { provider: block.provider }),
94
+ loading: 'lazy',
95
+ referrerpolicy: 'strict-origin-when-cross-origin',
96
+ allow:
97
+ 'accelerometer; clipboard-write; encrypted-media; picture-in-picture; fullscreen',
98
+ allowfullscreen: true,
99
+ }),
100
+ ),
101
+ ),
93
102
  )
94
103
  }