@glw907/cairn-cms 0.7.0 → 0.8.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 (46) hide show
  1. package/dist/components/ConceptList.svelte +8 -4
  2. package/dist/components/ConceptList.svelte.d.ts.map +1 -1
  3. package/dist/content/compose.d.ts +2 -2
  4. package/dist/content/compose.d.ts.map +1 -1
  5. package/dist/content/compose.js +2 -2
  6. package/dist/content/concepts.d.ts +7 -6
  7. package/dist/content/concepts.d.ts.map +1 -1
  8. package/dist/content/concepts.js +9 -6
  9. package/dist/content/ids.d.ts +14 -0
  10. package/dist/content/ids.d.ts.map +1 -1
  11. package/dist/content/ids.js +40 -0
  12. package/dist/content/permalink.d.ts +1 -0
  13. package/dist/content/permalink.d.ts.map +1 -1
  14. package/dist/content/permalink.js +1 -1
  15. package/dist/content/types.d.ts +12 -6
  16. package/dist/content/types.d.ts.map +1 -1
  17. package/dist/delivery/content-index.d.ts +1 -0
  18. package/dist/delivery/content-index.d.ts.map +1 -1
  19. package/dist/delivery/content-index.js +4 -2
  20. package/dist/delivery/site-index.d.ts +28 -0
  21. package/dist/delivery/site-index.d.ts.map +1 -0
  22. package/dist/delivery/site-index.js +38 -0
  23. package/dist/index.d.ts +6 -3
  24. package/dist/index.d.ts.map +1 -1
  25. package/dist/index.js +3 -2
  26. package/dist/nav/site-config.d.ts +5 -0
  27. package/dist/nav/site-config.d.ts.map +1 -1
  28. package/dist/nav/site-config.js +4 -0
  29. package/dist/sveltekit/content-routes.d.ts.map +1 -1
  30. package/dist/sveltekit/content-routes.js +18 -8
  31. package/dist/sveltekit/public-routes.d.ts +11 -12
  32. package/dist/sveltekit/public-routes.d.ts.map +1 -1
  33. package/dist/sveltekit/public-routes.js +36 -35
  34. package/package.json +1 -1
  35. package/src/lib/components/ConceptList.svelte +8 -4
  36. package/src/lib/content/compose.ts +3 -2
  37. package/src/lib/content/concepts.ts +10 -6
  38. package/src/lib/content/ids.ts +44 -0
  39. package/src/lib/content/permalink.ts +2 -2
  40. package/src/lib/content/types.ts +13 -6
  41. package/src/lib/delivery/content-index.ts +5 -2
  42. package/src/lib/delivery/site-index.ts +68 -0
  43. package/src/lib/index.ts +13 -1
  44. package/src/lib/nav/site-config.ts +8 -0
  45. package/src/lib/sveltekit/content-routes.ts +17 -7
  46. package/src/lib/sveltekit/public-routes.ts +38 -36
@@ -17,9 +17,14 @@ plus a new-entry form. The slug auto-derives from the title until the author edi
17
17
  let title = $state('');
18
18
  let slug = $state('');
19
19
  let slugEdited = $state(false);
20
+ // Default the date client-side so the SSR pass and hydration agree across UTC midnight.
21
+ let dateDefault = $state('');
22
+ $effect(() => {
23
+ dateDefault = new Date().toISOString().slice(0, 10);
24
+ });
20
25
 
21
26
  const derivedSlug = $derived(slugEdited ? slug : slugify(title));
22
- const slugPlaceholder = $derived(data.dated ? '2026-05-my-entry' : 'about-us');
27
+ const slugPlaceholder = $derived(data.dated ? 'my-entry' : 'about-us');
23
28
  </script>
24
29
 
25
30
  <header class="mb-4 flex items-center justify-between">
@@ -58,14 +63,13 @@ plus a new-entry form. The slug auto-derives from the title until the author edi
58
63
  <h2 class="text-sm font-semibold">New entry</h2>
59
64
  <label class="flex flex-col gap-1">
60
65
  <span class="text-sm font-medium">Title</span>
61
- <input class="input" name="title" aria-label="Title" bind:value={title} required />
66
+ <input class="input" name="title" bind:value={title} required />
62
67
  </label>
63
68
  <label class="flex flex-col gap-1">
64
69
  <span class="text-sm font-medium">Slug</span>
65
70
  <input
66
71
  class="input"
67
72
  name="slug"
68
- aria-label="Slug"
69
73
  placeholder={slugPlaceholder}
70
74
  value={derivedSlug}
71
75
  oninput={(e) => { slugEdited = true; slug = e.currentTarget.value; }}
