@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
|
@@ -5,304 +5,184 @@ intended use. A component with a schema opens the guided ComponentForm; a templa
|
|
|
5
5
|
inserts directly; a component with neither is not listed. Built on a native <dialog> for focus
|
|
6
6
|
trapping and Escape, following the dropdown's a11y conventions used elsewhere in the admin.
|
|
7
7
|
-->
|
|
8
|
-
<script module lang="ts">
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
export function insertableDefs(registry?: ComponentRegistry): ComponentDef[] {
|
|
26
|
-
return (registry?.defs ?? []).filter(
|
|
27
|
-
(def) => (hasSchema(def) || Boolean(def.insertTemplate)) && !def.hidden,
|
|
28
|
-
);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/** A heading-bearing group of rows. A group with an empty heading renders without an eyebrow.
|
|
32
|
-
* Groups appear in first-declared order, and rows keep declaration order within each group. */
|
|
33
|
-
interface CatalogGroup {
|
|
34
|
-
heading: string;
|
|
35
|
-
defs: ComponentDef[];
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/** Partition the defs into groups by `def.group`, preserving declaration order of both the
|
|
39
|
-
* groups (first time a group name is seen) and the rows within a group. A def with no `group`
|
|
40
|
-
* collects into one leading default group with no heading. */
|
|
41
|
-
function groupDefs(defs: ComponentDef[]): CatalogGroup[] {
|
|
42
|
-
const order: string[] = [];
|
|
43
|
-
const byHeading = new Map<string, ComponentDef[]>();
|
|
44
|
-
for (const def of defs) {
|
|
45
|
-
const heading = def.group ?? '';
|
|
46
|
-
if (!byHeading.has(heading)) {
|
|
47
|
-
byHeading.set(heading, []);
|
|
48
|
-
order.push(heading);
|
|
49
|
-
}
|
|
50
|
-
byHeading.get(heading)!.push(def);
|
|
8
|
+
<script module lang="ts">const SEARCH_THRESHOLD = 8;
|
|
9
|
+
export function hasSchema(def) {
|
|
10
|
+
return (def.attributes?.length ?? 0) > 0 || (def.slots?.length ?? 0) > 0;
|
|
11
|
+
}
|
|
12
|
+
export function insertableDefs(registry) {
|
|
13
|
+
return (registry?.defs ?? []).filter(
|
|
14
|
+
(def) => (hasSchema(def) || Boolean(def.insertTemplate)) && !def.hidden
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
function groupDefs(defs) {
|
|
18
|
+
const order = [];
|
|
19
|
+
const byHeading = /* @__PURE__ */ new Map();
|
|
20
|
+
for (const def of defs) {
|
|
21
|
+
const heading = def.group ?? "";
|
|
22
|
+
if (!byHeading.has(heading)) {
|
|
23
|
+
byHeading.set(heading, []);
|
|
24
|
+
order.push(heading);
|
|
51
25
|
}
|
|
52
|
-
|
|
26
|
+
byHeading.get(heading).push(def);
|
|
53
27
|
}
|
|
28
|
+
return order.map((heading) => ({ heading, defs: byHeading.get(heading) }));
|
|
29
|
+
}
|
|
54
30
|
</script>
|
|
55
31
|
|
|
56
|
-
<script lang="ts">
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
render?: (md: string, opts?: { stagger?: boolean; resolve?: LinkResolve }) => string | Promise<string>;
|
|
81
|
-
/** The adapter's resolved preview knob (stylesheets and container class), threaded to
|
|
82
|
-
* buildPreviewDoc so the preview frame links the site's own CSS, the same as EditPage. */
|
|
83
|
-
preview?: ResolvedPreview | null;
|
|
84
|
-
/** Disable the trigger; the host sets it while Preview shows. */
|
|
85
|
-
disabled?: boolean;
|
|
86
|
-
/** Render the built-in Insert block trigger. False mounts only the dialog, for a host that
|
|
87
|
-
* supplies its own trigger and opens the dialog through the exported open(). */
|
|
88
|
-
trigger?: boolean;
|
|
32
|
+
<script lang="ts">import { tick } from "svelte";
|
|
33
|
+
import { serializeComponent } from "../render/component-grammar.js";
|
|
34
|
+
import { buildPreviewDoc } from "./preview-doc.js";
|
|
35
|
+
import ComponentForm from "./ComponentForm.svelte";
|
|
36
|
+
let { registry, insert, update, icons, render, preview = null, disabled = false, trigger = true } = $props();
|
|
37
|
+
let dialog = $state(null);
|
|
38
|
+
let picked = $state(null);
|
|
39
|
+
let query = $state("");
|
|
40
|
+
let searchInput = $state(null);
|
|
41
|
+
let editValues = $state(null);
|
|
42
|
+
let editRange = $state(null);
|
|
43
|
+
const editing = $derived(editValues !== null);
|
|
44
|
+
let formValues = $state(void 0);
|
|
45
|
+
let formIncomplete = $state(false);
|
|
46
|
+
const twoPane = $derived(Boolean(picked?.preview) && Boolean(render));
|
|
47
|
+
let previewState = $state("settled");
|
|
48
|
+
let previewDoc = $state("");
|
|
49
|
+
const emptyRequired = $derived.by(() => {
|
|
50
|
+
if (!picked || !formValues) return [];
|
|
51
|
+
const out = [];
|
|
52
|
+
for (const field of picked.attributes ?? []) {
|
|
53
|
+
if (!field.required || field.type === "boolean") continue;
|
|
54
|
+
const v = formValues.attributes[field.key];
|
|
55
|
+
if (typeof v !== "string" || v === "") out.push(field.label);
|
|
89
56
|
}
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
let query = $state('');
|
|
96
|
-
let searchInput = $state<HTMLInputElement | null>(null);
|
|
97
|
-
|
|
98
|
-
// Edit mode re-opens a placed component into the same guided form. editValues seeds the form from
|
|
99
|
-
// the parsed block (not the catalog pick path), and editRange is the source span Update replaces.
|
|
100
|
-
// Both are null in the catalog insert flow. resetPreview clears them so a fresh open is clean.
|
|
101
|
-
let editValues = $state<ComponentValues | null>(null);
|
|
102
|
-
let editRange = $state<{ from: number; to: number } | null>(null);
|
|
103
|
-
const editing = $derived(editValues !== null);
|
|
104
|
-
|
|
105
|
-
// The form's live values and its required-empty state, bound out of ComponentForm so the preview
|
|
106
|
-
// pane can render from them and mirror the incomplete state.
|
|
107
|
-
let formValues = $state<ComponentValues | undefined>(undefined);
|
|
108
|
-
let formIncomplete = $state(false);
|
|
109
|
-
|
|
110
|
-
// Two-pane configure is opt-in: it appears only when the picked component declares a `preview`
|
|
111
|
-
// AND a render function is threaded. Otherwise the configure step stays single column.
|
|
112
|
-
const twoPane = $derived(Boolean(picked?.preview) && Boolean(render));
|
|
113
|
-
|
|
114
|
-
// The preview pane's settle state, the honest chip the mockup names. The empty/incomplete state
|
|
115
|
-
// wins over settling so the pane never claims to settle a fabricated block.
|
|
116
|
-
type PreviewState = 'settling' | 'settled' | 'failed';
|
|
117
|
-
let previewState = $state<PreviewState>('settled');
|
|
118
|
-
let previewDoc = $state('');
|
|
119
|
-
|
|
120
|
-
// The required regions left empty, named for the incomplete-state callout. A boolean attribute is
|
|
121
|
-
// always met; a text/select/icon attribute or a slot is unmet when empty.
|
|
122
|
-
const emptyRequired = $derived.by(() => {
|
|
123
|
-
if (!picked || !formValues) return [] as string[];
|
|
124
|
-
const out: string[] = [];
|
|
125
|
-
for (const field of picked.attributes ?? []) {
|
|
126
|
-
if (!field.required || field.type === 'boolean') continue;
|
|
127
|
-
const v = formValues.attributes[field.key];
|
|
128
|
-
if (typeof v !== 'string' || v === '') out.push(field.label);
|
|
129
|
-
}
|
|
130
|
-
for (const slot of picked.slots ?? []) {
|
|
131
|
-
if (!slot.required) continue;
|
|
132
|
-
const v = formValues.slots[slot.name];
|
|
133
|
-
const filled = Array.isArray(v) ? v.some((i) => i !== '') : typeof v === 'string' && v !== '';
|
|
134
|
-
if (!filled) out.push(slot.label);
|
|
135
|
-
}
|
|
136
|
-
return out;
|
|
137
|
-
});
|
|
138
|
-
|
|
139
|
-
// The debounced, latest-wins preview render, the same shape EditPage's preview effect uses: a
|
|
140
|
-
// setTimeout debounce (~200ms) guarded by a plain counter so a slow earlier render that resolves
|
|
141
|
-
// after a newer one started is discarded, one persistent iframe whose srcdoc is replaced. The
|
|
142
|
-
// incomplete state short-circuits the render (the skeleton renders from the template, not the
|
|
143
|
-
// pipeline), so a required-empty block never reaches the site render as a fabricated finish.
|
|
144
|
-
let previewRun = 0;
|
|
145
|
-
$effect(() => {
|
|
146
|
-
if (!twoPane || !render || !picked || !formValues) return;
|
|
147
|
-
if (formIncomplete) {
|
|
148
|
-
previewState = 'settled';
|
|
149
|
-
previewRun++;
|
|
150
|
-
return;
|
|
151
|
-
}
|
|
152
|
-
const md = serializeComponent(picked, formValues);
|
|
153
|
-
const run = ++previewRun;
|
|
154
|
-
previewState = 'settling';
|
|
155
|
-
const handle = setTimeout(async () => {
|
|
156
|
-
try {
|
|
157
|
-
const html = await render(md);
|
|
158
|
-
if (run === previewRun) {
|
|
159
|
-
previewDoc = buildPreviewDoc(html, preview);
|
|
160
|
-
previewState = 'settled';
|
|
161
|
-
}
|
|
162
|
-
} catch {
|
|
163
|
-
if (run === previewRun) {
|
|
164
|
-
previewState = 'failed';
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
}, 200);
|
|
168
|
-
return () => {
|
|
169
|
-
clearTimeout(handle);
|
|
170
|
-
previewRun++;
|
|
171
|
-
};
|
|
172
|
-
});
|
|
173
|
-
|
|
174
|
-
const defs = $derived(insertableDefs(registry));
|
|
175
|
-
/** The catalog grows a search input only once the actionable count crosses the threshold. */
|
|
176
|
-
const showSearch = $derived(defs.length > SEARCH_THRESHOLD);
|
|
177
|
-
/** The defs matching the live query, by label or description (case-insensitive). With no query
|
|
178
|
-
* the whole catalog shows. */
|
|
179
|
-
const filtered = $derived.by(() => {
|
|
180
|
-
const q = query.trim().toLowerCase();
|
|
181
|
-
if (!q) return defs;
|
|
182
|
-
return defs.filter(
|
|
183
|
-
(def) =>
|
|
184
|
-
def.label.toLowerCase().includes(q) || (def.description ?? '').toLowerCase().includes(q),
|
|
185
|
-
);
|
|
186
|
-
});
|
|
187
|
-
const groups = $derived(groupDefs(filtered));
|
|
188
|
-
|
|
189
|
-
/** Clear the four preview-coupled state cells so no stale preview HTML or settle state survives a
|
|
190
|
-
* re-open, a step back, or a fresh pick. Called from every transition that leaves the configure
|
|
191
|
-
* step or starts a new one. */
|
|
192
|
-
function resetPreview() {
|
|
193
|
-
formValues = undefined;
|
|
194
|
-
formIncomplete = false;
|
|
195
|
-
previewState = 'settled';
|
|
196
|
-
previewDoc = '';
|
|
197
|
-
editValues = null;
|
|
198
|
-
editRange = null;
|
|
57
|
+
for (const slot of picked.slots ?? []) {
|
|
58
|
+
if (!slot.required) continue;
|
|
59
|
+
const v = formValues.slots[slot.name];
|
|
60
|
+
const filled = Array.isArray(v) ? v.some((i) => i !== "") : typeof v === "string" && v !== "";
|
|
61
|
+
if (!filled) out.push(slot.label);
|
|
199
62
|
}
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
63
|
+
return out;
|
|
64
|
+
});
|
|
65
|
+
let previewRun = 0;
|
|
66
|
+
$effect(() => {
|
|
67
|
+
if (!twoPane || !render || !picked || !formValues) return;
|
|
68
|
+
if (formIncomplete) {
|
|
69
|
+
previewState = "settled";
|
|
70
|
+
previewRun++;
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
const md = serializeComponent(picked, formValues);
|
|
74
|
+
const run = ++previewRun;
|
|
75
|
+
previewState = "settling";
|
|
76
|
+
const handle = setTimeout(async () => {
|
|
77
|
+
try {
|
|
78
|
+
const html = await render(md);
|
|
79
|
+
if (run === previewRun) {
|
|
80
|
+
previewDoc = buildPreviewDoc(html, preview);
|
|
81
|
+
previewState = "settled";
|
|
82
|
+
}
|
|
83
|
+
} catch {
|
|
84
|
+
if (run === previewRun) {
|
|
85
|
+
previewState = "failed";
|
|
86
|
+
}
|
|
211
87
|
}
|
|
88
|
+
}, 200);
|
|
89
|
+
return () => {
|
|
90
|
+
clearTimeout(handle);
|
|
91
|
+
previewRun++;
|
|
92
|
+
};
|
|
93
|
+
});
|
|
94
|
+
const defs = $derived(insertableDefs(registry));
|
|
95
|
+
const showSearch = $derived(defs.length > SEARCH_THRESHOLD);
|
|
96
|
+
const filtered = $derived.by(() => {
|
|
97
|
+
const q = query.trim().toLowerCase();
|
|
98
|
+
if (!q) return defs;
|
|
99
|
+
return defs.filter(
|
|
100
|
+
(def) => def.label.toLowerCase().includes(q) || (def.description ?? "").toLowerCase().includes(q)
|
|
101
|
+
);
|
|
102
|
+
});
|
|
103
|
+
const groups = $derived(groupDefs(filtered));
|
|
104
|
+
function resetPreview() {
|
|
105
|
+
formValues = void 0;
|
|
106
|
+
formIncomplete = false;
|
|
107
|
+
previewState = "settled";
|
|
108
|
+
previewDoc = "";
|
|
109
|
+
editValues = null;
|
|
110
|
+
editRange = null;
|
|
111
|
+
}
|
|
112
|
+
export function open() {
|
|
113
|
+
picked = null;
|
|
114
|
+
query = "";
|
|
115
|
+
resetPreview();
|
|
116
|
+
dialog?.showModal();
|
|
117
|
+
if (showSearch) {
|
|
118
|
+
void tick().then(() => searchInput?.focus());
|
|
212
119
|
}
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
)
|
|
120
|
+
}
|
|
121
|
+
export function editComponent(def, values, range) {
|
|
122
|
+
resetPreview();
|
|
123
|
+
query = "";
|
|
124
|
+
editValues = values;
|
|
125
|
+
editRange = range;
|
|
126
|
+
picked = def;
|
|
127
|
+
dialog?.showModal();
|
|
128
|
+
}
|
|
129
|
+
function back() {
|
|
130
|
+
picked = null;
|
|
131
|
+
resetPreview();
|
|
132
|
+
}
|
|
133
|
+
function close() {
|
|
134
|
+
picked = null;
|
|
135
|
+
dialog?.close();
|
|
136
|
+
}
|
|
137
|
+
function onClose() {
|
|
138
|
+
picked = null;
|
|
139
|
+
resetPreview();
|
|
140
|
+
}
|
|
141
|
+
function choose(def) {
|
|
142
|
+
if (hasSchema(def)) {
|
|
221
143
|
resetPreview();
|
|
222
|
-
query = '';
|
|
223
|
-
editValues = values;
|
|
224
|
-
editRange = range;
|
|
225
144
|
picked = def;
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
function back() {
|
|
230
|
-
picked = null;
|
|
231
|
-
resetPreview();
|
|
232
|
-
}
|
|
233
|
-
function close() {
|
|
234
|
-
picked = null;
|
|
235
|
-
dialog?.close();
|
|
236
|
-
}
|
|
237
|
-
function onClose() {
|
|
238
|
-
picked = null;
|
|
239
|
-
resetPreview();
|
|
240
|
-
}
|
|
241
|
-
function choose(def: ComponentDef) {
|
|
242
|
-
if (hasSchema(def)) {
|
|
243
|
-
resetPreview();
|
|
244
|
-
picked = def;
|
|
245
|
-
// ComponentForm focuses its own first field on mount.
|
|
246
|
-
} else {
|
|
247
|
-
insert(def.insertTemplate ?? '');
|
|
248
|
-
close();
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
// The form's submit routes here. In edit mode it replaces the stored source span through update;
|
|
252
|
-
// otherwise it inserts at the cursor. Either way the dialog closes.
|
|
253
|
-
function onSubmit(markdown: string) {
|
|
254
|
-
if (editing && editRange) {
|
|
255
|
-
update?.(editRange, markdown);
|
|
256
|
-
} else {
|
|
257
|
-
insert(markdown);
|
|
258
|
-
}
|
|
145
|
+
} else {
|
|
146
|
+
insert(def.insertTemplate ?? "");
|
|
259
147
|
close();
|
|
260
148
|
}
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
// In edit mode there is no catalog to step back to, so Escape closes (the default). At the
|
|
268
|
-
// configure step of the insert flow, intercept the first Escape and step back to the catalog.
|
|
269
|
-
if (picked && !editing) {
|
|
270
|
-
e.preventDefault();
|
|
271
|
-
back();
|
|
272
|
-
}
|
|
149
|
+
}
|
|
150
|
+
function onSubmit(markdown) {
|
|
151
|
+
if (editing && editRange) {
|
|
152
|
+
update?.(editRange, markdown);
|
|
153
|
+
} else {
|
|
154
|
+
insert(markdown);
|
|
273
155
|
}
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
// expects. The handler sits on each row button (an interactive element), and finds its siblings
|
|
279
|
-
// through the shared scroll region so navigation crosses group boundaries.
|
|
280
|
-
function onRowKeydown(e: KeyboardEvent) {
|
|
281
|
-
const isNext = e.key === 'ArrowDown' || e.key === 'ArrowRight';
|
|
282
|
-
const isPrev = e.key === 'ArrowUp' || e.key === 'ArrowLeft';
|
|
283
|
-
if (!isNext && !isPrev) return;
|
|
284
|
-
const region = (e.currentTarget as HTMLElement).closest('[data-cairn-pk-list]');
|
|
285
|
-
if (!region) return;
|
|
286
|
-
const rows = [...region.querySelectorAll<HTMLButtonElement>('[data-testid="cairn-pk-row"]')];
|
|
287
|
-
const from = rows.indexOf(e.currentTarget as HTMLButtonElement);
|
|
288
|
-
if (from < 0) return;
|
|
289
|
-
e.preventDefault();
|
|
290
|
-
const next = (from + (isNext ? 1 : -1) + rows.length) % rows.length;
|
|
291
|
-
rows[next].focus();
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
// ArrowDown from the search input enters the list at the first row, ArrowUp at the last, so the
|
|
295
|
-
// keyboard model is whole: type to filter, then arrow into the results without reaching for the
|
|
296
|
-
// mouse. From a row, onRowKeydown takes over.
|
|
297
|
-
function onSearchKeydown(e: KeyboardEvent) {
|
|
298
|
-
const isNext = e.key === 'ArrowDown';
|
|
299
|
-
const isPrev = e.key === 'ArrowUp';
|
|
300
|
-
if (!isNext && !isPrev) return;
|
|
301
|
-
const rows = dialog?.querySelectorAll<HTMLButtonElement>('[data-testid="cairn-pk-row"]');
|
|
302
|
-
if (!rows || rows.length === 0) return;
|
|
156
|
+
close();
|
|
157
|
+
}
|
|
158
|
+
function onCancel(e) {
|
|
159
|
+
if (picked && !editing) {
|
|
303
160
|
e.preventDefault();
|
|
304
|
-
|
|
161
|
+
back();
|
|
305
162
|
}
|
|
163
|
+
}
|
|
164
|
+
function onRowKeydown(e) {
|
|
165
|
+
const isNext = e.key === "ArrowDown" || e.key === "ArrowRight";
|
|
166
|
+
const isPrev = e.key === "ArrowUp" || e.key === "ArrowLeft";
|
|
167
|
+
if (!isNext && !isPrev) return;
|
|
168
|
+
const region = e.currentTarget.closest("[data-cairn-pk-list]");
|
|
169
|
+
if (!region) return;
|
|
170
|
+
const rows = [...region.querySelectorAll('[data-testid="cairn-pk-row"]')];
|
|
171
|
+
const from = rows.indexOf(e.currentTarget);
|
|
172
|
+
if (from < 0) return;
|
|
173
|
+
e.preventDefault();
|
|
174
|
+
const next = (from + (isNext ? 1 : -1) + rows.length) % rows.length;
|
|
175
|
+
rows[next].focus();
|
|
176
|
+
}
|
|
177
|
+
function onSearchKeydown(e) {
|
|
178
|
+
const isNext = e.key === "ArrowDown";
|
|
179
|
+
const isPrev = e.key === "ArrowUp";
|
|
180
|
+
if (!isNext && !isPrev) return;
|
|
181
|
+
const rows = dialog?.querySelectorAll('[data-testid="cairn-pk-row"]');
|
|
182
|
+
if (!rows || rows.length === 0) return;
|
|
183
|
+
e.preventDefault();
|
|
184
|
+
rows[isNext ? 0 : rows.length - 1].focus();
|
|
185
|
+
}
|
|
306
186
|
</script>
|
|
307
187
|
|
|
308
188
|
<!-- The guided form, identical in either layout: the two-pane case wraps it in the left column, the
|