@glw907/cairn-cms 0.11.0 → 0.17.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.
Files changed (124) hide show
  1. package/dist/auth/crypto.d.ts +8 -2
  2. package/dist/auth/crypto.d.ts.map +1 -1
  3. package/dist/auth/crypto.js +12 -2
  4. package/dist/auth/store.d.ts +2 -0
  5. package/dist/auth/store.d.ts.map +1 -1
  6. package/dist/auth/store.js +17 -5
  7. package/dist/components/ComponentForm.svelte +33 -10
  8. package/dist/components/ComponentForm.svelte.d.ts.map +1 -1
  9. package/dist/components/EditPage.svelte +4 -6
  10. package/dist/components/EditPage.svelte.d.ts +1 -1
  11. package/dist/components/EditPage.svelte.d.ts.map +1 -1
  12. package/dist/components/IconPicker.svelte +53 -7
  13. package/dist/components/IconPicker.svelte.d.ts +7 -3
  14. package/dist/components/IconPicker.svelte.d.ts.map +1 -1
  15. package/dist/content/adapter.d.ts +4 -0
  16. package/dist/content/adapter.d.ts.map +1 -0
  17. package/dist/content/adapter.js +4 -0
  18. package/dist/content/concepts.js +2 -2
  19. package/dist/content/schema.d.ts +75 -0
  20. package/dist/content/schema.d.ts.map +1 -0
  21. package/dist/content/schema.js +72 -0
  22. package/dist/content/types.d.ts +30 -7
  23. package/dist/content/types.d.ts.map +1 -1
  24. package/dist/content/validate.d.ts +5 -3
  25. package/dist/content/validate.d.ts.map +1 -1
  26. package/dist/content/validate.js +14 -7
  27. package/dist/delivery/content-index.d.ts +8 -0
  28. package/dist/delivery/content-index.d.ts.map +1 -1
  29. package/dist/delivery/content-index.js +23 -12
  30. package/dist/delivery/feeds.d.ts +1 -1
  31. package/dist/delivery/feeds.d.ts.map +1 -1
  32. package/dist/delivery/feeds.js +31 -16
  33. package/dist/delivery/index.d.ts +5 -1
  34. package/dist/delivery/index.d.ts.map +1 -1
  35. package/dist/delivery/index.js +2 -0
  36. package/dist/delivery/seo-fields.d.ts +22 -0
  37. package/dist/delivery/seo-fields.d.ts.map +1 -0
  38. package/dist/delivery/seo-fields.js +32 -0
  39. package/dist/delivery/site-index.d.ts +2 -2
  40. package/dist/delivery/site-index.d.ts.map +1 -1
  41. package/dist/delivery/site-index.js +16 -18
  42. package/dist/delivery/site-indexes.d.ts +26 -0
  43. package/dist/delivery/site-indexes.d.ts.map +1 -0
  44. package/dist/delivery/site-indexes.js +30 -0
  45. package/dist/env.d.ts.map +1 -1
  46. package/dist/env.js +14 -0
  47. package/dist/github/signing.d.ts +12 -0
  48. package/dist/github/signing.d.ts.map +1 -1
  49. package/dist/github/signing.js +22 -0
  50. package/dist/index.d.ts +9 -3
  51. package/dist/index.d.ts.map +1 -1
  52. package/dist/index.js +5 -2
  53. package/dist/render/component-grammar.d.ts +7 -0
  54. package/dist/render/component-grammar.d.ts.map +1 -1
  55. package/dist/render/component-grammar.js +27 -8
  56. package/dist/render/component-validate.js +3 -3
  57. package/dist/render/glyph.d.ts +4 -1
  58. package/dist/render/glyph.d.ts.map +1 -1
  59. package/dist/render/glyph.js +6 -2
  60. package/dist/render/pipeline.d.ts +10 -0
  61. package/dist/render/pipeline.d.ts.map +1 -1
  62. package/dist/render/pipeline.js +15 -1
  63. package/dist/render/registry.d.ts +23 -5
  64. package/dist/render/registry.d.ts.map +1 -1
  65. package/dist/render/registry.js +6 -0
  66. package/dist/render/rehype-dispatch.d.ts +1 -5
  67. package/dist/render/rehype-dispatch.d.ts.map +1 -1
  68. package/dist/render/rehype-dispatch.js +71 -19
  69. package/dist/render/remark-directives.d.ts +1 -1
  70. package/dist/render/remark-directives.d.ts.map +1 -1
  71. package/dist/render/remark-directives.js +37 -0
  72. package/dist/render/sanitize-schema.d.ts +20 -0
  73. package/dist/render/sanitize-schema.d.ts.map +1 -0
  74. package/dist/render/sanitize-schema.js +48 -0
  75. package/dist/sveltekit/auth-routes.d.ts.map +1 -1
  76. package/dist/sveltekit/auth-routes.js +29 -11
  77. package/dist/sveltekit/content-routes.js +2 -2
  78. package/dist/sveltekit/guard.d.ts +1 -1
  79. package/dist/sveltekit/guard.d.ts.map +1 -1
  80. package/dist/sveltekit/guard.js +25 -10
  81. package/dist/sveltekit/nav-routes.js +2 -2
  82. package/dist/sveltekit/public-routes.d.ts +3 -0
  83. package/dist/sveltekit/public-routes.d.ts.map +1 -1
  84. package/dist/sveltekit/public-routes.js +10 -3
  85. package/dist/sveltekit/types.d.ts +6 -0
  86. package/dist/sveltekit/types.d.ts.map +1 -1
  87. package/package.json +3 -2
  88. package/src/lib/auth/crypto.ts +14 -2
  89. package/src/lib/auth/store.ts +18 -5
  90. package/src/lib/components/ComponentForm.svelte +33 -10
  91. package/src/lib/components/EditPage.svelte +4 -6
  92. package/src/lib/components/IconPicker.svelte +53 -7
  93. package/src/lib/content/adapter.ts +10 -0
  94. package/src/lib/content/concepts.ts +2 -2
  95. package/src/lib/content/schema.ts +133 -0
  96. package/src/lib/content/types.ts +30 -7
  97. package/src/lib/content/validate.ts +10 -7
  98. package/src/lib/delivery/content-index.ts +32 -12
  99. package/src/lib/delivery/feeds.ts +34 -19
  100. package/src/lib/delivery/index.ts +5 -1
  101. package/src/lib/delivery/seo-fields.ts +43 -0
  102. package/src/lib/delivery/site-index.ts +15 -16
  103. package/src/lib/delivery/site-indexes.ts +64 -0
  104. package/src/lib/env.ts +13 -0
  105. package/src/lib/github/signing.ts +32 -0
  106. package/src/lib/index.ts +8 -2
  107. package/src/lib/render/component-grammar.ts +34 -10
  108. package/src/lib/render/component-validate.ts +3 -3
  109. package/src/lib/render/glyph.ts +6 -2
  110. package/src/lib/render/pipeline.ts +25 -1
  111. package/src/lib/render/registry.ts +27 -5
  112. package/src/lib/render/rehype-dispatch.ts +67 -20
  113. package/src/lib/render/remark-directives.ts +39 -1
  114. package/src/lib/render/sanitize-schema.ts +57 -0
  115. package/src/lib/sveltekit/auth-routes.ts +30 -11
  116. package/src/lib/sveltekit/content-routes.ts +2 -2
  117. package/src/lib/sveltekit/guard.ts +25 -10
  118. package/src/lib/sveltekit/nav-routes.ts +2 -2
  119. package/src/lib/sveltekit/public-routes.ts +13 -3
  120. package/src/lib/sveltekit/types.ts +5 -1
  121. package/dist/render/sanitize.d.ts +0 -8
  122. package/dist/render/sanitize.d.ts.map +0 -1
  123. package/dist/render/sanitize.js +0 -26
  124. package/src/lib/render/sanitize.ts +0 -27
