@cogenta/theme-entreprise 1.1.3 → 1.4.0
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 +1 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +1 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/render/blocks/accordion.d.ts.map +1 -1
- package/dist/src/render/blocks/accordion.js +12 -15
- package/dist/src/render/blocks/accordion.js.map +1 -1
- package/dist/src/render/blocks/collection-list.d.ts +5 -7
- package/dist/src/render/blocks/collection-list.d.ts.map +1 -1
- package/dist/src/render/blocks/collection-list.js +61 -32
- package/dist/src/render/blocks/collection-list.js.map +1 -1
- package/dist/src/render/blocks/cta.d.ts +6 -6
- package/dist/src/render/blocks/cta.d.ts.map +1 -1
- package/dist/src/render/blocks/cta.js +12 -12
- 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 +22 -27
- package/dist/src/render/blocks/embed.js.map +1 -1
- package/dist/src/render/blocks/faq.d.ts.map +1 -1
- package/dist/src/render/blocks/faq.js +13 -16
- 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 +22 -22
- package/dist/src/render/blocks/feature-grid.js.map +1 -1
- package/dist/src/render/blocks/gallery.d.ts +7 -8
- package/dist/src/render/blocks/gallery.d.ts.map +1 -1
- package/dist/src/render/blocks/gallery.js +16 -14
- package/dist/src/render/blocks/gallery.js.map +1 -1
- package/dist/src/render/blocks/hero.d.ts +13 -8
- package/dist/src/render/blocks/hero.d.ts.map +1 -1
- package/dist/src/render/blocks/hero.js +27 -17
- package/dist/src/render/blocks/hero.js.map +1 -1
- package/dist/src/render/blocks/logo-strip.d.ts.map +1 -1
- package/dist/src/render/blocks/logo-strip.js +14 -11
- package/dist/src/render/blocks/logo-strip.js.map +1 -1
- package/dist/src/render/blocks/logos.d.ts.map +1 -1
- package/dist/src/render/blocks/logos.js +21 -12
- package/dist/src/render/blocks/logos.js.map +1 -1
- package/dist/src/render/blocks/media-figure.d.ts +10 -6
- package/dist/src/render/blocks/media-figure.d.ts.map +1 -1
- package/dist/src/render/blocks/media-figure.js +18 -13
- 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 +15 -11
- package/dist/src/render/blocks/pricing-table.js.map +1 -1
- package/dist/src/render/blocks/prose.d.ts +9 -3
- package/dist/src/render/blocks/prose.d.ts.map +1 -1
- package/dist/src/render/blocks/prose.js +11 -4
- package/dist/src/render/blocks/prose.js.map +1 -1
- package/dist/src/render/blocks/quote.d.ts +10 -10
- package/dist/src/render/blocks/quote.d.ts.map +1 -1
- package/dist/src/render/blocks/quote.js +14 -13
- package/dist/src/render/blocks/quote.js.map +1 -1
- package/dist/src/render/blocks/stat-counter.d.ts.map +1 -1
- package/dist/src/render/blocks/stat-counter.js +11 -13
- package/dist/src/render/blocks/stat-counter.js.map +1 -1
- package/dist/src/render/blocks/stats.d.ts.map +1 -1
- package/dist/src/render/blocks/stats.js +12 -13
- package/dist/src/render/blocks/stats.js.map +1 -1
- package/dist/src/render/blocks/testimonial.d.ts +9 -9
- package/dist/src/render/blocks/testimonial.d.ts.map +1 -1
- package/dist/src/render/blocks/testimonial.js +15 -14
- package/dist/src/render/blocks/testimonial.js.map +1 -1
- package/dist/src/render/chrome.d.ts +3 -1
- package/dist/src/render/chrome.d.ts.map +1 -1
- package/dist/src/render/chrome.js +71 -46
- package/dist/src/render/chrome.js.map +1 -1
- package/dist/src/render/layout.d.ts +26 -0
- package/dist/src/render/layout.d.ts.map +1 -0
- package/dist/src/render/layout.js +51 -0
- package/dist/src/render/layout.js.map +1 -0
- package/dist/src/render/render-block.d.ts.map +1 -1
- package/dist/src/render/render-block.js +23 -2
- package/dist/src/render/render-block.js.map +1 -1
- package/dist/src/render/term-archive.d.ts +8 -6
- package/dist/src/render/term-archive.d.ts.map +1 -1
- package/dist/src/render/term-archive.js +22 -16
- package/dist/src/render/term-archive.js.map +1 -1
- package/dist/src/render/widget-areas.d.ts +13 -0
- package/dist/src/render/widget-areas.d.ts.map +1 -0
- package/dist/src/render/widget-areas.js +20 -0
- package/dist/src/render/widget-areas.js.map +1 -0
- package/dist/theme.config.js +2 -2
- package/dist/theme.config.js.map +1 -1
- package/package.json +5 -5
- package/src/index.ts +1 -0
- package/src/render/blocks/accordion.ts +32 -27
- package/src/render/blocks/collection-list.ts +103 -53
- package/src/render/blocks/cta.ts +27 -28
- package/src/render/blocks/embed.ts +46 -43
- package/src/render/blocks/faq.ts +25 -21
- package/src/render/blocks/feature-grid.ts +42 -35
- package/src/render/blocks/gallery.ts +21 -16
- package/src/render/blocks/hero.ts +40 -28
- package/src/render/blocks/logo-strip.ts +26 -16
- package/src/render/blocks/logos.ts +33 -17
- package/src/render/blocks/media-figure.ts +22 -14
- package/src/render/blocks/pricing-table.ts +26 -15
- package/src/render/blocks/prose.ts +18 -4
- package/src/render/blocks/quote.ts +18 -14
- package/src/render/blocks/stat-counter.ts +25 -20
- package/src/render/blocks/stats.ts +31 -21
- package/src/render/blocks/testimonial.ts +22 -18
- package/src/render/chrome.ts +84 -49
- package/src/render/layout.ts +67 -0
- package/src/render/render-block.ts +24 -1
- package/src/render/term-archive.ts +54 -50
- package/src/render/widget-areas.ts +23 -0
- package/src/styles/archive.css +137 -38
- package/src/styles/base.css +553 -426
- package/src/styles/blocks.css +1343 -1010
- package/src/styles/theme.css +18 -24
- package/src/styles/tokens.css +166 -192
- package/src/styles/widgets.css +914 -0
- package/tokens.json +17 -17
package/src/render/chrome.ts
CHANGED
|
@@ -4,42 +4,39 @@ import {
|
|
|
4
4
|
type ChromeResult,
|
|
5
5
|
escapeAttribute,
|
|
6
6
|
escapeText,
|
|
7
|
+
type HtmlElement,
|
|
8
|
+
h,
|
|
7
9
|
renderBrandMark,
|
|
10
|
+
renderFooterWidgets,
|
|
8
11
|
renderSocialLinks,
|
|
9
12
|
renderThemeToggle,
|
|
10
13
|
serialize,
|
|
11
14
|
} from '@cogenta/theme-kit'
|
|
12
15
|
|
|
13
16
|
/**
|
|
14
|
-
* This theme's own header and footer (`theme@1.4`)
|
|
15
|
-
*
|
|
17
|
+
* This theme's own header and footer (`theme@1.4`), drawn the way a firm's
|
|
18
|
+
* website is: a quiet bar and a proper colophon.
|
|
16
19
|
*
|
|
17
|
-
* Header:
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
* the
|
|
25
|
-
*
|
|
26
|
-
* real `headerAction` field, inventing a role for a plain nav link is no
|
|
27
|
-
* longer the honest reading of the data.
|
|
20
|
+
* Header: the firm's name set as a wordmark in the display serif (or its
|
|
21
|
+
* uploaded logo), the primary navigation in small interface text, the
|
|
22
|
+
* `headerAction` as the one filled control, and the light/dark control last.
|
|
23
|
+
* Below the breakpoint the navigation collapses into a panel opened by a
|
|
24
|
+
* CSS-only toggle: a visually hidden checkbox paired with a `<label>`, no
|
|
25
|
+
* `<script>`. The label must precede the `<nav>` for the sibling combinator
|
|
26
|
+
* in `base.css` to reach it; the theme toggle follows the `<nav>`, outside
|
|
27
|
+
* the panel it does not control, so a visitor can switch appearance without
|
|
28
|
+
* opening the menu first.
|
|
28
29
|
*
|
|
29
|
-
* Footer: a
|
|
30
|
-
*
|
|
31
|
-
* `renderSocialLinks`),
|
|
32
|
-
*
|
|
33
|
-
* `brandingHtml
|
|
34
|
-
* received: never altered, never dropped.
|
|
30
|
+
* Footer: a twelve-column colophon. The name and `tagline` on the left, the
|
|
31
|
+
* footer navigation, the `footerNote` and the social profiles (real icons,
|
|
32
|
+
* via `renderSocialLinks`), then a legal line under a hairline with the
|
|
33
|
+
* copyright year and the site's name beside Cogenta's credit
|
|
34
|
+
* (`brandingHtml`, placed exactly once, exactly as received).
|
|
35
35
|
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
* flip appearance without opening that panel first, on desktop or mobile.
|
|
41
|
-
* This module ships no CSS of its own for it (`base.css` owns
|
|
42
|
-
* `.cg-theme-toggle`, in this theme's own visual register, not canonical's).
|
|
36
|
+
* `footerNote` is plain text. When an editor separates it into paragraphs
|
|
37
|
+
* with blank lines, each paragraph becomes its own block, and a paragraph of
|
|
38
|
+
* several lines gets its first line set as a label: an office city above its
|
|
39
|
+
* address, a heading above a legal mention. One line stays one paragraph.
|
|
43
40
|
*/
|
|
44
41
|
|
|
45
42
|
function navItems(links: readonly ChromeNavLink[]): string {
|
|
@@ -61,7 +58,7 @@ function renderNavList(links: readonly ChromeNavLink[]): string {
|
|
|
61
58
|
return items === '' ? '' : `<ul class="cg-nav__items">${items}</ul>`
|
|
62
59
|
}
|
|
63
60
|
|
|
64
|
-
/** The header's own call
|
|
61
|
+
/** The header's own call to action (`theme@1.4`): the one filled control in the bar. */
|
|
65
62
|
function renderHeaderAction(action: ChromeInput['headerAction']): string {
|
|
66
63
|
if (action === undefined) return ''
|
|
67
64
|
return (
|
|
@@ -70,13 +67,43 @@ function renderHeaderAction(action: ChromeInput['headerAction']): string {
|
|
|
70
67
|
)
|
|
71
68
|
}
|
|
72
69
|
|
|
70
|
+
/** `footerNote` as paragraphs: blank lines separate them, and a multi-line paragraph's first line is its label. */
|
|
71
|
+
export function renderFooterNote(note: string): HtmlElement | null {
|
|
72
|
+
const paragraphs = note
|
|
73
|
+
.replace(/\r\n?/g, '\n')
|
|
74
|
+
.split(/\n\s*\n/)
|
|
75
|
+
.map((paragraph) =>
|
|
76
|
+
paragraph
|
|
77
|
+
.split('\n')
|
|
78
|
+
.map((line) => line.trim())
|
|
79
|
+
.filter((line) => line !== ''),
|
|
80
|
+
)
|
|
81
|
+
.filter((lines) => lines.length > 0)
|
|
82
|
+
if (paragraphs.length === 0) return null
|
|
83
|
+
return h(
|
|
84
|
+
'div',
|
|
85
|
+
{ class: 'cg-site-footer__note', 'data-paragraphs': String(Math.min(paragraphs.length, 4)) },
|
|
86
|
+
paragraphs.map((lines) => {
|
|
87
|
+
const [first, ...rest] = lines as [string, ...string[]]
|
|
88
|
+
if (rest.length === 0) return h('p', { class: 'cg-site-footer__note-text' }, first)
|
|
89
|
+
return h(
|
|
90
|
+
'p',
|
|
91
|
+
{ class: 'cg-site-footer__note-text' },
|
|
92
|
+
h('span', { class: 'cg-site-footer__note-label' }, first),
|
|
93
|
+
rest.flatMap((line, index) => (index === 0 ? [line] : [h('br'), line])),
|
|
94
|
+
)
|
|
95
|
+
}),
|
|
96
|
+
)
|
|
97
|
+
}
|
|
98
|
+
|
|
73
99
|
export function renderChrome(input: ChromeInput): ChromeResult {
|
|
74
100
|
const siteNameText = escapeText(input.site.name)
|
|
75
101
|
const headerNavList = renderNavList(input.headerNav)
|
|
76
102
|
const headerAction = renderHeaderAction(input.headerAction)
|
|
77
103
|
const footerNavList = renderNavList(input.footerNav)
|
|
78
|
-
|
|
79
|
-
|
|
104
|
+
const mark =
|
|
105
|
+
renderBrandMark(input.brand, { className: 'cg-site-header__logo' }) ??
|
|
106
|
+
`<span class="cg-site-header__name">${siteNameText}</span>`
|
|
80
107
|
const themeToggle = serialize(renderThemeToggle(input.locale, { className: 'cg-theme-toggle' }))
|
|
81
108
|
|
|
82
109
|
const header =
|
|
@@ -84,45 +111,53 @@ export function renderChrome(input: ChromeInput): ChromeResult {
|
|
|
84
111
|
`<a class="cg-site-header__home" href="${escapeAttribute(input.homeHref)}">${mark}</a>` +
|
|
85
112
|
`<input type="checkbox" id="cg-nav-toggle" class="cg-nav-toggle-input" aria-label="Menu">` +
|
|
86
113
|
`<label for="cg-nav-toggle" class="cg-nav-toggle-label" aria-hidden="true">` +
|
|
87
|
-
`<span class="cg-nav-toggle-bar"></span><span class="cg-nav-toggle-bar"></span
|
|
114
|
+
`<span class="cg-nav-toggle-bar"></span><span class="cg-nav-toggle-bar"></span>` +
|
|
88
115
|
`</label>` +
|
|
89
|
-
`${themeToggle}` +
|
|
90
116
|
`<nav class="cg-site-header__nav" id="cg-nav" aria-label="Primary">` +
|
|
91
117
|
`${headerNavList}${headerAction}` +
|
|
92
118
|
`</nav>` +
|
|
119
|
+
`${themeToggle}` +
|
|
93
120
|
`</div></header>`
|
|
94
121
|
|
|
95
122
|
const tagline =
|
|
96
123
|
input.tagline === undefined
|
|
97
124
|
? ''
|
|
98
125
|
: `<p class="cg-site-footer__tagline" data-field="tagline">${escapeText(input.tagline)}</p>`
|
|
99
|
-
const social
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
126
|
+
const socialList = renderSocialLinks(input.social, {
|
|
127
|
+
className: 'cg-site-footer__social',
|
|
128
|
+
itemClassName: 'cg-site-footer__social-item',
|
|
129
|
+
})
|
|
130
|
+
const note = input.footerNote === undefined ? null : renderFooterNote(input.footerNote)
|
|
131
|
+
// The copyright year is the year the page is rendered, which is what a
|
|
132
|
+
// legal line on a live site means.
|
|
133
|
+
const year = new Date().getFullYear()
|
|
134
|
+
// The footer widget columns (`theme@1.6`) are one row under the colophon's
|
|
135
|
+
// own columns and above the legal line, in the same small type.
|
|
136
|
+
const footerWidgets = renderFooterWidgets(input.widgets, {
|
|
137
|
+
className: 'cg-site-footer__widgets',
|
|
138
|
+
headingLevel: 'h2',
|
|
139
|
+
})
|
|
112
140
|
|
|
113
141
|
const footer =
|
|
114
|
-
`<footer class="cg-site-footer"><div class="cg-site-
|
|
142
|
+
`<footer class="cg-site-footer"><div class="cg-site-footer__inner">` +
|
|
143
|
+
`<div class="cg-site-footer__top">` +
|
|
115
144
|
`<div class="cg-site-footer__brand">` +
|
|
116
|
-
`<a href="${escapeAttribute(input.homeHref)}">${siteNameText}</a>${tagline}` +
|
|
145
|
+
`<a class="cg-site-footer__name" href="${escapeAttribute(input.homeHref)}">${siteNameText}</a>${tagline}` +
|
|
117
146
|
`</div>` +
|
|
118
147
|
`${
|
|
119
148
|
footerNavList === ''
|
|
120
149
|
? ''
|
|
121
150
|
: `<nav class="cg-site-footer__nav" aria-label="Footer">${footerNavList}</nav>`
|
|
122
151
|
}` +
|
|
123
|
-
`${
|
|
124
|
-
`<div class="cg-site-
|
|
125
|
-
`</div
|
|
152
|
+
`${note === null ? '' : serialize(note)}` +
|
|
153
|
+
`${socialList === null ? '' : `<div class="cg-site-footer__social-col">${serialize(socialList)}</div>`}` +
|
|
154
|
+
`</div>` +
|
|
155
|
+
`${footerWidgets === null ? '' : serialize(footerWidgets)}` +
|
|
156
|
+
`<div class="cg-site-footer__bottom">` +
|
|
157
|
+
`<p class="cg-site-footer__legal">© ${year} ${siteNameText}</p>` +
|
|
158
|
+
`<div class="cg-site-footer__branding">${input.brandingHtml}</div>` +
|
|
159
|
+
`</div>` +
|
|
160
|
+
`</div></footer>`
|
|
126
161
|
|
|
127
162
|
return { header, footer }
|
|
128
163
|
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { type Attributes, type Child, type HtmlElement, h } from '@cogenta/theme-kit'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The frame every block of this theme renders into.
|
|
5
|
+
*
|
|
6
|
+
* The outer element is full width, so a band (the ink call to action, a
|
|
7
|
+
* muted variant) can paint edge to edge and so the section rhythm
|
|
8
|
+
* (`--cg-section`) is applied once, to every block alike. The inner element
|
|
9
|
+
* is the twelve-column container: one maximum width, one gutter, one column
|
|
10
|
+
* gap, for the whole site.
|
|
11
|
+
*
|
|
12
|
+
* `inner` is `figure` for the blocks whose own semantics are a figure (a
|
|
13
|
+
* quotation and its attribution, an image and its caption): `<figcaption>`
|
|
14
|
+
* must be a direct child of its `<figure>`, so the figure has to be the
|
|
15
|
+
* container itself rather than something nested inside it.
|
|
16
|
+
*/
|
|
17
|
+
export function section(
|
|
18
|
+
outer: 'section' | 'div',
|
|
19
|
+
block: string,
|
|
20
|
+
className: string,
|
|
21
|
+
attrs: Attributes,
|
|
22
|
+
inner: 'div' | 'figure',
|
|
23
|
+
...children: readonly Child[]
|
|
24
|
+
): HtmlElement {
|
|
25
|
+
return h(
|
|
26
|
+
outer,
|
|
27
|
+
{ class: `cg-section ${className}`, 'data-block': block, ...attrs },
|
|
28
|
+
h(inner, { class: `cg-container ${className}__inner` }, ...children),
|
|
29
|
+
)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** A two-digit, zero-padded ordinal: `01`, `02`… — the index language this theme uses for numbered rows. */
|
|
33
|
+
export function ordinal(index: number): string {
|
|
34
|
+
return String(index + 1).padStart(2, '0')
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* A thin right arrow, drawn inline and hidden from assistive technology: it
|
|
39
|
+
* repeats what the link it sits in already says.
|
|
40
|
+
*/
|
|
41
|
+
export function arrow(): HtmlElement {
|
|
42
|
+
return h(
|
|
43
|
+
'svg',
|
|
44
|
+
{
|
|
45
|
+
class: 'cg-arrow',
|
|
46
|
+
viewBox: '0 0 20 20',
|
|
47
|
+
fill: 'none',
|
|
48
|
+
stroke: 'currentColor',
|
|
49
|
+
'stroke-width': '1.5',
|
|
50
|
+
'stroke-linecap': 'square',
|
|
51
|
+
'aria-hidden': 'true',
|
|
52
|
+
focusable: 'false',
|
|
53
|
+
},
|
|
54
|
+
h('path', { d: 'M3 10h13M11.5 5.5 16 10l-4.5 4.5' }),
|
|
55
|
+
)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** `March 2026` in the page's locale, or `null` when the value is not a date `Intl` can format. */
|
|
59
|
+
export function monthYear(iso: string, locale: string): string | null {
|
|
60
|
+
const date = new Date(iso)
|
|
61
|
+
if (Number.isNaN(date.getTime())) return null
|
|
62
|
+
try {
|
|
63
|
+
return new Intl.DateTimeFormat(locale, { month: 'long', year: 'numeric' }).format(date)
|
|
64
|
+
} catch {
|
|
65
|
+
return new Intl.DateTimeFormat('en', { month: 'long', year: 'numeric' }).format(date)
|
|
66
|
+
}
|
|
67
|
+
}
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
h,
|
|
6
6
|
type PageContent,
|
|
7
7
|
pageHasOwnHeading,
|
|
8
|
+
providedBlockNode,
|
|
8
9
|
type RenderContext,
|
|
9
10
|
renderEntryHeader,
|
|
10
11
|
resolveBlockForRender,
|
|
@@ -38,6 +39,11 @@ export function renderBlock(
|
|
|
38
39
|
entries: FetchedEntries = {},
|
|
39
40
|
registry?: BlockRegistry,
|
|
40
41
|
): HtmlElement | null {
|
|
42
|
+
// A block a plugin provides arrives already rendered by the host, in a
|
|
43
|
+
// process of its own (contract D theme@1.7). Nothing else in this theme
|
|
44
|
+
// needs to know such blocks exist.
|
|
45
|
+
const provided = providedBlockNode(block, ctx)
|
|
46
|
+
if (provided !== null) return withBlockVariant(provided, block.variant)
|
|
41
47
|
// `block`'s type says `VocabularyBlock`, but the value crossing this
|
|
42
48
|
// boundary from stored content is not always literally one of the shared
|
|
43
49
|
// vocabulary — resolving here is what turns an unimplemented theme-private
|
|
@@ -106,6 +112,23 @@ function renderKnownBlock(
|
|
|
106
112
|
}
|
|
107
113
|
}
|
|
108
114
|
|
|
115
|
+
/** Below this, a reading time says nothing a reader can use. */
|
|
116
|
+
const MIN_READING_MINUTES_SHOWN = 3
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* A practice page or a short case study announcing "1 min read" reads like a
|
|
120
|
+
* blog, not like a firm: this theme shows a reading time only for a long
|
|
121
|
+
* read. The rest of the entry furniture is passed through untouched.
|
|
122
|
+
*/
|
|
123
|
+
function withoutShortReadingTime(page: PageContent): PageContent {
|
|
124
|
+
const entry = page.entry
|
|
125
|
+
if (entry?.readingMinutes === undefined || entry.readingMinutes >= MIN_READING_MINUTES_SHOWN) {
|
|
126
|
+
return page
|
|
127
|
+
}
|
|
128
|
+
const { readingMinutes: _short, ...rest } = entry
|
|
129
|
+
return { ...page, entry: rest }
|
|
130
|
+
}
|
|
131
|
+
|
|
109
132
|
/**
|
|
110
133
|
* `<main id="cg-main">` is mandatory: it is the skip-link's target, written
|
|
111
134
|
* once by `@cogenta/cli`'s `theme-render.ts` outside any theme's control.
|
|
@@ -129,7 +152,7 @@ export function renderPage(
|
|
|
129
152
|
entries: FetchedEntries = {},
|
|
130
153
|
registry?: BlockRegistry,
|
|
131
154
|
): HtmlElement {
|
|
132
|
-
const entryHeader = renderEntryHeader(page, ctx)
|
|
155
|
+
const entryHeader = renderEntryHeader(withoutShortReadingTime(page), ctx)
|
|
133
156
|
return h(
|
|
134
157
|
'main',
|
|
135
158
|
{ class: 'cg-main', id: 'cg-main' },
|
|
@@ -1,67 +1,71 @@
|
|
|
1
1
|
import { type HtmlElement, h, type TermArchiveInput } from '@cogenta/theme-kit'
|
|
2
|
+
import { monthYear } from './layout.js'
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
|
-
* The taxonomy-term archive (contract D `theme@1.3`)
|
|
5
|
+
* The taxonomy-term archive (contract D `theme@1.3`): an index, set the way a
|
|
6
|
+
* firm lists its work by sector.
|
|
5
7
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
8
|
+
* A breadcrumb in small capitals, the term as the page title in the display
|
|
9
|
+
* serif, its sub-terms as a ruled line of links, then one hairline row per
|
|
10
|
+
* entry: the title on the left half of the grid, the summary on the right,
|
|
11
|
+
* the date (when there is one) in the margin. `TermArchiveEntry` carries no
|
|
12
|
+
* picture, so this is a typographic index by design rather than a grid of
|
|
13
|
+
* cards with empty image slots.
|
|
11
14
|
*/
|
|
12
15
|
export function renderTermArchive(input: TermArchiveInput): HtmlElement {
|
|
13
|
-
const rows = input.entries.map((entry) =>
|
|
14
|
-
|
|
16
|
+
const rows = input.entries.map((entry) => {
|
|
17
|
+
const date = entry.publishedAt === null ? null : monthYear(entry.publishedAt, input.locale)
|
|
18
|
+
return h(
|
|
15
19
|
'li',
|
|
16
|
-
{ class: 'cg-
|
|
17
|
-
entry.publishedAt === null
|
|
18
|
-
? null
|
|
19
|
-
: h(
|
|
20
|
-
'time',
|
|
21
|
-
{ class: 'cg-list__date', datetime: entry.publishedAt },
|
|
22
|
-
entry.publishedAt.slice(0, 10),
|
|
23
|
-
),
|
|
20
|
+
{ class: 'cg-index__row' },
|
|
24
21
|
h(
|
|
25
|
-
'
|
|
26
|
-
{ class: 'cg-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
{ class: 'cg-
|
|
30
|
-
entry.href === null
|
|
31
|
-
? entry.title
|
|
32
|
-
: h('a', { class: 'cg-list__link', href: entry.href }, entry.title),
|
|
33
|
-
),
|
|
34
|
-
entry.summary === null ? null : h('p', { class: 'cg-list__excerpt' }, entry.summary),
|
|
22
|
+
'h2',
|
|
23
|
+
{ class: 'cg-index__title' },
|
|
24
|
+
entry.href === null
|
|
25
|
+
? entry.title
|
|
26
|
+
: h('a', { class: 'cg-index__link', href: entry.href }, entry.title),
|
|
35
27
|
),
|
|
36
|
-
|
|
37
|
-
|
|
28
|
+
entry.summary === null ? null : h('p', { class: 'cg-index__summary' }, entry.summary),
|
|
29
|
+
entry.publishedAt === null || date === null
|
|
30
|
+
? null
|
|
31
|
+
: h('time', { class: 'cg-index__date', datetime: entry.publishedAt }, date),
|
|
32
|
+
)
|
|
33
|
+
})
|
|
38
34
|
|
|
39
35
|
return h(
|
|
40
36
|
'main',
|
|
41
37
|
{ class: 'cg-main cg-archive', id: 'cg-main' },
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
:
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
'
|
|
49
|
-
{},
|
|
50
|
-
|
|
38
|
+
h(
|
|
39
|
+
'div',
|
|
40
|
+
{ class: 'cg-container cg-archive__inner' },
|
|
41
|
+
input.ancestors.length === 0
|
|
42
|
+
? null
|
|
43
|
+
: h(
|
|
44
|
+
'nav',
|
|
45
|
+
{ class: 'cg-archive__breadcrumb', 'aria-label': input.labels.breadcrumb },
|
|
46
|
+
h(
|
|
47
|
+
'ol',
|
|
48
|
+
{},
|
|
49
|
+
...input.ancestors.map((link) =>
|
|
50
|
+
h('li', {}, h('a', { href: link.href }, link.label)),
|
|
51
|
+
),
|
|
52
|
+
),
|
|
51
53
|
),
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
54
|
+
h('h1', { class: 'cg-archive__title' }, input.term.label),
|
|
55
|
+
input.children.length === 0
|
|
56
|
+
? null
|
|
57
|
+
: h(
|
|
58
|
+
'ul',
|
|
59
|
+
{ class: 'cg-archive__children', 'aria-label': input.labels.subterms },
|
|
60
|
+
...input.children.map((child) =>
|
|
61
|
+
h('li', {}, h('a', { href: child.href }, child.label)),
|
|
62
|
+
),
|
|
63
|
+
),
|
|
64
|
+
rows.length === 0
|
|
65
|
+
? h('p', { class: 'cg-archive__empty' }, input.labels.empty)
|
|
66
|
+
: h('ol', { class: 'cg-index' }, ...rows),
|
|
67
|
+
pager(input),
|
|
68
|
+
),
|
|
65
69
|
)
|
|
66
70
|
}
|
|
67
71
|
|
|
@@ -0,0 +1,23 @@
|
|
|
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 as the side
|
|
6
|
+
* column of a firm's report.
|
|
7
|
+
*/
|
|
8
|
+
export const widgetAreas: readonly {
|
|
9
|
+
readonly id: string
|
|
10
|
+
readonly label: string
|
|
11
|
+
readonly description?: string
|
|
12
|
+
}[] = [
|
|
13
|
+
{
|
|
14
|
+
id: 'sidebar',
|
|
15
|
+
label: 'Side column',
|
|
16
|
+
description: 'Beside a case study, a practice, a sector and search results.',
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
id: 'content-after',
|
|
20
|
+
label: 'Under the text',
|
|
21
|
+
description: 'After a case study, a practice or a page, before the comments.',
|
|
22
|
+
},
|
|
23
|
+
]
|