@glw907/cairn-cms 0.60.1 → 0.62.1
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/CHANGELOG.md +69 -0
- package/dist/components/AdminLayout.svelte +22 -0
- package/dist/components/CairnAdmin.svelte +3 -0
- package/dist/components/CairnTidySettings.svelte +2 -2
- package/dist/components/CairnTidySettings.svelte.d.ts +1 -1
- package/dist/components/EditPage.svelte +116 -39
- package/dist/components/HelpHome.svelte +824 -0
- package/dist/components/HelpHome.svelte.d.ts +22 -0
- package/dist/components/MarkdownHelpDialog.svelte +4 -15
- package/dist/components/client-ingest.d.ts +16 -8
- package/dist/components/client-ingest.js +12 -6
- package/dist/components/editor-media.js +16 -8
- package/dist/components/editor-placeholder.d.ts +4 -2
- package/dist/components/editor-tidy.d.ts +24 -12
- package/dist/components/editor-tidy.js +8 -4
- package/dist/components/index.d.ts +1 -0
- package/dist/components/index.js +1 -0
- package/dist/components/link-completion.d.ts +12 -6
- package/dist/components/link-completion.js +12 -6
- package/dist/components/markdown-directives.d.ts +9 -6
- package/dist/components/markdown-directives.js +9 -6
- package/dist/components/markdown-format.d.ts +7 -2
- package/dist/components/markdown-format.js +59 -28
- package/dist/components/markdown-reference.d.ts +8 -0
- package/dist/components/markdown-reference.js +22 -0
- package/dist/components/media-upload-outcome.d.ts +12 -6
- package/dist/components/objective-errors.d.ts +8 -4
- package/dist/components/objective-errors.js +8 -4
- package/dist/components/preview-doc.d.ts +4 -2
- package/dist/components/preview-doc.js +4 -2
- package/dist/components/spellcheck.d.ts +55 -29
- package/dist/components/spellcheck.js +39 -21
- package/dist/components/tidy-categorize.d.ts +20 -10
- package/dist/components/tidy-categorize.js +16 -8
- package/dist/components/tidy-validate.d.ts +12 -6
- package/dist/components/tidy-validate.js +20 -10
- package/dist/components/topbar-context.d.ts +4 -2
- package/dist/content/advisories.d.ts +51 -0
- package/dist/content/advisories.js +79 -0
- package/dist/content/compose.d.ts +4 -2
- package/dist/content/compose.js +1 -0
- package/dist/content/excerpt.js +4 -2
- package/dist/content/getting-started.d.ts +18 -0
- package/dist/content/getting-started.js +12 -0
- package/dist/content/links.d.ts +16 -8
- package/dist/content/links.js +12 -6
- package/dist/content/manifest.d.ts +36 -18
- package/dist/content/manifest.js +32 -16
- package/dist/content/media-refs.d.ts +4 -2
- package/dist/content/media-refs.js +4 -2
- package/dist/content/media-rewrite.d.ts +8 -4
- package/dist/content/media-rewrite.js +76 -38
- package/dist/content/schema.d.ts +20 -10
- package/dist/content/site-dictionary.d.ts +4 -2
- package/dist/content/site-dictionary.js +8 -4
- package/dist/content/types.d.ts +97 -42
- package/dist/delivery/content-index.d.ts +16 -8
- package/dist/delivery/feeds.js +4 -2
- package/dist/delivery/json-ld.d.ts +3 -0
- package/dist/delivery/json-ld.js +3 -0
- package/dist/delivery/manifest.d.ts +4 -2
- package/dist/delivery/manifest.js +4 -2
- package/dist/delivery/public-routes.d.ts +12 -6
- package/dist/delivery/public-routes.js +4 -2
- package/dist/delivery/seo-fields.d.ts +12 -6
- package/dist/delivery/seo-fields.js +8 -4
- package/dist/delivery/site-indexes.d.ts +4 -2
- package/dist/delivery/site-resolver.d.ts +4 -2
- package/dist/delivery/site-resolver.js +4 -2
- package/dist/doctor/cloudflare-api.d.ts +6 -0
- package/dist/doctor/cloudflare-api.js +6 -0
- package/dist/doctor/index.d.ts +12 -6
- package/dist/doctor/report.d.ts +3 -0
- package/dist/doctor/report.js +3 -0
- package/dist/doctor/run.d.ts +3 -0
- package/dist/doctor/run.js +3 -0
- package/dist/doctor/types.d.ts +10 -2
- package/dist/doctor/types.js +6 -0
- package/dist/doctor/wrangler-config.d.ts +7 -2
- package/dist/doctor/wrangler-config.js +3 -0
- package/dist/email.d.ts +4 -2
- package/dist/env.d.ts +0 -3
- package/dist/env.js +0 -3
- package/dist/github/branches.d.ts +4 -2
- package/dist/github/branches.js +4 -2
- package/dist/github/signing.d.ts +1 -1
- package/dist/github/signing.js +2 -2
- package/dist/log/events.d.ts +1 -1
- package/dist/media/bulk-delete-plan.d.ts +8 -4
- package/dist/media/config.d.ts +12 -6
- package/dist/media/config.js +16 -8
- package/dist/media/delivery-bucket.d.ts +4 -2
- package/dist/media/library-entry.d.ts +4 -2
- package/dist/media/library-entry.js +4 -2
- package/dist/media/manifest.d.ts +29 -15
- package/dist/media/manifest.js +29 -16
- package/dist/media/naming.d.ts +12 -6
- package/dist/media/naming.js +24 -12
- package/dist/media/orphan-scan.d.ts +4 -2
- package/dist/media/reconcile.d.ts +21 -11
- package/dist/media/reconcile.js +12 -6
- package/dist/media/reference.d.ts +8 -4
- package/dist/media/reference.js +12 -6
- package/dist/media/rewrite-plan.d.ts +12 -6
- package/dist/media/sniff.d.ts +4 -2
- package/dist/media/sniff.js +28 -14
- package/dist/media/store.d.ts +16 -8
- package/dist/media/store.js +4 -2
- package/dist/media/transform-url.d.ts +12 -6
- package/dist/media/transform-url.js +8 -4
- package/dist/media/usage.d.ts +8 -4
- package/dist/nav/site-config.d.ts +16 -8
- package/dist/render/component-grammar.d.ts +23 -10
- package/dist/render/component-grammar.js +19 -8
- package/dist/render/component-insert.d.ts +8 -4
- package/dist/render/component-insert.js +4 -2
- package/dist/render/component-reference.d.ts +4 -2
- package/dist/render/component-reference.js +4 -2
- package/dist/render/component-validate.d.ts +3 -0
- package/dist/render/component-validate.js +3 -0
- package/dist/render/glyph.d.ts +4 -2
- package/dist/render/glyph.js +4 -2
- package/dist/render/pipeline.d.ts +20 -10
- package/dist/render/pipeline.js +4 -2
- package/dist/render/registry.d.ts +40 -20
- package/dist/render/registry.js +16 -8
- package/dist/render/rehype-dispatch.d.ts +22 -8
- package/dist/render/rehype-dispatch.js +22 -8
- package/dist/render/remark-directives.d.ts +3 -0
- package/dist/render/remark-directives.js +3 -0
- package/dist/render/remark-figure.d.ts +4 -2
- package/dist/render/remark-figure.js +4 -2
- package/dist/render/resolve-links.d.ts +4 -2
- package/dist/render/resolve-links.js +4 -2
- package/dist/render/resolve-media.d.ts +16 -8
- package/dist/render/resolve-media.js +12 -6
- package/dist/sveltekit/admin-dispatch.d.ts +2 -0
- package/dist/sveltekit/admin-dispatch.js +9 -3
- package/dist/sveltekit/auth-routes.d.ts +3 -0
- package/dist/sveltekit/auth-routes.js +3 -0
- package/dist/sveltekit/cairn-admin.d.ts +16 -5
- package/dist/sveltekit/cairn-admin.js +26 -10
- package/dist/sveltekit/content-routes.d.ts +191 -86
- package/dist/sveltekit/content-routes.js +295 -107
- package/dist/sveltekit/editors-routes.d.ts +3 -0
- package/dist/sveltekit/editors-routes.js +3 -0
- package/dist/sveltekit/guard.d.ts +4 -2
- package/dist/sveltekit/guard.js +4 -2
- package/dist/sveltekit/https-required-page.d.ts +1 -1
- package/dist/sveltekit/https-required-page.js +1 -1
- package/dist/sveltekit/index.d.ts +1 -1
- package/dist/sveltekit/media-route.d.ts +1 -2
- package/dist/sveltekit/media-route.js +13 -8
- package/dist/sveltekit/nav-routes.d.ts +7 -2
- package/dist/sveltekit/nav-routes.js +3 -0
- package/dist/sveltekit/types.d.ts +4 -2
- package/dist/vite/index.d.ts +32 -16
- package/dist/vite/index.js +52 -26
- package/dist/vite/resolve-root.d.ts +8 -4
- package/dist/vite/resolve-root.js +4 -2
- package/package.json +7 -1
- package/src/lib/components/AdminLayout.svelte +22 -0
- package/src/lib/components/CairnAdmin.svelte +3 -0
- package/src/lib/components/CairnTidySettings.svelte +2 -2
- package/src/lib/components/ComponentForm.svelte +0 -1
- package/src/lib/components/EditPage.svelte +133 -41
- package/src/lib/components/HelpHome.svelte +850 -0
- package/src/lib/components/MarkdownHelpDialog.svelte +4 -15
- package/src/lib/components/client-ingest.ts +20 -10
- package/src/lib/components/editor-media.ts +20 -10
- package/src/lib/components/editor-placeholder.ts +12 -6
- package/src/lib/components/editor-tidy.ts +28 -14
- package/src/lib/components/index.ts +1 -0
- package/src/lib/components/link-completion.ts +12 -6
- package/src/lib/components/markdown-directives.ts +13 -8
- package/src/lib/components/markdown-format.ts +63 -30
- package/src/lib/components/markdown-reference.ts +30 -0
- package/src/lib/components/media-upload-outcome.ts +12 -6
- package/src/lib/components/objective-errors.ts +16 -8
- package/src/lib/components/preview-doc.ts +4 -2
- package/src/lib/components/spellcheck.ts +79 -41
- package/src/lib/components/tidy-categorize.ts +28 -14
- package/src/lib/components/tidy-validate.ts +28 -14
- package/src/lib/components/topbar-context.ts +4 -2
- package/src/lib/content/advisories.ts +141 -0
- package/src/lib/content/compose.ts +5 -2
- package/src/lib/content/excerpt.ts +4 -2
- package/src/lib/content/getting-started.ts +31 -0
- package/src/lib/content/links.ts +16 -8
- package/src/lib/content/manifest.ts +36 -18
- package/src/lib/content/media-refs.ts +4 -2
- package/src/lib/content/media-rewrite.ts +100 -50
- package/src/lib/content/schema.ts +20 -10
- package/src/lib/content/site-dictionary.ts +8 -4
- package/src/lib/content/types.ts +97 -42
- package/src/lib/delivery/content-index.ts +16 -8
- package/src/lib/delivery/feeds.ts +4 -2
- package/src/lib/delivery/json-ld.ts +3 -0
- package/src/lib/delivery/manifest.ts +4 -2
- package/src/lib/delivery/public-routes.ts +16 -8
- package/src/lib/delivery/seo-fields.ts +12 -6
- package/src/lib/delivery/site-indexes.ts +4 -2
- package/src/lib/delivery/site-resolver.ts +4 -2
- package/src/lib/doctor/cloudflare-api.ts +6 -0
- package/src/lib/doctor/index.ts +12 -6
- package/src/lib/doctor/report.ts +3 -0
- package/src/lib/doctor/run.ts +3 -0
- package/src/lib/doctor/types.ts +10 -2
- package/src/lib/doctor/wrangler-config.ts +7 -2
- package/src/lib/email.ts +4 -2
- package/src/lib/env.ts +0 -3
- package/src/lib/github/branches.ts +4 -2
- package/src/lib/github/signing.ts +2 -2
- package/src/lib/log/events.ts +1 -0
- package/src/lib/media/bulk-delete-plan.ts +8 -4
- package/src/lib/media/config.ts +24 -12
- package/src/lib/media/delivery-bucket.ts +4 -2
- package/src/lib/media/library-entry.ts +4 -2
- package/src/lib/media/manifest.ts +33 -18
- package/src/lib/media/naming.ts +24 -12
- package/src/lib/media/orphan-scan.ts +4 -2
- package/src/lib/media/reconcile.ts +21 -11
- package/src/lib/media/reference.ts +12 -6
- package/src/lib/media/rewrite-plan.ts +12 -6
- package/src/lib/media/sniff.ts +28 -14
- package/src/lib/media/store.ts +16 -8
- package/src/lib/media/transform-url.ts +12 -6
- package/src/lib/media/usage.ts +8 -4
- package/src/lib/nav/site-config.ts +16 -8
- package/src/lib/render/component-grammar.ts +23 -10
- package/src/lib/render/component-insert.ts +8 -4
- package/src/lib/render/component-reference.ts +4 -2
- package/src/lib/render/component-validate.ts +3 -0
- package/src/lib/render/glyph.ts +4 -2
- package/src/lib/render/pipeline.ts +20 -10
- package/src/lib/render/registry.ts +44 -22
- package/src/lib/render/rehype-dispatch.ts +22 -8
- package/src/lib/render/remark-directives.ts +3 -0
- package/src/lib/render/remark-figure.ts +4 -2
- package/src/lib/render/resolve-links.ts +4 -2
- package/src/lib/render/resolve-media.ts +16 -8
- package/src/lib/sveltekit/admin-dispatch.ts +10 -4
- package/src/lib/sveltekit/auth-routes.ts +3 -0
- package/src/lib/sveltekit/cairn-admin.ts +37 -15
- package/src/lib/sveltekit/content-routes.ts +492 -197
- package/src/lib/sveltekit/editors-routes.ts +3 -0
- package/src/lib/sveltekit/guard.ts +4 -2
- package/src/lib/sveltekit/https-required-page.ts +1 -1
- package/src/lib/sveltekit/index.ts +3 -0
- package/src/lib/sveltekit/media-route.ts +13 -8
- package/src/lib/sveltekit/nav-routes.ts +7 -2
- package/src/lib/sveltekit/types.ts +4 -2
- package/src/lib/vite/index.ts +60 -30
- package/src/lib/vite/resolve-root.ts +8 -4
package/dist/media/naming.js
CHANGED
|
@@ -5,12 +5,16 @@
|
|
|
5
5
|
// trips through the media: token unchanged.
|
|
6
6
|
// slugifyFilename output always satisfies parseMediaToken's grammar (lowercase alphanumerics joined
|
|
7
7
|
// by single internal hyphens, no leading or trailing hyphen), or is the literal `file`.
|
|
8
|
-
/**
|
|
8
|
+
/**
|
|
9
|
+
* Combining marks (Unicode block U+0300 to U+036F), left over after an NFD decompose, stripped to
|
|
9
10
|
* fold an accented letter down to its ASCII base. Written as escapes because the literal marks are
|
|
10
|
-
* invisible in source.
|
|
11
|
+
* invisible in source.
|
|
12
|
+
*/
|
|
11
13
|
const COMBINING_MARKS = /[\u0300-\u036f]/g;
|
|
12
|
-
/**
|
|
13
|
-
*
|
|
14
|
+
/**
|
|
15
|
+
* Windows reserved device names. A bare match (case-insensitive) cannot survive as the slug, since
|
|
16
|
+
* it names a device rather than a file on that platform.
|
|
17
|
+
*/
|
|
14
18
|
const RESERVED = new Set([
|
|
15
19
|
'con',
|
|
16
20
|
'prn',
|
|
@@ -37,8 +41,10 @@ const RESERVED = new Set([
|
|
|
37
41
|
]);
|
|
38
42
|
/** The maximum slug length, applied before the reserved-name and empty fallbacks. */
|
|
39
43
|
const MAX_SLUG = 80;
|
|
40
|
-
/**
|
|
41
|
-
*
|
|
44
|
+
/**
|
|
45
|
+
* A 16-character lowercase hex content-hash prefix, the bare-hash reference form. A slug that
|
|
46
|
+
* matches this shape would collide with `media:<hash>`, so slugifyFilename screens it.
|
|
47
|
+
*/
|
|
42
48
|
const HASH_RE = /^[0-9a-f]{16}$/;
|
|
43
49
|
/** A short alphanumeric extension (no dot), the only shape r2Key accepts, for example `webp`. */
|
|
44
50
|
const R2_EXT_RE = /^[a-z0-9]{1,5}$/;
|
|
@@ -59,10 +65,12 @@ export async function hashBytes(bytes) {
|
|
|
59
65
|
export function shortHash(full) {
|
|
60
66
|
return full.slice(0, 16);
|
|
61
67
|
}
|
|
62
|
-
/**
|
|
68
|
+
/**
|
|
69
|
+
* The strict ingest transform from a raw filename to a slug that satisfies the media: slug grammar,
|
|
63
70
|
* or the literal `file`. Drops the extension, lowercases, transliterates accents, collapses non-alphanumeric runs
|
|
64
71
|
* to a single hyphen, trims, caps at 80 chars, screens Windows reserved names, and falls back to
|
|
65
|
-
* `file` when nothing usable is left.
|
|
72
|
+
* `file` when nothing usable is left.
|
|
73
|
+
*/
|
|
66
74
|
export function slugifyFilename(name) {
|
|
67
75
|
const dot = name.lastIndexOf('.');
|
|
68
76
|
const stem = dot === -1 ? name : name.slice(0, dot);
|
|
@@ -86,9 +94,11 @@ export function slugifyFilename(name) {
|
|
|
86
94
|
return `${slug}-img`;
|
|
87
95
|
return slug;
|
|
88
96
|
}
|
|
89
|
-
/**
|
|
97
|
+
/**
|
|
98
|
+
* The content-addressed R2 object key `media/<aa>/<shortHash>.<ext>`, fanned out on the first two
|
|
90
99
|
* hex chars of the short hash. No leading slash: this is an object key, not a URL. `ext` is bare
|
|
91
|
-
* (no dot), for example `webp`.
|
|
100
|
+
* (no dot), for example `webp`.
|
|
101
|
+
*/
|
|
92
102
|
export function r2Key(shortHash, ext) {
|
|
93
103
|
if (!HASH_RE.test(shortHash)) {
|
|
94
104
|
throw new Error(`r2Key: hash must be 16 lowercase hex chars, got "${shortHash}"`);
|
|
@@ -98,10 +108,12 @@ export function r2Key(shortHash, ext) {
|
|
|
98
108
|
}
|
|
99
109
|
return `media/${shortHash.slice(0, 2)}/${shortHash}.${ext}`;
|
|
100
110
|
}
|
|
101
|
-
/**
|
|
111
|
+
/**
|
|
112
|
+
* The public delivery URL path, with a leading slash, under the delivery base (`publicBase`,
|
|
102
113
|
* default `/media`). The `slug` form is human-readable (`<base>/<slug>.<shortHash>.<ext>`, or
|
|
103
114
|
* `<base>/<shortHash>.<ext>` when the slug is null); the `opaque` form mirrors the R2 fan-out
|
|
104
|
-
* (`<base>/<aa>/<shortHash>.<ext>`) and ignores the slug.
|
|
115
|
+
* (`<base>/<aa>/<shortHash>.<ext>`) and ignores the slug.
|
|
116
|
+
*/
|
|
105
117
|
export function publicPath(slug, shortHash, ext, urlForm, publicBase = '/media') {
|
|
106
118
|
if (urlForm === 'opaque') {
|
|
107
119
|
return `${publicBase}/${shortHash.slice(0, 2)}/${shortHash}.${ext}`;
|
|
@@ -8,8 +8,10 @@ export interface OrphanByteRow {
|
|
|
8
8
|
/** The 16-hex content hash parsed from the key. */
|
|
9
9
|
hash: string;
|
|
10
10
|
}
|
|
11
|
-
/**
|
|
12
|
-
*
|
|
11
|
+
/**
|
|
12
|
+
* A broken reference: a manifest row whose bytes are gone. Read-only, since purging it would drop a
|
|
13
|
+
* still-referenced asset's record; the screen shows where it is used so an operator can re-ingest.
|
|
14
|
+
*/
|
|
13
15
|
export interface BrokenRefRow {
|
|
14
16
|
/** The 16-hex content hash of the manifest row whose bytes are missing. */
|
|
15
17
|
hash: string;
|
|
@@ -1,18 +1,24 @@
|
|
|
1
1
|
import type { MediaManifest } from './manifest.js';
|
|
2
|
-
/**
|
|
3
|
-
*
|
|
2
|
+
/**
|
|
3
|
+
* A stored media object key parses to its short hash via `media/<aa>/<shortHash>.<ext>`. Exported so
|
|
4
|
+
* the orphan-scan projection derives the same hash from an orphaned key without a second grammar.
|
|
5
|
+
*/
|
|
4
6
|
export declare const MEDIA_KEY_RE: RegExp;
|
|
5
|
-
/**
|
|
6
|
-
*
|
|
7
|
+
/**
|
|
8
|
+
* What a reconcile read found in either direction. `orphanedObjects` are stored R2 keys whose hash
|
|
9
|
+
* has no manifest row; `missingObjects` are manifest hashes with no stored object.
|
|
10
|
+
*/
|
|
7
11
|
export interface ReconcileResult {
|
|
8
12
|
/** Stored keys (full R2 keys) whose content hash is absent from the manifest. */
|
|
9
13
|
orphanedObjects: string[];
|
|
10
14
|
/** Manifest content-hash keys with no matching stored object. */
|
|
11
15
|
missingObjects: string[];
|
|
12
16
|
}
|
|
13
|
-
/**
|
|
17
|
+
/**
|
|
18
|
+
* The pure core: compare the stored R2 keys against the manifest's content-hash keys and report
|
|
14
19
|
* both orphan directions. A stored key that does not match the media-key grammar is ignored, since
|
|
15
|
-
* it is not a content-addressed media object this reconcile owns.
|
|
20
|
+
* it is not a content-addressed media object this reconcile owns.
|
|
21
|
+
*/
|
|
16
22
|
export declare function reconcileMedia(storedKeys: string[], manifest: MediaManifest): ReconcileResult;
|
|
17
23
|
/** One page of an R2 list, the narrow subset the reconcile read consumes. */
|
|
18
24
|
interface ReconcileListPage {
|
|
@@ -22,18 +28,22 @@ interface ReconcileListPage {
|
|
|
22
28
|
truncated: boolean;
|
|
23
29
|
cursor?: string;
|
|
24
30
|
}
|
|
25
|
-
/**
|
|
26
|
-
*
|
|
27
|
-
*
|
|
31
|
+
/**
|
|
32
|
+
* The R2 bucket surface the reconcile read needs: a single prefixed, paginated list. A local
|
|
33
|
+
* structural interface so no `@cloudflare/workers-types` name is imported (the module is internal and
|
|
34
|
+
* on no public subpath, but the narrow seam keeps the build self-contained either way).
|
|
35
|
+
*/
|
|
28
36
|
export interface ReconcileBucket {
|
|
29
37
|
list(opts?: {
|
|
30
38
|
prefix?: string;
|
|
31
39
|
cursor?: string;
|
|
32
40
|
}): Promise<ReconcileListPage>;
|
|
33
41
|
}
|
|
34
|
-
/**
|
|
42
|
+
/**
|
|
43
|
+
* The glue runner: list every stored key under the media/ prefix (paginating through R2's
|
|
35
44
|
* cursor/truncated), reconcile against the manifest, log the count summary, and return the result.
|
|
36
45
|
* The log record carries counts only, never bytes or a key list; the keys are content hashes and so
|
|
37
|
-
* carry no PII, but the count summary is all an operator needs to size the orphan state.
|
|
46
|
+
* carry no PII, but the count summary is all an operator needs to size the orphan state.
|
|
47
|
+
*/
|
|
38
48
|
export declare function runReconcile(bucket: ReconcileBucket, manifest: MediaManifest): Promise<ReconcileResult>;
|
|
39
49
|
export {};
|
package/dist/media/reconcile.js
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
import { log } from '../log/index.js';
|
|
2
|
-
/**
|
|
3
|
-
*
|
|
2
|
+
/**
|
|
3
|
+
* A stored media object key parses to its short hash via `media/<aa>/<shortHash>.<ext>`. Exported so
|
|
4
|
+
* the orphan-scan projection derives the same hash from an orphaned key without a second grammar.
|
|
5
|
+
*/
|
|
4
6
|
export const MEDIA_KEY_RE = /^media\/[0-9a-f]{2}\/([0-9a-f]{16})\.[a-z0-9]{1,5}$/;
|
|
5
|
-
/**
|
|
7
|
+
/**
|
|
8
|
+
* The pure core: compare the stored R2 keys against the manifest's content-hash keys and report
|
|
6
9
|
* both orphan directions. A stored key that does not match the media-key grammar is ignored, since
|
|
7
|
-
* it is not a content-addressed media object this reconcile owns.
|
|
10
|
+
* it is not a content-addressed media object this reconcile owns.
|
|
11
|
+
*/
|
|
8
12
|
export function reconcileMedia(storedKeys, manifest) {
|
|
9
13
|
const manifestHashes = new Set(Object.keys(manifest));
|
|
10
14
|
const storedHashes = new Set();
|
|
@@ -24,10 +28,12 @@ export function reconcileMedia(storedKeys, manifest) {
|
|
|
24
28
|
}
|
|
25
29
|
return { orphanedObjects, missingObjects };
|
|
26
30
|
}
|
|
27
|
-
/**
|
|
31
|
+
/**
|
|
32
|
+
* The glue runner: list every stored key under the media/ prefix (paginating through R2's
|
|
28
33
|
* cursor/truncated), reconcile against the manifest, log the count summary, and return the result.
|
|
29
34
|
* The log record carries counts only, never bytes or a key list; the keys are content hashes and so
|
|
30
|
-
* carry no PII, but the count summary is all an operator needs to size the orphan state.
|
|
35
|
+
* carry no PII, but the count summary is all an operator needs to size the orphan state.
|
|
36
|
+
*/
|
|
31
37
|
export async function runReconcile(bucket, manifest) {
|
|
32
38
|
const storedKeys = [];
|
|
33
39
|
let cursor;
|
|
@@ -3,10 +3,14 @@ export interface MediaRef {
|
|
|
3
3
|
slug: string | null;
|
|
4
4
|
hash: string;
|
|
5
5
|
}
|
|
6
|
-
/**
|
|
6
|
+
/**
|
|
7
|
+
* Parse a `media:<slug>.<hash>` href (or the bare `media:<hash>` form), or null for any other
|
|
7
8
|
* href or a malformed token. Splits on the last dot, so a slug that illegally contains a dot fails
|
|
8
|
-
* the slug grammar and returns null.
|
|
9
|
+
* the slug grammar and returns null.
|
|
10
|
+
*/
|
|
9
11
|
export declare function parseMediaToken(href: string): MediaRef | null;
|
|
10
|
-
/**
|
|
11
|
-
*
|
|
12
|
+
/**
|
|
13
|
+
* Write the canonical media: token for a ref. The inverse of parseMediaToken, so a parse then
|
|
14
|
+
* write round trip is stable: `media:<slug>.<hash>` when the slug is present, else `media:<hash>`.
|
|
15
|
+
*/
|
|
12
16
|
export declare function mediaToken(ref: MediaRef): string;
|
package/dist/media/reference.js
CHANGED
|
@@ -6,13 +6,17 @@
|
|
|
6
6
|
// the grammar; it mirrors the cairn: link codec in ../content/links.ts.
|
|
7
7
|
/** A 16-character lowercase hex content-hash prefix. */
|
|
8
8
|
const HASH_RE = /^[0-9a-f]{16}$/;
|
|
9
|
-
/**
|
|
9
|
+
/**
|
|
10
|
+
* The slug grammar from the Task 2 slugify transform: lowercase alphanumerics joined by single
|
|
10
11
|
* internal hyphens, with no leading or trailing hyphen and no dot (the dot is the slug/hash
|
|
11
|
-
* separator).
|
|
12
|
+
* separator).
|
|
13
|
+
*/
|
|
12
14
|
const SLUG_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
13
|
-
/**
|
|
15
|
+
/**
|
|
16
|
+
* Parse a `media:<slug>.<hash>` href (or the bare `media:<hash>` form), or null for any other
|
|
14
17
|
* href or a malformed token. Splits on the last dot, so a slug that illegally contains a dot fails
|
|
15
|
-
* the slug grammar and returns null.
|
|
18
|
+
* the slug grammar and returns null.
|
|
19
|
+
*/
|
|
16
20
|
export function parseMediaToken(href) {
|
|
17
21
|
if (!href.startsWith('media:'))
|
|
18
22
|
return null;
|
|
@@ -26,8 +30,10 @@ export function parseMediaToken(href) {
|
|
|
26
30
|
return null;
|
|
27
31
|
return { slug, hash };
|
|
28
32
|
}
|
|
29
|
-
/**
|
|
30
|
-
*
|
|
33
|
+
/**
|
|
34
|
+
* Write the canonical media: token for a ref. The inverse of parseMediaToken, so a parse then
|
|
35
|
+
* write round trip is stable: `media:<slug>.<hash>` when the slug is present, else `media:<hash>`.
|
|
36
|
+
*/
|
|
31
37
|
export function mediaToken(ref) {
|
|
32
38
|
return ref.slug === null ? `media:${ref.hash}` : `media:${ref.slug}.${ref.hash}`;
|
|
33
39
|
}
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import type { ConceptDescriptor } from '../content/types.js';
|
|
2
2
|
import type { RepoRef } from '../github/types.js';
|
|
3
3
|
import type { Manifest } from '../content/manifest.js';
|
|
4
|
-
/**
|
|
4
|
+
/**
|
|
5
|
+
* One main entry the rewrite will touch: its identity, its file path, the transform's per-placement
|
|
5
6
|
* diff, and the rewritten markdown a later apply commits. `P` is the transform's placement type
|
|
6
|
-
* (a RepointPlacement for replace, an AltPlacement for fill-alt).
|
|
7
|
+
* (a RepointPlacement for replace, an AltPlacement for fill-alt).
|
|
8
|
+
*/
|
|
7
9
|
export interface PlannedEntry<P = unknown> {
|
|
8
10
|
/** The concept id, e.g. "posts". */
|
|
9
11
|
concept: string;
|
|
@@ -16,9 +18,11 @@ export interface PlannedEntry<P = unknown> {
|
|
|
16
18
|
/** The entry's markdown after the transform, byte-identical to the source apart from the rewrite. */
|
|
17
19
|
newMarkdown: string;
|
|
18
20
|
}
|
|
19
|
-
/**
|
|
21
|
+
/**
|
|
22
|
+
* One open edit branch that also references the asset, with the entries on it. Report-only: an apply
|
|
20
23
|
* rewrites main, never a branch, so the screen surfaces these as a delta the editor handles by
|
|
21
|
-
* republishing the draft.
|
|
24
|
+
* republishing the draft.
|
|
25
|
+
*/
|
|
22
26
|
export interface BranchRef {
|
|
23
27
|
/** The cairn/* branch name. */
|
|
24
28
|
branch: string;
|
|
@@ -28,8 +32,10 @@ export interface BranchRef {
|
|
|
28
32
|
id: string;
|
|
29
33
|
}[];
|
|
30
34
|
}
|
|
31
|
-
/**
|
|
32
|
-
*
|
|
35
|
+
/**
|
|
36
|
+
* The preview plan: the main entries to rewrite, the report-only branch delta, and the distinct
|
|
37
|
+
* count of affected main entries (the entries the transform actually changed).
|
|
38
|
+
*/
|
|
33
39
|
export interface RewritePlan<P = unknown> {
|
|
34
40
|
entries: PlannedEntry<P>[];
|
|
35
41
|
branchDelta: BranchRef[];
|
package/dist/media/sniff.d.ts
CHANGED
|
@@ -5,8 +5,10 @@
|
|
|
5
5
|
* advisory. Recognizes JPEG, PNG, GIF, WebP, and the AVIF/HEIC ISO-BMFF brands.
|
|
6
6
|
*/
|
|
7
7
|
export declare function sniffMediaType(bytes: Uint8Array): string | null;
|
|
8
|
-
/**
|
|
9
|
-
*
|
|
8
|
+
/**
|
|
9
|
+
* The storage extension for a sniffed media type, or null for a type the upload path does not store
|
|
10
|
+
* (HEIC, an unknown type). Driven by the sniffed type, so the key's ext is server-owned.
|
|
11
|
+
*/
|
|
10
12
|
export declare function extForMediaType(type: string): string | null;
|
|
11
13
|
/**
|
|
12
14
|
* The engine-level upload deny predicate. Returns true (reject) when the upload is markup a site can
|
package/dist/media/sniff.js
CHANGED
|
@@ -7,21 +7,29 @@
|
|
|
7
7
|
// and this byte check sees only the magic. The delivery route's response headers (X-Content-Type-Options:
|
|
8
8
|
// nosniff, Content-Disposition: inline, a restrictive Content-Security-Policy) are the real XSS control
|
|
9
9
|
// for the served bytes; sniffing here is the ingest gate, not the served-bytes defense.
|
|
10
|
-
/**
|
|
11
|
-
*
|
|
10
|
+
/**
|
|
11
|
+
* The leading ASCII whitespace bytes skipped before the deny-list's first-byte-is-`<` check:
|
|
12
|
+
* tab (0x09), newline (0x0A), carriage return (0x0D), and space (0x20).
|
|
13
|
+
*/
|
|
12
14
|
const WHITESPACE = new Set([0x09, 0x0a, 0x0d, 0x20]);
|
|
13
|
-
/**
|
|
14
|
-
*
|
|
15
|
+
/**
|
|
16
|
+
* The single byte `<` (0x3C). A payload whose first non-whitespace byte is `<` is markup (SVG, HTML,
|
|
17
|
+
* XML) and is denied regardless of its declared type or any site `allowedTypes`.
|
|
18
|
+
*/
|
|
15
19
|
const LT = 0x3c;
|
|
16
|
-
/**
|
|
17
|
-
*
|
|
20
|
+
/**
|
|
21
|
+
* Declared content types denied at the engine level, independent of any site `allowedTypes`. SVG and
|
|
22
|
+
* the markup types carry active content (script, foreignObject), so they never ingest as media.
|
|
23
|
+
*/
|
|
18
24
|
const DENIED_TYPES = new Set(['image/svg+xml', 'image/svg', 'text/html', 'application/xml']);
|
|
19
25
|
/** The ISO-BMFF major-brand codes (at bytes 8..11 of an `ftyp` box) that mean an AVIF image. */
|
|
20
26
|
const AVIF_BRANDS = new Set(['avif', 'avis']);
|
|
21
27
|
/** The ISO-BMFF major-brand codes that mean a HEIF/HEIC image. */
|
|
22
28
|
const HEIC_BRANDS = new Set(['heic', 'heix', 'heif', 'hevc', 'hevx', 'mif1', 'msf1']);
|
|
23
|
-
/**
|
|
24
|
-
*
|
|
29
|
+
/**
|
|
30
|
+
* True when every byte of `magic` matches `bytes` starting at `offset`. False if `bytes` is too
|
|
31
|
+
* short to hold the whole magic.
|
|
32
|
+
*/
|
|
25
33
|
function matches(bytes, offset, magic) {
|
|
26
34
|
if (bytes.length < offset + magic.length)
|
|
27
35
|
return false;
|
|
@@ -31,8 +39,10 @@ function matches(bytes, offset, magic) {
|
|
|
31
39
|
}
|
|
32
40
|
return true;
|
|
33
41
|
}
|
|
34
|
-
/**
|
|
35
|
-
*
|
|
42
|
+
/**
|
|
43
|
+
* The four ASCII characters at bytes `offset..offset+3`, or null when the input is too short. Used to
|
|
44
|
+
* read an ISO-BMFF brand code as a string.
|
|
45
|
+
*/
|
|
36
46
|
function ascii4(bytes, offset) {
|
|
37
47
|
if (bytes.length < offset + 4)
|
|
38
48
|
return null;
|
|
@@ -71,9 +81,11 @@ export function sniffMediaType(bytes) {
|
|
|
71
81
|
}
|
|
72
82
|
return null;
|
|
73
83
|
}
|
|
74
|
-
/**
|
|
84
|
+
/**
|
|
85
|
+
* The bare file extension (no dot) for each sniffed media type the upload path stores. The ext is
|
|
75
86
|
* derived from the server-sniffed type, never the client filename, so the stored key and the
|
|
76
|
-
* delivery extension allow-list always agree. An unmappable type returns null (the upload 415s).
|
|
87
|
+
* delivery extension allow-list always agree. An unmappable type returns null (the upload 415s).
|
|
88
|
+
*/
|
|
77
89
|
const EXT_BY_TYPE = {
|
|
78
90
|
'image/jpeg': 'jpg',
|
|
79
91
|
'image/png': 'png',
|
|
@@ -81,8 +93,10 @@ const EXT_BY_TYPE = {
|
|
|
81
93
|
'image/webp': 'webp',
|
|
82
94
|
'image/avif': 'avif',
|
|
83
95
|
};
|
|
84
|
-
/**
|
|
85
|
-
*
|
|
96
|
+
/**
|
|
97
|
+
* The storage extension for a sniffed media type, or null for a type the upload path does not store
|
|
98
|
+
* (HEIC, an unknown type). Driven by the sniffed type, so the key's ext is server-owned.
|
|
99
|
+
*/
|
|
86
100
|
export function extForMediaType(type) {
|
|
87
101
|
return EXT_BY_TYPE[type] ?? null;
|
|
88
102
|
}
|
package/dist/media/store.d.ts
CHANGED
|
@@ -1,17 +1,23 @@
|
|
|
1
1
|
import type { R2Bucket, R2Conditional, R2HTTPMetadata, R2Object, R2ObjectBody, R2Range } from '@cloudflare/workers-types';
|
|
2
|
-
/**
|
|
3
|
-
*
|
|
2
|
+
/**
|
|
3
|
+
* The narrow R2 surface the media pipeline uses. The engine depends on this, not on R2Bucket, so the
|
|
4
|
+
* multipart, list, and conditional-read surface R2 also carries never leaks into the media code.
|
|
5
|
+
*/
|
|
4
6
|
export interface MediaStore {
|
|
5
|
-
/**
|
|
7
|
+
/**
|
|
8
|
+
* Store bytes under a content-addressed key, with the response HTTP metadata (the content type)
|
|
6
9
|
* and optional custom metadata (the upload stores the full sha256 here, so a short-hash collision
|
|
7
|
-
* is detectable on a later dedup probe).
|
|
10
|
+
* is detectable on a later dedup probe).
|
|
11
|
+
*/
|
|
8
12
|
put(key: string, bytes: ArrayBuffer | Uint8Array, httpMetadata?: R2HTTPMetadata, customMetadata?: Record<string, string>): Promise<void>;
|
|
9
13
|
/** The object's metadata, or null when no object lives at the key (the dedup probe). */
|
|
10
14
|
head(key: string): Promise<R2Object | null>;
|
|
11
|
-
/**
|
|
15
|
+
/**
|
|
16
|
+
* The object body for streaming to a delivery response, or null when the key is absent. The
|
|
12
17
|
* delivery route passes `onlyIf` and `range` through for conditional and partial reads: an
|
|
13
18
|
* `onlyIf` etag match returns a body-less R2Object (the 304 shape), so the return widens to
|
|
14
|
-
* `R2Object` alongside `R2ObjectBody`.
|
|
19
|
+
* `R2Object` alongside `R2ObjectBody`.
|
|
20
|
+
*/
|
|
15
21
|
get(key: string, opts?: {
|
|
16
22
|
range?: R2Range;
|
|
17
23
|
onlyIf?: R2Conditional;
|
|
@@ -19,7 +25,9 @@ export interface MediaStore {
|
|
|
19
25
|
/** Remove the object at the key. A delete of an absent key is a no-op, the R2 contract. */
|
|
20
26
|
delete(key: string): Promise<void>;
|
|
21
27
|
}
|
|
22
|
-
/**
|
|
28
|
+
/**
|
|
29
|
+
* Wrap an R2 bucket binding as a MediaStore. Each method delegates to the binding; put folds the
|
|
23
30
|
* HTTP and custom metadata into R2's options shape and drops the returned R2Object the pipeline does
|
|
24
|
-
* not read.
|
|
31
|
+
* not read.
|
|
32
|
+
*/
|
|
25
33
|
export declare function r2Store(bucket: R2Bucket): MediaStore;
|
package/dist/media/store.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
/**
|
|
1
|
+
/**
|
|
2
|
+
* Wrap an R2 bucket binding as a MediaStore. Each method delegates to the binding; put folds the
|
|
2
3
|
* HTTP and custom metadata into R2's options shape and drops the returned R2Object the pipeline does
|
|
3
|
-
* not read.
|
|
4
|
+
* not read.
|
|
5
|
+
*/
|
|
4
6
|
export function r2Store(bucket) {
|
|
5
7
|
return {
|
|
6
8
|
async put(key, bytes, httpMetadata, customMetadata) {
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
/**
|
|
1
|
+
/**
|
|
2
|
+
* A single image variant: the resize and format directives Cloudflare Images applies to the
|
|
2
3
|
* original bytes. Every field is optional. width, height, quality, and fit are emitted only when
|
|
3
|
-
* set; format and gravity always appear, defaulting to auto.
|
|
4
|
+
* set; format and gravity always appear, defaulting to auto.
|
|
5
|
+
*/
|
|
4
6
|
export interface VariantSpec {
|
|
5
7
|
/** Target width in pixels. */
|
|
6
8
|
width?: number;
|
|
@@ -15,12 +17,16 @@ export interface VariantSpec {
|
|
|
15
17
|
/** Output format, `auto` to let Cloudflare negotiate, or a forced codec. */
|
|
16
18
|
format?: 'auto' | 'webp' | 'avif' | string;
|
|
17
19
|
}
|
|
18
|
-
/**
|
|
20
|
+
/**
|
|
21
|
+
* Build the on-demand Cloudflare Images transform URL for a delivery path. The options are
|
|
19
22
|
* comma-joined in the stable order width, height, quality, fit, format, gravity, with width through
|
|
20
23
|
* fit emitted only when the spec sets them and format and gravity always present (defaulting to
|
|
21
|
-
* auto). The publicPath is appended unaltered, so the result is `/cdn-cgi/image/<options><publicPath>`.
|
|
24
|
+
* auto). The publicPath is appended unaltered, so the result is `/cdn-cgi/image/<options><publicPath>`.
|
|
25
|
+
*/
|
|
22
26
|
export declare function variantUrl(publicPath: string, spec: VariantSpec): string;
|
|
23
|
-
/**
|
|
27
|
+
/**
|
|
28
|
+
* Build a variant URL from a named preset. Looks up presetName in variants and builds its spec with
|
|
24
29
|
* variantUrl. Throws a cairn:-prefixed error naming the unknown preset when the name is absent, so a
|
|
25
|
-
* typo in a preset name fails loudly rather than silently rendering an unsized image.
|
|
30
|
+
* typo in a preset name fails loudly rather than silently rendering an unsized image.
|
|
31
|
+
*/
|
|
26
32
|
export declare function presetUrl(publicPath: string, presetName: string, variants: Record<string, VariantSpec>): string;
|
|
@@ -4,10 +4,12 @@
|
|
|
4
4
|
// the option encoding and the stable option order, so the same spec always builds the same URL and
|
|
5
5
|
// a CDN cache keys on it cleanly. The delivery path is appended unaltered, since it already carries
|
|
6
6
|
// its own leading slash.
|
|
7
|
-
/**
|
|
7
|
+
/**
|
|
8
|
+
* Build the on-demand Cloudflare Images transform URL for a delivery path. The options are
|
|
8
9
|
* comma-joined in the stable order width, height, quality, fit, format, gravity, with width through
|
|
9
10
|
* fit emitted only when the spec sets them and format and gravity always present (defaulting to
|
|
10
|
-
* auto). The publicPath is appended unaltered, so the result is `/cdn-cgi/image/<options><publicPath>`.
|
|
11
|
+
* auto). The publicPath is appended unaltered, so the result is `/cdn-cgi/image/<options><publicPath>`.
|
|
12
|
+
*/
|
|
11
13
|
export function variantUrl(publicPath, spec) {
|
|
12
14
|
const options = [];
|
|
13
15
|
if (spec.width !== undefined)
|
|
@@ -26,9 +28,11 @@ export function variantUrl(publicPath, spec) {
|
|
|
26
28
|
const source = publicPath.startsWith('/') ? publicPath : `/${publicPath}`;
|
|
27
29
|
return `/cdn-cgi/image/${options.join(',')}${source}`;
|
|
28
30
|
}
|
|
29
|
-
/**
|
|
31
|
+
/**
|
|
32
|
+
* Build a variant URL from a named preset. Looks up presetName in variants and builds its spec with
|
|
30
33
|
* variantUrl. Throws a cairn:-prefixed error naming the unknown preset when the name is absent, so a
|
|
31
|
-
* typo in a preset name fails loudly rather than silently rendering an unsized image.
|
|
34
|
+
* typo in a preset name fails loudly rather than silently rendering an unsized image.
|
|
35
|
+
*/
|
|
32
36
|
export function presetUrl(publicPath, presetName, variants) {
|
|
33
37
|
const spec = variants[presetName];
|
|
34
38
|
if (spec === undefined) {
|
package/dist/media/usage.d.ts
CHANGED
|
@@ -21,13 +21,17 @@ export interface UsageEntry {
|
|
|
21
21
|
/** Published vs the cairn/* branch the edit lives on. */
|
|
22
22
|
origin: UsageOrigin;
|
|
23
23
|
}
|
|
24
|
-
/**
|
|
25
|
-
*
|
|
24
|
+
/**
|
|
25
|
+
* Content hash to the distinct entries that reference it. A hash with no row is "no references
|
|
26
|
+
* found" (see the raw-HTML caveat above), never a proven orphan.
|
|
27
|
+
*/
|
|
26
28
|
export type UsageIndex = Map<string, UsageEntry[]>;
|
|
27
|
-
/**
|
|
29
|
+
/**
|
|
30
|
+
* Build options. `branches` lets a caller that already listed the open cairn/* branches pass them
|
|
28
31
|
* in so the index does not list them a second time (the load path lists once for the media-union).
|
|
29
32
|
* `strict` flips the per-branch read from degrade-and-skip to fail-closed: a delete gate must not
|
|
30
|
-
* treat a transient branch-read failure as an absent reference, so it rethrows instead.
|
|
33
|
+
* treat a transient branch-read failure as an absent reference, so it rethrows instead.
|
|
34
|
+
*/
|
|
31
35
|
export interface BuildUsageOptions {
|
|
32
36
|
/** The open cairn/* branch names, already listed. When present the index skips its own listing. */
|
|
33
37
|
branches?: string[];
|
|
@@ -34,16 +34,20 @@ export interface SiteConfig {
|
|
|
34
34
|
menus?: Record<string, unknown>;
|
|
35
35
|
/** Per-concept URL policy: the permalink pattern and date-prefix granularity, keyed by concept id. */
|
|
36
36
|
content?: Record<string, ConceptUrlPolicy>;
|
|
37
|
-
/**
|
|
37
|
+
/**
|
|
38
|
+
* The editor spellcheck settings. The dialect is declared once per site (spec 1.2), so a British
|
|
38
39
|
* site loads the British word list and "colour" reads as correct. Today only US English ships, so an
|
|
39
|
-
* unset or unknown dialect resolves to it.
|
|
40
|
+
* unset or unknown dialect resolves to it.
|
|
41
|
+
*/
|
|
40
42
|
spellcheck?: {
|
|
41
43
|
dialect?: string;
|
|
42
44
|
};
|
|
43
|
-
/**
|
|
45
|
+
/**
|
|
46
|
+
* The editor tidy (LLM copy-edit) settings. Opt-in at the site level (spec 2.8): tidy is a remote,
|
|
44
47
|
* costly model call, so the whole block is optional and `enabled` defaults false. The model is a
|
|
45
48
|
* developer-tier fact; the `conventions` block is the editor-tier per-convention config that builds
|
|
46
|
-
* the prompt's CONVENTIONS section. The Anthropic API key is a Worker secret, never config.
|
|
49
|
+
* the prompt's CONVENTIONS section. The Anthropic API key is a Worker secret, never config.
|
|
50
|
+
*/
|
|
47
51
|
tidy?: TidyConfig;
|
|
48
52
|
[key: string]: unknown;
|
|
49
53
|
}
|
|
@@ -69,14 +73,18 @@ export declare const DEFAULT_TIDY_MODEL = "claude-sonnet-4-6";
|
|
|
69
73
|
* Sentence spacing is dropped on purpose and regional spelling is `spellcheck.dialect`, not a toggle.
|
|
70
74
|
*/
|
|
71
75
|
export interface TidyConventions {
|
|
72
|
-
/**
|
|
76
|
+
/**
|
|
77
|
+
* The objective Fixes group (spelling, grammar, doubled words, whitespace, capitals, terminal
|
|
73
78
|
* punctuation). Default on. The always-on core governs it; this toggle lets the screen turn the
|
|
74
|
-
* group off.
|
|
79
|
+
* group off.
|
|
80
|
+
*/
|
|
75
81
|
fixes: boolean;
|
|
76
82
|
/** Oxford comma position. Off when undefined; `always` | `complex-only` (AP) | `never`. */
|
|
77
83
|
oxfordComma?: 'always' | 'complex-only' | 'never';
|
|
78
|
-
/**
|
|
79
|
-
*
|
|
84
|
+
/**
|
|
85
|
+
* Number style threshold. Off when undefined; the always-numeral exception sets (ages, dates,
|
|
86
|
+
* measurements, percentages) apply at any threshold.
|
|
87
|
+
*/
|
|
80
88
|
numberStyle?: 'under-ten' | 'under-hundred' | 'always-numerals';
|
|
81
89
|
/** Measurement notation only (never the system, never the number). Off when undefined. */
|
|
82
90
|
measurements?: 'abbreviate' | 'spell-out';
|
|
@@ -1,21 +1,31 @@
|
|
|
1
1
|
import type { ComponentDef, ComponentValues } from './registry.js';
|
|
2
|
+
/**
|
|
3
|
+
*
|
|
4
|
+
*/
|
|
2
5
|
export declare function serializeComponent(def: ComponentDef, values: ComponentValues): string;
|
|
3
|
-
/**
|
|
6
|
+
/**
|
|
7
|
+
* Parse a serialized component directive back into guided-form values, the inverse of
|
|
4
8
|
* {@link serializeComponent}. The grammar is reversible, so the editor can round-trip a
|
|
5
|
-
* saved directive through the form.
|
|
9
|
+
* saved directive through the form.
|
|
10
|
+
*/
|
|
6
11
|
export declare function parseComponent(markdown: string, def: ComponentDef): Promise<ComponentValues>;
|
|
7
|
-
/**
|
|
8
|
-
*
|
|
12
|
+
/**
|
|
13
|
+
* The raw attribute keys present on the component's opening directive, read from the parsed tree
|
|
14
|
+
* (quote-aware, unlike a regex over the source). Used by validation to flag unknown keys.
|
|
15
|
+
*/
|
|
9
16
|
export declare function parseRawAttributeKeys(markdown: string, def: ComponentDef): string[];
|
|
10
|
-
/**
|
|
11
|
-
*
|
|
17
|
+
/**
|
|
18
|
+
* The result of {@link componentRoundTripSafety}: whether re-opening a placed block into the
|
|
19
|
+
* guided form and re-serializing it is provably lossless.
|
|
20
|
+
*/
|
|
12
21
|
export type RoundTripSafety = {
|
|
13
22
|
safe: true;
|
|
14
23
|
} | {
|
|
15
24
|
safe: false;
|
|
16
25
|
reason: 'unknown-attribute' | 'undeclared-child' | 'not-idempotent' | 'not-a-component';
|
|
17
26
|
};
|
|
18
|
-
/**
|
|
27
|
+
/**
|
|
28
|
+
* Decide whether guided edit of this placed block is provably lossless. A block a person typed by
|
|
19
29
|
* hand can carry more than the schema models (an attribute the def does not list, a child container
|
|
20
30
|
* the def does not declare, slot content the form cannot represent stably), and parsing such a block
|
|
21
31
|
* into the form then re-serializing would silently drop it. The edit affordance is offered only when
|
|
@@ -25,11 +35,14 @@ export type RoundTripSafety = {
|
|
|
25
35
|
* 2. `unknown-attribute`: the block carries an attribute key the def does not declare.
|
|
26
36
|
* 3. `undeclared-child`: the root has a direct child container directive that is not a declared
|
|
27
37
|
* nested slot. Such a child would otherwise fold into the body slot and move on re-serialize.
|
|
28
|
-
* 4. `not-idempotent`: `parse -> serialize -> parse` does not recover the same values.
|
|
38
|
+
* 4. `not-idempotent`: `parse -> serialize -> parse` does not recover the same values.
|
|
39
|
+
*/
|
|
29
40
|
export declare function componentRoundTripSafety(markdown: string, def: ComponentDef): Promise<RoundTripSafety>;
|
|
30
|
-
/**
|
|
41
|
+
/**
|
|
42
|
+
* Parse the component once and derive both the guided-form values and the raw attribute keys.
|
|
31
43
|
* Validation needs both, so this seam spares it the double parse that calling
|
|
32
|
-
* {@link parseComponent} and {@link parseRawAttributeKeys} separately would cost.
|
|
44
|
+
* {@link parseComponent} and {@link parseRawAttributeKeys} separately would cost.
|
|
45
|
+
*/
|
|
33
46
|
export declare function parseComponentWithRawKeys(markdown: string, def: ComponentDef): Promise<{
|
|
34
47
|
values: ComponentValues;
|
|
35
48
|
rawKeys: string[];
|