@glw907/cairn-cms 0.21.0 → 0.24.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 (52) hide show
  1. package/dist/content/concepts.d.ts.map +1 -1
  2. package/dist/content/concepts.js +7 -0
  3. package/dist/content/frontmatter.d.ts +8 -0
  4. package/dist/content/frontmatter.d.ts.map +1 -1
  5. package/dist/content/frontmatter.js +19 -0
  6. package/dist/content/types.d.ts +6 -0
  7. package/dist/content/types.d.ts.map +1 -1
  8. package/dist/content/validate.d.ts.map +1 -1
  9. package/dist/content/validate.js +8 -1
  10. package/dist/delivery/content-index.d.ts +7 -0
  11. package/dist/delivery/content-index.d.ts.map +1 -1
  12. package/dist/delivery/content-index.js +7 -0
  13. package/dist/delivery/head.d.ts +2 -0
  14. package/dist/delivery/head.d.ts.map +1 -0
  15. package/dist/delivery/head.js +4 -0
  16. package/dist/delivery/index.d.ts +0 -1
  17. package/dist/delivery/index.d.ts.map +1 -1
  18. package/dist/delivery/index.js +0 -1
  19. package/dist/index.d.ts +4 -1
  20. package/dist/index.d.ts.map +1 -1
  21. package/dist/index.js +6 -1
  22. package/dist/render/pipeline.d.ts +4 -0
  23. package/dist/render/pipeline.d.ts.map +1 -1
  24. package/dist/render/pipeline.js +3 -1
  25. package/dist/render/registry.d.ts +1 -1
  26. package/dist/render/registry.d.ts.map +1 -1
  27. package/dist/render/rehype-dispatch.d.ts +5 -0
  28. package/dist/render/rehype-dispatch.d.ts.map +1 -1
  29. package/dist/render/rehype-dispatch.js +12 -1
  30. package/dist/render/remark-directives.d.ts.map +1 -1
  31. package/dist/render/remark-directives.js +15 -6
  32. package/dist/render/sanitize-schema.d.ts +4 -3
  33. package/dist/render/sanitize-schema.d.ts.map +1 -1
  34. package/dist/render/sanitize-schema.js +6 -5
  35. package/dist/sveltekit/public-routes.d.ts +1 -0
  36. package/dist/sveltekit/public-routes.d.ts.map +1 -1
  37. package/dist/sveltekit/public-routes.js +1 -1
  38. package/package.json +6 -1
  39. package/src/lib/content/concepts.ts +9 -0
  40. package/src/lib/content/frontmatter.ts +21 -0
  41. package/src/lib/content/types.ts +6 -0
  42. package/src/lib/content/validate.ts +6 -1
  43. package/src/lib/delivery/content-index.ts +13 -0
  44. package/src/lib/delivery/head.ts +4 -0
  45. package/src/lib/delivery/index.ts +0 -1
  46. package/src/lib/index.ts +7 -0
  47. package/src/lib/render/pipeline.ts +6 -1
  48. package/src/lib/render/registry.ts +1 -1
  49. package/src/lib/render/rehype-dispatch.ts +12 -1
  50. package/src/lib/render/remark-directives.ts +16 -5
  51. package/src/lib/render/sanitize-schema.ts +6 -5
  52. package/src/lib/sveltekit/public-routes.ts +2 -1