@@ -74,7 +78,7 @@ plus a new-entry form. The slug auto-derives from the title until the author edi
74
78
  {#if data.dated}
75
79
  <label class="flex flex-col gap-1">
76
80
  <span class="text-sm font-medium">Date</span>
77
- <input class="input" type="date" name="date" aria-label="Date" />
81
+ <input class="input" type="date" name="date" value={dateDefault} />
78
82
  </label>
79
83
  {/if}
80
84
  <button type="submit" class="btn btn-primary self-start">Create</button>
@@ -1 +1 @@
1
- {"version":3,"file":"ConceptList.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/components/ConceptList.svelte.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gCAAgC,CAAC;AAG7D,UAAU,KAAK;IACb,qFAAqF;IACrF,IAAI,EAAE,QAAQ,CAAC;CAChB;AAsEH;;;GAGG;AACH,QAAA,MAAM,WAAW,2CAAwC,CAAC;AAC1D,KAAK,WAAW,GAAG,UAAU,CAAC,OAAO,WAAW,CAAC,CAAC;AAClD,eAAe,WAAW,CAAC"}
1
+ {"version":3,"file":"ConceptList.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/components/ConceptList.svelte.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gCAAgC,CAAC;AAG7D,UAAU,KAAK;IACb,qFAAqF;IACrF,IAAI,EAAE,QAAQ,CAAC;CAChB;AA2EH;;;GAGG;AACH,QAAA,MAAM,WAAW,2CAAwC,CAAC;AAC1D,KAAK,WAAW,GAAG,UAAU,CAAC,OAAO,WAAW,CAAC,CAAC;AAClD,eAAe,WAAW,CAAC"}
@@ -1,7 +1,7 @@
1
- import type { CairnAdapter, CairnExtension, CairnRuntime } from './types.js';
1
+ import type { CairnAdapter, CairnExtension, CairnRuntime, ConceptUrlPolicy } from './types.js';
2
2
  /**
3
3
  * Fold an adapter and any extensions into the composed runtime (seam 2). Extension concepts
4
4
  * merge after the adapter's. The asset slot (seam 4) passes through untouched.
5
5
  */
6
- export declare function composeRuntime(adapter: CairnAdapter, extensions?: CairnExtension[]): CairnRuntime;
6
+ export declare function composeRuntime(adapter: CairnAdapter, extensions?: CairnExtension[], urlPolicy?: Record<string, ConceptUrlPolicy | undefined>): CairnRuntime;
7
7
  //# sourceMappingURL=compose.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"compose.d.ts","sourceRoot":"","sources":["../../src/lib/content/compose.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAc,YAAY,EAAE,cAAc,EAAE,YAAY,EAA+B,MAAM,YAAY,CAAC;AAGtH;;;GAGG;AACH,wBAAgB,cAAc,CAC5B,OAAO,EAAE,YAAY,EACrB,UAAU,GAAE,cAAc,EAAO,GAChC,YAAY,CAuBd"}
1
+ {"version":3,"file":"compose.d.ts","sourceRoot":"","sources":["../../src/lib/content/compose.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAc,YAAY,EAAE,cAAc,EAAE,YAAY,EAAiB,gBAAgB,EAAgB,MAAM,YAAY,CAAC;AAGxI;;;GAGG;AACH,wBAAgB,cAAc,CAC5B,OAAO,EAAE,YAAY,EACrB,UAAU,GAAE,cAAc,EAAO,EACjC,SAAS,GAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,GAAG,SAAS,CAAM,GAC3D,YAAY,CAuBd"}
@@ -3,7 +3,7 @@ import { normalizeConcepts } from './concepts.js';
3
3
  * Fold an adapter and any extensions into the composed runtime (seam 2). Extension concepts
4
4
  * merge after the adapter's. The asset slot (seam 4) passes through untouched.
5
5
  */
6
- export function composeRuntime(adapter, extensions = []) {
6
+ export function composeRuntime(adapter, extensions = [], urlPolicy = {}) {
7
7
  const content = { ...adapter.content };
8
8
  const adminPanels = [];
9
9
  const fieldTypes = [];
@@ -19,7 +19,7 @@ export function composeRuntime(adapter, extensions = []) {
19
19
  }
20
20
  return {
21
21
  siteName: adapter.siteName,
22
- concepts: normalizeConcepts(content),
22
+ concepts: normalizeConcepts(content, urlPolicy),
23
23
  backend: adapter.backend,
24
24
  sender: adapter.sender,
25
25
  render: adapter.render,
@@ -1,4 +1,4 @@
1
- import type { ConceptConfig, ConceptDescriptor, RoutingRule } from './types.js';
1
+ import type { ConceptConfig, ConceptDescriptor, ConceptUrlPolicy, RoutingRule } from './types.js';
2
2
  /**
3
3
  * Concept-fixed routing, keyed by concept id (spec §7.2). Posts are dated feed entries;
4
4
  * pages are plain navigable structure. Not in adapter config. A future Fragments adds one
@@ -6,12 +6,13 @@ import type { ConceptConfig, ConceptDescriptor, RoutingRule } from './types.js';
6
6
  */
7
7
  export declare const CONCEPT_ROUTING: Readonly<Record<string, RoutingRule>>;
8
8
  /**
9
- * Normalize an adapter's declared concepts into uniform descriptors (seam 1). Each declared
10
- * key under `content` becomes one descriptor; an undeclared (`undefined`) concept is
11
- * skipped. `routing` is injectable so a contract test can prove a new concept attaches
12
- * additively; production passes the default `CONCEPT_ROUTING`.
9
+ * Normalize an adapter's declared concepts into uniform descriptors (seam 1). URL policy
10
+ * (`permalink`, `datePrefix`) comes from the YAML site-config, passed here as `urlPolicy` keyed by
11
+ * concept id; each value defaults when the YAML omits it (`/:slug` for Pages, `/<id>/:slug`
12
+ * otherwise; `datePrefix` defaults to `day`). `routing` is injectable so a contract test can prove
13
+ * a new concept attaches additively; production passes the default `CONCEPT_ROUTING`.
13
14
  */
14
- export declare function normalizeConcepts(content: Record<string, ConceptConfig | undefined>, routing?: Readonly<Record<string, RoutingRule>>): ConceptDescriptor[];
15
+ export declare function normalizeConcepts(content: Record<string, ConceptConfig | undefined>, urlPolicy?: Record<string, ConceptUrlPolicy | undefined>, routing?: Readonly<Record<string, RoutingRule>>): ConceptDescriptor[];
15
16
  /** Look up a normalized concept by id, or undefined when the site does not enable it. */
16
17
  export declare function findConcept(concepts: ConceptDescriptor[], id: string): ConceptDescriptor | undefined;
17
18
  //# sourceMappingURL=concepts.d.ts.map
@@ -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,WAAW,EAAE,MAAM,YAAY,CAAC;AAEhF;;;;GAIG;AACH,eAAO,MAAM,eAAe,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAGjE,CAAC;AAeF;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,aAAa,GAAG,SAAS,CAAC,EAClD,OAAO,GAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAmB,GAC/D,iBAAiB,EAAE,CAerB;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,CAiBrB;AAED,yFAAyF;AACzF,wBAAgB,WAAW,CACzB,QAAQ,EAAE,iBAAiB,EAAE,EAC7B,EAAE,EAAE,MAAM,GACT,iBAAiB,GAAG,SAAS,CAE/B"}
@@ -18,22 +18,25 @@ function defaultPermalink(id) {
18
18
  return id === 'pages' ? '/:slug' : `/${id}/:slug`;
19
19
  }
20
20
  /**
21
- * Normalize an adapter's declared concepts into uniform descriptors (seam 1). Each declared
22
- * key under `content` becomes one descriptor; an undeclared (`undefined`) concept is
23
- * skipped. `routing` is injectable so a contract test can prove a new concept attaches
24
- * additively; production passes the default `CONCEPT_ROUTING`.
21
+ * Normalize an adapter's declared concepts into uniform descriptors (seam 1). URL policy
22
+ * (`permalink`, `datePrefix`) comes from the YAML site-config, passed here as `urlPolicy` keyed by
23
+ * concept id; each value defaults when the YAML omits it (`/:slug` for Pages, `/<id>/:slug`
24
+ * otherwise; `datePrefix` defaults to `day`). `routing` is injectable so a contract test can prove
25
+ * a new concept attaches additively; production passes the default `CONCEPT_ROUTING`.
25
26
  */
26
- export function normalizeConcepts(content, routing = CONCEPT_ROUTING) {
27
+ export function normalizeConcepts(content, urlPolicy = {}, routing = CONCEPT_ROUTING) {
27
28
  const descriptors = [];
28
29
  for (const [id, config] of Object.entries(content)) {
29
30
  if (!config)
30
31
  continue;
32
+ const policy = urlPolicy[id] ?? {};
31
33
  descriptors.push({
32
34
  id,
33
35
  label: config.label ?? defaultLabel(id),
34
36
  dir: config.dir,
35
37
  routing: routing[id] ?? DEFAULT_ROUTING,
36
- permalink: config.permalink ?? defaultPermalink(id),
38
+ permalink: policy.permalink ?? defaultPermalink(id),
39
+ datePrefix: policy.datePrefix ?? 'day',
37
40
  fields: config.fields,
38
41
  validate: config.validate,
39
42
  });
@@ -14,4 +14,18 @@ export declare function filenameFromId(id: string): string;
14
14
  * single hyphen; leading and trailing hyphens are trimmed.
15
15
  */
16
16
  export declare function slugify(title: string): string;
17
+ /** Filename date-prefix granularity for a dated concept: the leading `YYYY[-MM[-DD]]-` on the stem. */
18
+ export type DatePrefix = 'year' | 'month' | 'day';
19
+ /**
20
+ * The URL slug for an id. A dated concept passes its `datePrefix` and the leading date prefix is
21
+ * stripped when present; a non-dated concept passes `null` and the id is returned verbatim. Only
22
+ * the leading prefix is removed, so a year-like tail (a post titled "2024 Recap") stays in the slug.
23
+ */
24
+ export declare function slugFromId(id: string, datePrefix: DatePrefix | null): string;
25
+ /**
26
+ * Compose a dated entry's id from a `YYYY-MM-DD` date, a date-free slug, and the concept's
27
+ * granularity: the date truncated to the granularity, a hyphen, then the slug. Throws on a
28
+ * malformed date so a bad create fails before touching git.
29
+ */
30
+ export declare function composeDatedId(date: string, slug: string, datePrefix: DatePrefix): string;
17
31
  //# sourceMappingURL=ids.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ids.d.ts","sourceRoot":"","sources":["../../src/lib/content/ids.ts"],"names":[],"mappings":"AAOA,qGAAqG;AACrG,wBAAgB,SAAS,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAE7C;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAEvD;AAED,yDAAyD;AACzD,wBAAgB,cAAc,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAEjD;AAED;;;;GAIG;AACH,wBAAgB,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAM7C"}
1
+ {"version":3,"file":"ids.d.ts","sourceRoot":"","sources":["../../src/lib/content/ids.ts"],"names":[],"mappings":"AAOA,qGAAqG;AACrG,wBAAgB,SAAS,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAE7C;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAEvD;AAED,yDAAyD;AACzD,wBAAgB,cAAc,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAEjD;AAED;;;;GAIG;AACH,wBAAgB,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAM7C;AAED,uGAAuG;AACvG,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,OAAO,GAAG,KAAK,CAAC;AASlD;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,EAAE,EAAE,MAAM,EAAE,UAAU,EAAE,UAAU,GAAG,IAAI,GAAG,MAAM,CAG5E;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,UAAU,GAAG,MAAM,CAiBzF"}
@@ -31,3 +31,43 @@ export function slugify(title) {
31
31
  .replace(/[^a-z0-9]+/g, '-')
32
32
  .replace(/^-+|-+$/g, '');
33
33
  }
34
+ /** The leading date-prefix shape for each granularity. */
35
+ const DATE_PREFIX_RE = {
36
+ year: /^\d{4}-/,
37
+ month: /^\d{4}-\d{2}-/,
38
+ day: /^\d{4}-\d{2}-\d{2}-/,
39
+ };
40
+ /**
41
+ * The URL slug for an id. A dated concept passes its `datePrefix` and the leading date prefix is
42
+ * stripped when present; a non-dated concept passes `null` and the id is returned verbatim. Only
43
+ * the leading prefix is removed, so a year-like tail (a post titled "2024 Recap") stays in the slug.
44
+ */
45
+ export function slugFromId(id, datePrefix) {
46
+ if (!datePrefix)
47
+ return id;
48
+ return id.replace(DATE_PREFIX_RE[datePrefix], '');
49
+ }
50
+ /**
51
+ * Compose a dated entry's id from a `YYYY-MM-DD` date, a date-free slug, and the concept's
52
+ * granularity: the date truncated to the granularity, a hyphen, then the slug. Throws on a
53
+ * malformed date so a bad create fails before touching git.
54
+ */
55
+ export function composeDatedId(date, slug, datePrefix) {
56
+ const m = date.match(/^(\d{4})-(\d{2})-(\d{2})$/);
57
+ if (!m)
58
+ throw new Error(`composeDatedId: malformed date "${date}"`);
59
+ const [, year, month, day] = m;
60
+ let prefix;
61
+ switch (datePrefix) {
62
+ case 'year':
63
+ prefix = year;
64
+ break;
65
+ case 'month':
66
+ prefix = `${year}-${month}`;
67
+ break;
68
+ case 'day':
69
+ prefix = `${year}-${month}-${day}`;
70
+ break;
71
+ }
72
+ return `${prefix}-${slug}`;
73
+ }
@@ -6,6 +6,7 @@ import type { ConceptDescriptor } from './types.js';
6
6
  */
7
7
  export declare function permalink(descriptor: ConceptDescriptor, entry: {
8
8
  id: string;
9
+ slug: string;
9
10
  date?: string;
10
11
  }): string;
11
12
  //# sourceMappingURL=permalink.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"permalink.d.ts","sourceRoot":"","sources":["../../src/lib/content/permalink.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAWpD;;;;GAIG;AACH,wBAAgB,SAAS,CACvB,UAAU,EAAE,iBAAiB,EAC7B,KAAK,EAAE;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,GACnC,MAAM,CAgBR"}
1
+ {"version":3,"file":"permalink.d.ts","sourceRoot":"","sources":["../../src/lib/content/permalink.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAWpD;;;;GAIG;AACH,wBAAgB,SAAS,CACvB,UAAU,EAAE,iBAAiB,EAC7B,KAAK,EAAE;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,GACjD,MAAM,CAgBR"}
@@ -13,7 +13,7 @@ function dateParts(date) {
13
13
  export function permalink(descriptor, entry) {
14
14
  return descriptor.permalink.replace(/:(\w+)/g, (_match, token) => {
15
15
  if (token === 'slug')
16
- return entry.id;
16
+ return entry.slug;
17
17
  if (token === 'year' || token === 'month' || token === 'day') {
18
18
  const parts = dateParts(entry.date);
19
19
  if (!parts) {
@@ -1,4 +1,5 @@
1
1
  import type { ComponentRegistry } from '../render/registry.js';
2
+ import type { DatePrefix } from './ids.js';
2
3
  /** Common to every frontmatter field: the frontmatter key, the form label, and whether it is required. */
3
4
  interface FieldBase {
4
5
  /** Frontmatter key and form input name. */
@@ -68,13 +69,16 @@ export interface ConceptConfig {
68
69
  fields: FrontmatterField[];
69
70
  /** Validate submitted frontmatter before any commit. */
70
71
  validate(frontmatter: Record<string, unknown>, body: string): ValidationResult;
71
- /**
72
- * Public URL pattern for this concept, a `/`-prefixed string of literal segments and the
73
- * tokens `:slug`, `:year`, `:month`, `:day`. `normalizeConcepts` fills a per-concept
74
- * default when omitted (`/:slug` for Pages, `/<conceptId>/:slug` otherwise). The pattern
75
- * must agree with the site's filesystem route directory.
76
- */
72
+ }
73
+ /**
74
+ * A concept's URL policy, set per concept in the YAML site-config (not the adapter). `permalink` is
75
+ * a `/`-prefixed pattern of literal segments and the tokens `:slug`, `:year`, `:month`, `:day`.
76
+ * `datePrefix` is the filename date-prefix granularity for a dated concept. Both default in
77
+ * `normalizeConcepts` when omitted.
78
+ */
79
+ export interface ConceptUrlPolicy {
77
80
  permalink?: string;
81
+ datePrefix?: DatePrefix;
78
82
  }
79
83
  /** The GitHub App backend a site reads from and commits to (spec §8). Plain data the GitHub engine (Plan 03) consumes. */
80
84
  export interface BackendConfig {
@@ -154,6 +158,8 @@ export interface ConceptDescriptor {
154
158
  routing: RoutingRule;
155
159
  /** The resolved permalink pattern, defaulted by `normalizeConcepts`. */
156
160
  permalink: string;
161
+ /** Filename date-prefix granularity for a dated concept; resolved by `normalizeConcepts`. */
162
+ datePrefix: DatePrefix;
157
163
  fields: FrontmatterField[];
158
164
  validate(frontmatter: Record<string, unknown>, body: string): ValidationResult;
159
165
  }
@@ -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":"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"}
@@ -7,6 +7,7 @@ export interface RawFile {
7
7
  /** The cheap, plain-data view of one entry, for lists, feeds, and the sitemap. */
8
8
  export interface ContentSummary {
9
9
  id: string;
10
+ slug: string;
10
11
  permalink: string;
11
12
  title: string;
12
13
  date?: string;
@@ -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,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,8EAA8E;AAC9E,MAAM,WAAW,YAAa,SAAQ,cAAc;IAClD,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACrC,IAAI,EAAE,MAAM,CAAC;CACd;AAED,qCAAqC;AACrC,MAAM,WAAW,YAAY;IAC3B,GAAG,CAAC,IAAI,CAAC,EAAE;QAAE,aAAa,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,cAAc,EAAE,CAAC;IAC1D,IAAI,CAAC,EAAE,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS,CAAC;IAC3C,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;CAC1E;AAED,4EAA4E;AAC5E,wBAAgB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,OAAO,EAAE,CAElE;AAqBD,4EAA4E;AAC5E,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE,UAAU,EAAE,iBAAiB,GAAG,YAAY,CAyDhG"}
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,8EAA8E;AAC9E,MAAM,WAAW,YAAa,SAAQ,cAAc;IAClD,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACrC,IAAI,EAAE,MAAM,CAAC;CACd;AAED,qCAAqC;AACrC,MAAM,WAAW,YAAY;IAC3B,GAAG,CAAC,IAAI,CAAC,EAAE;QAAE,aAAa,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,cAAc,EAAE,CAAC;IAC1D,IAAI,CAAC,EAAE,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS,CAAC;IAC3C,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;CAC1E;AAED,4EAA4E;AAC5E,wBAAgB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,OAAO,EAAE,CAElE;AAqBD,4EAA4E;AAC5E,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE,UAAU,EAAE,iBAAiB,GAAG,YAAY,CA2DhG"}
@@ -3,7 +3,7 @@
3
3
  // returns cheap plain-data summaries plus an on-demand detail lookup. It is concept-generic:
4
4
  // every operation reads the descriptor and its routing rule, never a hardcoded concept id.
5
5
  import { parseMarkdown } from '../content/frontmatter.js';
6
- import { idFromFilename } from '../content/ids.js';
6
+ import { idFromFilename, slugFromId } from '../content/ids.js';
7
7
  import { permalink } from '../content/permalink.js';
8
8
  import { deriveExcerpt, wordCount } from './excerpt.js';
9
9
  /** Map a Vite eager `?raw` glob record (`{ path: raw }`) to `RawFile[]`. */
@@ -31,11 +31,13 @@ function asTags(value) {
31
31
  export function createContentIndex(files, descriptor) {
32
32
  const entries = files.map((file) => {
33
33
  const id = idFromFilename(basename(file.path));
34
+ const slug = slugFromId(id, descriptor.routing.dated ? descriptor.datePrefix : null);
34
35
  const { frontmatter, body } = parseMarkdown(file.raw);
35
36
  const date = asDate(frontmatter.date);
36
37
  return {
37
38
  id,
38
- permalink: permalink(descriptor, { id, date }),
39
+ slug,
40
+ permalink: permalink(descriptor, { id, slug, date }),
39
41
  title: asString(frontmatter.title) ?? id,
40
42
  date,
41
43
  updated: asDate(frontmatter.updated),
@@ -0,0 +1,28 @@
1
+ import type { ConceptDescriptor } from '../content/types.js';
2
+ import type { ContentEntry, ContentIndex, ContentSummary } from './content-index.js';
3
+ /** One concept's descriptor paired with its built index. */
4
+ export interface ConceptIndex {
5
+ descriptor: ConceptDescriptor;
6
+ index: ContentIndex;
7
+ }
8
+ /** The cross-concept query surface a catch-all route and the sitemap read. */
9
+ export interface SiteIndex {
10
+ /** Resolve a request path (with or without a trailing slash) to its entry, or undefined. */
11
+ byPermalink(path: string): ContentEntry | undefined;
12
+ /** Newer/older neighbors within the entry's own concept, for prev/next links. */
13
+ adjacent(entry: ContentSummary): {
14
+ newer?: ContentSummary;
15
+ older?: ContentSummary;
16
+ };
17
+ /** Every entry's path across concepts, leading slash stripped, for SvelteKit `[...path]` prerender. */
18
+ entries(): {
19
+ path: string;
20
+ }[];
21
+ /** One concept's index, for its archive, tag, and feed loaders. */
22
+ concept(id: string): ContentIndex | undefined;
23
+ /** Every non-draft summary across concepts, for the site-wide sitemap. */
24
+ all(): ContentSummary[];
25
+ }
26
+ /** Union per-concept indexes into a site-level resolver; throw on a duplicate permalink. */
27
+ export declare function createSiteIndex(concepts: ConceptIndex[]): SiteIndex;
28
+ //# sourceMappingURL=site-index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"site-index.d.ts","sourceRoot":"","sources":["../../src/lib/delivery/site-index.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAC7D,OAAO,KAAK,EAAE,YAAY,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAErF,4DAA4D;AAC5D,MAAM,WAAW,YAAY;IAC3B,UAAU,EAAE,iBAAiB,CAAC;IAC9B,KAAK,EAAE,YAAY,CAAC;CACrB;AAED,8EAA8E;AAC9E,MAAM,WAAW,SAAS;IACxB,4FAA4F;IAC5F,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS,CAAC;IACpD,iFAAiF;IACjF,QAAQ,CAAC,KAAK,EAAE,cAAc,GAAG;QAAE,KAAK,CAAC,EAAE,cAAc,CAAC;QAAC,KAAK,CAAC,EAAE,cAAc,CAAA;KAAE,CAAC;IACpF,uGAAuG;IACvG,OAAO,IAAI;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAC9B,mEAAmE;IACnE,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS,CAAC;IAC9C,0EAA0E;IAC1E,GAAG,IAAI,cAAc,EAAE,CAAC;CACzB;AAOD,4FAA4F;AAC5F,wBAAgB,eAAe,CAAC,QAAQ,EAAE,YAAY,EAAE,GAAG,SAAS,CAkCnE"}
@@ -0,0 +1,38 @@
1
+ /** Strip a trailing slash from a path, keeping the root "/" intact. */
2
+ function normalizePath(path) {
3
+ return path.length > 1 ? path.replace(/\/+$/, '') : path;
4
+ }
5
+ /** Union per-concept indexes into a site-level resolver; throw on a duplicate permalink. */
6
+ export function createSiteIndex(concepts) {
7
+ const byPath = new Map();
8
+ const byId = new Map();
9
+ for (const { descriptor, index } of concepts) {
10
+ byId.set(descriptor.id, index);
11
+ for (const summary of index.all()) {
12
+ const existing = byPath.get(summary.permalink);
13
+ if (existing) {
14
+ throw new Error(`site index: "${existing.id}" and "${summary.id}" both resolve to "${summary.permalink}"`);
15
+ }
16
+ byPath.set(summary.permalink, { index, id: summary.id });
17
+ }
18
+ }
19
+ return {
20
+ byPermalink(path) {
21
+ const hit = byPath.get(normalizePath(path));
22
+ return hit ? hit.index.byId(hit.id) : undefined;
23
+ },
24
+ adjacent(entry) {
25
+ const hit = byPath.get(entry.permalink);
26
+ return hit ? hit.index.adjacent(entry.id) : {};
27
+ },
28
+ entries() {
29
+ return [...byPath.keys()].map((p) => ({ path: p.replace(/^\//, '') }));
30
+ },
31
+ concept(id) {
32
+ return byId.get(id);
33
+ },
34
+ all() {
35
+ return concepts.flatMap(({ index }) => index.all());
36
+ },
37
+ };
38
+ }
package/dist/index.d.ts CHANGED
@@ -2,12 +2,13 @@ export { requireOrigin } from './env.js';
2
2
  export type { Role, Editor, AuthEnv } from './auth/types.js';
3
3
  export type { AuthBranding, MagicLinkMessage, SendMagicLink } from './email.js';
4
4
  export { buildMagicLinkMessage, cloudflareSend } from './email.js';
5
- export type { CairnAdapter, ConceptConfig, FrontmatterField, TextField, TextareaField, DateField, BooleanField, TagsField, FreeTagsField, ValidationResult, BackendConfig, SenderConfig, NavMenuConfig, AssetConfig, RoutingRule, ConceptDescriptor, CairnExtension, CairnRuntime, AdminPanel, FieldTypeDef, } from './content/types.js';
5
+ export type { CairnAdapter, ConceptConfig, FrontmatterField, TextField, TextareaField, DateField, BooleanField, TagsField, FreeTagsField, ValidationResult, BackendConfig, SenderConfig, NavMenuConfig, AssetConfig, RoutingRule, ConceptDescriptor, ConceptUrlPolicy, CairnExtension, CairnRuntime, AdminPanel, FieldTypeDef, } from './content/types.js';
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
9
  export { validateFields } from './content/validate.js';
10
- export { isValidId, idFromFilename, filenameFromId, slugify } from './content/ids.js';
10
+ export { isValidId, idFromFilename, filenameFromId, slugify, slugFromId, composeDatedId, } from './content/ids.js';
11
+ export type { DatePrefix } from './content/ids.js';
11
12
  export { defineRegistry } from './render/registry.js';
12
13
  export type { ComponentDef, ComponentRegistry } from './render/registry.js';
13
14
  export { glyph } from './render/glyph.js';
@@ -23,11 +24,13 @@ export { appJwt, installationToken, signingSelfTest } from './github/signing.js'
23
24
  export { treeUrl, markdownFilesIn, listMarkdown, contentsUrl, readRaw, fileSha, commitFile, } from './github/repo.js';
24
25
  export { appCredentials } from './github/credentials.js';
25
26
  export type { GithubKeyEnv } from './github/credentials.js';
26
- export { parseSiteConfig, extractMenu, setMenu, validateNavTree, MAX_NAV_NODES, NavValidationError, SiteConfigError, } from './nav/site-config.js';
27
+ export { parseSiteConfig, urlPolicyFrom, extractMenu, setMenu, validateNavTree, MAX_NAV_NODES, NavValidationError, SiteConfigError, } from './nav/site-config.js';
27
28
  export type { NavNode, SiteConfig } from './nav/site-config.js';
28
29
  export { permalink } from './content/permalink.js';
29
30
  export { createContentIndex, fromGlob } from './delivery/content-index.js';
30
31
  export type { RawFile, ContentSummary, ContentEntry, ContentIndex, } from './delivery/content-index.js';
32
+ export { createSiteIndex } from './delivery/site-index.js';
33
+ export type { SiteIndex, ConceptIndex } from './delivery/site-index.js';
31
34
  export { deriveExcerpt, wordCount } from './delivery/excerpt.js';
32
35
  export { buildRssFeed, buildJsonFeed } from './delivery/feeds.js';
33
36
  export type { FeedChannel, FeedItem } from './delivery/feeds.js';
@@ -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
@@ -6,7 +6,7 @@ export { CONCEPT_ROUTING, normalizeConcepts, findConcept } from './content/conce
6
6
  export { composeRuntime } from './content/compose.js';
7
7
  export { frontmatterFromForm, dateInputValue, serializeMarkdown, parseMarkdown, } from './content/frontmatter.js';
8
8
  export { validateFields } from './content/validate.js';
9
- export { isValidId, idFromFilename, filenameFromId, slugify } from './content/ids.js';
9
+ export { isValidId, idFromFilename, filenameFromId, slugify, slugFromId, composeDatedId, } from './content/ids.js';
10
10
  // Render engine (Plan 04): generic directive pipeline; sites own the component registry.
11
11
  export { defineRegistry } from './render/registry.js';
12
12
  export { glyph } from './render/glyph.js';
@@ -18,12 +18,13 @@ export { appJwt, installationToken, signingSelfTest } from './github/signing.js'
18
18
  export { treeUrl, markdownFilesIn, listMarkdown, contentsUrl, readRaw, fileSha, commitFile, } from './github/repo.js';
19
19
  export { appCredentials } from './github/credentials.js';
20
20
  // Nav tree and site-config helpers (Plan 06).
21
- export { parseSiteConfig, extractMenu, setMenu, validateNavTree, MAX_NAV_NODES, NavValidationError, SiteConfigError, } from './nav/site-config.js';
21
+ export { parseSiteConfig, urlPolicyFrom, extractMenu, setMenu, validateNavTree, MAX_NAV_NODES, NavValidationError, SiteConfigError, } from './nav/site-config.js';
22
22
  // Public content delivery (public-delivery design): the query index, syndication, and
23
23
  // discovery surface that sites read. Pure builders plus the one permalink resolver; the
24
24
  // SvelteKit loaders live under the /sveltekit subpath.
25
25
  export { permalink } from './content/permalink.js';
26
26
  export { createContentIndex, fromGlob } from './delivery/content-index.js';
27
+ export { createSiteIndex } from './delivery/site-index.js';
27
28
  export { deriveExcerpt, wordCount } from './delivery/excerpt.js';
28
29
  export { buildRssFeed, buildJsonFeed } from './delivery/feeds.js';
29
30
  export { buildSitemap } from './delivery/sitemap.js';
@@ -1,3 +1,4 @@
1
+ import type { ConceptUrlPolicy } from '../content/types.js';
1
2
  /** One navigation node. An omitted or empty `url` is a label-only grouping header; no `children` is a leaf. */
2
3
  export interface NavNode {
3
4
  label: string;
@@ -31,6 +32,8 @@ export interface SiteConfig {
31
32
  locale?: string;
32
33
  /** Named navigation menus, each a NavNode[] (normalized by extractMenu). */
33
34
  menus?: Record<string, unknown>;
35
+ /** Per-concept URL policy: the permalink pattern and date-prefix granularity, keyed by concept id. */
36
+ content?: Record<string, ConceptUrlPolicy>;
34
37
  [key: string]: unknown;
35
38
  }
36
39
  export declare class SiteConfigError extends Error {
@@ -40,6 +43,8 @@ export declare class SiteConfigError extends Error {
40
43
  export declare function parseSiteConfig(raw: string): SiteConfig;
41
44
  /** Extract one named menu from a parsed config and validate it. Returns [] when the menu is absent. */
42
45
  export declare function extractMenu(config: SiteConfig, name: string, maxDepth: number): NavNode[];
46
+ /** The per-concept URL policy from a parsed config, or an empty policy when the `content` key is absent. */
47
+ export declare function urlPolicyFrom(config: SiteConfig): Record<string, ConceptUrlPolicy>;
43
48
  /**
44
49
  * Replace one named menu in the YAML site-config text and reserialize, preserving every other
45
50
  * top-level key (siteName, other menus, settings). Parses into a Document so the rest of the file
@@ -1 +1 @@
1
- {"version":3,"file":"site-config.d.ts","sourceRoot":"","sources":["../../src/lib/nav/site-config.ts"],"names":[],"mappings":"AAMA,+GAA+G;AAC/G,MAAM,WAAW,OAAO;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,OAAO,EAAE,CAAC;CACtB;AAED,+EAA+E;AAC/E,eAAO,MAAM,aAAa,MAAM,CAAC;AAEjC,iDAAiD;AACjD,eAAO,MAAM,gBAAgB,MAAM,CAAC;AAEpC,+CAA+C;AAC/C,eAAO,MAAM,cAAc,OAAO,CAAC;AAKnC,qBAAa,kBAAmB,SAAQ,KAAK;gBAC/B,OAAO,EAAE,MAAM;CAI5B;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,EAAE,CA6B3E;AAED;;;GAGG;AACH,MAAM,WAAW,UAAU;IACzB,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,4EAA4E;IAC5E,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,qBAAa,eAAgB,SAAQ,KAAK;gBAC5B,OAAO,EAAE,MAAM;CAI5B;AAED,uGAAuG;AACvG,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,UAAU,CAUvD;AAED,uGAAuG;AACvG,wBAAgB,WAAW,CAAC,MAAM,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,EAAE,CAIzF;AAED;;;;;GAKG;AACH,wBAAgB,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,MAAM,CAO1E"}
1
+ {"version":3,"file":"site-config.d.ts","sourceRoot":"","sources":["../../src/lib/nav/site-config.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAE5D,+GAA+G;AAC/G,MAAM,WAAW,OAAO;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,OAAO,EAAE,CAAC;CACtB;AAED,+EAA+E;AAC/E,eAAO,MAAM,aAAa,MAAM,CAAC;AAEjC,iDAAiD;AACjD,eAAO,MAAM,gBAAgB,MAAM,CAAC;AAEpC,+CAA+C;AAC/C,eAAO,MAAM,cAAc,OAAO,CAAC;AAKnC,qBAAa,kBAAmB,SAAQ,KAAK;gBAC/B,OAAO,EAAE,MAAM;CAI5B;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,EAAE,CA6B3E;AAED;;;GAGG;AACH,MAAM,WAAW,UAAU;IACzB,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,4EAA4E;IAC5E,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC,sGAAsG;IACtG,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC;IAC3C,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,qBAAa,eAAgB,SAAQ,KAAK;gBAC5B,OAAO,EAAE,MAAM;CAI5B;AAED,uGAAuG;AACvG,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,UAAU,CAUvD;AAED,uGAAuG;AACvG,wBAAgB,WAAW,CAAC,MAAM,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,EAAE,CAIzF;AAED,4GAA4G;AAC5G,wBAAgB,aAAa,CAAC,MAAM,EAAE,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAElF;AAED;;;;;GAKG;AACH,wBAAgB,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,MAAM,CAO1E"}
@@ -84,6 +84,10 @@ export function extractMenu(config, name, maxDepth) {
84
84
  return [];
85
85
  return validateNavTree(menu, maxDepth);
86
86
  }
87
+ /** The per-concept URL policy from a parsed config, or an empty policy when the `content` key is absent. */
88
+ export function urlPolicyFrom(config) {
89
+ return config.content ?? {};
90
+ }
87
91
  /**
88
92
  * Replace one named menu in the YAML site-config text and reserialize, preserving every other
89
93
  * top-level key (siteName, other menus, settings). Parses into a Document so the rest of the file
@@ -1 +1 @@
1
- {"version":3,"file":"content-routes.d.ts","sourceRoot":"","sources":["../../src/lib/sveltekit/content-routes.ts"],"names":[],"mappings":"AAQA,OAAO,EAAkB,KAAK,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAI7E,OAAO,KAAK,EAAE,YAAY,EAAqB,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAC7F,OAAO,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAErD,wGAAwG;AACxG,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;CACf;AAED,gGAAgG;AAChG,MAAM,WAAW,UAAU;IACzB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE;QAAE,WAAW,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,IAAI,CAAA;KAAE,CAAC;IAC1C,QAAQ,EAAE,UAAU,EAAE,CAAC;IACvB,QAAQ,EAAE,MAAM,CAAC;IACjB,gBAAgB,EAAE,OAAO,CAAC;IAC1B,yGAAyG;IACzG,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;CACzB;AAED,wCAAwC;AACxC,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,KAAK,EAAE,OAAO,CAAC;CAChB;AAED,oCAAoC;AACpC,MAAM,WAAW,QAAQ;IACvB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,2FAA2F;IAC3F,KAAK,EAAE,OAAO,CAAC;IACf,OAAO,EAAE,YAAY,EAAE,CAAC;IACxB,gFAAgF;IAChF,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,qDAAqD;IACrD,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;AAED,6FAA6F;AAC7F,MAAM,WAAW,QAAQ;IACvB,SAAS,EAAE,MAAM,CAAC;IAClB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,gBAAgB,EAAE,CAAC;IAC3B,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACrC,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,OAAO,CAAC;IACf,KAAK,EAAE,OAAO,CAAC;IACf,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB;AAED,gGAAgG;AAChG,MAAM,WAAW,YAAY;IAC3B,GAAG,EAAE,GAAG,CAAC;IACT,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/B,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE;QAAE,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,CAAC;IACnC,QAAQ,CAAC,EAAE;QAAE,GAAG,CAAC,EAAE,YAAY,CAAA;KAAE,CAAC;CACnC;AAED,sFAAsF;AACtF,MAAM,WAAW,iBAAiB;IAChC,6FAA6F;IAC7F,SAAS,CAAC,EAAE,CAAC,GAAG,EAAE,YAAY,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;CACpD;AAgBD,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,YAAY,EAAE,IAAI,GAAE,iBAAsB;wBAK1D,YAAY,KAAG,UAAU;yBAa1B,KAAK;sBAqBA,YAAY,KAAG,OAAO,CAAC,QAAQ,CAAC;0BAqB5B,YAAY,KAAG,OAAO,CAAC,KAAK,CAAC;sBA+BjC,YAAY,KAAG,OAAO,CAAC,QAAQ,CAAC;wBAgC9B,YAAY,KAAG,OAAO,CAAC,KAAK,CAAC;qBA5I5C,YAAY,KAAK,OAAO,CAAC,MAAM,CAAC;EAqLnD"}
1
+ {"version":3,"file":"content-routes.d.ts","sourceRoot":"","sources":["../../src/lib/sveltekit/content-routes.ts"],"names":[],"mappings":"AAQA,OAAO,EAAkB,KAAK,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAI7E,OAAO,KAAK,EAAE,YAAY,EAAqB,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAC7F,OAAO,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAErD,wGAAwG;AACxG,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;CACf;AAED,gGAAgG;AAChG,MAAM,WAAW,UAAU;IACzB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE;QAAE,WAAW,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,IAAI,CAAA;KAAE,CAAC;IAC1C,QAAQ,EAAE,UAAU,EAAE,CAAC;IACvB,QAAQ,EAAE,MAAM,CAAC;IACjB,gBAAgB,EAAE,OAAO,CAAC;IAC1B,yGAAyG;IACzG,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;CACzB;AAED,wCAAwC;AACxC,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,KAAK,EAAE,OAAO,CAAC;CAChB;AAED,oCAAoC;AACpC,MAAM,WAAW,QAAQ;IACvB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,2FAA2F;IAC3F,KAAK,EAAE,OAAO,CAAC;IACf,OAAO,EAAE,YAAY,EAAE,CAAC;IACxB,gFAAgF;IAChF,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,qDAAqD;IACrD,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;AAED,6FAA6F;AAC7F,MAAM,WAAW,QAAQ;IACvB,SAAS,EAAE,MAAM,CAAC;IAClB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,gBAAgB,EAAE,CAAC;IAC3B,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACrC,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,OAAO,CAAC;IACf,KAAK,EAAE,OAAO,CAAC;IACf,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB;AAED,gGAAgG;AAChG,MAAM,WAAW,YAAY;IAC3B,GAAG,EAAE,GAAG,CAAC;IACT,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/B,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE;QAAE,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,CAAC;IACnC,QAAQ,CAAC,EAAE;QAAE,GAAG,CAAC,EAAE,YAAY,CAAA;KAAE,CAAC;CACnC;AAED,sFAAsF;AACtF,MAAM,WAAW,iBAAiB;IAChC,6FAA6F;IAC7F,SAAS,CAAC,EAAE,CAAC,GAAG,EAAE,YAAY,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;CACpD;AAgBD,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,YAAY,EAAE,IAAI,GAAE,iBAAsB;wBAK1D,YAAY,KAAG,UAAU;yBAa1B,KAAK;sBAqBA,YAAY,KAAG,OAAO,CAAC,QAAQ,CAAC;0BAqB5B,YAAY,KAAG,OAAO,CAAC,KAAK,CAAC;sBAyCjC,YAAY,KAAG,OAAO,CAAC,QAAQ,CAAC;wBAgC9B,YAAY,KAAG,OAAO,CAAC,KAAK,CAAC;qBAtJ5C,YAAY,KAAK,OAAO,CAAC,MAAM,CAAC;EA+LnD"}
@@ -5,7 +5,7 @@
5
5
  import { redirect, error } from '@sveltejs/kit';
6
6
  import { findConcept } from '../content/concepts.js';
7
7
  import { frontmatterFromForm, parseMarkdown, dateInputValue, serializeMarkdown } from '../content/frontmatter.js';
8
- import { isValidId, slugify, filenameFromId } from '../content/ids.js';
8
+ import { isValidId, slugify, filenameFromId, composeDatedId } from '../content/ids.js';
9
9
  import { appCredentials } from '../github/credentials.js';
10
10
  import { listMarkdown, readRaw, commitFile } from '../github/repo.js';
11
11
  import { installationToken } from '../github/signing.js';
@@ -82,22 +82,32 @@ export function createContentRoutes(runtime, deps = {}) {
82
82
  return { ...base, entries: [], error: 'Could not load this content type from GitHub.' };
83
83
  }
84
84
  }
85
- /** Create a new entry: validate the slug, refuse to clobber, and redirect to the editor. */
85
+ /** Create a new entry: validate the slug, compose a dated id when the concept is dated, refuse to clobber. */
86
86
  async function createAction(event) {
87
87
  sessionOf(event);
88
88
  const concept = conceptOf(runtime, event.params);
89
89
  const form = await event.request.formData();
90
- const raw = String(form.get('slug') ?? '').trim() || slugify(String(form.get('title') ?? ''));
90
+ const slug = String(form.get('slug') ?? '').trim() || slugify(String(form.get('title') ?? ''));
91
+ const date = String(form.get('date') ?? '').trim();
91
92
  const bounce = (msg) => {
92
93
  throw redirect(303, `/admin/${concept.id}?error=${encodeURIComponent(msg)}`);
93
94
  };
94
- if (!isValidId(raw))
95
- bounce('Enter a valid slug: lowercase letters, numbers, and hyphens.');
95
+ if (!isValidId(slug))
96
+ return bounce('Enter a valid slug: lowercase letters, numbers, and hyphens.');
97
+ let id = slug;
98
+ if (concept.routing.dated) {
99
+ if (!/^\d{4}-\d{2}-\d{2}$/.test(date))
100
+ return bounce('Pick a date for this entry.');
101
+ if (/^\d{4}-/.test(slug)) {
102
+ return bounce('Leave the date out of the slug; set it in the date field.');
103
+ }
104
+ id = composeDatedId(date, slug, concept.datePrefix);
105
+ }
96
106
  const token = await mintToken(event.platform?.env ?? {});
97
- const existing = await readRaw(runtime.backend, `${concept.dir}/${filenameFromId(raw)}`, token);
107
+ const existing = await readRaw(runtime.backend, `${concept.dir}/${filenameFromId(id)}`, token);
98
108
  if (existing !== null)
99
- bounce('An entry with that slug already exists.');
100
- throw redirect(303, `/admin/${concept.id}/${raw}?new=1`);
109
+ return bounce('An entry with that slug already exists.');
110
+ throw redirect(303, `/admin/${concept.id}/${id}?new=1`);
101
111
  }
102
112
  /** Coerce parsed frontmatter to the form-ready values the editor inputs expect. */
103
113
  function formValues(fields, frontmatter) {