@glw907/cairn-cms 0.5.1 → 0.6.0-rc.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/auth/crypto.d.ts +13 -0
- package/dist/auth/crypto.d.ts.map +1 -0
- package/dist/auth/crypto.js +31 -0
- package/dist/auth/store.d.ts +41 -0
- package/dist/auth/store.d.ts.map +1 -0
- package/dist/auth/store.js +115 -0
- package/dist/auth/types.d.ts +25 -0
- package/dist/auth/types.d.ts.map +1 -0
- package/dist/auth/types.js +1 -0
- package/dist/components/AdminLayout.svelte +58 -164
- package/dist/components/AdminLayout.svelte.d.ts +14 -18
- package/dist/components/AdminLayout.svelte.d.ts.map +1 -1
- package/dist/components/ComponentPalette.svelte +36 -20
- package/dist/components/ComponentPalette.svelte.d.ts +11 -4
- package/dist/components/ComponentPalette.svelte.d.ts.map +1 -1
- package/dist/components/ConceptList.svelte +81 -0
- package/dist/components/ConceptList.svelte.d.ts +13 -0
- package/dist/components/ConceptList.svelte.d.ts.map +1 -0
- package/dist/components/ConfirmPage.svelte +23 -20
- package/dist/components/ConfirmPage.svelte.d.ts +6 -0
- package/dist/components/ConfirmPage.svelte.d.ts.map +1 -1
- package/dist/components/EditPage.svelte +155 -136
- package/dist/components/EditPage.svelte.d.ts +16 -8
- package/dist/components/EditPage.svelte.d.ts.map +1 -1
- package/dist/components/LoginPage.svelte +42 -52
- package/dist/components/LoginPage.svelte.d.ts +12 -0
- package/dist/components/LoginPage.svelte.d.ts.map +1 -1
- package/dist/components/ManageEditors.svelte +81 -0
- package/dist/components/ManageEditors.svelte.d.ts +24 -0
- package/dist/components/ManageEditors.svelte.d.ts.map +1 -0
- package/dist/components/MarkdownEditor.svelte +81 -0
- package/dist/components/MarkdownEditor.svelte.d.ts +20 -0
- package/dist/components/MarkdownEditor.svelte.d.ts.map +1 -0
- package/dist/components/NavTree.svelte +73 -63
- package/dist/components/NavTree.svelte.d.ts +13 -4
- package/dist/components/NavTree.svelte.d.ts.map +1 -1
- package/dist/components/cairn-admin.css +42 -0
- package/dist/components/index.d.ts +3 -2
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/index.js +5 -4
- package/dist/content/compose.d.ts +7 -0
- package/dist/content/compose.d.ts.map +1 -0
- package/dist/content/compose.js +32 -0
- package/dist/content/concepts.d.ts +17 -0
- package/dist/content/concepts.d.ts.map +1 -0
- package/dist/content/concepts.js +41 -0
- package/dist/content/frontmatter.d.ts +18 -0
- package/dist/content/frontmatter.d.ts.map +1 -0
- package/dist/content/frontmatter.js +58 -0
- package/dist/content/ids.d.ts +17 -0
- package/dist/content/ids.d.ts.map +1 -0
- package/dist/content/ids.js +33 -0
- package/dist/content/types.d.ts +210 -0
- package/dist/content/types.d.ts.map +1 -0
- package/dist/content/types.js +1 -0
- package/dist/content/validate.d.ts +13 -0
- package/dist/content/validate.d.ts.map +1 -0
- package/dist/content/validate.js +45 -0
- package/dist/email.d.ts +25 -12
- package/dist/email.d.ts.map +1 -1
- package/dist/email.js +24 -24
- package/dist/env.d.ts +24 -0
- package/dist/env.d.ts.map +1 -0
- package/dist/env.js +29 -0
- package/dist/github/credentials.d.ts +12 -0
- package/dist/github/credentials.d.ts.map +1 -0
- package/dist/github/credentials.js +11 -0
- package/dist/github/repo.d.ts +49 -0
- package/dist/github/repo.d.ts.map +1 -0
- package/dist/github/repo.js +123 -0
- package/dist/github/signing.d.ts +17 -0
- package/dist/github/signing.d.ts.map +1 -0
- package/dist/github/signing.js +79 -0
- package/dist/github/types.d.ts +35 -0
- package/dist/github/types.d.ts.map +1 -0
- package/dist/github/types.js +19 -0
- package/dist/index.d.ts +27 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +21 -10
- package/dist/{nav.d.ts → nav/site-config.d.ts} +16 -24
- package/dist/nav/site-config.d.ts.map +1 -0
- package/dist/{nav.js → nav/site-config.js} +27 -13
- package/dist/render/glyph.d.ts +1 -1
- package/dist/render/glyph.d.ts.map +1 -1
- package/dist/render/index.d.ts +5 -5
- package/dist/render/index.d.ts.map +1 -1
- package/dist/render/index.js +6 -6
- package/dist/render/pipeline.d.ts +3 -3
- package/dist/render/pipeline.d.ts.map +1 -1
- package/dist/render/pipeline.js +4 -4
- package/dist/render/registry.d.ts +6 -4
- package/dist/render/registry.d.ts.map +1 -1
- package/dist/render/registry.js +8 -6
- package/dist/render/rehype-dispatch.d.ts +1 -1
- package/dist/render/rehype-dispatch.d.ts.map +1 -1
- package/dist/render/remark-directives.d.ts +1 -1
- package/dist/render/remark-directives.d.ts.map +1 -1
- package/dist/render/sanitize.d.ts +8 -0
- package/dist/render/sanitize.d.ts.map +1 -0
- package/dist/render/sanitize.js +26 -0
- package/dist/sveltekit/auth-routes.d.ts +23 -0
- package/dist/sveltekit/auth-routes.d.ts.map +1 -0
- package/dist/sveltekit/auth-routes.js +85 -0
- package/dist/sveltekit/content-routes.d.ts +80 -0
- package/dist/sveltekit/content-routes.d.ts.map +1 -0
- package/dist/sveltekit/content-routes.js +183 -0
- package/dist/sveltekit/editors-routes.d.ts +24 -0
- package/dist/sveltekit/editors-routes.d.ts.map +1 -0
- package/dist/sveltekit/editors-routes.js +73 -0
- package/dist/sveltekit/guard.d.ts +9 -0
- package/dist/sveltekit/guard.d.ts.map +1 -0
- package/dist/sveltekit/guard.js +43 -0
- package/dist/sveltekit/health.d.ts +19 -0
- package/dist/sveltekit/health.d.ts.map +1 -0
- package/dist/sveltekit/health.js +12 -0
- package/dist/sveltekit/index.d.ts +9 -173
- package/dist/sveltekit/index.d.ts.map +1 -1
- package/dist/sveltekit/index.js +8 -348
- package/dist/sveltekit/nav-routes.d.ts +30 -0
- package/dist/sveltekit/nav-routes.d.ts.map +1 -0
- package/dist/sveltekit/nav-routes.js +103 -0
- package/dist/sveltekit/types.d.ts +32 -0
- package/dist/sveltekit/types.d.ts.map +1 -0
- package/dist/sveltekit/types.js +1 -0
- package/package.json +32 -57
- package/src/lib/auth/crypto.ts +37 -0
- package/src/lib/auth/store.ts +158 -0
- package/src/lib/auth/types.ts +27 -0
- package/src/lib/components/AdminLayout.svelte +58 -164
- package/src/lib/components/ComponentPalette.svelte +36 -20
- package/src/lib/components/ConceptList.svelte +81 -0
- package/src/lib/components/ConfirmPage.svelte +23 -20
- package/src/lib/components/EditPage.svelte +155 -136
- package/src/lib/components/LoginPage.svelte +42 -52
- package/src/lib/components/ManageEditors.svelte +81 -0
- package/src/lib/components/MarkdownEditor.svelte +81 -0
- package/src/lib/components/NavTree.svelte +73 -63
- package/src/lib/components/cairn-admin.css +42 -0
- package/src/lib/components/index.ts +5 -4
- package/src/lib/content/compose.ts +39 -0
- package/src/lib/content/concepts.ts +57 -0
- package/src/lib/content/frontmatter.ts +71 -0
- package/src/lib/content/ids.ts +38 -0
- package/src/lib/content/types.ts +235 -0
- package/src/lib/content/validate.ts +51 -0
- package/src/lib/email.ts +52 -38
- package/src/lib/env.ts +32 -0
- package/src/lib/github/credentials.ts +27 -0
- package/src/lib/github/repo.ts +138 -0
- package/src/lib/github/signing.ts +97 -0
- package/src/lib/github/types.ts +46 -0
- package/src/lib/index.ts +86 -10
- package/src/lib/{nav.ts → nav/site-config.ts} +31 -24
- package/src/lib/render/glyph.ts +6 -6
- package/src/lib/render/index.ts +6 -6
- package/src/lib/render/pipeline.ts +22 -22
- package/src/lib/render/registry.ts +33 -26
- package/src/lib/render/rehype-dispatch.ts +47 -47
- package/src/lib/render/remark-directives.ts +46 -46
- package/src/lib/render/sanitize.ts +27 -0
- package/src/lib/sveltekit/auth-routes.ts +107 -0
- package/src/lib/sveltekit/content-routes.ts +261 -0
- package/src/lib/sveltekit/editors-routes.ts +82 -0
- package/src/lib/sveltekit/guard.ts +47 -0
- package/src/lib/sveltekit/health.ts +24 -0
- package/src/lib/sveltekit/index.ts +19 -512
- package/src/lib/sveltekit/nav-routes.ts +139 -0
- package/src/lib/sveltekit/types.ts +33 -0
- package/dist/adapter.d.ts +0 -93
- package/dist/adapter.d.ts.map +0 -1
- package/dist/adapter.js +0 -30
- package/dist/auth/admins.d.ts +0 -33
- package/dist/auth/admins.d.ts.map +0 -1
- package/dist/auth/admins.js +0 -90
- package/dist/auth/capabilities.d.ts +0 -7
- package/dist/auth/capabilities.d.ts.map +0 -1
- package/dist/auth/capabilities.js +0 -26
- package/dist/auth/config.d.ts +0 -2097
- package/dist/auth/config.d.ts.map +0 -1
- package/dist/auth/config.js +0 -78
- package/dist/auth/guard.d.ts +0 -34
- package/dist/auth/guard.d.ts.map +0 -1
- package/dist/auth/guard.js +0 -47
- package/dist/auth/index.d.ts +0 -5
- package/dist/auth/index.d.ts.map +0 -1
- package/dist/auth/index.js +0 -7
- package/dist/auth/schema.d.ts +0 -750
- package/dist/auth/schema.d.ts.map +0 -1
- package/dist/auth/schema.js +0 -93
- package/dist/carta.d.ts +0 -39
- package/dist/carta.d.ts.map +0 -1
- package/dist/carta.js +0 -30
- package/dist/components/CollectionList.svelte +0 -96
- package/dist/components/CollectionList.svelte.d.ts +0 -8
- package/dist/components/CollectionList.svelte.d.ts.map +0 -1
- package/dist/components/ManageAdmins.svelte +0 -84
- package/dist/components/ManageAdmins.svelte.d.ts +0 -10
- package/dist/components/ManageAdmins.svelte.d.ts.map +0 -1
- package/dist/content.d.ts +0 -3
- package/dist/content.d.ts.map +0 -1
- package/dist/content.js +0 -10
- package/dist/editor.d.ts +0 -25
- package/dist/editor.d.ts.map +0 -1
- package/dist/editor.js +0 -20
- package/dist/frontmatter.d.ts +0 -3
- package/dist/frontmatter.d.ts.map +0 -1
- package/dist/frontmatter.js +0 -16
- package/dist/github.d.ts +0 -72
- package/dist/github.d.ts.map +0 -1
- package/dist/github.js +0 -171
- package/dist/nav.d.ts.map +0 -1
- package/dist/slug.d.ts +0 -7
- package/dist/slug.d.ts.map +0 -1
- package/dist/slug.js +0 -15
- package/dist/utils.d.ts +0 -3
- package/dist/utils.d.ts.map +0 -1
- package/dist/utils.js +0 -11
- package/src/lib/adapter.ts +0 -144
- package/src/lib/auth/admins.ts +0 -106
- package/src/lib/auth/capabilities.ts +0 -35
- package/src/lib/auth/config.ts +0 -108
- package/src/lib/auth/guard.ts +0 -60
- package/src/lib/auth/index.ts +0 -7
- package/src/lib/auth/schema.ts +0 -112
- package/src/lib/carta.ts +0 -59
- package/src/lib/components/CollectionList.svelte +0 -96
- package/src/lib/components/ManageAdmins.svelte +0 -84
- package/src/lib/content.ts +0 -11
- package/src/lib/editor.ts +0 -38
- package/src/lib/frontmatter.ts +0 -17
- package/src/lib/github.ts +0 -220
- package/src/lib/slug.ts +0 -16
- package/src/lib/utils.ts +0 -12
package/dist/components/index.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
1
|
+
// Admin Svelte components (Plan 05). The Warm Stone theme ships as a CSS side effect imported
|
|
2
|
+
// by the components that set `data-theme="cairn-admin"`.
|
|
3
3
|
export { default as AdminLayout } from './AdminLayout.svelte';
|
|
4
|
-
export { default as CollectionList } from './CollectionList.svelte';
|
|
5
4
|
export { default as LoginPage } from './LoginPage.svelte';
|
|
6
5
|
export { default as ConfirmPage } from './ConfirmPage.svelte';
|
|
6
|
+
export { default as ConceptList } from './ConceptList.svelte';
|
|
7
7
|
export { default as EditPage } from './EditPage.svelte';
|
|
8
|
-
export { default as
|
|
8
|
+
export { default as ManageEditors } from './ManageEditors.svelte';
|
|
9
|
+
export { default as MarkdownEditor } from './MarkdownEditor.svelte';
|
|
9
10
|
export { default as ComponentPalette } from './ComponentPalette.svelte';
|
|
10
11
|
export { default as NavTree } from './NavTree.svelte';
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { CairnAdapter, CairnExtension, CairnRuntime } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Fold an adapter and any extensions into the composed runtime (seam 2). Extension concepts
|
|
4
|
+
* merge after the adapter's. The asset slot (seam 4) passes through untouched.
|
|
5
|
+
*/
|
|
6
|
+
export declare function composeRuntime(adapter: CairnAdapter, extensions?: CairnExtension[]): CairnRuntime;
|
|
7
|
+
//# sourceMappingURL=compose.d.ts.map
|
|
@@ -0,0 +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"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { normalizeConcepts } from './concepts.js';
|
|
2
|
+
/**
|
|
3
|
+
* Fold an adapter and any extensions into the composed runtime (seam 2). Extension concepts
|
|
4
|
+
* merge after the adapter's. The asset slot (seam 4) passes through untouched.
|
|
5
|
+
*/
|
|
6
|
+
export function composeRuntime(adapter, extensions = []) {
|
|
7
|
+
const content = { ...adapter.content };
|
|
8
|
+
const adminPanels = [];
|
|
9
|
+
const fieldTypes = [];
|
|
10
|
+
for (const extension of extensions) {
|
|
11
|
+
// An extension adds concepts; a key that collides with the adapter is last-write-wins.
|
|
12
|
+
// Reserved seam, unused today, so the collision policy is deliberately left simple.
|
|
13
|
+
if (extension.content)
|
|
14
|
+
Object.assign(content, extension.content);
|
|
15
|
+
if (extension.adminPanels)
|
|
16
|
+
adminPanels.push(...extension.adminPanels);
|
|
17
|
+
if (extension.fieldTypes)
|
|
18
|
+
fieldTypes.push(...extension.fieldTypes);
|
|
19
|
+
}
|
|
20
|
+
return {
|
|
21
|
+
siteName: adapter.siteName,
|
|
22
|
+
concepts: normalizeConcepts(content),
|
|
23
|
+
backend: adapter.backend,
|
|
24
|
+
sender: adapter.sender,
|
|
25
|
+
renderPreview: adapter.renderPreview,
|
|
26
|
+
registry: adapter.registry,
|
|
27
|
+
navMenu: adapter.navMenu,
|
|
28
|
+
assets: adapter.assets,
|
|
29
|
+
adminPanels,
|
|
30
|
+
fieldTypes,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { ConceptConfig, ConceptDescriptor, RoutingRule } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Concept-fixed routing, keyed by concept id (spec §7.2). Posts are dated feed entries;
|
|
4
|
+
* pages are plain navigable structure. Not in adapter config. A future Fragments adds one
|
|
5
|
+
* entry here and one key under `content`.
|
|
6
|
+
*/
|
|
7
|
+
export declare const CONCEPT_ROUTING: Readonly<Record<string, RoutingRule>>;
|
|
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`.
|
|
13
|
+
*/
|
|
14
|
+
export declare function normalizeConcepts(content: Record<string, ConceptConfig | undefined>, routing?: Readonly<Record<string, RoutingRule>>): ConceptDescriptor[];
|
|
15
|
+
/** Look up a normalized concept by id, or undefined when the site does not enable it. */
|
|
16
|
+
export declare function findConcept(concepts: ConceptDescriptor[], id: string): ConceptDescriptor | undefined;
|
|
17
|
+
//# sourceMappingURL=concepts.d.ts.map
|
|
@@ -0,0 +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;AAUF;;;;;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,CAcrB;AAED,yFAAyF;AACzF,wBAAgB,WAAW,CACzB,QAAQ,EAAE,iBAAiB,EAAE,EAC7B,EAAE,EAAE,MAAM,GACT,iBAAiB,GAAG,SAAS,CAE/B"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Concept-fixed routing, keyed by concept id (spec §7.2). Posts are dated feed entries;
|
|
3
|
+
* pages are plain navigable structure. Not in adapter config. A future Fragments adds one
|
|
4
|
+
* entry here and one key under `content`.
|
|
5
|
+
*/
|
|
6
|
+
export const CONCEPT_ROUTING = {
|
|
7
|
+
posts: { routable: true, dated: true, inFeeds: true },
|
|
8
|
+
pages: { routable: true, dated: false, inFeeds: false },
|
|
9
|
+
};
|
|
10
|
+
/** Routing for a concept with no table entry: a plain, non-feed, routable page. */
|
|
11
|
+
const DEFAULT_ROUTING = { routable: true, dated: false, inFeeds: false };
|
|
12
|
+
/** Title-case a concept id for the default sidebar label, e.g. "posts" to "Posts". */
|
|
13
|
+
function defaultLabel(id) {
|
|
14
|
+
return id.charAt(0).toUpperCase() + id.slice(1);
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Normalize an adapter's declared concepts into uniform descriptors (seam 1). Each declared
|
|
18
|
+
* key under `content` becomes one descriptor; an undeclared (`undefined`) concept is
|
|
19
|
+
* skipped. `routing` is injectable so a contract test can prove a new concept attaches
|
|
20
|
+
* additively; production passes the default `CONCEPT_ROUTING`.
|
|
21
|
+
*/
|
|
22
|
+
export function normalizeConcepts(content, routing = CONCEPT_ROUTING) {
|
|
23
|
+
const descriptors = [];
|
|
24
|
+
for (const [id, config] of Object.entries(content)) {
|
|
25
|
+
if (!config)
|
|
26
|
+
continue;
|
|
27
|
+
descriptors.push({
|
|
28
|
+
id,
|
|
29
|
+
label: config.label ?? defaultLabel(id),
|
|
30
|
+
dir: config.dir,
|
|
31
|
+
routing: routing[id] ?? DEFAULT_ROUTING,
|
|
32
|
+
fields: config.fields,
|
|
33
|
+
validate: config.validate,
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
return descriptors;
|
|
37
|
+
}
|
|
38
|
+
/** Look up a normalized concept by id, or undefined when the site does not enable it. */
|
|
39
|
+
export function findConcept(concepts, id) {
|
|
40
|
+
return concepts.find((concept) => concept.id === id);
|
|
41
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { FrontmatterField } from './types.js';
|
|
2
|
+
/** Decode submitted form data into raw frontmatter, one rule per field type. */
|
|
3
|
+
export declare function frontmatterFromForm(fields: FrontmatterField[], form: FormData): Record<string, unknown>;
|
|
4
|
+
/**
|
|
5
|
+
* Coerce a frontmatter date value to the `YYYY-MM-DD` an `<input type="date">` wants.
|
|
6
|
+
* gray-matter parses an unquoted YAML date into a JS Date, so a string-only read would
|
|
7
|
+
* leave the input empty and drop the date on save. A parsed YAML date is UTC midnight, so
|
|
8
|
+
* slicing the ISO string avoids a local-timezone shift.
|
|
9
|
+
*/
|
|
10
|
+
export declare function dateInputValue(value: unknown): string;
|
|
11
|
+
/** Reassemble a markdown file from frontmatter and body for committing. */
|
|
12
|
+
export declare function serializeMarkdown(frontmatter: object, body: string): string;
|
|
13
|
+
/** Parse a markdown file into its frontmatter and body: the read-side inverse of serialize. */
|
|
14
|
+
export declare function parseMarkdown(source: string): {
|
|
15
|
+
frontmatter: Record<string, unknown>;
|
|
16
|
+
body: string;
|
|
17
|
+
};
|
|
18
|
+
//# sourceMappingURL=frontmatter.d.ts.map
|
|
@@ -0,0 +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"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// cairn-cms: frontmatter form decoding and on-disk serialization. `frontmatterFromForm`
|
|
2
|
+
// is the form-to-data half of the edit loop; `serializeMarkdown`/`parseMarkdown` are the
|
|
3
|
+
// on-disk write/read pair. Kept as one seam so a site owns its serialization contract
|
|
4
|
+
// (quoting, key order) without the save endpoint reaching for gray-matter directly.
|
|
5
|
+
import matter from 'gray-matter';
|
|
6
|
+
/** Decode submitted form data into raw frontmatter, one rule per field type. */
|
|
7
|
+
export function frontmatterFromForm(fields, form) {
|
|
8
|
+
const data = {};
|
|
9
|
+
for (const field of fields) {
|
|
10
|
+
switch (field.type) {
|
|
11
|
+
case 'boolean':
|
|
12
|
+
data[field.name] = form.get(field.name) === 'on';
|
|
13
|
+
break;
|
|
14
|
+
case 'tags':
|
|
15
|
+
data[field.name] = form.getAll(field.name).map(String);
|
|
16
|
+
break;
|
|
17
|
+
case 'freetags':
|
|
18
|
+
// One comma-separated input to trimmed, de-duplicated, non-empty tags.
|
|
19
|
+
data[field.name] = [
|
|
20
|
+
...new Set(String(form.get(field.name) ?? '')
|
|
21
|
+
.split(',')
|
|
22
|
+
.map((tag) => tag.trim())
|
|
23
|
+
.filter(Boolean)),
|
|
24
|
+
];
|
|
25
|
+
break;
|
|
26
|
+
default:
|
|
27
|
+
// FormData.get returns null for an absent field; normalize to an empty string so
|
|
28
|
+
// a caller reading a text value never gets null.
|
|
29
|
+
data[field.name] = form.get(field.name) ?? '';
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return data;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Coerce a frontmatter date value to the `YYYY-MM-DD` an `<input type="date">` wants.
|
|
36
|
+
* gray-matter parses an unquoted YAML date into a JS Date, so a string-only read would
|
|
37
|
+
* leave the input empty and drop the date on save. A parsed YAML date is UTC midnight, so
|
|
38
|
+
* slicing the ISO string avoids a local-timezone shift.
|
|
39
|
+
*/
|
|
40
|
+
export function dateInputValue(value) {
|
|
41
|
+
if (value instanceof Date) {
|
|
42
|
+
return Number.isNaN(value.getTime()) ? '' : value.toISOString().slice(0, 10);
|
|
43
|
+
}
|
|
44
|
+
if (typeof value === 'string') {
|
|
45
|
+
const match = value.match(/^\d{4}-\d{2}-\d{2}/);
|
|
46
|
+
return match ? match[0] : '';
|
|
47
|
+
}
|
|
48
|
+
return '';
|
|
49
|
+
}
|
|
50
|
+
/** Reassemble a markdown file from frontmatter and body for committing. */
|
|
51
|
+
export function serializeMarkdown(frontmatter, body) {
|
|
52
|
+
return matter.stringify(body, frontmatter);
|
|
53
|
+
}
|
|
54
|
+
/** Parse a markdown file into its frontmatter and body: the read-side inverse of serialize. */
|
|
55
|
+
export function parseMarkdown(source) {
|
|
56
|
+
const parsed = matter(source);
|
|
57
|
+
return { frontmatter: parsed.data, body: parsed.content };
|
|
58
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/** True when `id` is a valid filename stem: lowercase, no slashes, no leading or trailing hyphen. */
|
|
2
|
+
export declare function isValidId(id: string): boolean;
|
|
3
|
+
/**
|
|
4
|
+
* A content entry's id from its filename: the basename without the `.md` suffix. Pass a
|
|
5
|
+
* basename, not a path; the caller strips any directory prefix first (Plan 03's Git Trees
|
|
6
|
+
* listing yields basenames directly).
|
|
7
|
+
*/
|
|
8
|
+
export declare function idFromFilename(filename: string): string;
|
|
9
|
+
/** The on-disk filename for an id: the id plus `.md`. */
|
|
10
|
+
export declare function filenameFromId(id: string): string;
|
|
11
|
+
/**
|
|
12
|
+
* Lowercase a title into a filename-safe slug stem. Apostrophes are dropped so "Geoff's"
|
|
13
|
+
* becomes "geoffs" (no spurious hyphen). All other non-alphanumeric runs collapse to a
|
|
14
|
+
* single hyphen; leading and trailing hyphens are trimmed.
|
|
15
|
+
*/
|
|
16
|
+
export declare function slugify(title: string): string;
|
|
17
|
+
//# sourceMappingURL=ids.d.ts.map
|
|
@@ -0,0 +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"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// cairn-cms: filename-based content ids (spec §7.2). An entry's id is its markdown filename
|
|
2
|
+
// without `.md`, so there is no slug codec. `slugify` derives a filename-safe stem from a
|
|
3
|
+
// title for the create-entry form.
|
|
4
|
+
/** Lowercase alphanumerics with single internal hyphens: the on-disk filename stem rule. */
|
|
5
|
+
const ID_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
6
|
+
/** True when `id` is a valid filename stem: lowercase, no slashes, no leading or trailing hyphen. */
|
|
7
|
+
export function isValidId(id) {
|
|
8
|
+
return ID_RE.test(id);
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* A content entry's id from its filename: the basename without the `.md` suffix. Pass a
|
|
12
|
+
* basename, not a path; the caller strips any directory prefix first (Plan 03's Git Trees
|
|
13
|
+
* listing yields basenames directly).
|
|
14
|
+
*/
|
|
15
|
+
export function idFromFilename(filename) {
|
|
16
|
+
return filename.replace(/\.md$/, '');
|
|
17
|
+
}
|
|
18
|
+
/** The on-disk filename for an id: the id plus `.md`. */
|
|
19
|
+
export function filenameFromId(id) {
|
|
20
|
+
return `${id}.md`;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Lowercase a title into a filename-safe slug stem. Apostrophes are dropped so "Geoff's"
|
|
24
|
+
* becomes "geoffs" (no spurious hyphen). All other non-alphanumeric runs collapse to a
|
|
25
|
+
* single hyphen; leading and trailing hyphens are trimmed.
|
|
26
|
+
*/
|
|
27
|
+
export function slugify(title) {
|
|
28
|
+
return title
|
|
29
|
+
.toLowerCase()
|
|
30
|
+
.replace(/'/g, '')
|
|
31
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
32
|
+
.replace(/^-+|-+$/g, '');
|
|
33
|
+
}
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import type { ComponentRegistry } from '../render/registry.js';
|
|
2
|
+
/** Common to every frontmatter field: the frontmatter key, the form label, and whether it is required. */
|
|
3
|
+
interface FieldBase {
|
|
4
|
+
/** Frontmatter key and form input name. */
|
|
5
|
+
name: string;
|
|
6
|
+
/** Form label. */
|
|
7
|
+
label: string;
|
|
8
|
+
/** A required field fails validation when empty (spec §7.4). */
|
|
9
|
+
required?: boolean;
|
|
10
|
+
}
|
|
11
|
+
/** A single-line text input. */
|
|
12
|
+
export interface TextField extends FieldBase {
|
|
13
|
+
type: 'text';
|
|
14
|
+
}
|
|
15
|
+
/** A multi-line text input. */
|
|
16
|
+
export interface TextareaField extends FieldBase {
|
|
17
|
+
type: 'textarea';
|
|
18
|
+
/** Visible rows; the editor picks a default when omitted. */
|
|
19
|
+
rows?: number;
|
|
20
|
+
}
|
|
21
|
+
/** A `YYYY-MM-DD` date input. */
|
|
22
|
+
export interface DateField extends FieldBase {
|
|
23
|
+
type: 'date';
|
|
24
|
+
}
|
|
25
|
+
/** A checkbox; absent means false. */
|
|
26
|
+
export interface BooleanField extends FieldBase {
|
|
27
|
+
type: 'boolean';
|
|
28
|
+
}
|
|
29
|
+
/** A closed-vocabulary tag set, rendered as checkboxes (ecnordic). */
|
|
30
|
+
export interface TagsField extends FieldBase {
|
|
31
|
+
type: 'tags';
|
|
32
|
+
/** The controlled vocabulary. */
|
|
33
|
+
options: readonly string[];
|
|
34
|
+
}
|
|
35
|
+
/** Free-form tags, edited as one comma-separated input (907). */
|
|
36
|
+
export interface FreeTagsField extends FieldBase {
|
|
37
|
+
type: 'freetags';
|
|
38
|
+
placeholder?: string;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* The discriminated union the per-concept frontmatter form is generated from. Adding a
|
|
42
|
+
* field type is one variant here plus one decode arm in `frontmatterFromForm` and one in
|
|
43
|
+
* `validateFields`.
|
|
44
|
+
*/
|
|
45
|
+
export type FrontmatterField = TextField | TextareaField | DateField | BooleanField | TagsField | FreeTagsField;
|
|
46
|
+
/**
|
|
47
|
+
* A validator's verdict. On success it carries the normalized frontmatter to commit; on
|
|
48
|
+
* failure it carries field-keyed error messages (the empty key is a form-level error).
|
|
49
|
+
* Invalid input bounces to the form and never reaches git (spec §7.4).
|
|
50
|
+
*/
|
|
51
|
+
export type ValidationResult = {
|
|
52
|
+
ok: true;
|
|
53
|
+
data: Record<string, unknown>;
|
|
54
|
+
} | {
|
|
55
|
+
ok: false;
|
|
56
|
+
errors: Record<string, string>;
|
|
57
|
+
};
|
|
58
|
+
/**
|
|
59
|
+
* Per-site configuration for one content concept (spec §8). Concept-fixed behavior such as
|
|
60
|
+
* routability is not here; it lives in the engine's routing table (`CONCEPT_ROUTING`).
|
|
61
|
+
*/
|
|
62
|
+
export interface ConceptConfig {
|
|
63
|
+
/** Repo-relative content directory, e.g. "src/content/posts". */
|
|
64
|
+
dir: string;
|
|
65
|
+
/** Sidebar label; defaults from the concept id when omitted. */
|
|
66
|
+
label?: string;
|
|
67
|
+
/** Drives the per-concept frontmatter form, in order. */
|
|
68
|
+
fields: FrontmatterField[];
|
|
69
|
+
/** Validate submitted frontmatter before any commit. */
|
|
70
|
+
validate(frontmatter: Record<string, unknown>, body: string): ValidationResult;
|
|
71
|
+
}
|
|
72
|
+
/** The GitHub App backend a site reads from and commits to (spec §8). Plain data the GitHub engine (Plan 03) consumes. */
|
|
73
|
+
export interface BackendConfig {
|
|
74
|
+
owner: string;
|
|
75
|
+
repo: string;
|
|
76
|
+
/** Commit target, e.g. "main". */
|
|
77
|
+
branch: string;
|
|
78
|
+
appId: string;
|
|
79
|
+
installationId: string;
|
|
80
|
+
}
|
|
81
|
+
/** Magic-link sender identity for Cloudflare Email Sending. */
|
|
82
|
+
export interface SenderConfig {
|
|
83
|
+
from: string;
|
|
84
|
+
replyTo?: string;
|
|
85
|
+
}
|
|
86
|
+
/** A git-committed YAML menu this site's nav editor manages (Plan 06). */
|
|
87
|
+
export interface NavMenuConfig {
|
|
88
|
+
/** Repo-relative path to the site-config YAML, e.g. "src/lib/site.config.yaml". */
|
|
89
|
+
configPath: string;
|
|
90
|
+
/** Key within the file's menus map, e.g. "primary". */
|
|
91
|
+
menuName: string;
|
|
92
|
+
/** Sidebar label for the menu. */
|
|
93
|
+
label: string;
|
|
94
|
+
/** Max nesting depth allowed in the editor; defaults to 2. */
|
|
95
|
+
maxDepth?: number;
|
|
96
|
+
}
|
|
97
|
+
/** Reserved asset slot (seam 4). Typed and unused in the rebuild; R7/R9 read it later with no contract change. */
|
|
98
|
+
export interface AssetConfig {
|
|
99
|
+
/** Repo-relative asset roots, e.g. ["static/images"]. */
|
|
100
|
+
roots: string[];
|
|
101
|
+
/** Public URL base, e.g. "/images". */
|
|
102
|
+
publicBase: string;
|
|
103
|
+
}
|
|
104
|
+
/** The single seam the engine consumes. A site implements this at `src/lib/cairn.config.ts`. */
|
|
105
|
+
export interface CairnAdapter {
|
|
106
|
+
siteName: string;
|
|
107
|
+
/**
|
|
108
|
+
* Which content concepts this site enables. A future `fragments?` key attaches here with
|
|
109
|
+
* no reshape of the contract (seam 1). A site never has two of the same concept.
|
|
110
|
+
*/
|
|
111
|
+
content: {
|
|
112
|
+
posts?: ConceptConfig;
|
|
113
|
+
pages?: ConceptConfig;
|
|
114
|
+
};
|
|
115
|
+
backend: BackendConfig;
|
|
116
|
+
sender: SenderConfig;
|
|
117
|
+
/** Design-accurate preview: the same render pipeline the site ships. */
|
|
118
|
+
renderPreview(md: string): string | Promise<string>;
|
|
119
|
+
/** Directive component registry; the renderer and the future palette derive from it (seam 3). */
|
|
120
|
+
registry?: ComponentRegistry;
|
|
121
|
+
navMenu?: NavMenuConfig;
|
|
122
|
+
assets?: AssetConfig;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Concept-fixed routing for a normalized concept (spec §7.2). Posts are dated feed entries;
|
|
126
|
+
* pages are plain navigable structure. Not in adapter config.
|
|
127
|
+
*/
|
|
128
|
+
export interface RoutingRule {
|
|
129
|
+
/** Routable as a standalone URL. A future Fragments concept is embedded, not routable. */
|
|
130
|
+
routable: boolean;
|
|
131
|
+
/** Carries a date (posts). */
|
|
132
|
+
dated: boolean;
|
|
133
|
+
/** Appears in feeds and the sitemap (posts). */
|
|
134
|
+
inFeeds: boolean;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* The engine-internal, uniform view of one concept after normalization (seam 1). The admin
|
|
138
|
+
* nav, the list views, and the editor all read this, never the raw config.
|
|
139
|
+
*/
|
|
140
|
+
export interface ConceptDescriptor {
|
|
141
|
+
/** Concept id, the key under `content`, e.g. "posts". */
|
|
142
|
+
id: string;
|
|
143
|
+
label: string;
|
|
144
|
+
dir: string;
|
|
145
|
+
routing: RoutingRule;
|
|
146
|
+
fields: FrontmatterField[];
|
|
147
|
+
validate(frontmatter: Record<string, unknown>, body: string): ValidationResult;
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* A site-defined admin screen contributed by an extension (Mode 2). It gains a sidebar entry, the
|
|
151
|
+
* `/admin` guard, and the session, and may commit through the same GitHub pipeline. The dispatch
|
|
152
|
+
* route is built in Plan 09; the `load`/`actions`/`component` members are typed loosely here and
|
|
153
|
+
* tightened when the machinery lands.
|
|
154
|
+
*/
|
|
155
|
+
export interface AdminPanel {
|
|
156
|
+
/** Routes under `/admin/<id>`; also the sidebar key. */
|
|
157
|
+
id: string;
|
|
158
|
+
/** Sidebar label. */
|
|
159
|
+
label: string;
|
|
160
|
+
/** Owner-gated, like editor management. */
|
|
161
|
+
owner?: boolean;
|
|
162
|
+
/** Server load, behind the guard. Typed in Plan 09. */
|
|
163
|
+
load?: (event: unknown) => unknown;
|
|
164
|
+
/** Named form actions, which may use the commit pipeline. Typed in Plan 09. */
|
|
165
|
+
actions?: Record<string, (event: unknown) => Promise<unknown>>;
|
|
166
|
+
/** The panel UI, rendered inside the admin shell. Typed as a component in Plan 09. */
|
|
167
|
+
component: unknown;
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* A custom frontmatter field type contributed by an extension (Mode 2): a renderer plus a validator
|
|
171
|
+
* dispatched alongside the built-in field union. The renderer and validator are typed in Plan 09
|
|
172
|
+
* when the form dispatch becomes a registry; the `type` key reserves the discriminator now.
|
|
173
|
+
*/
|
|
174
|
+
export interface FieldTypeDef {
|
|
175
|
+
/** The field-type discriminator, e.g. "color". */
|
|
176
|
+
type: string;
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* A future build-time extension (seam 2). It folds in the same way the adapter does and
|
|
180
|
+
* contributes the same kinds of things. Reserved and unused in the rebuild; the shape is
|
|
181
|
+
* fixed now so the extension contract is additive later.
|
|
182
|
+
*/
|
|
183
|
+
export interface CairnExtension {
|
|
184
|
+
/** Additional concepts, merged after the adapter's. */
|
|
185
|
+
content?: Record<string, ConceptConfig>;
|
|
186
|
+
/** Site-defined admin panels (Mode 2). Carried onto the runtime now; dispatched in Plan 09. */
|
|
187
|
+
adminPanels?: AdminPanel[];
|
|
188
|
+
/** Custom field types (Mode 2). Carried onto the runtime now; dispatched in Plan 09. */
|
|
189
|
+
fieldTypes?: FieldTypeDef[];
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* The composed runtime the engine serves from (seam 2 output). The single aggregation point
|
|
193
|
+
* (`composeRuntime`) folds the adapter and any extensions into this shape.
|
|
194
|
+
*/
|
|
195
|
+
export interface CairnRuntime {
|
|
196
|
+
siteName: string;
|
|
197
|
+
concepts: ConceptDescriptor[];
|
|
198
|
+
backend: BackendConfig;
|
|
199
|
+
sender: SenderConfig;
|
|
200
|
+
renderPreview(md: string): string | Promise<string>;
|
|
201
|
+
registry?: ComponentRegistry;
|
|
202
|
+
navMenu?: NavMenuConfig;
|
|
203
|
+
assets?: AssetConfig;
|
|
204
|
+
/** Admin panels contributed by extensions (Mode 2). Empty until Plan 09 wires the dispatch route. */
|
|
205
|
+
adminPanels?: AdminPanel[];
|
|
206
|
+
/** Field types contributed by extensions (Mode 2). Empty until Plan 09 wires the form dispatch. */
|
|
207
|
+
fieldTypes?: FieldTypeDef[];
|
|
208
|
+
}
|
|
209
|
+
export {};
|
|
210
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/lib/content/types.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { FrontmatterField, ValidationResult } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Validate raw frontmatter against a field list. Required text and date fields must be
|
|
4
|
+
* non-empty; required tag fields must be non-empty lists. Booleans coerce to `true`/`false`
|
|
5
|
+
* and tag fields to string arrays. Returns the normalized data, or field-keyed errors when
|
|
6
|
+
* any required field is empty.
|
|
7
|
+
*
|
|
8
|
+
* Frontmatter may arrive from the edit form (all string values) or from `parseMarkdown`,
|
|
9
|
+
* where gray-matter turns an unquoted YAML date into a JS `Date`. The `date` case coerces a
|
|
10
|
+
* `Date` to `YYYY-MM-DD` so a valid parsed date is not mistaken for an empty one.
|
|
11
|
+
*/
|
|
12
|
+
export declare function validateFields(fields: FrontmatterField[], frontmatter: Record<string, unknown>): ValidationResult;
|
|
13
|
+
//# sourceMappingURL=validate.d.ts.map
|
|
@@ -0,0 +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;;;;;;;;;GASG;AACH,wBAAgB,cAAc,CAC5B,MAAM,EAAE,gBAAgB,EAAE,EAC1B,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GACnC,gBAAgB,CA8BlB"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { dateInputValue } from './frontmatter.js';
|
|
2
|
+
/**
|
|
3
|
+
* Validate raw frontmatter against a field list. Required text and date fields must be
|
|
4
|
+
* non-empty; required tag fields must be non-empty lists. Booleans coerce to `true`/`false`
|
|
5
|
+
* and tag fields to string arrays. Returns the normalized data, or field-keyed errors when
|
|
6
|
+
* any required field is empty.
|
|
7
|
+
*
|
|
8
|
+
* Frontmatter may arrive from the edit form (all string values) or from `parseMarkdown`,
|
|
9
|
+
* where gray-matter turns an unquoted YAML date into a JS `Date`. The `date` case coerces a
|
|
10
|
+
* `Date` to `YYYY-MM-DD` so a valid parsed date is not mistaken for an empty one.
|
|
11
|
+
*/
|
|
12
|
+
export function validateFields(fields, frontmatter) {
|
|
13
|
+
const data = {};
|
|
14
|
+
const errors = {};
|
|
15
|
+
for (const field of fields) {
|
|
16
|
+
const value = frontmatter[field.name];
|
|
17
|
+
switch (field.type) {
|
|
18
|
+
case 'boolean':
|
|
19
|
+
data[field.name] = value === true;
|
|
20
|
+
break;
|
|
21
|
+
case 'tags':
|
|
22
|
+
case 'freetags': {
|
|
23
|
+
const list = Array.isArray(value) ? value.map(String) : [];
|
|
24
|
+
if (field.required && list.length === 0)
|
|
25
|
+
errors[field.name] = `${field.label} is required`;
|
|
26
|
+
data[field.name] = list;
|
|
27
|
+
break;
|
|
28
|
+
}
|
|
29
|
+
case 'date': {
|
|
30
|
+
const text = value instanceof Date ? dateInputValue(value) : typeof value === 'string' ? value.trim() : '';
|
|
31
|
+
if (field.required && text === '')
|
|
32
|
+
errors[field.name] = `${field.label} is required`;
|
|
33
|
+
data[field.name] = text;
|
|
34
|
+
break;
|
|
35
|
+
}
|
|
36
|
+
default: {
|
|
37
|
+
const text = typeof value === 'string' ? value.trim() : '';
|
|
38
|
+
if (field.required && text === '')
|
|
39
|
+
errors[field.name] = `${field.label} is required`;
|
|
40
|
+
data[field.name] = text;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return Object.keys(errors).length > 0 ? { ok: false, errors } : { ok: true, data };
|
|
45
|
+
}
|
package/dist/email.d.ts
CHANGED
|
@@ -1,14 +1,27 @@
|
|
|
1
|
-
|
|
2
|
-
export
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
messageId: string;
|
|
11
|
-
}>;
|
|
1
|
+
import type { AuthEnv } from './auth/types.js';
|
|
2
|
+
export type { AuthEnv };
|
|
3
|
+
/** The message a built magic-link email carries. */
|
|
4
|
+
export interface MagicLinkMessage {
|
|
5
|
+
to: string;
|
|
6
|
+
from: string;
|
|
7
|
+
subject: string;
|
|
8
|
+
html: string;
|
|
9
|
+
text: string;
|
|
12
10
|
}
|
|
13
|
-
|
|
11
|
+
/** Per-site identity for the magic-link email, sourced from the adapter. */
|
|
12
|
+
export interface AuthBranding {
|
|
13
|
+
siteName: string;
|
|
14
|
+
from: string;
|
|
15
|
+
replyTo?: string;
|
|
16
|
+
}
|
|
17
|
+
/** The injected send. Production uses `cloudflareSend`; tests pass a sink. */
|
|
18
|
+
export type SendMagicLink = (env: AuthEnv, message: MagicLinkMessage) => Promise<void>;
|
|
19
|
+
/** Build the confirmation email. The link is the only action; the copy stays plain. */
|
|
20
|
+
export declare function buildMagicLinkMessage(input: {
|
|
21
|
+
to: string;
|
|
22
|
+
branding: AuthBranding;
|
|
23
|
+
link: string;
|
|
24
|
+
}): MagicLinkMessage;
|
|
25
|
+
/** The production send: Cloudflare Email Sending through the EMAIL binding. */
|
|
26
|
+
export declare const cloudflareSend: SendMagicLink;
|
|
14
27
|
//# sourceMappingURL=email.d.ts.map
|
package/dist/email.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"email.d.ts","sourceRoot":"","sources":["../src/lib/email.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"email.d.ts","sourceRoot":"","sources":["../src/lib/email.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAE/C,YAAY,EAAE,OAAO,EAAE,CAAC;AAExB,oDAAoD;AACpD,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd;AAED,4EAA4E;AAC5E,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,8EAA8E;AAC9E,MAAM,MAAM,aAAa,GAAG,CAAC,GAAG,EAAE,OAAO,EAAE,OAAO,EAAE,gBAAgB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;AAYvF,uFAAuF;AACvF,wBAAgB,qBAAqB,CAAC,KAAK,EAAE;IAC3C,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,YAAY,CAAC;IACvB,IAAI,EAAE,MAAM,CAAC;CACd,GAAG,gBAAgB,CAQnB;AAED,+EAA+E;AAC/E,eAAO,MAAM,cAAc,EAAE,aAG5B,CAAC"}
|