@@ -1 +1 @@
1
- {"version":3,"file":"concepts.d.ts","sourceRoot":"","sources":["../../src/lib/content/concepts.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,aAAa,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAElG;;;;GAIG;AACH,eAAO,MAAM,eAAe,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAGjE,CAAC;AAeF;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,aAAa,GAAG,SAAS,CAAC,EAClD,SAAS,GAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,GAAG,SAAS,CAAM,EAC5D,OAAO,GAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAmB,GAC/D,iBAAiB,EAAE,CAiBrB;AAED,yFAAyF;AACzF,wBAAgB,WAAW,CACzB,QAAQ,EAAE,iBAAiB,EAAE,EAC7B,EAAE,EAAE,MAAM,GACT,iBAAiB,GAAG,SAAS,CAE/B"}
1
+ {"version":3,"file":"concepts.d.ts","sourceRoot":"","sources":["../../src/lib/content/concepts.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,aAAa,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAElG;;;;GAIG;AACH,eAAO,MAAM,eAAe,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAGjE,CAAC;AAeF;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,aAAa,GAAG,SAAS,CAAC,EAClD,SAAS,GAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,GAAG,SAAS,CAAM,EAC5D,OAAO,GAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAmB,GAC/D,iBAAiB,EAAE,CA0BrB;AAED,yFAAyF;AACzF,wBAAgB,WAAW,CACzB,QAAQ,EAAE,iBAAiB,EAAE,EAC7B,EAAE,EAAE,MAAM,GACT,iBAAiB,GAAG,SAAS,CAE/B"}
@@ -29,6 +29,12 @@ export function normalizeConcepts(content, urlPolicy = {}, routing = CONCEPT_ROU
29
29
  for (const [id, config] of Object.entries(content)) {
30
30
  if (!config)
31
31
  continue;
32
+ const summaryFields = config.summaryFields ?? [];
33
+ const declared = new Set(config.schema.fields.map((field) => field.name));
34
+ const undeclared = summaryFields.find((key) => !declared.has(key));
35
+ if (undeclared !== undefined) {
36
+ throw new Error(`cairn: concept "${id}" summaryFields key "${undeclared}" is not a declared field`);
37
+ }
32
38
  const policy = urlPolicy[id] ?? {};
33
39
  descriptors.push({
34
40
  id,
@@ -38,6 +44,7 @@ export function normalizeConcepts(content, urlPolicy = {}, routing = CONCEPT_ROU
38
44
  permalink: policy.permalink ?? defaultPermalink(id),
39
45
  datePrefix: policy.datePrefix ?? 'day',
40
46
  fields: config.schema.fields,
47
+ summaryFields,
41
48
  validate: config.schema.validate,
42
49
  });
43
50
  }
@@ -8,6 +8,14 @@ export declare function frontmatterFromForm(fields: FrontmatterField[], form: Fo
8
8
  * slicing the ISO string avoids a local-timezone shift.
9
9
  */
10
10
  export declare function dateInputValue(value: unknown): string;
11
+ /**
12
+ * True when `s` is a canonical zero-padded `YYYY-MM-DD` string naming a real calendar date.
13
+ * Rejects a wrong format, an impossible month or day, and a JS date-rollover such as
14
+ * `2026-02-30` (which `Date` would silently roll forward to March 2). The committed form a
15
+ * date field carries is exactly this canonical shape, which is what the form and
16
+ * `dateInputValue` emit, so a value outside it is a hand-edit or odd-YAML error.
17
+ */
18
+ export declare function isCalendarDate(s: string): boolean;
11
19
  /** Reassemble a markdown file from frontmatter and body for committing. */
12
20
  export declare function serializeMarkdown(frontmatter: object, body: string): string;
13
21
  /** Parse a markdown file into its frontmatter and body: the read-side inverse of serialize. */
@@ -1 +1 @@
1
- {"version":3,"file":"frontmatter.d.ts","sourceRoot":"","sources":["../../src/lib/content/frontmatter.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAEnD,gFAAgF;AAChF,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,gBAAgB,EAAE,EAC1B,IAAI,EAAE,QAAQ,GACb,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CA4BzB;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CASrD;AAED,2EAA2E;AAC3E,wBAAgB,iBAAiB,CAAC,WAAW,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAE3E;AAED,+FAA+F;AAC/F,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG;IAC7C,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACrC,IAAI,EAAE,MAAM,CAAC;CACd,CAGA"}
1
+ {"version":3,"file":"frontmatter.d.ts","sourceRoot":"","sources":["../../src/lib/content/frontmatter.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAEnD,gFAAgF;AAChF,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,gBAAgB,EAAE,EAC1B,IAAI,EAAE,QAAQ,GACb,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CA4BzB;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CASrD;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAYjD;AAED,2EAA2E;AAC3E,wBAAgB,iBAAiB,CAAC,WAAW,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAE3E;AAED,+FAA+F;AAC/F,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG;IAC7C,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACrC,IAAI,EAAE,MAAM,CAAC;CACd,CAGA"}
@@ -47,6 +47,25 @@ export function dateInputValue(value) {
47
47
  }
48
48
  return '';
49
49
  }
50
+ /**
51
+ * True when `s` is a canonical zero-padded `YYYY-MM-DD` string naming a real calendar date.
52
+ * Rejects a wrong format, an impossible month or day, and a JS date-rollover such as
53
+ * `2026-02-30` (which `Date` would silently roll forward to March 2). The committed form a
54
+ * date field carries is exactly this canonical shape, which is what the form and
55
+ * `dateInputValue` emit, so a value outside it is a hand-edit or odd-YAML error.
56
+ */
57
+ export function isCalendarDate(s) {
58
+ const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(s);
59
+ if (!match)
60
+ return false;
61
+ const year = Number(match[1]);
62
+ const month = Number(match[2]);
63
+ const day = Number(match[3]);
64
+ const date = new Date(Date.UTC(year, month - 1, day));
65
+ return (date.getUTCFullYear() === year &&
66
+ date.getUTCMonth() === month - 1 &&
67
+ date.getUTCDate() === day);
68
+ }
50
69
  /** Reassemble a markdown file from frontmatter and body for committing. */
51
70
  export function serializeMarkdown(frontmatter, body) {
52
71
  return matter.stringify(body, frontmatter);
@@ -94,6 +94,9 @@ export interface ConceptConfig<S extends ConceptSchema = ConceptSchema> {
94
94
  label?: string;
95
95
  /** The concept's schema: the form projection, the generated validator, and the inferred type. */
96
96
  schema: S;
97
+ /** Frontmatter keys to surface on each `ContentSummary.fields`, so a list card reads an authored
98
+ * field without a per-entry detail read. Each key should also be declared in `schema`. */
99
+ summaryFields?: string[];
97
100
  }
98
101
  /**
99
102
  * A concept's URL policy, set per concept in the YAML site-config (not the adapter). `permalink` is
@@ -194,6 +197,9 @@ export interface ConceptDescriptor {
194
197
  /** Filename date-prefix granularity for a dated concept; resolved by `normalizeConcepts`. */
195
198
  datePrefix: DatePrefix;
196
199
  fields: FrontmatterField[];
200
+ /** Frontmatter keys the index copies onto each summary's `fields` record. `normalizeConcepts`
201
+ * resolves it to `[]` when a concept omits `summaryFields`. */
202
+ summaryFields: string[];
197
203
  validate(frontmatter: Record<string, unknown>, body: string): ValidationResult;
198
204
  }
199
205
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/lib/content/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/lib/content/types.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAC/D,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAC3C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AACjD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE9C,0GAA0G;AAC1G,UAAU,SAAS;IACjB,2CAA2C;IAC3C,IAAI,EAAE,MAAM,CAAC;IACb,kBAAkB;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,gEAAgE;IAChE,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,gCAAgC;AAChC,MAAM,WAAW,SAAU,SAAQ,SAAS;IAC1C,IAAI,EAAE,MAAM,CAAC;IACb,qDAAqD;IACrD,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,gCAAgC;IAChC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,uCAAuC;IACvC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;yEACqE;IACrE,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AACD,+BAA+B;AAC/B,MAAM,WAAW,aAAc,SAAQ,SAAS;IAC9C,IAAI,EAAE,UAAU,CAAC;IACjB,6DAA6D;IAC7D,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,qDAAqD;IACrD,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,gCAAgC;IAChC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,uCAAuC;IACvC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,+DAA+D;IAC/D,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AACD,iCAAiC;AACjC,MAAM,WAAW,SAAU,SAAQ,SAAS;IAC1C,IAAI,EAAE,MAAM,CAAC;IACb,8CAA8C;IAC9C,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,4CAA4C;IAC5C,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AACD,sCAAsC;AACtC,MAAM,WAAW,YAAa,SAAQ,SAAS;IAC7C,IAAI,EAAE,SAAS,CAAC;CACjB;AACD,sEAAsE;AACtE,MAAM,WAAW,SAAU,SAAQ,SAAS;IAC1C,IAAI,EAAE,MAAM,CAAC;IACb,iCAAiC;IACjC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;CAC5B;AACD,iEAAiE;AACjE,MAAM,WAAW,aAAc,SAAQ,SAAS;IAC9C,IAAI,EAAE,UAAU,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;;GAIG;AACH,MAAM,MAAM,gBAAgB,GACxB,SAAS,GACT,aAAa,GACb,SAAS,GACT,YAAY,GACZ,SAAS,GACT,aAAa,CAAC;AAElB;;;;GAIG;AACH,MAAM,MAAM,gBAAgB,GACxB;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,GAC3C;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAAE,CAAC;AAElD;;;;;;GAMG;AACH,MAAM,WAAW,aAAa,CAAC,CAAC,SAAS,aAAa,GAAG,aAAa;IACpE,iEAAiE;IACjE,GAAG,EAAE,MAAM,CAAC;IACZ,gEAAgE;IAChE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,iGAAiG;IACjG,MAAM,EAAE,CAAC,CAAC;IACV;+FAC2F;IAC3F,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;CAC1B;AAED;;;;;GAKG;AACH,MAAM,WAAW,gBAAgB;IAC/B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,UAAU,CAAC;CACzB;AAED,0HAA0H;AAC1H,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,kCAAkC;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,cAAc,EAAE,MAAM,CAAC;CACxB;AAED,+DAA+D;AAC/D,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,0EAA0E;AAC1E,MAAM,WAAW,aAAa;IAC5B,mFAAmF;IACnF,UAAU,EAAE,MAAM,CAAC;IACnB,uDAAuD;IACvD,QAAQ,EAAE,MAAM,CAAC;IACjB,kCAAkC;IAClC,KAAK,EAAE,MAAM,CAAC;IACd,8DAA8D;IAC9D,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,kHAAkH;AAClH,MAAM,WAAW,WAAW;IAC1B,yDAAyD;IACzD,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,uCAAuC;IACvC,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,gGAAgG;AAChG,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB;;;OAGG;IACH,OAAO,EAAE;QACP,KAAK,CAAC,EAAE,aAAa,CAAC;QACtB,KAAK,CAAC,EAAE,aAAa,CAAC;KACvB,CAAC;IACF,OAAO,EAAE,aAAa,CAAC;IACvB,MAAM,EAAE,YAAY,CAAC;IACrB;;kCAE8B;IAC9B,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE;QAAE,OAAO,CAAC,EAAE,OAAO,CAAC;QAAC,OAAO,CAAC,EAAE,WAAW,CAAA;KAAE,GAAG,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAClG;2GACuG;IACvG,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,iGAAiG;IACjG,QAAQ,CAAC,EAAE,iBAAiB,CAAC;IAC7B,8FAA8F;IAC9F,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,OAAO,CAAC,EAAE,aAAa,CAAC;IACxB,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAED;;;GAGG;AACH,MAAM,WAAW,WAAW;IAC1B,0FAA0F;IAC1F,QAAQ,EAAE,OAAO,CAAC;IAClB,8BAA8B;IAC9B,KAAK,EAAE,OAAO,CAAC;IACf,gDAAgD;IAChD,OAAO,EAAE,OAAO,CAAC;CAClB;AAED;;;GAGG;AACH,MAAM,WAAW,iBAAiB;IAChC,yDAAyD;IACzD,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,WAAW,CAAC;IACrB,wEAAwE;IACxE,SAAS,EAAE,MAAM,CAAC;IAClB,6FAA6F;IAC7F,UAAU,EAAE,UAAU,CAAC;IACvB,MAAM,EAAE,gBAAgB,EAAE,CAAC;IAC3B;oEACgE;IAChE,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,IAAI,EAAE,MAAM,GAAG,gBAAgB,CAAC;CAChF;AAED;;;;;GAKG;AACH,MAAM,WAAW,UAAU;IACzB,wDAAwD;IACxD,EAAE,EAAE,MAAM,CAAC;IACX,qBAAqB;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,2CAA2C;IAC3C,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,uDAAuD;IACvD,IAAI,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,OAAO,CAAC;IACnC,+EAA+E;IAC/E,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC;IAC/D,sFAAsF;IACtF,SAAS,EAAE,OAAO,CAAC;CACpB;AAED;;;;GAIG;AACH,MAAM,WAAW,YAAY;IAC3B,kDAAkD;IAClD,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;;;GAIG;AACH,MAAM,WAAW,cAAc;IAC7B,uDAAuD;IACvD,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;IACxC,+FAA+F;IAC/F,WAAW,CAAC,EAAE,UAAU,EAAE,CAAC;IAC3B,wFAAwF;IACxF,UAAU,CAAC,EAAE,YAAY,EAAE,CAAC;CAC7B;AAED;;;GAGG;AACH,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,iBAAiB,EAAE,CAAC;IAC9B,OAAO,EAAE,aAAa,CAAC;IACvB,MAAM,EAAE,YAAY,CAAC;IACrB,qGAAqG;IACrG,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE;QAAE,OAAO,CAAC,EAAE,OAAO,CAAC;QAAC,OAAO,CAAC,EAAE,WAAW,CAAA;KAAE,GAAG,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAClG,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,iBAAiB,CAAC;IAC7B,8FAA8F;IAC9F,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,OAAO,CAAC,EAAE,aAAa,CAAC;IACxB,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,qGAAqG;IACrG,WAAW,CAAC,EAAE,UAAU,EAAE,CAAC;IAC3B,mGAAmG;IACnG,UAAU,CAAC,EAAE,YAAY,EAAE,CAAC;CAC7B"}
@@ -1 +1 @@
1
- {"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../../src/lib/content/validate.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAGrE;;;;;;;;;;;GAWG;AACH,wBAAgB,cAAc,CAC5B,MAAM,EAAE,gBAAgB,EAAE,EAC1B,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GACnC,gBAAgB,CA+BlB"}
1
+ {"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../../src/lib/content/validate.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAGrE;;;;;;;;;;;GAWG;AACH,wBAAgB,cAAc,CAC5B,MAAM,EAAE,gBAAgB,EAAE,EAC1B,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GACnC,gBAAgB,CAoClB"}
@@ -1,4 +1,4 @@
1
- import { dateInputValue } from './frontmatter.js';
1
+ import { dateInputValue, isCalendarDate } from './frontmatter.js';
2
2
  /**
3
3
  * Validate raw frontmatter against a field list. Required text and date fields must be
4
4
  * non-empty; required tag fields must be non-empty lists. A present boolean coerces to `true`
@@ -27,6 +27,11 @@ export function validateFields(fields, frontmatter) {
27
27
  const list = Array.isArray(value) ? value.map(String) : [];
28
28
  if (field.required && list.length === 0)
29
29
  errors[field.name] = `${field.label} is required`;
30
+ else if (field.type === 'tags') {
31
+ const unknown = list.find((tag) => !field.options.includes(tag));
32
+ if (unknown !== undefined)
33
+ errors[field.name] = `${field.label} contains an unknown value: ${unknown}`;
34
+ }
30
35
  if (list.length > 0)
31
36
  data[field.name] = list;
32
37
  break;
@@ -35,6 +40,8 @@ export function validateFields(fields, frontmatter) {
35
40
  const text = value instanceof Date ? dateInputValue(value) : typeof value === 'string' ? value.trim() : '';
36
41
  if (field.required && text === '')
37
42
  errors[field.name] = `${field.label} is required`;
43
+ else if (text !== '' && !isCalendarDate(text))
44
+ errors[field.name] = `${field.label} must be a valid date (YYYY-MM-DD)`;
38
45
  if (text !== '')
39
46
  data[field.name] = text;
40
47
  break;
@@ -6,6 +6,9 @@ export interface RawFile {
6
6
  }
7
7
  /** The cheap, plain-data view of one entry, for lists, feeds, and the sitemap. */
8
8
  export interface ContentSummary {
9
+ /** The descriptor id this entry belongs to, e.g. "posts". Lets a list or page branch per
10
+ * concept without re-deriving it from a proxy like `entry.date`. */
11
+ concept: string;
9
12
  id: string;
10
13
  slug: string;
11
14
  permalink: string;
@@ -16,6 +19,10 @@ export interface ContentSummary {
16
19
  excerpt: string;
17
20
  wordCount: number;
18
21
  draft: boolean;
22
+ /** The frontmatter keys the descriptor nominated via `summaryFields`, read off the validated,
23
+ * normalized frontmatter. Held in a separate record so a nominated key cannot collide with a
24
+ * typed summary field. Empty when the concept declares no `summaryFields`. */
25
+ fields: Record<string, unknown>;
19
26
  }
20
27
  /** The detail view: a summary plus the frontmatter and the body to render. The frontmatter
21
28
  * type defaults to `Record<string, unknown>`; the typed-reads pass infers it from the concept
@@ -1 +1 @@
1
- {"version":3,"file":"content-index.d.ts","sourceRoot":"","sources":["../../src/lib/delivery/content-index.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAE7D,yFAAyF;AACzF,MAAM,WAAW,OAAO;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;CACb;AAED,kFAAkF;AAClF,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,OAAO,CAAC;CAChB;AAED;;wEAEwE;AACxE,MAAM,WAAW,YAAY,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAE,SAAQ,cAAc;IAC/E,WAAW,EAAE,CAAC,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;CACd;AAED,wFAAwF;AACxF,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,OAAO,CAAC;IACf,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAChC;AAED,qCAAqC;AACrC,MAAM,WAAW,YAAY,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IACvD,GAAG,CAAC,IAAI,CAAC,EAAE;QAAE,aAAa,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,cAAc,EAAE,CAAC;IAC1D,IAAI,CAAC,EAAE,EAAE,MAAM,GAAG,YAAY,CAAC,CAAC,CAAC,GAAG,SAAS,CAAC;IAC9C,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE;QAAE,aAAa,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,cAAc,EAAE,CAAC;IACzE,OAAO,IAAI;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAC5C,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG;QAAE,KAAK,CAAC,EAAE,cAAc,CAAC;QAAC,KAAK,CAAC,EAAE,cAAc,CAAA;KAAE,CAAC;IACzE,sFAAsF;IACtF,QAAQ,IAAI,cAAc,EAAE,CAAC;CAC9B;AAED,4EAA4E;AAC5E,wBAAgB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,OAAO,EAAE,CAElE;AAqBD,4EAA4E;AAC5E,wBAAgB,kBAAkB,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC5D,KAAK,EAAE,OAAO,EAAE,EAChB,UAAU,EAAE,iBAAiB,GAC5B,YAAY,CAAC,CAAC,CAAC,CAsEjB"}
1
+ {"version":3,"file":"content-index.d.ts","sourceRoot":"","sources":["../../src/lib/delivery/content-index.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAE7D,yFAAyF;AACzF,MAAM,WAAW,OAAO;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;CACb;AAED,kFAAkF;AAClF,MAAM,WAAW,cAAc;IAC7B;yEACqE;IACrE,OAAO,EAAE,MAAM,CAAC;IAChB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,OAAO,CAAC;IACf;;mFAE+E;IAC/E,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACjC;AAED;;wEAEwE;AACxE,MAAM,WAAW,YAAY,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAE,SAAQ,cAAc;IAC/E,WAAW,EAAE,CAAC,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;CACd;AAED,wFAAwF;AACxF,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,OAAO,CAAC;IACf,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAChC;AAED,qCAAqC;AACrC,MAAM,WAAW,YAAY,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IACvD,GAAG,CAAC,IAAI,CAAC,EAAE;QAAE,aAAa,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,cAAc,EAAE,CAAC;IAC1D,IAAI,CAAC,EAAE,EAAE,MAAM,GAAG,YAAY,CAAC,CAAC,CAAC,GAAG,SAAS,CAAC;IAC9C,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE;QAAE,aAAa,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,cAAc,EAAE,CAAC;IACzE,OAAO,IAAI;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAC5C,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG;QAAE,KAAK,CAAC,EAAE,cAAc,CAAC;QAAC,KAAK,CAAC,EAAE,cAAc,CAAA;KAAE,CAAC;IACzE,sFAAsF;IACtF,QAAQ,IAAI,cAAc,EAAE,CAAC;CAC9B;AAED,4EAA4E;AAC5E,wBAAgB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,OAAO,EAAE,CAElE;AAqBD,4EAA4E;AAC5E,wBAAgB,kBAAkB,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC5D,KAAK,EAAE,OAAO,EAAE,EAChB,UAAU,EAAE,iBAAiB,GAC5B,YAAY,CAAC,CAAC,CAAC,CA4EjB"}
@@ -44,7 +44,13 @@ export function createContentIndex(files, descriptor) {
44
44
  problems.push({ id, draft, errors: result.errors });
45
45
  continue;
46
46
  }
47
+ const summaryFieldValues = {};
48
+ for (const key of descriptor.summaryFields) {
49
+ if (key in result.data)
50
+ summaryFieldValues[key] = result.data[key];
51
+ }
47
52
  entries.push({
53
+ concept: descriptor.id,
48
54
  id,
49
55
  slug,
50
56
  permalink: permalink(descriptor, { id, slug, date }),
@@ -55,6 +61,7 @@ export function createContentIndex(files, descriptor) {
55
61
  excerpt: deriveExcerpt(body, { description: asString(raw.description) }),
56
62
  wordCount: wordCount(body),
57
63
  draft,
64
+ fields: summaryFieldValues,
58
65
  frontmatter: result.data,
59
66
  body,
60
67
  });
@@ -0,0 +1,2 @@
1
+ export { default as CairnHead } from './CairnHead.svelte';
2
+ //# sourceMappingURL=head.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"head.d.ts","sourceRoot":"","sources":["../../src/lib/delivery/head.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,OAAO,IAAI,SAAS,EAAE,MAAM,oBAAoB,CAAC"}
@@ -0,0 +1,4 @@
1
+ // cairn-cms: the delivery head component entry (@glw907/cairn-cms/delivery/head). CairnHead lives
2
+ // behind its own export so importing a delivery data helper from /delivery never pulls a .svelte
3
+ // module into the graph. A node-environment data import then needs no Svelte plugin.
4
+ export { default as CairnHead } from './CairnHead.svelte';
@@ -23,5 +23,4 @@ export { permalink } from '../content/permalink.js';
23
23
  export { buildSiteManifest, buildLinkResolver } from './manifest.js';
24
24
  export { createPublicRoutes } from '../sveltekit/public-routes.js';
25
25
  export type { PublicRoutesDeps, ListData, TagData, TagIndexData, EntryData, } from '../sveltekit/public-routes.js';
26
- export { default as CairnHead } from './CairnHead.svelte';
27
26
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/lib/delivery/index.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,kBAAkB,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAClE,YAAY,EAAE,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAC9G,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAClD,YAAY,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC/D,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AACtD,YAAY,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAChE,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACxD,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AACzD,YAAY,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACxD,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAC5C,YAAY,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC/C,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AACxC,YAAY,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AAClD,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AACjE,YAAY,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AACjD,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,YAAY,EAAE,IAAI,EAAE,MAAM,eAAe,CAAC;AAC1C,OAAO,EAAE,WAAW,EAAE,gBAAgB,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAChG,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AACpD,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AACrE,OAAO,EAAE,kBAAkB,EAAE,MAAM,+BAA+B,CAAC;AACnE,YAAY,EACV,gBAAgB,EAChB,QAAQ,EACR,OAAO,EACP,YAAY,EACZ,SAAS,GACV,MAAM,+BAA+B,CAAC;AACvC,OAAO,EAAE,OAAO,IAAI,SAAS,EAAE,MAAM,oBAAoB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/lib/delivery/index.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,kBAAkB,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAClE,YAAY,EAAE,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAC9G,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAClD,YAAY,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC/D,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AACtD,YAAY,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAChE,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACxD,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AACzD,YAAY,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACxD,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAC5C,YAAY,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC/C,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AACxC,YAAY,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AAClD,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AACjE,YAAY,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AACjD,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,YAAY,EAAE,IAAI,EAAE,MAAM,eAAe,CAAC;AAC1C,OAAO,EAAE,WAAW,EAAE,gBAAgB,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAChG,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AACpD,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AACrE,OAAO,EAAE,kBAAkB,EAAE,MAAM,+BAA+B,CAAC;AACnE,YAAY,EACV,gBAAgB,EAChB,QAAQ,EACR,OAAO,EACP,YAAY,EACZ,SAAS,GACV,MAAM,+BAA+B,CAAC"}
@@ -19,4 +19,3 @@ export { jsonLdScript } from './json-ld.js';
19
19
  export { permalink } from '../content/permalink.js';
20
20
  export { buildSiteManifest, buildLinkResolver } from './manifest.js';
21
21
  export { createPublicRoutes } from '../sveltekit/public-routes.js';
22
- export { default as CairnHead } from './CairnHead.svelte';
package/dist/index.d.ts CHANGED
@@ -26,7 +26,7 @@ export type { ReferenceOptions } from './render/component-reference.js';
26
26
  export { glyph } from './render/glyph.js';
27
27
  export type { IconSet } from './render/glyph.js';
28
28
  export { remarkDirectiveStamp } from './render/remark-directives.js';
29
- export { rehypeDispatch, isElement, strProp, iconSpan, cardShell, markFirstList, } from './render/rehype-dispatch.js';
29
+ export { rehypeDispatch, isElement, strProp, iconSpan, cardShell, headRow, markFirstList, } from './render/rehype-dispatch.js';
30
30
  export type { MakeIcon } from './render/rehype-dispatch.js';
31
31
  export { createRenderer } from './render/pipeline.js';
32
32
  export type { RendererOptions } from './render/pipeline.js';
@@ -57,4 +57,7 @@ export { readSeoFields, resolveImageUrl } from './delivery/seo-fields.js';
57
57
  export type { SeoFields } from './delivery/seo-fields.js';
58
58
  export { paginate } from './delivery/paginate.js';
59
59
  export type { Page } from './delivery/paginate.js';
60
+ export { rssResponse, jsonFeedResponse, sitemapResponse, robotsResponse } from './delivery/responses.js';
61
+ export { createPublicRoutes } from './sveltekit/public-routes.js';
62
+ export type { PublicRoutesDeps, ListData, TagData, TagIndexData, EntryData } from './sveltekit/public-routes.js';
60
63
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/lib/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,YAAY,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAC7D,YAAY,EAAE,YAAY,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAChF,OAAO,EAAE,qBAAqB,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAGnE,YAAY,EACV,YAAY,EACZ,aAAa,EACb,gBAAgB,EAChB,SAAS,EACT,aAAa,EACb,SAAS,EACT,YAAY,EACZ,SAAS,EACT,aAAa,EACb,gBAAgB,EAChB,aAAa,EACb,YAAY,EACZ,aAAa,EACb,WAAW,EACX,WAAW,EACX,iBAAiB,EACjB,gBAAgB,EAChB,cAAc,EACd,YAAY,EACZ,UAAU,EACV,YAAY,GACb,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,eAAe,EAAE,iBAAiB,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AACxF,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EACL,mBAAmB,EACnB,cAAc,EACd,iBAAiB,EACjB,aAAa,GACd,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,YAAY,EAAE,aAAa,EAAE,KAAK,EAAE,WAAW,EAAE,mBAAmB,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AACnI,OAAO,EACL,SAAS,EACT,cAAc,EACd,cAAc,EACd,OAAO,EACP,UAAU,EACV,cAAc,GACf,MAAM,kBAAkB,CAAC;AAC1B,YAAY,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAInD,OAAO,EAAE,eAAe,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAC1G,YAAY,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAChE,OAAO,EACL,iBAAiB,EACjB,aAAa,EACb,aAAa,EACb,cAAc,EACd,WAAW,EACX,WAAW,EACX,qBAAqB,EACrB,oBAAoB,EACpB,YAAY,GACb,MAAM,uBAAuB,CAAC;AAC/B,YAAY,EAAE,QAAQ,EAAE,aAAa,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAE9F,OAAO,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AACnE,YAAY,EACV,YAAY,EACZ,iBAAiB,EACjB,SAAS,EACT,cAAc,EACd,QAAQ,EACR,OAAO,EACP,eAAe,GAChB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAC;AACnF,OAAO,EAAE,iBAAiB,EAAE,MAAM,gCAAgC,CAAC;AACnE,YAAY,EAAE,mBAAmB,EAAE,MAAM,gCAAgC,CAAC;AAC1E,OAAO,EAAE,oBAAoB,EAAE,KAAK,eAAe,EAAE,MAAM,8BAA8B,CAAC;AAC1F,OAAO,EAAE,0BAA0B,EAAE,MAAM,iCAAiC,CAAC;AAC7E,YAAY,EAAE,gBAAgB,EAAE,MAAM,iCAAiC,CAAC;AACxE,OAAO,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAC1C,YAAY,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,EAAE,oBAAoB,EAAE,MAAM,+BAA+B,CAAC;AACrE,OAAO,EACL,cAAc,EACd,SAAS,EACT,OAAO,EACP,QAAQ,EACR,SAAS,EACT,aAAa,GACd,MAAM,6BAA6B,CAAC;AACrC,YAAY,EAAE,QAAQ,EAAE,MAAM,6BAA6B,CAAC;AAC5D,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,YAAY,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAG5D,YAAY,EAAE,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACzF,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,EAAE,MAAM,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AACjF,OAAO,EACL,OAAO,EACP,eAAe,EACf,YAAY,EACZ,WAAW,EACX,OAAO,EACP,OAAO,EACP,UAAU,GACX,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AACzD,YAAY,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAG5D,OAAO,EACL,eAAe,EACf,aAAa,EACb,WAAW,EACX,OAAO,EACP,eAAe,EACf,aAAa,EACb,kBAAkB,EAClB,eAAe,GAChB,MAAM,sBAAsB,CAAC;AAC9B,YAAY,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAKhE,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACnD,OAAO,EAAE,kBAAkB,EAAE,QAAQ,EAAE,MAAM,6BAA6B,CAAC;AAC3E,YAAY,EACV,OAAO,EACP,cAAc,EACd,YAAY,EACZ,YAAY,EACZ,cAAc,GACf,MAAM,6BAA6B,CAAC;AACrC,OAAO,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAC;AAC3D,YAAY,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AACxE,OAAO,EAAE,iBAAiB,EAAE,MAAM,4BAA4B,CAAC;AAC/D,YAAY,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAC;AACzE,OAAO,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AACjE,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAClE,YAAY,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AACjE,OAAO,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AACrD,YAAY,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AACnD,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,YAAY,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAC3D,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAC;AAC1E,YAAY,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAC1D,OAAO,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAClD,YAAY,EAAE,IAAI,EAAE,MAAM,wBAAwB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/lib/index.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -21,7 +21,7 @@ export { buildComponentInsert } from './render/component-insert.js';
21
21
  export { generateComponentReference } from './render/component-reference.js';
22
22
  export { glyph } from './render/glyph.js';
23
23
  export { remarkDirectiveStamp } from './render/remark-directives.js';
24
- export { rehypeDispatch, isElement, strProp, iconSpan, cardShell, markFirstList, } from './render/rehype-dispatch.js';
24
+ export { rehypeDispatch, isElement, strProp, iconSpan, cardShell, headRow, markFirstList, } from './render/rehype-dispatch.js';
25
25
  export { createRenderer } from './render/pipeline.js';
26
26
  export { CommitConflictError } from './github/types.js';
27
27
  export { appJwt, installationToken, signingSelfTest } from './github/signing.js';
@@ -43,3 +43,8 @@ export { buildRobots } from './delivery/robots.js';
43
43
  export { buildSeoMeta } from './delivery/seo.js';
44
44
  export { readSeoFields, resolveImageUrl } from './delivery/seo-fields.js';
45
45
  export { paginate } from './delivery/paginate.js';
46
+ // Root superset of the delivery route surface: a wrong guess from root for a route loader or a
47
+ // response helper now resolves. The CairnHead component stays out of root so the root barrel stays
48
+ // node-importable for the unit suite; it resolves from @glw907/cairn-cms/delivery/head.
49
+ export { rssResponse, jsonFeedResponse, sitemapResponse, robotsResponse } from './delivery/responses.js';
50
+ export { createPublicRoutes } from './sveltekit/public-routes.js';
@@ -16,6 +16,10 @@ export interface RendererOptions {
16
16
  * vector the floor closes, so it is only for a site whose content is fully developer-controlled.
17
17
  * It is a code-level adapter decision, never an editor-facing setting. */
18
18
  unsafeDisableSanitize?: boolean;
19
+ /** The `rel` value forced on every `target="_blank"` anchor, applied last so it also covers
20
+ * component-built anchors. Defaults to `'noopener noreferrer'`. Set a different string to change
21
+ * it, or `false` to disable the injection (a site that owns its own anchor hardening). */
22
+ anchorRel?: string | false;
19
23
  }
20
24
  /** Compose a site's render pipeline from its component registry: directive syntax to
21
25
  * 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;AAStD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAMjD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AACvD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,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,SAAQ;QAAE,OAAO,CAAC,EAAE,WAAW,CAAA;KAAE,KAAQ,OAAO,CAAC,MAAM,CAAC;EAKjG"}
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;AAMjD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AACvD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,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;IAChC;;+FAE2F;IAC3F,SAAS,CAAC,EAAE,MAAM,GAAG,KAAK,CAAC;CAC5B;AAED;;uFAEuF;AACvF,wBAAgB,cAAc,CAAC,QAAQ,EAAE,iBAAiB,EAAE,OAAO,GAAE,eAAoB;;;8BA0BrD,MAAM,SAAQ;QAAE,OAAO,CAAC,EAAE,WAAW,CAAA;KAAE,KAAQ,OAAO,CAAC,MAAM,CAAC;EAKjG"}
@@ -23,13 +23,15 @@ export function createRenderer(registry, options = {}) {
23
23
  const floor = options.unsafeDisableSanitize
24
24
  ? []
25
25
  : [[rehypeSanitize, buildSanitizeSchema(registry, options.sanitizeSchema)]];
26
+ const rel = options.anchorRel ?? 'noopener noreferrer';
26
27
  const rehypePlugins = [
27
28
  rehypeRaw,
28
29
  ...floor,
29
30
  [rehypeDispatch, registry, options.stagger],
30
31
  rehypeSlug,
31
- rehypeAnchorRel,
32
32
  ];
33
+ if (rel !== false)
34
+ rehypePlugins.push([rehypeAnchorRel, rel]);
33
35
  const processor = unified()
34
36
  .use(remarkParse)
35
37
  .use(remarkGfm)
@@ -12,7 +12,7 @@ export interface AttributeField {
12
12
  /** Initial value; a string for text/select/icon, a boolean for boolean. */
13
13
  default?: string | boolean;
14
14
  /** Allowed values for `type: 'select'`. */
15
- options?: string[];
15
+ options?: readonly string[];
16
16
  /** Helper text shown under the field. */
17
17
  help?: string;
18
18
  }
@@ -1 +1 @@
1
- {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/lib/render/registry.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/lib/render/registry.ts"],"names":[],"mappings":"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"}
@@ -8,6 +8,11 @@ export declare function iconSpan(glyphEl: Element, role?: string): Element;
8
8
  export type MakeIcon = (name: string, role?: string) => Element;
9
9
  /** Section wrapper: `<section class=…><div class="card-body">…</div></section>`. */
10
10
  export declare function cardShell(classes: string[], body: ElementContent[]): Element;
11
+ /** Card head row: `<div class="ec-head">[icon]<h2 class="card-title">{title}</h2></div>`.
12
+ * Pass the title's inline children and an optional pre-built icon element, the way `cardShell`
13
+ * takes already-built body content. This factors the icon-plus-heading head that a titled
14
+ * component build would otherwise rebuild by hand (the shape the removed `splitHead` produced). */
15
+ export declare function headRow(title: ElementContent[], icon?: Element): Element;
11
16
  /** Tag the first <ul> among children with `ec-grid` and strip its whitespace-only
12
17
  * text nodes so the bare list serializes without newlines. Returns that <ul>. */
13
18
  export declare function markFirstList(children: ElementContent[]): Element | undefined;
@@ -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,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
+ {"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;;;oGAGoG;AACpG,wBAAgB,OAAO,CAAC,KAAK,EAAE,cAAc,EAAE,EAAE,IAAI,CAAC,EAAE,OAAO,GAAG,OAAO,CAKxE;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"}
@@ -4,7 +4,7 @@ export function isElement(node) {
4
4
  return !!node && node.type === 'element';
5
5
  }
6
6
  // hast Properties values are PropertyValue (string | number | boolean | array | null).
7
- // Directive markers (dataIcon/dataRole/dataPrimitive) are always stamped as strings;
7
+ // Directive markers (dataPrimitive/dataRole/dataAttr<Key>) are always stamped as strings;
8
8
  // this reads them back with that guarantee instead of casting at each call site.
9
9
  export function strProp(node, name) {
10
10
  const value = node.properties?.[name];
@@ -19,6 +19,17 @@ export function iconSpan(glyphEl, role) {
19
19
  export function cardShell(classes, body) {
20
20
  return h('section', { className: classes }, [h('div', { className: ['card-body'] }, body)]);
21
21
  }
22
+ /** Card head row: `<div class="ec-head">[icon]<h2 class="card-title">{title}</h2></div>`.
23
+ * Pass the title's inline children and an optional pre-built icon element, the way `cardShell`
24
+ * takes already-built body content. This factors the icon-plus-heading head that a titled
25
+ * component build would otherwise rebuild by hand (the shape the removed `splitHead` produced). */
26
+ export function headRow(title, icon) {
27
+ const children = [];
28
+ if (icon)
29
+ children.push(icon);
30
+ children.push(h('h2', { className: ['card-title'] }, title));
31
+ return h('div', { className: ['ec-head'] }, children);
32
+ }
22
33
  /** Tag the first <ul> among children with `ec-grid` and strip its whitespace-only
23
34
  * text nodes so the bare list serializes without newlines. Returns that <ul>. */
24
35
  export function markFirstList(children) {
@@ -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,EAAgB,KAAK,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAkDrE,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,iBAAiB,IAEtD,MAAM,IAAI,UAqDnB"}
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,UAgEnB"}
@@ -60,19 +60,23 @@ export function remarkDirectiveStamp(registry) {
60
60
  const def = registry.get(node.name);
61
61
  const attrs = node.attributes ?? {};
62
62
  const role = attrs.role || undefined;
63
- let icon = attrs.icon || undefined;
63
+ const iconField = def?.attributes?.find((field) => field.type === 'icon');
64
+ const iconKey = iconField?.key ?? 'icon';
65
+ let icon = attrs[iconKey] || undefined;
64
66
  if (!icon && role)
65
67
  icon = registry.defaultIcon(node.name, role);
66
68
  const properties = { dataPrimitive: node.name };
67
- if (icon)
68
- properties.dataIcon = icon;
69
69
  if (role)
70
70
  properties.dataRole = role;
71
71
  // Carry every declared attribute to hast so the dispatch partitioner can build the
72
- // component context. data-attr-<key> survives to the element; build() consumes it and
73
- // returns a fresh element, so the marker never reaches the published DOM.
72
+ // component context. The icon attribute uses the already-resolved `icon` (the author value
73
+ // coerced through the same empty-is-absent rule above, or the defaultIconByRole default), so
74
+ // a role default reaches the build through the one declared path and a blank `icon=` falls
75
+ // back to that default the same way a missing one does. data-attr-<key> survives to the
76
+ // element; build() consumes it and returns a fresh element, so the marker never reaches the
77
+ // published DOM.
74
78
  for (const field of def?.attributes ?? []) {
75
- const raw = attrs[field.key];
79
+ const raw = field === iconField ? icon : attrs[field.key];
76
80
  if (raw != null)
77
81
  properties[dataAttrProp(field.key)] = raw;
78
82
  }
@@ -92,6 +96,11 @@ export function remarkDirectiveStamp(registry) {
92
96
  markSlot(child, child.name);
93
97
  }
94
98
  }
99
+ // A directive [label] that the component has no `title` slot to claim would otherwise fall
100
+ // through as body content and render as a stray paragraph. Drop it.
101
+ if (!slotNames.has('title')) {
102
+ node.children = node.children.filter((child) => !isDirectiveLabel(child));
103
+ }
95
104
  });
96
105
  visit(tree, ['textDirective', 'leafDirective'], (node, index, parent) => {
97
106
  if (!parent || index == null)
@@ -12,9 +12,10 @@ import { type ComponentRegistry } from './registry.js';
12
12
  */
13
13
  export declare function buildSanitizeSchema(registry: ComponentRegistry, extend?: (defaults: Schema) => Schema): Schema;
14
14
  /**
15
- * Force rel="noopener noreferrer" on every target="_blank" anchor, to prevent reverse-tabnabbing.
15
+ * Force a `rel` value on every target="_blank" anchor, to prevent reverse-tabnabbing.
16
16
  * hast-util-sanitize runs no per-node hook, so this small transform carries the behavior the old
17
- * DOMPurify preview pass enforced, now on the delivered output as well.
17
+ * DOMPurify preview pass enforced, now on the delivered output as well. The value is the renderer's
18
+ * `anchorRel` option (default `noopener noreferrer`); a site can override it or disable it entirely.
18
19
  */
19
- export declare function rehypeAnchorRel(): (tree: Root) => void;
20
+ export declare function rehypeAnchorRel(rel: string): (tree: Root) => void;
20
21
  //# sourceMappingURL=sanitize-schema.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"sanitize-schema.d.ts","sourceRoot":"","sources":["../../src/lib/render/sanitize-schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAiB,KAAK,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAChE,OAAO,KAAK,EAAE,IAAI,EAAW,MAAM,MAAM,CAAC;AAE1C,OAAO,EAAgB,KAAK,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAMrE;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,iBAAiB,EAC3B,MAAM,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,MAAM,GACpC,MAAM,CA6BR;AAED;;;;GAIG;AACH,wBAAgB,eAAe,KACrB,MAAM,IAAI,UAOnB"}
1
+ {"version":3,"file":"sanitize-schema.d.ts","sourceRoot":"","sources":["../../src/lib/render/sanitize-schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAiB,KAAK,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAChE,OAAO,KAAK,EAAE,IAAI,EAAW,MAAM,MAAM,CAAC;AAE1C,OAAO,EAAgB,KAAK,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAMrE;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,iBAAiB,EAC3B,MAAM,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,MAAM,GACpC,MAAM,CA6BR;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,IACjC,MAAM,IAAI,UAOnB"}
@@ -3,7 +3,7 @@ import { visit } from 'unist-util-visit';
3
3
  import { dataAttrProp } from './registry.js';
4
4
  // The fixed directive markers the stamp writes and the dispatch reads. They are inert data
5
5
  // attributes, never a script vector, and must survive the floor so the dispatch still runs.
6
- const FIXED_MARKERS = ['dataPrimitive', 'dataSlot', 'dataIcon', 'dataRole', 'dataRise'];
6
+ const FIXED_MARKERS = ['dataPrimitive', 'dataSlot', 'dataRole', 'dataRise'];
7
7
  /**
8
8
  * Build the delivery sanitize schema. Starts from hast-util-sanitize's defaultSchema, the
9
9
  * GitHub-lineage allowlist that strips scripts, inline event handlers, and javascript:/data: URLs,
@@ -42,15 +42,16 @@ export function buildSanitizeSchema(registry, extend) {
42
42
  return extend ? extend(schema) : schema;
43
43
  }
44
44
  /**
45
- * Force rel="noopener noreferrer" on every target="_blank" anchor, to prevent reverse-tabnabbing.
45
+ * Force a `rel` value on every target="_blank" anchor, to prevent reverse-tabnabbing.
46
46
  * hast-util-sanitize runs no per-node hook, so this small transform carries the behavior the old
47
- * DOMPurify preview pass enforced, now on the delivered output as well.
47
+ * DOMPurify preview pass enforced, now on the delivered output as well. The value is the renderer's
48
+ * `anchorRel` option (default `noopener noreferrer`); a site can override it or disable it entirely.
48
49
  */
49
- export function rehypeAnchorRel() {
50
+ export function rehypeAnchorRel(rel) {
50
51
  return (tree) => {
51
52
  visit(tree, 'element', (node) => {
52
53
  if (node.tagName === 'a' && node.properties?.target === '_blank') {
53
- node.properties.rel = 'noopener noreferrer';
54
+ node.properties.rel = rel;
54
55
  }
55
56
  });
56
57
  };
@@ -40,6 +40,7 @@ export interface TagIndexData {
40
40
  }
41
41
  /** One entry's data: the detail entry, its rendered html, and its canonical URL. */
42
42
  export interface EntryData {
43
+ concept: string;
43
44
  entry: ContentEntry;
44
45
  html: string;
45
46
  canonicalUrl: string;
@@ -1 +1 @@
1
- {"version":3,"file":"public-routes.d.ts","sourceRoot":"","sources":["../../src/lib/sveltekit/public-routes.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAC;AACjF,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC;AAE3D,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAGlD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAEvD,oDAAoD;AACpD,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,SAAS,CAAC;IAChB,MAAM,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE;QAAE,OAAO,CAAC,EAAE,OAAO,CAAC;QAAC,OAAO,CAAC,EAAE,WAAW,CAAA;KAAE,KAAK,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACtG,MAAM,EAAE,MAAM,CAAC;IACf,mDAAmD;IACnD,QAAQ,EAAE,MAAM,CAAC;IACjB,uDAAuD;IACvD,WAAW,EAAE,MAAM,CAAC;IACpB,6DAA6D;IAC7D,KAAK,CAAC,EAAE;QAAE,GAAG,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACxC;6EACyE;IACzE,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,qEAAqE;AACrE,MAAM,WAAW,QAAQ;IACvB,OAAO,EAAE,cAAc,EAAE,CAAC;CAC3B;AAED,uDAAuD;AACvD,MAAM,WAAW,OAAQ,SAAQ,QAAQ;IACvC,GAAG,EAAE,MAAM,CAAC;CACb;AAED,oDAAoD;AACpD,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CACxC;AAED,oFAAoF;AACpF,MAAM,WAAW,SAAS;IACxB,KAAK,EAAE,YAAY,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,CAAC;IACrB,GAAG,EAAE,OAAO,CAAC;IACb,KAAK,CAAC,EAAE,cAAc,CAAC;IACvB,KAAK,CAAC,EAAE,cAAc,CAAC;CACxB;AAED,2DAA2D;AAC3D,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,gBAAgB;uBAWvB;QAAE,GAAG,EAAE,GAAG,CAAA;KAAE,KAAG,OAAO,CAAC,SAAS,CAAC;6BA0BjC,MAAM,KAAG,QAAQ;8BAKhB,MAAM,KAAG,YAAY;yBAK1B,MAAM,SAAS;QAAE,MAAM,EAAE;YAAE,GAAG,EAAE,MAAM,CAAA;SAAE,CAAA;KAAE,KAAG,OAAO;mBAO5D;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,EAAE;EAKvC"}
1
+ {"version":3,"file":"public-routes.d.ts","sourceRoot":"","sources":["../../src/lib/sveltekit/public-routes.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAC;AACjF,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC;AAE3D,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAGlD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAEvD,oDAAoD;AACpD,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,SAAS,CAAC;IAChB,MAAM,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE;QAAE,OAAO,CAAC,EAAE,OAAO,CAAC;QAAC,OAAO,CAAC,EAAE,WAAW,CAAA;KAAE,KAAK,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACtG,MAAM,EAAE,MAAM,CAAC;IACf,mDAAmD;IACnD,QAAQ,EAAE,MAAM,CAAC;IACjB,uDAAuD;IACvD,WAAW,EAAE,MAAM,CAAC;IACpB,6DAA6D;IAC7D,KAAK,CAAC,EAAE;QAAE,GAAG,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACxC;6EACyE;IACzE,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,qEAAqE;AACrE,MAAM,WAAW,QAAQ;IACvB,OAAO,EAAE,cAAc,EAAE,CAAC;CAC3B;AAED,uDAAuD;AACvD,MAAM,WAAW,OAAQ,SAAQ,QAAQ;IACvC,GAAG,EAAE,MAAM,CAAC;CACb;AAED,oDAAoD;AACpD,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CACxC;AAED,oFAAoF;AACpF,MAAM,WAAW,SAAS;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,YAAY,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,CAAC;IACrB,GAAG,EAAE,OAAO,CAAC;IACb,KAAK,CAAC,EAAE,cAAc,CAAC;IACvB,KAAK,CAAC,EAAE,cAAc,CAAC;CACxB;AAED,2DAA2D;AAC3D,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,gBAAgB;uBAWvB;QAAE,GAAG,EAAE,GAAG,CAAA;KAAE,KAAG,OAAO,CAAC,SAAS,CAAC;6BA0BjC,MAAM,KAAG,QAAQ;8BAKhB,MAAM,KAAG,YAAY;yBAK1B,MAAM,SAAS;QAAE,MAAM,EAAE;YAAE,GAAG,EAAE,MAAM,CAAA;SAAE,CAAA;KAAE,KAAG,OAAO;mBAO5D;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,EAAE;EAKvC"}
@@ -41,7 +41,7 @@ export function createPublicRoutes(deps) {
41
41
  ...(fields.author ? { author: fields.author } : {}),
42
42
  ...(entry.date ? { feeds } : {}),
43
43
  });
44
- return { entry, html: await render(entry.body, { stagger: true, resolve: buildLinkResolver(site) }), canonicalUrl, seo, newer, older };
44
+ return { concept: entry.concept, entry, html: await render(entry.body, { stagger: true, resolve: buildLinkResolver(site) }), canonicalUrl, seo, newer, older };
45
45
  }
46
46
  /** The chronological archive for one concept: every non-draft summary, newest-first. */
47
47
  function archiveLoad(conceptId) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@glw907/cairn-cms",
3
- "version": "0.21.0",
3
+ "version": "0.24.0",
4
4
  "description": "Embedded, magic-link, GitHub-committing CMS for SvelteKit/Cloudflare sites.",
5
5
  "type": "module",
6
6
  "sideEffects": [
@@ -53,6 +53,11 @@
53
53
  "svelte": "./dist/delivery/index.js",
54
54
  "default": "./dist/delivery/index.js"
55
55
  },
56
+ "./delivery/head": {
57
+ "types": "./dist/delivery/head.d.ts",
58
+ "svelte": "./dist/delivery/head.js",
59
+ "default": "./dist/delivery/head.js"
60
+ },
56
61
  "./package.json": "./package.json"
57
62
  },
58
63
  "files": [
@@ -43,6 +43,14 @@ export function normalizeConcepts(
43
43
  const descriptors: ConceptDescriptor[] = [];
44
44
  for (const [id, config] of Object.entries(content)) {
45
45
  if (!config) continue;
46
+ const summaryFields = config.summaryFields ?? [];
47
+ const declared = new Set(config.schema.fields.map((field) => field.name));
48
+ const undeclared = summaryFields.find((key) => !declared.has(key));
49
+ if (undeclared !== undefined) {
50
+ throw new Error(
51
+ `cairn: concept "${id}" summaryFields key "${undeclared}" is not a declared field`,
52
+ );
53
+ }
46
54
  const policy = urlPolicy[id] ?? {};
47
55
  descriptors.push({
48
56
  id,
@@ -52,6 +60,7 @@ export function normalizeConcepts(
52
60
  permalink: policy.permalink ?? defaultPermalink(id),
53
61
  datePrefix: policy.datePrefix ?? 'day',
54
62
  fields: config.schema.fields,
63
+ summaryFields,
55
64
  validate: config.schema.validate,
56
65
  });
57
66
  }
@@ -56,6 +56,27 @@ export function dateInputValue(value: unknown): string {
56
56
  return '';
57
57
  }
58
58
 
59
+ /**
60
+ * True when `s` is a canonical zero-padded `YYYY-MM-DD` string naming a real calendar date.
61
+ * Rejects a wrong format, an impossible month or day, and a JS date-rollover such as
62
+ * `2026-02-30` (which `Date` would silently roll forward to March 2). The committed form a
63
+ * date field carries is exactly this canonical shape, which is what the form and
64
+ * `dateInputValue` emit, so a value outside it is a hand-edit or odd-YAML error.
65
+ */
66
+ export function isCalendarDate(s: string): boolean {
67
+ const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(s);
68
+ if (!match) return false;
69
+ const year = Number(match[1]);
70
+ const month = Number(match[2]);
71
+ const day = Number(match[3]);
72
+ const date = new Date(Date.UTC(year, month - 1, day));
73
+ return (
74
+ date.getUTCFullYear() === year &&
75
+ date.getUTCMonth() === month - 1 &&
76
+ date.getUTCDate() === day
77
+ );
78
+ }
79
+
59
80
  /** Reassemble a markdown file from frontmatter and body for committing. */
60
81
  export function serializeMarkdown(frontmatter: object, body: string): string {
61
82
  return matter.stringify(body, frontmatter);
@@ -110,6 +110,9 @@ export interface ConceptConfig<S extends ConceptSchema = ConceptSchema> {
110
110
  label?: string;
111
111
  /** The concept's schema: the form projection, the generated validator, and the inferred type. */
112
112
  schema: S;
113
+ /** Frontmatter keys to surface on each `ContentSummary.fields`, so a list card reads an authored
114
+ * field without a per-entry detail read. Each key should also be declared in `schema`. */
115
+ summaryFields?: string[];
113
116
  }
114
117
 
115
118
  /**
@@ -215,6 +218,9 @@ export interface ConceptDescriptor {
215
218
  /** Filename date-prefix granularity for a dated concept; resolved by `normalizeConcepts`. */
216
219
  datePrefix: DatePrefix;
217
220
  fields: FrontmatterField[];
221
+ /** Frontmatter keys the index copies onto each summary's `fields` record. `normalizeConcepts`
222
+ * resolves it to `[]` when a concept omits `summaryFields`. */
223
+ summaryFields: string[];
218
224
  validate(frontmatter: Record<string, unknown>, body: string): ValidationResult;
219
225
  }
220
226
 
@@ -3,7 +3,7 @@
3
3
  // validator stays thin (engine-fat rule). Saving runs the concept's validator on the
4
4
  // server before any commit; invalid input bounces to the form (spec §7.4).
5
5
  import type { FrontmatterField, ValidationResult } from './types.js';
6
- import { dateInputValue } from './frontmatter.js';
6
+ import { dateInputValue, isCalendarDate } from './frontmatter.js';
7
7
 
8
8
  /**
9
9
  * Validate raw frontmatter against a field list. Required text and date fields must be
@@ -34,12 +34,17 @@ export function validateFields(
34
34
  case 'freetags': {
35
35
  const list = Array.isArray(value) ? value.map(String) : [];
36
36
  if (field.required && list.length === 0) errors[field.name] = `${field.label} is required`;
37
+ else if (field.type === 'tags') {
38
+ const unknown = list.find((tag) => !field.options.includes(tag));
39
+ if (unknown !== undefined) errors[field.name] = `${field.label} contains an unknown value: ${unknown}`;
40
+ }
37
41
  if (list.length > 0) data[field.name] = list;
38
42
  break;
39
43
  }
40
44
  case 'date': {
41
45
  const text = value instanceof Date ? dateInputValue(value) : typeof value === 'string' ? value.trim() : '';
42
46
  if (field.required && text === '') errors[field.name] = `${field.label} is required`;
47
+ else if (text !== '' && !isCalendarDate(text)) errors[field.name] = `${field.label} must be a valid date (YYYY-MM-DD)`;
43
48
  if (text !== '') data[field.name] = text;
44
49
  break;
45
50
  }
@@ -16,6 +16,9 @@ export interface RawFile {
16
16
 
17
17
  /** The cheap, plain-data view of one entry, for lists, feeds, and the sitemap. */
18
18
  export interface ContentSummary {
19
+ /** The descriptor id this entry belongs to, e.g. "posts". Lets a list or page branch per
20
+ * concept without re-deriving it from a proxy like `entry.date`. */
21
+ concept: string;
19
22
  id: string;
20
23
  slug: string;
21
24
  permalink: string;
@@ -26,6 +29,10 @@ export interface ContentSummary {
26
29
  excerpt: string;
27
30
  wordCount: number;
28
31
  draft: boolean;
32
+ /** The frontmatter keys the descriptor nominated via `summaryFields`, read off the validated,
33
+ * normalized frontmatter. Held in a separate record so a nominated key cannot collide with a
34
+ * typed summary field. Empty when the concept declares no `summaryFields`. */
35
+ fields: Record<string, unknown>;
29
36
  }
30
37
 
31
38
  /** The detail view: a summary plus the frontmatter and the body to render. The frontmatter
@@ -98,7 +105,12 @@ export function createContentIndex<F = Record<string, unknown>>(
98
105
  problems.push({ id, draft, errors: result.errors });
99
106
  continue;
100
107
  }
108
+ const summaryFieldValues: Record<string, unknown> = {};
109
+ for (const key of descriptor.summaryFields) {
110
+ if (key in result.data) summaryFieldValues[key] = result.data[key];
111
+ }
101
112
  entries.push({
113
+ concept: descriptor.id,
102
114
  id,
103
115
  slug,
104
116
  permalink: permalink(descriptor, { id, slug, date }),
@@ -109,6 +121,7 @@ export function createContentIndex<F = Record<string, unknown>>(
109
121
  excerpt: deriveExcerpt(body, { description: asString(raw.description) }),
110
122
  wordCount: wordCount(body),
111
123
  draft,
124
+ fields: summaryFieldValues,
112
125
  frontmatter: result.data as F,
113
126
  body,
114
127
  });
@@ -0,0 +1,4 @@
1
+ // cairn-cms: the delivery head component entry (@glw907/cairn-cms/delivery/head). CairnHead lives
2
+ // behind its own export so importing a delivery data helper from /delivery never pulls a .svelte
3
+ // module into the graph. A node-environment data import then needs no Svelte plugin.
4
+ export { default as CairnHead } from './CairnHead.svelte';
@@ -34,4 +34,3 @@ export type {
34
34
  TagIndexData,
35
35
  EntryData,
36
36
  } from '../sveltekit/public-routes.js';
37
- export { default as CairnHead } from './CairnHead.svelte';
package/src/lib/index.ts CHANGED
@@ -92,6 +92,7 @@ export {
92
92
  strProp,
93
93
  iconSpan,
94
94
  cardShell,
95
+ headRow,
95
96
  markFirstList,
96
97
  } from './render/rehype-dispatch.js';
97
98
  export type { MakeIcon } from './render/rehype-dispatch.js';
@@ -155,3 +156,9 @@ export { readSeoFields, resolveImageUrl } from './delivery/seo-fields.js';
155
156
  export type { SeoFields } from './delivery/seo-fields.js';
156
157
  export { paginate } from './delivery/paginate.js';
157
158
  export type { Page } from './delivery/paginate.js';
159
+ // Root superset of the delivery route surface: a wrong guess from root for a route loader or a
160
+ // response helper now resolves. The CairnHead component stays out of root so the root barrel stays
161
+ // node-importable for the unit suite; it resolves from @glw907/cairn-cms/delivery/head.
162
+ export { rssResponse, jsonFeedResponse, sitemapResponse, robotsResponse } from './delivery/responses.js';
163
+ export { createPublicRoutes } from './sveltekit/public-routes.js';
164
+ export type { PublicRoutesDeps, ListData, TagData, TagIndexData, EntryData } from './sveltekit/public-routes.js';
@@ -30,6 +30,10 @@ export interface RendererOptions {
30
30
  * vector the floor closes, so it is only for a site whose content is fully developer-controlled.
31
31
  * It is a code-level adapter decision, never an editor-facing setting. */
32
32
  unsafeDisableSanitize?: boolean;
33
+ /** The `rel` value forced on every `target="_blank"` anchor, applied last so it also covers
34
+ * component-built anchors. Defaults to `'noopener noreferrer'`. Set a different string to change
35
+ * it, or `false` to disable the injection (a site that owns its own anchor hardening). */
36
+ anchorRel?: string | false;
33
37
  }
34
38
 
35
39
  /** Compose a site's render pipeline from its component registry: directive syntax to
@@ -43,13 +47,14 @@ export function createRenderer(registry: ComponentRegistry, options: RendererOpt
43
47
  const floor: PluggableList = options.unsafeDisableSanitize
44
48
  ? []
45
49
  : [[rehypeSanitize, buildSanitizeSchema(registry, options.sanitizeSchema)]];
50
+ const rel = options.anchorRel ?? 'noopener noreferrer';
46
51
  const rehypePlugins: PluggableList = [
47
52
  rehypeRaw,
48
53
  ...floor,
49
54
  [rehypeDispatch, registry, options.stagger],
50
55
  rehypeSlug,
51
- rehypeAnchorRel,
52
56
  ];
57
+ if (rel !== false) rehypePlugins.push([rehypeAnchorRel, rel]);
53
58
  const processor = unified()
54
59
  .use(remarkParse)
55
60
  .use(remarkGfm)
@@ -19,7 +19,7 @@ export interface AttributeField {
19
19
  /** Initial value; a string for text/select/icon, a boolean for boolean. */
20
20
  default?: string | boolean;
21
21
  /** Allowed values for `type: 'select'`. */
22
- options?: string[];
22
+ options?: readonly string[];
23
23
  /** Helper text shown under the field. */
24
24
  help?: string;
25
25
  }
@@ -7,7 +7,7 @@ export function isElement(node: ElementContent | undefined): node is Element {
7
7
  }
8
8
 
9
9
  // hast Properties values are PropertyValue (string | number | boolean | array | null).
10
- // Directive markers (dataIcon/dataRole/dataPrimitive) are always stamped as strings;
10
+ // Directive markers (dataPrimitive/dataRole/dataAttr<Key>) are always stamped as strings;
11
11
  // this reads them back with that guarantee instead of casting at each call site.
12
12
  export function strProp(node: Element, name: string): string | undefined {
13
13
  const value = node.properties?.[name];
@@ -28,6 +28,17 @@ export function cardShell(classes: string[], body: ElementContent[]): Element {
28
28
  return h('section', { className: classes }, [h('div', { className: ['card-body'] }, body)]);
29
29
  }
30
30
 
31
+ /** Card head row: `<div class="ec-head">[icon]<h2 class="card-title">{title}</h2></div>`.
32
+ * Pass the title's inline children and an optional pre-built icon element, the way `cardShell`
33
+ * takes already-built body content. This factors the icon-plus-heading head that a titled
34
+ * component build would otherwise rebuild by hand (the shape the removed `splitHead` produced). */
35
+ export function headRow(title: ElementContent[], icon?: Element): Element {
36
+ const children: ElementContent[] = [];
37
+ if (icon) children.push(icon);
38
+ children.push(h('h2', { className: ['card-title'] }, title));
39
+ return h('div', { className: ['ec-head'] }, children);
40
+ }
41
+
31
42
  /** Tag the first <ul> among children with `ec-grid` and strip its whitespace-only
32
43
  * text nodes so the bare list serializes without newlines. Returns that <ul>. */
33
44
  export function markFirstList(children: ElementContent[]): Element | undefined {
@@ -59,17 +59,22 @@ export function remarkDirectiveStamp(registry: ComponentRegistry) {
59
59
  const def = registry.get(node.name);
60
60
  const attrs = node.attributes ?? {};
61
61
  const role = attrs.role || undefined;
62
- let icon = attrs.icon || undefined;
62
+ const iconField = def?.attributes?.find((field) => field.type === 'icon');
63
+ const iconKey = iconField?.key ?? 'icon';
64
+ let icon = attrs[iconKey] || undefined;
63
65
  if (!icon && role) icon = registry.defaultIcon(node.name, role);
64
66
 
65
67
  const properties: Record<string, string> = { dataPrimitive: node.name };
66
- if (icon) properties.dataIcon = icon;
67
68
  if (role) properties.dataRole = role;
68
69
  // Carry every declared attribute to hast so the dispatch partitioner can build the
69
- // component context. data-attr-<key> survives to the element; build() consumes it and
70
- // returns a fresh element, so the marker never reaches the published DOM.
70
+ // component context. The icon attribute uses the already-resolved `icon` (the author value
71
+ // coerced through the same empty-is-absent rule above, or the defaultIconByRole default), so
72
+ // a role default reaches the build through the one declared path and a blank `icon=` falls
73
+ // back to that default the same way a missing one does. data-attr-<key> survives to the
74
+ // element; build() consumes it and returns a fresh element, so the marker never reaches the
75
+ // published DOM.
71
76
  for (const field of def?.attributes ?? []) {
72
- const raw = attrs[field.key];
77
+ const raw = field === iconField ? icon : attrs[field.key];
73
78
  if (raw != null) properties[dataAttrProp(field.key)] = raw;
74
79
  }
75
80
 
@@ -91,6 +96,12 @@ export function remarkDirectiveStamp(registry: ComponentRegistry) {
91
96
  markSlot(child, (child as { name: string }).name);
92
97
  }
93
98
  }
99
+
100
+ // A directive [label] that the component has no `title` slot to claim would otherwise fall
101
+ // through as body content and render as a stray paragraph. Drop it.
102
+ if (!slotNames.has('title')) {
103
+ node.children = node.children.filter((child) => !isDirectiveLabel(child)) as typeof node.children;
104
+ }
94
105
  });
95
106
 
96
107
  visit(tree, ['textDirective', 'leafDirective'], (node, index, parent) => {
@@ -5,7 +5,7 @@ import { dataAttrProp, type ComponentRegistry } from './registry.js';
5
5
 
6
6
  // The fixed directive markers the stamp writes and the dispatch reads. They are inert data
7
7
  // attributes, never a script vector, and must survive the floor so the dispatch still runs.
8
- const FIXED_MARKERS = ['dataPrimitive', 'dataSlot', 'dataIcon', 'dataRole', 'dataRise'];
8
+ const FIXED_MARKERS = ['dataPrimitive', 'dataSlot', 'dataRole', 'dataRise'];
9
9
 
10
10
  /**
11
11
  * Build the delivery sanitize schema. Starts from hast-util-sanitize's defaultSchema, the
@@ -51,15 +51,16 @@ export function buildSanitizeSchema(
51
51
  }
52
52
 
53
53
  /**
54
- * Force rel="noopener noreferrer" on every target="_blank" anchor, to prevent reverse-tabnabbing.
54
+ * Force a `rel` value on every target="_blank" anchor, to prevent reverse-tabnabbing.
55
55
  * hast-util-sanitize runs no per-node hook, so this small transform carries the behavior the old
56
- * DOMPurify preview pass enforced, now on the delivered output as well.
56
+ * DOMPurify preview pass enforced, now on the delivered output as well. The value is the renderer's
57
+ * `anchorRel` option (default `noopener noreferrer`); a site can override it or disable it entirely.
57
58
  */
58
- export function rehypeAnchorRel() {
59
+ export function rehypeAnchorRel(rel: string) {
59
60
  return (tree: Root) => {
60
61
  visit(tree, 'element', (node: Element) => {
61
62
  if (node.tagName === 'a' && node.properties?.target === '_blank') {
62
- node.properties.rel = 'noopener noreferrer';
63
+ node.properties.rel = rel;
63
64
  }
64
65
  });
65
66
  };
@@ -45,6 +45,7 @@ export interface TagIndexData {
45
45
 
46
46
  /** One entry's data: the detail entry, its rendered html, and its canonical URL. */
47
47
  export interface EntryData {
48
+ concept: string;
48
49
  entry: ContentEntry;
49
50
  html: string;
50
51
  canonicalUrl: string;
@@ -87,7 +88,7 @@ export function createPublicRoutes(deps: PublicRoutesDeps) {
87
88
  ...(fields.author ? { author: fields.author } : {}),
88
89
  ...(entry.date ? { feeds } : {}),
89
90
  });
90
- return { entry, html: await render(entry.body, { stagger: true, resolve: buildLinkResolver(site) }), canonicalUrl, seo, newer, older };
91
+ return { concept: entry.concept, entry, html: await render(entry.body, { stagger: true, resolve: buildLinkResolver(site) }), canonicalUrl, seo, newer, older };
91
92
  }
92
93
 
93
94
  /** The chronological archive for one concept: every non-draft summary, newest-first. */