package/dist/env.js CHANGED
@@ -11,6 +11,20 @@ export function requireOrigin(env) {
11
11
  if (!origin) {
12
12
  throw new Error('PUBLIC_ORIGIN is not configured');
13
13
  }
14
+ let hostname;
15
+ try {
16
+ hostname = new URL(origin).hostname;
17
+ }
18
+ catch {
19
+ throw new Error(`PUBLIC_ORIGIN is not a valid URL, got ${origin}`);
20
+ }
21
+ // The magic-link origin must be https in production so the link and the __Host- cookie are
22
+ // origin-bound. http is allowed only for local dev on localhost or 127.0.0.1, matched exactly so
23
+ // a lookalike host like localhost.example.com cannot skip the https requirement.
24
+ const isLocal = hostname === 'localhost' || hostname === '127.0.0.1';
25
+ if (!origin.startsWith('https://') && !isLocal) {
26
+ throw new Error(`PUBLIC_ORIGIN must be https in production, got ${origin}`);
27
+ }
14
28
  return origin;
15
29
  }
16
30
  /**
@@ -3,6 +3,18 @@ import type { AppCredentials } from './types.js';
3
3
  export declare function appJwt(appId: string, privateKeyPem: string): Promise<string>;
4
4
  /** Exchange the App JWT for a short-lived installation access token. */
5
5
  export declare function installationToken(creds: AppCredentials): Promise<string>;
