@glw907/cairn-cms 0.6.0-rc.0 → 0.6.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/components/ManageEditors.svelte +2 -2
- package/dist/components/ManageEditors.svelte.d.ts +1 -2
- package/dist/components/ManageEditors.svelte.d.ts.map +1 -1
- package/dist/render/pipeline.d.ts +4 -3
- package/dist/render/pipeline.d.ts.map +1 -1
- package/dist/render/pipeline.js +1 -1
- package/dist/render/registry.d.ts +4 -2
- package/dist/render/registry.d.ts.map +1 -1
- package/dist/render/rehype-dispatch.d.ts +7 -6
- package/dist/render/rehype-dispatch.d.ts.map +1 -1
- package/dist/render/rehype-dispatch.js +16 -14
- package/package.json +2 -1
- package/src/lib/components/ManageEditors.svelte +2 -2
- package/src/lib/render/pipeline.ts +5 -4
- package/src/lib/render/registry.ts +4 -2
- package/src/lib/render/rehype-dispatch.ts +16 -14
|
@@ -9,8 +9,8 @@ named `?/setRole`, `?/remove`, and `?/add` actions.
|
|
|
9
9
|
import type { Editor } from '../auth/types.js';
|
|
10
10
|
|
|
11
11
|
interface Props {
|
|
12
|
-
/** The editors load's data
|
|
13
|
-
data: { editors: Editor[]; self: string
|
|
12
|
+
/** The editors load's data: the allowlist and the acting owner's email. */
|
|
13
|
+
data: { editors: Editor[]; self: string };
|
|
14
14
|
/** The last action's result (an error message when it failed). */
|
|
15
15
|
form: { error?: string; ok?: boolean } | null;
|
|
16
16
|
}
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import type { Editor } from '../auth/types.js';
|
|
2
2
|
interface Props {
|
|
3
|
-
/** The editors load's data
|
|
3
|
+
/** The editors load's data: the allowlist and the acting owner's email. */
|
|
4
4
|
data: {
|
|
5
5
|
editors: Editor[];
|
|
6
6
|
self: string;
|
|
7
|
-
siteName: string;
|
|
8
7
|
};
|
|
9
8
|
/** The last action's result (an error message when it failed). */
|
|
10
9
|
form: {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ManageEditors.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/components/ManageEditors.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAG7C,UAAU,KAAK;IACb,
|
|
1
|
+
{"version":3,"file":"ManageEditors.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/components/ManageEditors.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAG7C,UAAU,KAAK;IACb,2EAA2E;IAC3E,IAAI,EAAE;QAAE,OAAO,EAAE,MAAM,EAAE,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IAC1C,kEAAkE;IAClE,IAAI,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,EAAE,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,IAAI,CAAC;CAC/C;AAyEH;;;;;GAKG;AACH,QAAA,MAAM,aAAa,2CAAwC,CAAC;AAC5D,KAAK,aAAa,GAAG,UAAU,CAAC,OAAO,aAAa,CAAC,CAAC;AACtD,eAAe,aAAa,CAAC"}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { type PluggableList } from 'unified';
|
|
2
2
|
import type { ComponentRegistry } from './registry.js';
|
|
3
3
|
export interface RendererOptions {
|
|
4
|
-
/**
|
|
5
|
-
*
|
|
6
|
-
|
|
4
|
+
/** Stamp a `data-rise` ordinal (0, 1, 2, …) on each top-level component so a site's
|
|
5
|
+
* CSS can drive an entrance-cascade delay off it. Omit for no stagger. The ordinal
|
|
6
|
+
* is inert, so a consumer's sanitize floor can keep `data-rise` and drop `style`. */
|
|
7
|
+
stagger?: boolean;
|
|
7
8
|
}
|
|
8
9
|
/** Compose a site's render pipeline from its component registry: directive syntax to
|
|
9
10
|
* stamped markers to registry-built hast. Returns `renderMarkdown` plus the remark/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pipeline.d.ts","sourceRoot":"","sources":["../../src/lib/render/pipeline.ts"],"names":[],"mappings":"AAAA,OAAO,EAAW,KAAK,aAAa,EAAE,MAAM,SAAS,CAAC;AAUtD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAEvD,MAAM,WAAW,eAAe;IAC9B;
|
|
1
|
+
{"version":3,"file":"pipeline.d.ts","sourceRoot":"","sources":["../../src/lib/render/pipeline.ts"],"names":[],"mappings":"AAAA,OAAO,EAAW,KAAK,aAAa,EAAE,MAAM,SAAS,CAAC;AAUtD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAEvD,MAAM,WAAW,eAAe;IAC9B;;0FAEsF;IACtF,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED;;uFAEuF;AACvF,wBAAgB,cAAc,CAAC,QAAQ,EAAE,iBAAiB,EAAE,OAAO,GAAE,eAAoB;;;8BAarD,MAAM,KAAG,OAAO,CAAC,MAAM,CAAC;EAE3D"}
|
package/dist/render/pipeline.js
CHANGED
|
@@ -13,7 +13,7 @@ import { rehypeDispatch } from './rehype-dispatch.js';
|
|
|
13
13
|
* rehype plugin arrays (so the Carta editor preview can reuse the exact same set). */
|
|
14
14
|
export function createRenderer(registry, options = {}) {
|
|
15
15
|
const remarkPlugins = [remarkDirective, [remarkDirectiveStamp, registry]];
|
|
16
|
-
const rehypePlugins = [rehypeRaw, [rehypeDispatch, registry, options.
|
|
16
|
+
const rehypePlugins = [rehypeRaw, [rehypeDispatch, registry, options.stagger], rehypeSlug];
|
|
17
17
|
const processor = unified()
|
|
18
18
|
.use(remarkParse)
|
|
19
19
|
.use(remarkGfm)
|
|
@@ -9,8 +9,10 @@ export interface ComponentDef {
|
|
|
9
9
|
description: string;
|
|
10
10
|
/** Markdown scaffold inserted at the cursor by the editor palette. */
|
|
11
11
|
insertTemplate: string;
|
|
12
|
-
/** Build the final hast element from the stamped directive element.
|
|
13
|
-
|
|
12
|
+
/** Build the final hast element from the stamped directive element. The engine
|
|
13
|
+
* stamps the entrance-stagger ordinal (`data-rise`) on the top-level result, so a
|
|
14
|
+
* build fn stays free of any motion concern. */
|
|
15
|
+
build: (node: Element) => Element;
|
|
14
16
|
/** Optional role-to-default-icon, e.g. `{ caution: 'warning' }`. */
|
|
15
17
|
defaultIconByRole?: Record<string, string>;
|
|
16
18
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/lib/render/registry.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AAEpC,6EAA6E;AAC7E,MAAM,WAAW,YAAY;IAC3B,uDAAuD;IACvD,IAAI,EAAE,MAAM,CAAC;IACb,qBAAqB;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,2BAA2B;IAC3B,WAAW,EAAE,MAAM,CAAC;IACpB,sEAAsE;IACtE,cAAc,EAAE,MAAM,CAAC;IACvB
|
|
1
|
+
{"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/lib/render/registry.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AAEpC,6EAA6E;AAC7E,MAAM,WAAW,YAAY;IAC3B,uDAAuD;IACvD,IAAI,EAAE,MAAM,CAAC;IACb,qBAAqB;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,2BAA2B;IAC3B,WAAW,EAAE,MAAM,CAAC;IACpB,sEAAsE;IACtE,cAAc,EAAE,MAAM,CAAC;IACvB;;qDAEiD;IACjD,KAAK,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,OAAO,CAAC;IAClC,oEAAoE;IACpE,iBAAiB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC5C;AAED,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,YAAY,EAAE,CAAC;IACrB,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS,CAAC;IAC5C,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;CAC9D;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,EAAE,UAAU,EAAE,EAAE;IAAE,UAAU,EAAE,YAAY,EAAE,CAAA;CAAE,GAAG,iBAAiB,CAQhG"}
|
|
@@ -10,15 +10,16 @@ export declare function splitHead(node: Element, makeIcon?: MakeIcon): {
|
|
|
10
10
|
head: Element;
|
|
11
11
|
rest: ElementContent[];
|
|
12
12
|
};
|
|
13
|
-
/** Section wrapper: `<section class=…><div class="card-body">…</div></section
|
|
14
|
-
|
|
15
|
-
export declare function cardShell(classes: string[], rise: string | undefined, body: ElementContent[]): Element;
|
|
13
|
+
/** Section wrapper: `<section class=…><div class="card-body">…</div></section>`. */
|
|
14
|
+
export declare function cardShell(classes: string[], body: ElementContent[]): Element;
|
|
16
15
|
/** Tag the first <ul> among children with `ec-grid` and strip its whitespace-only
|
|
17
16
|
* text nodes so the bare list serializes without newlines. Returns that <ul>. */
|
|
18
17
|
export declare function markFirstList(children: ElementContent[]): Element | undefined;
|
|
19
18
|
/** Rehype transformer: dispatch each stamped element through its registry `build`
|
|
20
|
-
* fn.
|
|
21
|
-
*
|
|
19
|
+
* fn. When `stagger` is on, each top-level primitive gets a `data-rise` attribute
|
|
20
|
+
* carrying its document-order index (0, 1, 2, …); the site's CSS maps that ordinal
|
|
21
|
+
* to an entrance delay. The index is inert, so a consumer's sanitize floor can keep
|
|
22
|
+
* `data-rise` while dropping `style`. Nested primitives never get it. Non-primitive
|
|
22
23
|
* content (lede, intro paragraphs, the page-toc nav) passes through untouched. */
|
|
23
|
-
export declare function rehypeDispatch(registry: ComponentRegistry,
|
|
24
|
+
export declare function rehypeDispatch(registry: ComponentRegistry, stagger?: boolean): (tree: Root) => void;
|
|
24
25
|
//# sourceMappingURL=rehype-dispatch.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rehype-dispatch.d.ts","sourceRoot":"","sources":["../../src/lib/render/rehype-dispatch.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,cAAc,
|
|
1
|
+
{"version":3,"file":"rehype-dispatch.d.ts","sourceRoot":"","sources":["../../src/lib/render/rehype-dispatch.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,cAAc,EAAE,MAAM,MAAM,CAAC;AAE1D,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAEvD,wBAAgB,SAAS,CAAC,IAAI,EAAE,cAAc,GAAG,SAAS,GAAG,IAAI,IAAI,OAAO,CAE3E;AAKD,wBAAgB,OAAO,CAAC,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAGvE;AAED,mFAAmF;AACnF,wBAAgB,QAAQ,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,CAGjE;AAED,kFAAkF;AAClF,MAAM,MAAM,QAAQ,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC;AAMhE,wBAAgB,SAAS,CAAC,IAAI,EAAE,OAAO,EAAE,QAAQ,CAAC,EAAE,QAAQ,GAAG;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,cAAc,EAAE,CAAA;CAAE,CAYvG;AAED,oFAAoF;AACpF,wBAAgB,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,cAAc,EAAE,GAAG,OAAO,CAE5E;AAED;kFACkF;AAClF,wBAAgB,aAAa,CAAC,QAAQ,EAAE,cAAc,EAAE,GAAG,OAAO,GAAG,SAAS,CAS7E;AAoBD;;;;;mFAKmF;AACnF,wBAAgB,cAAc,CAAC,QAAQ,EAAE,iBAAiB,EAAE,OAAO,CAAC,EAAE,OAAO,IACnE,MAAM,IAAI,UAYnB"}
|
|
@@ -32,13 +32,9 @@ export function splitHead(node, makeIcon) {
|
|
|
32
32
|
headKids.push(h2);
|
|
33
33
|
return { head: h('div', { className: ['ec-head'] }, headKids), rest };
|
|
34
34
|
}
|
|
35
|
-
/** Section wrapper: `<section class=…><div class="card-body">…</div></section
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
const properties = { className: classes };
|
|
39
|
-
if (rise)
|
|
40
|
-
properties.style = rise;
|
|
41
|
-
return h('section', properties, [h('div', { className: ['card-body'] }, body)]);
|
|
35
|
+
/** Section wrapper: `<section class=…><div class="card-body">…</div></section>`. */
|
|
36
|
+
export function cardShell(classes, body) {
|
|
37
|
+
return h('section', { className: classes }, [h('div', { className: ['card-body'] }, body)]);
|
|
42
38
|
}
|
|
43
39
|
/** Tag the first <ul> among children with `ec-grid` and strip its whitespace-only
|
|
44
40
|
* text nodes so the bare list serializes without newlines. Returns that <ul>. */
|
|
@@ -51,7 +47,8 @@ export function markFirstList(children) {
|
|
|
51
47
|
return ul;
|
|
52
48
|
}
|
|
53
49
|
// Recurse into a node's children, transforming any nested primitive sections
|
|
54
|
-
// (a grid inside a card, panels inside a split)
|
|
50
|
+
// (a grid inside a card, panels inside a split). Nested primitives never carry the
|
|
51
|
+
// entrance stagger; only top-level ones do (stamped in the transformer below).
|
|
55
52
|
function transformChildren(children, registry) {
|
|
56
53
|
return children.map((c) => {
|
|
57
54
|
if (isElement(c) && c.properties?.dataPrimitive)
|
|
@@ -61,22 +58,27 @@ function transformChildren(children, registry) {
|
|
|
61
58
|
return c;
|
|
62
59
|
});
|
|
63
60
|
}
|
|
64
|
-
function transformNode(node, registry
|
|
61
|
+
function transformNode(node, registry) {
|
|
65
62
|
node.children = transformChildren(node.children, registry);
|
|
66
63
|
const name = strProp(node, 'dataPrimitive');
|
|
67
64
|
const def = name ? registry.get(name) : undefined;
|
|
68
|
-
return def ? def.build(node
|
|
65
|
+
return def ? def.build(node) : node;
|
|
69
66
|
}
|
|
70
67
|
/** Rehype transformer: dispatch each stamped element through its registry `build`
|
|
71
|
-
* fn.
|
|
72
|
-
*
|
|
68
|
+
* fn. When `stagger` is on, each top-level primitive gets a `data-rise` attribute
|
|
69
|
+
* carrying its document-order index (0, 1, 2, …); the site's CSS maps that ordinal
|
|
70
|
+
* to an entrance delay. The index is inert, so a consumer's sanitize floor can keep
|
|
71
|
+
* `data-rise` while dropping `style`. Nested primitives never get it. Non-primitive
|
|
73
72
|
* content (lede, intro paragraphs, the page-toc nav) passes through untouched. */
|
|
74
|
-
export function rehypeDispatch(registry,
|
|
73
|
+
export function rehypeDispatch(registry, stagger) {
|
|
75
74
|
return (tree) => {
|
|
76
75
|
let idx = 0;
|
|
77
76
|
tree.children = tree.children.map((child) => {
|
|
78
77
|
if (isElement(child) && child.properties?.dataPrimitive) {
|
|
79
|
-
|
|
78
|
+
const el = transformNode(child, registry);
|
|
79
|
+
if (stagger)
|
|
80
|
+
el.properties = { ...el.properties, dataRise: String(idx++) };
|
|
81
|
+
return el;
|
|
80
82
|
}
|
|
81
83
|
if (isElement(child))
|
|
82
84
|
child.children = transformChildren(child.children, registry);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@glw907/cairn-cms",
|
|
3
|
-
"version": "0.6.0
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Embedded, magic-link, GitHub-committing CMS for SvelteKit/Cloudflare sites.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": [
|
|
@@ -85,6 +85,7 @@
|
|
|
85
85
|
"@sveltejs/kit": "^2.61",
|
|
86
86
|
"@sveltejs/package": "^2",
|
|
87
87
|
"@sveltejs/vite-plugin-svelte": "^7.1",
|
|
88
|
+
"@types/node": "^22.19.19",
|
|
88
89
|
"@vitest/browser": "^4.1.7",
|
|
89
90
|
"@vitest/browser-playwright": "^4.1.7",
|
|
90
91
|
"carta-md": "^4.11",
|
|
@@ -9,8 +9,8 @@ named `?/setRole`, `?/remove`, and `?/add` actions.
|
|
|
9
9
|
import type { Editor } from '../auth/types.js';
|
|
10
10
|
|
|
11
11
|
interface Props {
|
|
12
|
-
/** The editors load's data
|
|
13
|
-
data: { editors: Editor[]; self: string
|
|
12
|
+
/** The editors load's data: the allowlist and the acting owner's email. */
|
|
13
|
+
data: { editors: Editor[]; self: string };
|
|
14
14
|
/** The last action's result (an error message when it failed). */
|
|
15
15
|
form: { error?: string; ok?: boolean } | null;
|
|
16
16
|
}
|
|
@@ -11,9 +11,10 @@ import { rehypeDispatch } from './rehype-dispatch.js';
|
|
|
11
11
|
import type { ComponentRegistry } from './registry.js';
|
|
12
12
|
|
|
13
13
|
export interface RendererOptions {
|
|
14
|
-
/**
|
|
15
|
-
*
|
|
16
|
-
rise
|
|
14
|
+
/** Stamp a `data-rise` ordinal (0, 1, 2, …) on each top-level component so a site's
|
|
15
|
+
* CSS can drive an entrance-cascade delay off it. Omit for no stagger. The ordinal
|
|
16
|
+
* is inert, so a consumer's sanitize floor can keep `data-rise` and drop `style`. */
|
|
17
|
+
stagger?: boolean;
|
|
17
18
|
}
|
|
18
19
|
|
|
19
20
|
/** Compose a site's render pipeline from its component registry: directive syntax to
|
|
@@ -21,7 +22,7 @@ export interface RendererOptions {
|
|
|
21
22
|
* rehype plugin arrays (so the Carta editor preview can reuse the exact same set). */
|
|
22
23
|
export function createRenderer(registry: ComponentRegistry, options: RendererOptions = {}) {
|
|
23
24
|
const remarkPlugins: PluggableList = [remarkDirective, [remarkDirectiveStamp, registry]];
|
|
24
|
-
const rehypePlugins: PluggableList = [rehypeRaw, [rehypeDispatch, registry, options.
|
|
25
|
+
const rehypePlugins: PluggableList = [rehypeRaw, [rehypeDispatch, registry, options.stagger], rehypeSlug];
|
|
25
26
|
const processor = unified()
|
|
26
27
|
.use(remarkParse)
|
|
27
28
|
.use(remarkGfm)
|
|
@@ -15,8 +15,10 @@ export interface ComponentDef {
|
|
|
15
15
|
description: string;
|
|
16
16
|
/** Markdown scaffold inserted at the cursor by the editor palette. */
|
|
17
17
|
insertTemplate: string;
|
|
18
|
-
/** Build the final hast element from the stamped directive element.
|
|
19
|
-
|
|
18
|
+
/** Build the final hast element from the stamped directive element. The engine
|
|
19
|
+
* stamps the entrance-stagger ordinal (`data-rise`) on the top-level result, so a
|
|
20
|
+
* build fn stays free of any motion concern. */
|
|
21
|
+
build: (node: Element) => Element;
|
|
20
22
|
/** Optional role-to-default-icon, e.g. `{ caution: 'warning' }`. */
|
|
21
23
|
defaultIconByRole?: Record<string, string>;
|
|
22
24
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Root, Element, ElementContent
|
|
1
|
+
import type { Root, Element, ElementContent } from 'hast';
|
|
2
2
|
import { h } from 'hastscript';
|
|
3
3
|
import type { ComponentRegistry } from './registry.js';
|
|
4
4
|
|
|
@@ -41,12 +41,9 @@ export function splitHead(node: Element, makeIcon?: MakeIcon): { head: Element;
|
|
|
41
41
|
return { head: h('div', { className: ['ec-head'] }, headKids), rest };
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
/** Section wrapper: `<section class=…><div class="card-body">…</div></section
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
const properties: Properties = { className: classes };
|
|
48
|
-
if (rise) properties.style = rise;
|
|
49
|
-
return h('section', properties, [h('div', { className: ['card-body'] }, body)]);
|
|
44
|
+
/** Section wrapper: `<section class=…><div class="card-body">…</div></section>`. */
|
|
45
|
+
export function cardShell(classes: string[], body: ElementContent[]): Element {
|
|
46
|
+
return h('section', { className: classes }, [h('div', { className: ['card-body'] }, body)]);
|
|
50
47
|
}
|
|
51
48
|
|
|
52
49
|
/** Tag the first <ul> among children with `ec-grid` and strip its whitespace-only
|
|
@@ -63,7 +60,8 @@ export function markFirstList(children: ElementContent[]): Element | undefined {
|
|
|
63
60
|
}
|
|
64
61
|
|
|
65
62
|
// Recurse into a node's children, transforming any nested primitive sections
|
|
66
|
-
// (a grid inside a card, panels inside a split)
|
|
63
|
+
// (a grid inside a card, panels inside a split). Nested primitives never carry the
|
|
64
|
+
// entrance stagger; only top-level ones do (stamped in the transformer below).
|
|
67
65
|
function transformChildren(children: ElementContent[], registry: ComponentRegistry): ElementContent[] {
|
|
68
66
|
return children.map((c) => {
|
|
69
67
|
if (isElement(c) && c.properties?.dataPrimitive) return transformNode(c, registry);
|
|
@@ -72,23 +70,27 @@ function transformChildren(children: ElementContent[], registry: ComponentRegist
|
|
|
72
70
|
});
|
|
73
71
|
}
|
|
74
72
|
|
|
75
|
-
function transformNode(node: Element, registry: ComponentRegistry
|
|
73
|
+
function transformNode(node: Element, registry: ComponentRegistry): Element {
|
|
76
74
|
node.children = transformChildren(node.children as ElementContent[], registry);
|
|
77
75
|
const name = strProp(node, 'dataPrimitive');
|
|
78
76
|
const def = name ? registry.get(name) : undefined;
|
|
79
|
-
return def ? def.build(node
|
|
77
|
+
return def ? def.build(node) : node;
|
|
80
78
|
}
|
|
81
79
|
|
|
82
80
|
/** Rehype transformer: dispatch each stamped element through its registry `build`
|
|
83
|
-
* fn.
|
|
84
|
-
*
|
|
81
|
+
* fn. When `stagger` is on, each top-level primitive gets a `data-rise` attribute
|
|
82
|
+
* carrying its document-order index (0, 1, 2, …); the site's CSS maps that ordinal
|
|
83
|
+
* to an entrance delay. The index is inert, so a consumer's sanitize floor can keep
|
|
84
|
+
* `data-rise` while dropping `style`. Nested primitives never get it. Non-primitive
|
|
85
85
|
* content (lede, intro paragraphs, the page-toc nav) passes through untouched. */
|
|
86
|
-
export function rehypeDispatch(registry: ComponentRegistry,
|
|
86
|
+
export function rehypeDispatch(registry: ComponentRegistry, stagger?: boolean) {
|
|
87
87
|
return (tree: Root) => {
|
|
88
88
|
let idx = 0;
|
|
89
89
|
tree.children = (tree.children as ElementContent[]).map((child) => {
|
|
90
90
|
if (isElement(child) && child.properties?.dataPrimitive) {
|
|
91
|
-
|
|
91
|
+
const el = transformNode(child, registry);
|
|
92
|
+
if (stagger) el.properties = { ...el.properties, dataRise: String(idx++) };
|
|
93
|
+
return el;
|
|
92
94
|
}
|
|
93
95
|
if (isElement(child)) child.children = transformChildren(child.children as ElementContent[], registry);
|
|
94
96
|
return child;
|