@glw907/cairn-cms 0.60.0 → 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 +82 -0
- package/dist/components/AdminLayout.svelte +152 -229
- package/dist/components/CairnAdmin.svelte +13 -42
- package/dist/components/CairnLogo.svelte +1 -6
- package/dist/components/CairnMediaLibrary.svelte +821 -1210
- package/dist/components/CairnTidySettings.svelte +194 -261
- package/dist/components/CairnTidySettings.svelte.d.ts +1 -1
- package/dist/components/ComponentForm.svelte +110 -185
- package/dist/components/ComponentInsertDialog.svelte +163 -283
- package/dist/components/ConceptList.svelte +111 -191
- package/dist/components/ConfirmPage.svelte +5 -12
- package/dist/components/CsrfField.svelte +5 -11
- package/dist/components/DeleteDialog.svelte +15 -42
- package/dist/components/EditPage.svelte +781 -1205
- package/dist/components/EditorToolbar.svelte +108 -170
- package/dist/components/HelpHome.svelte +824 -0
- package/dist/components/HelpHome.svelte.d.ts +22 -0
- package/dist/components/IconPicker.svelte +23 -53
- package/dist/components/LinkPicker.svelte +34 -58
- package/dist/components/LoginPage.svelte +14 -27
- package/dist/components/ManageEditors.svelte +3 -15
- package/dist/components/MarkdownEditor.svelte +689 -957
- package/dist/components/MarkdownHelpDialog.svelte +12 -27
- package/dist/components/MediaCaptureCard.svelte +18 -57
- package/dist/components/MediaFigureControl.svelte +32 -71
- package/dist/components/MediaHeroField.svelte +210 -329
- package/dist/components/MediaInsertPopover.svelte +156 -283
- package/dist/components/MediaPicker.svelte +67 -131
- package/dist/components/NavTree.svelte +46 -78
- package/dist/components/RenameDialog.svelte +16 -43
- package/dist/components/ShortcutsDialog.svelte +9 -13
- package/dist/components/ShortcutsGrid.svelte +1 -2
- package/dist/components/TidyReview.svelte +140 -248
- package/dist/components/WebLinkDialog.svelte +19 -40
- package/dist/components/cairn-admin.css +4 -0
- 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 +57 -29
- package/dist/components/spellcheck.js +50 -20
- 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/CairnHead.svelte +8 -11
- 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 +8 -2
- 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 +92 -40
- 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
|
@@ -9,196 +9,121 @@ This is not a nested HTML form; Insert calls a callback. The dialog owns the hea
|
|
|
9
9
|
group breadcrumb and the Back control) and, in the two-pane case, the preview pane; this component
|
|
10
10
|
binds out its live `values` and `incomplete` so the dialog can render that preview.
|
|
11
11
|
-->
|
|
12
|
-
<script lang="ts">
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
12
|
+
<script lang="ts">import { untrack } from "svelte";
|
|
13
|
+
import { previewValues } from "../render/registry.js";
|
|
14
|
+
import { buildComponentInsert } from "../render/component-insert.js";
|
|
15
|
+
import IconPicker from "./IconPicker.svelte";
|
|
16
|
+
let {
|
|
17
|
+
def,
|
|
18
|
+
icons,
|
|
19
|
+
onInsert,
|
|
20
|
+
values = $bindable(),
|
|
21
|
+
incomplete = $bindable(),
|
|
22
|
+
initial,
|
|
23
|
+
submitLabel = "Insert"
|
|
24
|
+
} = $props();
|
|
25
|
+
let working = $state(untrack(() => initial ?? previewValues(def)));
|
|
26
|
+
$effect(() => {
|
|
27
|
+
values = working;
|
|
28
|
+
});
|
|
29
|
+
const attributes = $derived(def.attributes ?? []);
|
|
30
|
+
const flatSlots = $derived((def.slots ?? []).filter((s) => s.kind !== "repeatable"));
|
|
31
|
+
const repeatableSlots = $derived((def.slots ?? []).filter((s) => s.kind === "repeatable"));
|
|
32
|
+
function slotItems(name) {
|
|
33
|
+
const v = working.slots[name];
|
|
34
|
+
return Array.isArray(v) ? v : [];
|
|
35
|
+
}
|
|
36
|
+
let nextId = 0;
|
|
37
|
+
const itemIds = $state(
|
|
38
|
+
untrack(
|
|
39
|
+
() => Object.fromEntries(
|
|
40
|
+
(def.slots ?? []).filter((s) => s.kind === "repeatable").map((s) => {
|
|
41
|
+
const seeded = working.slots[s.name];
|
|
42
|
+
const count = Array.isArray(seeded) ? seeded.length : 0;
|
|
43
|
+
return [s.name, Array.from({ length: count }, () => nextId++)];
|
|
44
|
+
})
|
|
45
|
+
)
|
|
46
|
+
)
|
|
47
|
+
);
|
|
48
|
+
function slotIds(name) {
|
|
49
|
+
return itemIds[name] ?? [];
|
|
50
|
+
}
|
|
51
|
+
function addItem(name) {
|
|
52
|
+
slotItems(name).push("");
|
|
53
|
+
slotIds(name).push(nextId++);
|
|
54
|
+
}
|
|
55
|
+
function removeItem(name, index) {
|
|
56
|
+
slotItems(name).splice(index, 1);
|
|
57
|
+
slotIds(name).splice(index, 1);
|
|
58
|
+
}
|
|
59
|
+
function rowLabel(slot, value, index) {
|
|
60
|
+
const fallback = `${slot.label} ${index + 1}`;
|
|
61
|
+
if (!slot.itemLabel) return fallback;
|
|
62
|
+
const key = slot.itemFields?.[0]?.key ?? "text";
|
|
63
|
+
const derived = slot.itemLabel({ [key]: value }, index);
|
|
64
|
+
return derived && derived.trim() ? derived : fallback;
|
|
65
|
+
}
|
|
66
|
+
function asString(key) {
|
|
67
|
+
const v = working.attributes[key];
|
|
68
|
+
return typeof v === "string" ? v : "";
|
|
69
|
+
}
|
|
70
|
+
function asBool(key) {
|
|
71
|
+
return working.attributes[key] === true;
|
|
72
|
+
}
|
|
73
|
+
function slotString(name) {
|
|
74
|
+
const v = working.slots[name];
|
|
75
|
+
return typeof v === "string" ? v : "";
|
|
76
|
+
}
|
|
77
|
+
const incompleteState = $derived.by(() => {
|
|
78
|
+
for (const field of attributes) {
|
|
79
|
+
if (!field.required || field.type === "boolean") continue;
|
|
80
|
+
if (asString(field.key) === "") return true;
|
|
67
81
|
}
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
// reads, so the emitted markdown is unchanged. The preview seed can fill a repeatable slot, so
|
|
74
|
-
// each seeded item needs a parallel id from the start.
|
|
75
|
-
let nextId = 0;
|
|
76
|
-
const itemIds = $state<Record<string, number[]>>(
|
|
77
|
-
untrack(() =>
|
|
78
|
-
Object.fromEntries(
|
|
79
|
-
(def.slots ?? [])
|
|
80
|
-
.filter((s) => s.kind === 'repeatable')
|
|
81
|
-
.map((s) => {
|
|
82
|
-
const seeded = working.slots[s.name];
|
|
83
|
-
const count = Array.isArray(seeded) ? seeded.length : 0;
|
|
84
|
-
return [s.name, Array.from({ length: count }, () => nextId++)];
|
|
85
|
-
}),
|
|
86
|
-
),
|
|
87
|
-
),
|
|
88
|
-
);
|
|
89
|
-
|
|
90
|
-
// previewValues and the itemIds seed both cover every repeatable slot, so this read always hits.
|
|
91
|
-
function slotIds(name: string): number[] {
|
|
92
|
-
return itemIds[name] ?? [];
|
|
82
|
+
for (const slot of def.slots ?? []) {
|
|
83
|
+
if (!slot.required) continue;
|
|
84
|
+
const v = working.slots[slot.name];
|
|
85
|
+
const filled = Array.isArray(v) ? v.some((i) => i !== "") : typeof v === "string" && v !== "";
|
|
86
|
+
if (!filled) return true;
|
|
93
87
|
}
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
const fallback = `${slot.label} ${index + 1}`;
|
|
110
|
-
if (!slot.itemLabel) return fallback;
|
|
111
|
-
const key = slot.itemFields?.[0]?.key ?? 'text';
|
|
112
|
-
const derived = slot.itemLabel({ [key]: value }, index);
|
|
113
|
-
return derived && derived.trim() ? derived : fallback;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
// Typed accessors over the unions so explicit value targets stay sound.
|
|
117
|
-
function asString(key: string): string {
|
|
118
|
-
const v = working.attributes[key];
|
|
119
|
-
return typeof v === 'string' ? v : '';
|
|
120
|
-
}
|
|
121
|
-
function asBool(key: string): boolean {
|
|
122
|
-
return working.attributes[key] === true;
|
|
123
|
-
}
|
|
124
|
-
function slotString(name: string): string {
|
|
125
|
-
const v = working.slots[name];
|
|
126
|
-
return typeof v === 'string' ? v : '';
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
// A required attribute is unmet only for a text/select/icon field left empty; a boolean is always
|
|
130
|
-
// met (its false is a real choice). A required slot is unmet when its string is empty or its
|
|
131
|
-
// repeatable list has no non-empty item. This drives the asterisk-marked fields, the disabled
|
|
132
|
-
// Insert, and (through the bound `incomplete`) the dialog's incomplete preview state.
|
|
133
|
-
const incompleteState = $derived.by(() => {
|
|
134
|
-
for (const field of attributes) {
|
|
135
|
-
if (!field.required || field.type === 'boolean') continue;
|
|
136
|
-
if (asString(field.key) === '') return true;
|
|
88
|
+
return false;
|
|
89
|
+
});
|
|
90
|
+
$effect(() => {
|
|
91
|
+
incomplete = incompleteState;
|
|
92
|
+
});
|
|
93
|
+
let submitErrors = $state({});
|
|
94
|
+
let touched = $state({});
|
|
95
|
+
function markTouched(key) {
|
|
96
|
+
touched[key] = true;
|
|
97
|
+
}
|
|
98
|
+
const errors = $derived.by(() => {
|
|
99
|
+
const out = {};
|
|
100
|
+
for (const field of attributes) {
|
|
101
|
+
if (field.required && field.type !== "boolean" && touched[field.key] && asString(field.key) === "") {
|
|
102
|
+
out[field.key] = `${field.label} is required.`;
|
|
137
103
|
}
|
|
138
|
-
for (const slot of def.slots ?? []) {
|
|
139
|
-
if (!slot.required) continue;
|
|
140
|
-
const v = working.slots[slot.name];
|
|
141
|
-
const filled = Array.isArray(v) ? v.some((i) => i !== '') : typeof v === 'string' && v !== '';
|
|
142
|
-
if (!filled) return true;
|
|
143
|
-
}
|
|
144
|
-
return false;
|
|
145
|
-
});
|
|
146
|
-
$effect(() => {
|
|
147
|
-
incomplete = incompleteState;
|
|
148
|
-
});
|
|
149
|
-
|
|
150
|
-
// Field-keyed validation errors from the last submit (pattern, validate, select-domain), keyed by
|
|
151
|
-
// attribute key or slot name.
|
|
152
|
-
let submitErrors = $state<Record<string, string>>({});
|
|
153
|
-
|
|
154
|
-
// Fields the editor has touched, so a required-empty error shows after interaction rather than on
|
|
155
|
-
// a fresh open. Keyed by attribute key or slot name.
|
|
156
|
-
let touched = $state<Record<string, boolean>>({});
|
|
157
|
-
function markTouched(key: string): void {
|
|
158
|
-
touched[key] = true;
|
|
159
104
|
}
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
});
|
|
181
|
-
|
|
182
|
-
// The form container. Once it is bound, focus its first focusable control so the editor types
|
|
183
|
-
// straight into the form. The effect tracks formEl so it runs when the node lands; the focus call
|
|
184
|
-
// is untracked so a later value change does not steal focus back to the first field.
|
|
185
|
-
let formEl = $state<HTMLElement | null>(null);
|
|
186
|
-
$effect(() => {
|
|
187
|
-
if (!formEl) return;
|
|
188
|
-
untrack(() => formEl!.querySelector<HTMLElement>('input, select, textarea')?.focus());
|
|
189
|
-
});
|
|
190
|
-
|
|
191
|
-
// Serialize and validate through the pure helper. On success clear errors and emit the markdown;
|
|
192
|
-
// on failure keep the field-keyed errors so each field can show its message and insert nothing.
|
|
193
|
-
async function submit() {
|
|
194
|
-
const result = await buildComponentInsert(def, working);
|
|
195
|
-
if (result.ok) {
|
|
196
|
-
submitErrors = {};
|
|
197
|
-
onInsert(result.markdown);
|
|
198
|
-
} else {
|
|
199
|
-
submitErrors = result.errors;
|
|
200
|
-
}
|
|
105
|
+
for (const slot of def.slots ?? []) {
|
|
106
|
+
if (!slot.required) continue;
|
|
107
|
+
const v = working.slots[slot.name];
|
|
108
|
+
const filled = Array.isArray(v) ? v.some((i) => i !== "") : typeof v === "string" && v !== "";
|
|
109
|
+
if (touched[slot.name] && !filled) out[slot.name] = `${slot.label} is required.`;
|
|
110
|
+
}
|
|
111
|
+
return { ...out, ...submitErrors };
|
|
112
|
+
});
|
|
113
|
+
let formEl = $state(null);
|
|
114
|
+
$effect(() => {
|
|
115
|
+
if (!formEl) return;
|
|
116
|
+
untrack(() => formEl.querySelector("input, select, textarea")?.focus());
|
|
117
|
+
});
|
|
118
|
+
async function submit() {
|
|
119
|
+
const result = await buildComponentInsert(def, working);
|
|
120
|
+
if (result.ok) {
|
|
121
|
+
submitErrors = {};
|
|
122
|
+
onInsert(result.markdown);
|
|
123
|
+
} else {
|
|
124
|
+
submitErrors = result.errors;
|
|
201
125
|
}
|
|
126
|
+
}
|
|
202
127
|
</script>
|
|
203
128
|
|
|
204
129
|
<div class="flex flex-col gap-3" bind:this={formEl}>
|