6
+ /**
7
+ * Build an installation-token cache. A module-global instance memoizes the minted token per
8
+ * installation for most of its one-hour life, so a warm Worker isolate reuses it across requests
9
+ * instead of re-signing and re-calling GitHub on every list and commit. A cold isolate re-mints,
10
+ * which is always safe. This mirrors the default of @octokit/auth-app, which caches installation
11
+ * tokens in memory and returns them until expiry. The TTL stays under GitHub's documented one-hour
12
+ * lifetime, so a fixed margin avoids parsing the API expiry. `mint` and `now` are injected so the
13
+ * cache is testable with no network call and no real clock.
14
+ */
15
+ export declare function createInstallationTokenCache(mint?: (creds: AppCredentials) => Promise<string>, now?: () => number, ttlMs?: number): (creds: AppCredentials) => Promise<string>;
16
+ /** The shared installation-token cache, one instance per Worker isolate. */
17
+ export declare const cachedInstallationToken: (creds: AppCredentials) => Promise<string>;
6
18
  /**
7
19
  * Deploy-time self-test for the App signer: sign a dummy JWT with the configured key. It
8
20
  * exercises the brittle PKCS#1-to-PKCS#8 conversion and the Web Crypto import and sign with
@@ -1 +1 @@
1
- {"version":3,"file":"signing.d.ts","sourceRoot":"","sources":["../../src/lib/github/signing.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AA0CjD,wFAAwF;AACxF,wBAAsB,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAclF;AAED,wEAAwE;AACxE,wBAAsB,iBAAiB,CAAC,KAAK,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CAa9E;AAED;;;;;;GAMG;AACH,wBAAsB,eAAe,CAAC,KAAK,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC;IAAE,EAAE,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAQrH"}
1
+ {"version":3,"file":"signing.d.ts","sourceRoot":"","sources":["../../src/lib/github/signing.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AA0CjD,wFAAwF;AACxF,wBAAsB,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAclF;AAED,wEAAwE;AACxE,wBAAsB,iBAAiB,CAAC,KAAK,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CAa9E;AAOD;;;;;;;;GAQG;AACH,wBAAgB,4BAA4B,CAC1C,IAAI,GAAE,CAAC,KAAK,EAAE,cAAc,KAAK,OAAO,CAAC,MAAM,CAAqB,EACpE,GAAG,GAAE,MAAM,MAAyB,EACpC,KAAK,SAAiB,GACrB,CAAC,KAAK,EAAE,cAAc,KAAK,OAAO,CAAC,MAAM,CAAC,CAS5C;AAED,4EAA4E;AAC5E,eAAO,MAAM,uBAAuB,UAZzB,cAAc,KAAK,OAAO,CAAC,MAAM,CAYyB,CAAC;AAEtE;;;;;;GAMG;AACH,wBAAsB,eAAe,CAAC,KAAK,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC;IAAE,EAAE,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAQrH"}
@@ -59,6 +59,28 @@ export async function installationToken(creds) {
59
59
  throw new Error(`GitHub installation token failed: ${res.status}`);
60
60
  return (await res.json()).token;
61
61
  }
62
+ /**
63
+ * Build an installation-token cache. A module-global instance memoizes the minted token per
64
+ * installation for most of its one-hour life, so a warm Worker isolate reuses it across requests
65
+ * instead of re-signing and re-calling GitHub on every list and commit. A cold isolate re-mints,
66
+ * which is always safe. This mirrors the default of @octokit/auth-app, which caches installation
67
+ * tokens in memory and returns them until expiry. The TTL stays under GitHub's documented one-hour
68
+ * lifetime, so a fixed margin avoids parsing the API expiry. `mint` and `now` are injected so the
69
+ * cache is testable with no network call and no real clock.
70
+ */
71
+ export function createInstallationTokenCache(mint = installationToken, now = () => Date.now(), ttlMs = 55 * 60 * 1000) {
72
+ const cache = new Map();
73
+ return async function get(creds) {
74
+ const hit = cache.get(creds.installationId);
75
+ if (hit && hit.expiresAt > now())
76
+ return hit.token;
77
+ const token = await mint(creds);
78
+ cache.set(creds.installationId, { token, expiresAt: now() + ttlMs });
79
+ return token;
80
+ };
81
+ }
82
+ /** The shared installation-token cache, one instance per Worker isolate. */
83
+ export const cachedInstallationToken = createInstallationTokenCache();
62
84
  /**
63
85
  * Deploy-time self-test for the App signer: sign a dummy JWT with the configured key. It
64
86
  * exercises the brittle PKCS#1-to-PKCS#8 conversion and the Web Crypto import and sign with
package/dist/index.d.ts CHANGED
@@ -6,7 +6,9 @@ export type { CairnAdapter, ConceptConfig, FrontmatterField, TextField, Textarea
6
6
  export { CONCEPT_ROUTING, normalizeConcepts, findConcept } from './content/concepts.js';
7
7
  export { composeRuntime } from './content/compose.js';
8
8
  export { frontmatterFromForm, dateInputValue, serializeMarkdown, parseMarkdown, } from './content/frontmatter.js';
9
- export { validateFields } from './content/validate.js';
9
+ export { defineFields } from './content/schema.js';
10
+ export { defineAdapter } from './content/adapter.js';
11
+ export type { ConceptSchema, Infer, InferFields, DefineFieldsOptions, StandardInput, StandardSchemaV1 } from './content/schema.js';
10
12
  export { isValidId, idFromFilename, filenameFromId, slugify, slugFromId, composeDatedId, } from './content/ids.js';
11
13
  export type { DatePrefix } from './content/ids.js';
12
14
  export { defineRegistry, emptyValues } from './render/registry.js';
@@ -20,7 +22,7 @@ export type { ReferenceOptions } from './render/component-reference.js';
20
22
  export { glyph } from './render/glyph.js';
21
23
  export type { IconSet } from './render/glyph.js';
22
24
  export { remarkDirectiveStamp } from './render/remark-directives.js';
23
- export { rehypeDispatch, isElement, strProp, iconSpan, splitHead, cardShell, markFirstList, } from './render/rehype-dispatch.js';
25
+ export { rehypeDispatch, isElement, strProp, iconSpan, cardShell, markFirstList, } from './render/rehype-dispatch.js';
24
26
  export type { MakeIcon } from './render/rehype-dispatch.js';
25
27
  export { createRenderer } from './render/pipeline.js';
26
28
  export type { RendererOptions } from './render/pipeline.js';
@@ -34,9 +36,11 @@ export { parseSiteConfig, urlPolicyFrom, extractMenu, setMenu, validateNavTree,
34
36
  export type { NavNode, SiteConfig } from './nav/site-config.js';
35
37
  export { permalink } from './content/permalink.js';
36
38
  export { createContentIndex, fromGlob } from './delivery/content-index.js';
37
- export type { RawFile, ContentSummary, ContentEntry, ContentIndex, } from './delivery/content-index.js';
39
+ export type { RawFile, ContentSummary, ContentEntry, ContentIndex, ContentProblem, } from './delivery/content-index.js';
38
40
  export { createSiteIndex } from './delivery/site-index.js';
39
41
  export type { SiteIndex, ConceptIndex } from './delivery/site-index.js';
42
+ export { createSiteIndexes } from './delivery/site-indexes.js';
43
+ export type { SiteIndexes, SiteGlobs } from './delivery/site-indexes.js';
40
44
  export { deriveExcerpt, wordCount } from './delivery/excerpt.js';
41
45
  export { buildRssFeed, buildJsonFeed } from './delivery/feeds.js';
42
46
  export type { FeedChannel, FeedItem } from './delivery/feeds.js';
@@ -45,6 +49,8 @@ export type { SitemapUrl } from './delivery/sitemap.js';
45
49
  export { buildRobots } from './delivery/robots.js';
46
50
  export { buildSeoMeta } from './delivery/seo.js';
47
51
  export type { SeoInput, SeoMeta } from './delivery/seo.js';
52
+ export { readSeoFields, resolveImageUrl } from './delivery/seo-fields.js';
53
+ export type { SeoFields } from './delivery/seo-fields.js';
48
54
  export { paginate } from './delivery/paginate.js';
49
55
  export type { Page } from './delivery/paginate.js';
50
56
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/lib/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/lib/index.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -5,7 +5,8 @@ export { buildMagicLinkMessage, cloudflareSend } from './email.js';
5
5
  export { CONCEPT_ROUTING, normalizeConcepts, findConcept } from './content/concepts.js';
6
6
  export { composeRuntime } from './content/compose.js';
7
7
  export { frontmatterFromForm, dateInputValue, serializeMarkdown, parseMarkdown, } from './content/frontmatter.js';
8
- export { validateFields } from './content/validate.js';
8
+ export { defineFields } from './content/schema.js';
9
+ export { defineAdapter } from './content/adapter.js';
9
10
  export { isValidId, idFromFilename, filenameFromId, slugify, slugFromId, composeDatedId, } from './content/ids.js';
10
11
  // Render engine (Plan 04): generic directive pipeline; sites own the component registry.
11
12
  export { defineRegistry, emptyValues } from './render/registry.js';
@@ -15,7 +16,7 @@ export { buildComponentInsert } from './render/component-insert.js';
15
16
  export { generateComponentReference } from './render/component-reference.js';
16
17
  export { glyph } from './render/glyph.js';
17
18
  export { remarkDirectiveStamp } from './render/remark-directives.js';
18
- export { rehypeDispatch, isElement, strProp, iconSpan, splitHead, cardShell, markFirstList, } from './render/rehype-dispatch.js';
19
+ export { rehypeDispatch, isElement, strProp, iconSpan, cardShell, markFirstList, } from './render/rehype-dispatch.js';
19
20
  export { createRenderer } from './render/pipeline.js';
20
21
  export { CommitConflictError } from './github/types.js';
21
22
  export { appJwt, installationToken, signingSelfTest } from './github/signing.js';
@@ -29,9 +30,11 @@ export { parseSiteConfig, urlPolicyFrom, extractMenu, setMenu, validateNavTree,
29
30
  export { permalink } from './content/permalink.js';
30
31
  export { createContentIndex, fromGlob } from './delivery/content-index.js';
31
32
  export { createSiteIndex } from './delivery/site-index.js';
33
+ export { createSiteIndexes } from './delivery/site-indexes.js';
32
34
  export { deriveExcerpt, wordCount } from './delivery/excerpt.js';
33
35
  export { buildRssFeed, buildJsonFeed } from './delivery/feeds.js';
34
36
  export { buildSitemap } from './delivery/sitemap.js';
35
37
  export { buildRobots } from './delivery/robots.js';
36
38
  export { buildSeoMeta } from './delivery/seo.js';
39
+ export { readSeoFields, resolveImageUrl } from './delivery/seo-fields.js';
37
40
  export { paginate } from './delivery/paginate.js';
@@ -7,4 +7,11 @@ export declare function parseComponent(markdown: string, def: ComponentDef): Pro
7
7
  /** The raw attribute keys present on the component's opening directive, read from the parsed tree
8
8
  * (quote-aware, unlike a regex over the source). Used by validation to flag unknown keys. */
9
9
  export declare function parseRawAttributeKeys(markdown: string, def: ComponentDef): string[];
10
+ /** Parse the component once and derive both the guided-form values and the raw attribute keys.
11
+ * Validation needs both, so this seam spares it the double parse that calling
12
+ * {@link parseComponent} and {@link parseRawAttributeKeys} separately would cost. */
13
+ export declare function parseComponentWithRawKeys(markdown: string, def: ComponentDef): Promise<{
14
+ values: ComponentValues;
15
+ rawKeys: string[];
16
+ }>;
10
17
  //# sourceMappingURL=component-grammar.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"component-grammar.d.ts","sourceRoot":"","sources":["../../src/lib/render/component-grammar.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,YAAY,EAAE,eAAe,EAAW,MAAM,eAAe,CAAC;AA8B5E,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,YAAY,EAAE,MAAM,EAAE,eAAe,GAAG,MAAM,CA2BrF;AAwBD;;wCAEwC;AACxC,wBAAsB,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,YAAY,GAAG,OAAO,CAAC,eAAe,CAAC,CAqClG;AAED;8FAC8F;AAC9F,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,YAAY,GAAG,MAAM,EAAE,CAMnF"}
1
+ {"version":3,"file":"component-grammar.d.ts","sourceRoot":"","sources":["../../src/lib/render/component-grammar.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,YAAY,EAAE,eAAe,EAAW,MAAM,eAAe,CAAC;AA8B5E,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,YAAY,EAAE,MAAM,EAAE,eAAe,GAAG,MAAM,CA2BrF;AA4ED;;wCAEwC;AACxC,wBAAsB,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,YAAY,GAAG,OAAO,CAAC,eAAe,CAAC,CAElG;AAED;8FAC8F;AAC9F,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,YAAY,GAAG,MAAM,EAAE,CAEnF;AAED;;sFAEsF;AACtF,wBAAsB,yBAAyB,CAC7C,QAAQ,EAAE,MAAM,EAChB,GAAG,EAAE,YAAY,GAChB,OAAO,CAAC;IAAE,MAAM,EAAE,eAAe,CAAC;IAAC,OAAO,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC,CAGzD"}
@@ -64,12 +64,16 @@ function childrenToText(children) {
64
64
  const root = { type: 'root', children };
65
65
  return String(toMd.stringify(root)).trim();
66
66
  }
67
- /** Parse a serialized component directive back into guided-form values, the inverse of
68
- * {@link serializeComponent}. The grammar is reversible, so the editor can round-trip a
69
- * saved directive through the form. */
70
- export async function parseComponent(markdown, def) {
67
+ // Parse the markdown and find the component's opening container directive. The single seam both
68
+ // parseComponent and parseRawAttributeKeys (and the combined validator helper) build on, so one
69
+ // parse derives both the form values and the raw attribute keys.
70
+ function findComponentRoot(markdown, def) {
71
71
  const tree = unified().use(remarkParse).use(remarkDirective).parse(markdown);
72
- const root = tree.children.find((c) => isContainer(c) && c.name === def.name);
72
+ return tree.children.find((c) => isContainer(c) && c.name === def.name);
73
+ }
74
+ // Build guided-form values from an already-found component root. Returns the empty base when the
75
+ // root is absent.
76
+ function valuesFromRoot(root, def) {
73
77
  const values = emptyComponentValues(def);
74
78
  if (!root)
75
79
  return values;
@@ -101,12 +105,27 @@ export async function parseComponent(markdown, def) {
101
105
  }
102
106
  return values;
103
107
  }
108
+ // The raw attribute keys on an already-found component root.
109
+ function rawKeysFromRoot(root) {
110
+ return Object.keys(root?.attributes ?? {});
111
+ }
112
+ /** Parse a serialized component directive back into guided-form values, the inverse of
113
+ * {@link serializeComponent}. The grammar is reversible, so the editor can round-trip a
114
+ * saved directive through the form. */
115
+ export async function parseComponent(markdown, def) {
116
+ return valuesFromRoot(findComponentRoot(markdown, def), def);
117
+ }
104
118
  /** The raw attribute keys present on the component's opening directive, read from the parsed tree
105
119
  * (quote-aware, unlike a regex over the source). Used by validation to flag unknown keys. */
106
120
  export function parseRawAttributeKeys(markdown, def) {
107
- const tree = unified().use(remarkParse).use(remarkDirective).parse(markdown);
108
- const root = tree.children.find((c) => isContainer(c) && c.name === def.name);
109
- return Object.keys(root?.attributes ?? {});
121
+ return rawKeysFromRoot(findComponentRoot(markdown, def));
122
+ }
123
+ /** Parse the component once and derive both the guided-form values and the raw attribute keys.
124
+ * Validation needs both, so this seam spares it the double parse that calling
125
+ * {@link parseComponent} and {@link parseRawAttributeKeys} separately would cost. */
126
+ export async function parseComponentWithRawKeys(markdown, def) {
127
+ const root = findComponentRoot(markdown, def);
128
+ return { values: valuesFromRoot(root, def), rawKeys: rawKeysFromRoot(root) };
110
129
  }
111
130
  // A bare parse base: empty strings, false, and empty lists, with no attribute defaults applied. The
112
131
  // `emptyValues` helper in registry.ts seeds form defaults instead, so it is deliberately not reused
@@ -1,6 +1,6 @@
1
- import { parseComponent, parseRawAttributeKeys } from './component-grammar.js';
1
+ import { parseComponentWithRawKeys } from './component-grammar.js';
2
2
  export async function validateComponent(markdown, def) {
3
- const values = await parseComponent(markdown, def);
3
+ const { values, rawKeys } = await parseComponentWithRawKeys(markdown, def);
4
4
  const errors = {};
5
5
  const declared = new Set((def.attributes ?? []).map((f) => f.key));
6
6
  for (const field of def.attributes ?? []) {
@@ -14,7 +14,7 @@ export async function validateComponent(markdown, def) {
14
14
  errors[field.key] = `${field.label} must be one of: ${(field.options ?? []).join(', ')}.`;
15
15
  }
16
16
  }
17
- for (const key of parseRawAttributeKeys(markdown, def)) {
17
+ for (const key of rawKeys) {
18
18
  if (!declared.has(key))
19
19
  errors[key] = `Unknown attribute "${key}".`;
20
20
  }
@@ -1,6 +1,9 @@
1
1
  import type { Element } from 'hast';
2
2
  /** A glyph name to SVG path-data map (the site owns the icon set). */
3
3
  export type IconSet = Record<string, string>;
4
- /** Inline SVG glyph as a real hast node: class ec-glyph, 256 viewBox, currentColor fill. */
4
+ /** Inline SVG glyph as a real hast node: class ec-glyph, 256 viewBox, currentColor fill.
5
+ * An unknown icon name yields the bare svg shell with no path child, so it never serializes
6
+ * a stray empty (or undefined) path. Callers always wrap the returned element, so the shell
7
+ * keeps them safe. */
5
8
  export declare function glyph(name: string, icons: IconSet): Element;
6
9
  //# sourceMappingURL=glyph.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"glyph.d.ts","sourceRoot":"","sources":["../../src/lib/render/glyph.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AAEpC,sEAAsE;AACtE,MAAM,MAAM,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AAE7C,4FAA4F;AAC5F,wBAAgB,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,OAAO,CAM3D"}
1
+ {"version":3,"file":"glyph.d.ts","sourceRoot":"","sources":["../../src/lib/render/glyph.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AAEpC,sEAAsE;AACtE,MAAM,MAAM,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AAE7C;;;uBAGuB;AACvB,wBAAgB,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,OAAO,CAO3D"}
@@ -1,5 +1,9 @@
1
1
  import { s } from 'hastscript';
2
- /** Inline SVG glyph as a real hast node: class ec-glyph, 256 viewBox, currentColor fill. */
2
+ /** Inline SVG glyph as a real hast node: class ec-glyph, 256 viewBox, currentColor fill.
3
+ * An unknown icon name yields the bare svg shell with no path child, so it never serializes
4
+ * a stray empty (or undefined) path. Callers always wrap the returned element, so the shell
5
+ * keeps them safe. */
3
6
  export function glyph(name, icons) {
4
- return s('svg', { className: ['ec-glyph'], viewBox: '0 0 256 256', fill: 'currentColor', ariaHidden: 'true' }, [s('path', { d: icons[name] })]);
7
+ const d = icons[name];
8
+ return s('svg', { className: ['ec-glyph'], viewBox: '0 0 256 256', fill: 'currentColor', ariaHidden: 'true' }, d == null ? [] : [s('path', { d })]);
5
9
  }
@@ -1,10 +1,20 @@
1
1
  import { type PluggableList } from 'unified';
2
+ import type { Schema } from 'hast-util-sanitize';
2
3
  import type { ComponentRegistry } from './registry.js';
3
4
  export interface RendererOptions {
4
5
  /** Stamp a `data-rise` ordinal (0, 1, 2, …) on each top-level component so a site's
5
6
  * CSS can drive an entrance-cascade delay off it. Omit for no stagger. The ordinal
6
7
  * is inert, so a consumer's sanitize floor can keep `data-rise` and drop `style`. */
7
8
  stagger?: boolean;
9
+ /** Extend the sanitize allowlist. Receives cairn's default schema (defaultSchema plus the
10
+ * directive markers and the common benign tags) and returns the schema to use. Add to the
11
+ * allowlist for the benign HTML a site's content needs; start from the argument so the
12
+ * dangerous strip is preserved. */
13
+ sanitizeSchema?: (defaults: Schema) => Schema;
14
+ /** Developer-only escape hatch: disable the sanitize floor entirely. This reintroduces the XSS
15
+ * vector the floor closes, so it is only for a site whose content is fully developer-controlled.
16
+ * It is a code-level adapter decision, never an editor-facing setting. */
17
+ unsafeDisableSanitize?: boolean;
8
18
  }
9
19
  /** Compose a site's render pipeline from its component registry: directive syntax to
10
20
  * 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;;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"}
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;AAStD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAIjD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAEvD,MAAM,WAAW,eAAe;IAC9B;;0FAEsF;IACtF,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB;;;wCAGoC;IACpC,cAAc,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,MAAM,CAAC;IAC9C;;+EAE2E;IAC3E,qBAAqB,CAAC,EAAE,OAAO,CAAC;CACjC;AAED;;uFAEuF;AACvF,wBAAgB,cAAc,CAAC,QAAQ,EAAE,iBAAiB,EAAE,OAAO,GAAE,eAAoB;;;8BAyBrD,MAAM,KAAG,OAAO,CAAC,MAAM,CAAC;EAE3D"}
@@ -6,6 +6,8 @@ import remarkRehype from 'remark-rehype';
6
6
  import rehypeRaw from 'rehype-raw';
7
7
  import rehypeSlug from 'rehype-slug';
8
8
  import rehypeStringify from 'rehype-stringify';
9
+ import rehypeSanitize from 'rehype-sanitize';
10
+ import { buildSanitizeSchema, rehypeAnchorRel } from './sanitize-schema.js';
9
11
  import { remarkDirectiveStamp } from './remark-directives.js';
10
12
  import { rehypeDispatch } from './rehype-dispatch.js';
11
13
  /** Compose a site's render pipeline from its component registry: directive syntax to
@@ -13,7 +15,19 @@ import { rehypeDispatch } from './rehype-dispatch.js';
13
15
  * rehype plugin arrays (so the admin editor preview can reuse the exact same set). */
14
16
  export function createRenderer(registry, options = {}) {
15
17
  const remarkPlugins = [remarkDirective, [remarkDirectiveStamp, registry]];
16
- const rehypePlugins = [rehypeRaw, [rehypeDispatch, registry, options.stagger], rehypeSlug];
18
+ // The sanitize floor runs after rehype-raw (so author raw HTML is parsed, then cleaned) and
19
+ // before the dispatch (so the site's trusted build() output and its inline SVG icons are never
20
+ // sanitized). The anchor-rel hardening runs last so it also covers component-built anchors.
21
+ const floor = options.unsafeDisableSanitize
22
+ ? []
23
+ : [[rehypeSanitize, buildSanitizeSchema(registry, options.sanitizeSchema)]];
24
+ const rehypePlugins = [
25
+ rehypeRaw,
26
+ ...floor,
27
+ [rehypeDispatch, registry, options.stagger],
28
+ rehypeSlug,
29
+ rehypeAnchorRel,
30
+ ];
17
31
  const processor = unified()
18
32
  .use(remarkParse)
19
33
  .use(remarkGfm)
@@ -1,4 +1,4 @@
1
- import type { Element } from 'hast';
1
+ import type { Element, ElementContent } from 'hast';
2
2
  /** The input types a component attribute or repeatable item field can take. */
3
3
  export type FieldType = 'text' | 'select' | 'icon' | 'boolean';
4
4
  /** One `{key="value"}` attribute on a component directive, or one field of a repeatable item. */
@@ -28,6 +28,20 @@ export interface SlotDef {
28
28
  /** For `kind: 'repeatable'`: the fields composing each list item (v1 uses the first field). */
29
29
  itemFields?: AttributeField[];
30
30
  }
31
+ /** The structured input a component's `build` receives. The engine stamps the component's
32
+ * attributes and partitions its slots from the rendered hast, so `build` arranges hast and
33
+ * never walks the tree. `slot(name)` returns a slot's rendered children (title, body, or any
34
+ * named slot); `items(name)` returns a repeatable slot's items, one child list per item. */
35
+ export interface ComponentContext {
36
+ /** Declared attribute values, keyed by attribute key. Booleans are real booleans. */
37
+ attributes: Record<string, string | boolean>;
38
+ /** A named slot's rendered children. Returns `[]` for an absent or empty slot. */
39
+ slot(name: string): ElementContent[];
40
+ /** A repeatable slot's items, each item its own list of rendered children. `[]` when absent. */
41
+ items(name: string): ElementContent[][];
42
+ /** The stamped component element, for an escape hatch. Most builds never need it. */
43
+ node: Element;
44
+ }
31
45
  /** A site component: how it inserts (editor) and how it renders (rehype). */
32
46
  export interface ComponentDef {
33
47
  /** Directive name, e.g. 'card' (matches `:::card`). */
@@ -38,10 +52,10 @@ export interface ComponentDef {
38
52
  description: string;
39
53
  /** Markdown scaffold inserted at the cursor by the editor palette. */
40
54
  insertTemplate?: string;
41
- /** Build the final hast element from the stamped directive element. The engine
42
- * stamps the entrance-stagger ordinal (`data-rise`) on the top-level result, so a
43
- * build fn stays free of any motion concern. */
44
- build: (node: Element) => Element;
55
+ /** Build the final hast element from the component context (attributes plus partitioned
56
+ * slots). The engine stamps the entrance-stagger ordinal (`data-rise`) on the top-level
57
+ * result, so a build fn stays free of any motion concern. */
58
+ build: (ctx: ComponentContext) => Element;
45
59
  /** Optional role-to-default-icon, e.g. `{ caution: 'warning' }`. */
46
60
  defaultIconByRole?: Record<string, string>;
47
61
  /** One line on when to reach for this component; feeds the picker and the reference file. */
@@ -57,6 +71,10 @@ export interface ComponentRegistry {
57
71
  get(name: string): ComponentDef | undefined;
58
72
  defaultIcon(name: string, role?: string): string | undefined;
59
73
  }
74
+ /** The hast property name carrying one declared attribute from stamp to dispatch, e.g. `tone`
75
+ * becomes `dataAttrTone`. The directive stamp writes it and the rehype dispatch reads it, so both
76
+ * sides derive the name from this one helper rather than spelling the capitalize twice. */
77
+ export declare function dataAttrProp(key: string): string;
60
78
  /**
61
79
  * Build a registry from a site's component definitions. The single source the render
62
80
  * pipeline (directive stamp plus rehype dispatch) and the editor palette both read.
@@ -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,+EAA+E;AAC/E,MAAM,MAAM,SAAS,GAAG,MAAM,GAAG,QAAQ,GAAG,MAAM,GAAG,SAAS,CAAC;AAE/D,iGAAiG;AACjG,MAAM,WAAW,cAAc;IAC7B,sEAAsE;IACtE,GAAG,EAAE,MAAM,CAAC;IACZ,sBAAsB;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,SAAS,CAAC;IAChB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,2EAA2E;IAC3E,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IAC3B,2CAA2C;IAC3C,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,yCAAyC;IACzC,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,MAAM,QAAQ,GAAG,UAAU,GAAG,QAAQ,GAAG,YAAY,CAAC;AAE5D;4GAC4G;AAC5G,MAAM,WAAW,OAAO;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,QAAQ,CAAC;IACf,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,+FAA+F;IAC/F,UAAU,CAAC,EAAE,cAAc,EAAE,CAAC;CAC/B;AAED,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,CAAC,EAAE,MAAM,CAAC;IACxB;;qDAEiD;IACjD,KAAK,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,OAAO,CAAC;IAClC,oEAAoE;IACpE,iBAAiB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC3C,6FAA6F;IAC7F,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,6DAA6D;IAC7D,UAAU,CAAC,EAAE,cAAc,EAAE,CAAC;IAC9B,wDAAwD;IACxD,KAAK,CAAC,EAAE,OAAO,EAAE,CAAC;CACnB;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;AAED;uEACuE;AACvE,MAAM,WAAW,eAAe;IAC9B,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC;IAC7C,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC,CAAC;CAC1C;AAED;+DAC+D;AAC/D,wBAAgB,WAAW,CAAC,GAAG,EAAE,YAAY,GAAG,eAAe,CAU9D"}
1
+ {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/lib/render/registry.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,OAAO,EAAE,cAAc,EAAE,MAAM,MAAM,CAAC;AAEpD,+EAA+E;AAC/E,MAAM,MAAM,SAAS,GAAG,MAAM,GAAG,QAAQ,GAAG,MAAM,GAAG,SAAS,CAAC;AAE/D,iGAAiG;AACjG,MAAM,WAAW,cAAc;IAC7B,sEAAsE;IACtE,GAAG,EAAE,MAAM,CAAC;IACZ,sBAAsB;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,SAAS,CAAC;IAChB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,2EAA2E;IAC3E,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IAC3B,2CAA2C;IAC3C,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,yCAAyC;IACzC,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,MAAM,QAAQ,GAAG,UAAU,GAAG,QAAQ,GAAG,YAAY,CAAC;AAE5D;4GAC4G;AAC5G,MAAM,WAAW,OAAO;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,QAAQ,CAAC;IACf,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,+FAA+F;IAC/F,UAAU,CAAC,EAAE,cAAc,EAAE,CAAC;CAC/B;AAED;;;6FAG6F;AAC7F,MAAM,WAAW,gBAAgB;IAC/B,qFAAqF;IACrF,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC;IAC7C,kFAAkF;IAClF,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,cAAc,EAAE,CAAC;IACrC,gGAAgG;IAChG,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,cAAc,EAAE,EAAE,CAAC;IACxC,qFAAqF;IACrF,IAAI,EAAE,OAAO,CAAC;CACf;AAED,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,CAAC,EAAE,MAAM,CAAC;IACxB;;kEAE8D;IAC9D,KAAK,EAAE,CAAC,GAAG,EAAE,gBAAgB,KAAK,OAAO,CAAC;IAC1C,oEAAoE;IACpE,iBAAiB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC3C,6FAA6F;IAC7F,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,6DAA6D;IAC7D,UAAU,CAAC,EAAE,cAAc,EAAE,CAAC;IAC9B,wDAAwD;IACxD,KAAK,CAAC,EAAE,OAAO,EAAE,CAAC;CACnB;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;;4FAE4F;AAC5F,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAEhD;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,EAAE,UAAU,EAAE,EAAE;IAAE,UAAU,EAAE,YAAY,EAAE,CAAA;CAAE,GAAG,iBAAiB,CAQhG;AAED;uEACuE;AACvE,MAAM,WAAW,eAAe;IAC9B,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC;IAC7C,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC,CAAC;CAC1C;AAED;+DAC+D;AAC/D,wBAAgB,WAAW,CAAC,GAAG,EAAE,YAAY,GAAG,eAAe,CAU9D"}
@@ -1,3 +1,9 @@
1
+ /** The hast property name carrying one declared attribute from stamp to dispatch, e.g. `tone`
2
+ * becomes `dataAttrTone`. The directive stamp writes it and the rehype dispatch reads it, so both
3
+ * sides derive the name from this one helper rather than spelling the capitalize twice. */
4
+ export function dataAttrProp(key) {
5
+ return `dataAttr${key.charAt(0).toUpperCase()}${key.slice(1)}`;
6
+ }
1
7
  /**
2
8
  * Build a registry from a site's component definitions. The single source the render
3
9
  * pipeline (directive stamp plus rehype dispatch) and the editor palette both read.
@@ -1,15 +1,11 @@
1
1
  import type { Root, Element, ElementContent } from 'hast';
2
- import type { ComponentRegistry } from './registry.js';
2
+ import { type ComponentRegistry } from './registry.js';
3
3
  export declare function isElement(node: ElementContent | undefined): node is Element;
4
4
  export declare function strProp(node: Element, name: string): string | undefined;
5
5
  /** Wrap a pre-built glyph in an ec-icon span; secondary role adds the modifier. */
6
6
  export declare function iconSpan(glyphEl: Element, role?: string): Element;
7
7
  /** A site's icon factory: turn a stamped icon name + role into a hast element. */
8
8
  export type MakeIcon = (name: string, role?: string) => Element;
9
- export declare function splitHead(node: Element, makeIcon?: MakeIcon): {
10
- head: Element;
11
- rest: ElementContent[];
12
- };
13
9
  /** Section wrapper: `<section class=…><div class="card-body">…</div></section>`. */
14
10
  export declare function cardShell(classes: string[], body: ElementContent[]): Element;
15
11
  /** Tag the first <ul> among children with `ec-grid` and strip its whitespace-only
@@ -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,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"}
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,EAA0D,KAAK,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAE/G,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;AAEhE,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;AAqFD;;;;;mFAKmF;AACnF,wBAAgB,cAAc,CAAC,QAAQ,EAAE,iBAAiB,EAAE,OAAO,CAAC,EAAE,OAAO,IACnE,MAAM,IAAI,UAYnB"}
@@ -1,4 +1,5 @@
1
1
  import { h } from 'hastscript';
2
+ import { dataAttrProp } from './registry.js';
2
3
  export function isElement(node) {
3
4
  return !!node && node.type === 'element';
4
5
  }
@@ -14,24 +15,6 @@ export function iconSpan(glyphEl, role) {
14
15
  const className = role === 'secondary' ? ['ec-icon', 'ec-icon-secondary'] : ['ec-icon'];
15
16
  return h('span', { className }, [glyphEl]);
16
17
  }
17
- // Pull the section's <h2> out, retag it .card-title, and build the .ec-head row
18
- // (optional icon + heading). Returns the head plus the remaining body children.
19
- // `makeIcon` (site-supplied) turns the stamped data-icon into an element; omit it
20
- // for a head with no icon.
21
- export function splitHead(node, makeIcon) {
22
- const children = node.children;
23
- const i = children.findIndex((c) => isElement(c) && c.tagName === 'h2');
24
- const h2 = children[i];
25
- h2.properties = { ...h2.properties, className: ['card-title'] };
26
- const rest = children.filter((_, j) => j !== i);
27
- const icon = strProp(node, 'dataIcon');
28
- const role = strProp(node, 'dataRole');
29
- const headKids = [];
30
- if (makeIcon && icon)
31
- headKids.push(makeIcon(icon, role));
32
- headKids.push(h2);
33
- return { head: h('div', { className: ['ec-head'] }, headKids), rest };
34
- }
35
18
  /** Section wrapper: `<section class=…><div class="card-body">…</div></section>`. */
36
19
  export function cardShell(classes, body) {
37
20
  return h('section', { className: classes }, [h('div', { className: ['card-body'] }, body)]);
@@ -58,11 +41,80 @@ function transformChildren(children, registry) {
58
41
  return c;
59
42
  });
60
43
  }
44
+ // Read a stamped attribute back into its typed value. Booleans arrive as the strings
45
+ // 'true'/'false'; everything else is the literal string the author wrote.
46
+ function readAttributes(node, def) {
47
+ const out = {};
48
+ for (const field of def.attributes ?? []) {
49
+ const value = strProp(node, dataAttrProp(field.key));
50
+ if (value == null)
51
+ continue;
52
+ out[field.key] = field.type === 'boolean' ? value === 'true' : value;
53
+ }
54
+ return out;
55
+ }
56
+ // The title label paragraph carries data-slot="title"; build() wants its inline children, not
57
+ // the marked paragraph. Return the paragraph's children.
58
+ function stripSlotMarker(child) {
59
+ return isElement(child) ? child.children : [child];
60
+ }
61
+ // Split a component's stamped children into named slots and the default body. A child marked
62
+ // data-slot="title"/<name> routes to that slot; an unmarked child is body. A repeatable slot
63
+ // wraps a <ul>, so its items are that list's <li> children, one child-list per item.
64
+ function partitionSlots(node) {
65
+ const named = new Map();
66
+ const body = [];
67
+ for (const child of node.children) {
68
+ const slotName = isElement(child) ? strProp(child, 'dataSlot') : undefined;
69
+ if (slotName === 'title')
70
+ named.set('title', stripSlotMarker(child));
71
+ else if (slotName)
72
+ named.set(slotName, [child]);
73
+ else
74
+ body.push(child);
75
+ }
76
+ return {
77
+ slot(name) {
78
+ if (name === 'body')
79
+ return body;
80
+ const wrap = named.get(name);
81
+ if (!wrap)
82
+ return [];
83
+ // For title we stored the label's own children, so return them as-is. For a markdown or
84
+ // inline named slot the wrapper <div> holds the rendered children; unwrap it.
85
+ if (name === 'title')
86
+ return wrap;
87
+ const div = wrap[0];
88
+ return isElement(div) ? div.children : wrap;
89
+ },
90
+ items(name) {
91
+ const wrap = named.get(name);
92
+ const div = wrap?.[0];
93
+ if (!div || !isElement(div))
94
+ return [];
95
+ const ul = div.children.find((c) => isElement(c) && c.tagName === 'ul');
96
+ if (!ul || !isElement(ul))
97
+ return [];
98
+ return ul.children
99
+ .filter((li) => isElement(li) && li.tagName === 'li')
100
+ .map((li) => li.children);
101
+ },
102
+ };
103
+ }
61
104
  function transformNode(node, registry) {
62
105
  node.children = transformChildren(node.children, registry);
63
106
  const name = strProp(node, 'dataPrimitive');
64
107
  const def = name ? registry.get(name) : undefined;
65
- return def ? def.build(node) : node;
108
+ if (!def)
109
+ return node;
110
+ const parts = partitionSlots(node);
111
+ const ctx = {
112
+ attributes: readAttributes(node, def),
113
+ slot: parts.slot,
114
+ items: parts.items,
115
+ node,
116
+ };
117
+ return def.build(ctx);
66
118
  }
67
119
  /** Rehype transformer: dispatch each stamped element through its registry `build`
68
120
  * fn. When `stagger` is on, each top-level primitive gets a `data-rise` attribute
@@ -1,4 +1,4 @@
1
1
  import type { Root } from 'mdast';
2
- import type { ComponentRegistry } from './registry.js';
2
+ import { type ComponentRegistry } from './registry.js';
3
3
  export declare function remarkDirectiveStamp(registry: ComponentRegistry): (tree: Root) => void;
4
4
  //# sourceMappingURL=remark-directives.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"remark-directives.d.ts","sourceRoot":"","sources":["../../src/lib/render/remark-directives.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAA8B,IAAI,EAAQ,MAAM,OAAO,CAAC;AAGpE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAmCvD,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,iBAAiB,IAEtD,MAAM,IAAI,UA8BnB"}
1
+ {"version":3,"file":"remark-directives.d.ts","sourceRoot":"","sources":["../../src/lib/render/remark-directives.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAA8B,IAAI,EAAQ,MAAM,OAAO,CAAC;AAGpE,OAAO,EAAgB,KAAK,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAkDrE,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,iBAAiB,IAEtD,MAAM,IAAI,UAqDnB"}