@glw907/cairn-cms 0.60.0 → 0.60.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.
Files changed (37) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/dist/components/AdminLayout.svelte +130 -229
  3. package/dist/components/CairnAdmin.svelte +10 -42
  4. package/dist/components/CairnLogo.svelte +1 -6
  5. package/dist/components/CairnMediaLibrary.svelte +821 -1210
  6. package/dist/components/CairnTidySettings.svelte +192 -259
  7. package/dist/components/ComponentForm.svelte +110 -185
  8. package/dist/components/ComponentInsertDialog.svelte +163 -283
  9. package/dist/components/ConceptList.svelte +111 -191
  10. package/dist/components/ConfirmPage.svelte +5 -12
  11. package/dist/components/CsrfField.svelte +5 -11
  12. package/dist/components/DeleteDialog.svelte +15 -42
  13. package/dist/components/EditPage.svelte +665 -1166
  14. package/dist/components/EditorToolbar.svelte +108 -170
  15. package/dist/components/IconPicker.svelte +23 -53
  16. package/dist/components/LinkPicker.svelte +34 -58
  17. package/dist/components/LoginPage.svelte +14 -27
  18. package/dist/components/ManageEditors.svelte +3 -15
  19. package/dist/components/MarkdownEditor.svelte +689 -957
  20. package/dist/components/MarkdownHelpDialog.svelte +8 -12
  21. package/dist/components/MediaCaptureCard.svelte +18 -57
  22. package/dist/components/MediaFigureControl.svelte +32 -71
  23. package/dist/components/MediaHeroField.svelte +210 -329
  24. package/dist/components/MediaInsertPopover.svelte +156 -283
  25. package/dist/components/MediaPicker.svelte +67 -131
  26. package/dist/components/NavTree.svelte +46 -78
  27. package/dist/components/RenameDialog.svelte +16 -43
  28. package/dist/components/ShortcutsDialog.svelte +9 -13
  29. package/dist/components/ShortcutsGrid.svelte +1 -2
  30. package/dist/components/TidyReview.svelte +140 -248
  31. package/dist/components/WebLinkDialog.svelte +19 -40
  32. package/dist/components/cairn-admin.css +4 -0
  33. package/dist/components/spellcheck.d.ts +3 -1
  34. package/dist/components/spellcheck.js +14 -2
  35. package/dist/delivery/CairnHead.svelte +8 -11
  36. package/package.json +2 -2
  37. package/src/lib/components/spellcheck.ts +16 -2
package/CHANGELOG.md CHANGED
@@ -2,6 +2,19 @@
2
2
 
3
3
  All notable changes to this project are recorded here, most recent first.
4
4
 
5
+ ## 0.60.1
6
+
7
+ A packaging fix so the library bundles cleanly in a Vite 8 consumer. It supersedes `0.60.0`, whose
8
+ consumer build failed on Vite 8 / Rolldown. `svelte-package` ships `.svelte` with `<script lang="ts">`
9
+ and the TypeScript intact, and Rolldown parses that `<script>` as JavaScript before the Svelte plugin
10
+ compiles the file, failing on a TypeScript optional parameter (`registry?: T` loses its type but keeps
11
+ the `?`). The shipped `.svelte` now carry a plain-JavaScript `<script>` body. The `lang="ts"` tag
12
+ stays, because the component markup still uses TypeScript that the Svelte compiler reads (typed
13
+ `{#snippet}` parameters and `{@const x = y as T}` casts).
14
+
15
+ No consumer action is required. The change is to the published `dist` only; the public API and the
16
+ types are unchanged.
17
+
5
18
  ## 0.60.0
6
19
 
7
20
  <!-- release-size: minor -->
@@ -6,239 +6,140 @@ root sets `data-theme` to the resolved light or dark theme (seeded from the SSR'
6
6
  flipped by the topbar toggle) and imports the self-contained Warm Stone theme, so the admin looks
7
7
  identical on every host regardless of the site's own theme.
8
8
  -->
9
- <script lang="ts">
10
- import { onMount, setContext, untrack, type Component, type Snippet } from 'svelte';
11
- import type { LayoutData } from '../sveltekit/content-routes.js';
12
- import CsrfField from './CsrfField.svelte';
13
- import { CSRF_CONTEXT_KEY } from './csrf-context.js';
14
- import { provideTopbar, type TopbarHolder } from './topbar-context.js';
15
- import { MenuIcon, LogOutIcon, SunIcon, MoonIcon, ChevronRightIcon, SearchIcon } from './admin-icons.js';
16
- import CairnLogo from './CairnLogo.svelte';
17
- import { cairnFaviconHref } from './cairn-favicon.js';
18
- import { warnIfChromeWrapped } from './chrome-guard.js';
19
- import FileTextIcon from '@lucide/svelte/icons/file-text';
20
- import SignpostIcon from '@lucide/svelte/icons/signpost';
21
- import SettingsIcon from '@lucide/svelte/icons/settings';
22
- import UsersIcon from '@lucide/svelte/icons/users';
23
- import ImageIcon from '@lucide/svelte/icons/image';
24
- import BlocksIcon from '@lucide/svelte/icons/blocks';
25
- import ExternalLinkIcon from '@lucide/svelte/icons/external-link';
26
- import './cairn-admin.css';
27
-
28
- interface Props {
29
- /** The layout load's data: site name, user, nav concepts, active path, owner capability. */
30
- data: LayoutData;
31
- /** The page body. */
32
- children: Snippet;
33
- }
34
-
35
- let { data, children }: Props = $props();
36
-
37
- // Hand descendant forms a live getter for the CSRF token layoutLoad issued, so the field stays
38
- // correct even if the token ever rotates mid-session.
39
- setContext(CSRF_CONTEXT_KEY, () => data.csrf);
40
-
41
- // Persist an admin preference for a year, path-scoped to /admin so the cookie never reaches the
42
- // host's own pages.
43
- function writeAdminCookie(name: string, value: string) {
44
- document.cookie = `${name}=${value}; path=/admin; max-age=31536000; samesite=lax`;
45
- }
46
-
47
- // A nav entry. `href` makes it a link; without one it is an inert stub (a developer-tool slot the
48
- // extension mechanism has not wired yet).
49
- interface NavItem {
50
- label: string;
51
- icon: Component;
52
- href?: string;
53
- }
54
-
55
- // The core Cairn functions, all in one group: the content concepts, the nav-menu editor (when the
56
- // site configures one; a signpost, kept distinct from the Settings gear), the site Settings, and
57
- // the owner-only Editors.
58
- const coreItems: NavItem[] = $derived([
59
- ...data.concepts.map((c) => ({ label: c.label, icon: FileTextIcon, href: `/admin/${c.id}` })),
60
- // Media is a content peer, immediately after the concepts.
61
- { label: 'Media', icon: ImageIcon, href: '/admin/media' },
62
- ...(data.navLabel ? [{ label: data.navLabel, icon: SignpostIcon, href: '/admin/nav' }] : []),
63
- { label: 'Settings', icon: SettingsIcon, href: '/admin/settings' },
64
- ...(data.canManageEditors ? [{ label: 'Editors', icon: UsersIcon, href: '/admin/editors' }] : []),
65
- ]);
66
-
67
- // The developer-extension groups: each custom-named, with its own items, collapsible like the core
68
- // group. The CairnExtension seam will supply these; until it lands they are inert example stubs that
69
- // show the shape, multiple named groups kept visually apart from the core functions.
70
- const extensionGroups: { name: string; items: NavItem[] }[] = [
71
- { name: 'Marketing', items: [
72
- { label: 'Campaigns', icon: BlocksIcon },
73
- { label: 'Audiences', icon: BlocksIcon },
74
- ] },
75
- { name: 'Shop', items: [
76
- { label: 'Products', icon: BlocksIcon },
77
- { label: 'Orders', icon: BlocksIcon },
78
- ] },
79
- ];
80
-
81
- // Up to two uppercase initials from the display name, falling back to '?' for an empty name.
82
- function initialsOf(displayName: string): string {
83
- const letters = displayName
84
- .split(/\s+/)
85
- .filter(Boolean)
86
- .slice(0, 2)
87
- .map((word) => word[0]?.toUpperCase() ?? '')
88
- .join('');
89
- return letters || '?';
90
- }
91
-
92
- const initials = $derived(initialsOf(data.user.displayName));
93
-
94
- function isActive(href: string): boolean {
95
- return data.pathname === href || data.pathname.startsWith(`${href}/`);
96
- }
97
-
98
- // Which nav groups are collapsed. Seeded once from the SSR'd cookie (so a collapsed group renders
99
- // collapsed with no flash), then owned by the toggle below, which mirrors each change to the cookie.
100
- let collapsed = $state(new Set(untrack(() => data.collapsedNav)));
101
-
102
- function onToggleSection(label: string, open: boolean) {
103
- const next = new Set(collapsed);
104
- if (open) next.delete(label);
105
- else next.add(label);
106
- collapsed = next;
107
- const value = [...next].map((entry) => encodeURIComponent(entry)).join(',');
108
- writeAdminCookie('cairn-admin-nav-collapsed', value);
109
- }
110
-
111
- let drawerOpen = $state(false);
112
-
113
- function onKeydown(e: KeyboardEvent) {
114
- if (e.key.toLowerCase() === 'b' && (e.metaKey || e.ctrlKey)) {
115
- e.preventDefault();
116
- drawerOpen = !drawerOpen;
117
- }
118
- if (e.key.toLowerCase() === 'k' && (e.metaKey || e.ctrlKey)) {
119
- e.preventDefault();
120
- openPalette();
121
- }
9
+ <script lang="ts">import { onMount, setContext, untrack } from "svelte";
10
+ import CsrfField from "./CsrfField.svelte";
11
+ import { CSRF_CONTEXT_KEY } from "./csrf-context.js";
12
+ import { provideTopbar } from "./topbar-context.js";
13
+ import { MenuIcon, LogOutIcon, SunIcon, MoonIcon, ChevronRightIcon, SearchIcon } from "./admin-icons.js";
14
+ import CairnLogo from "./CairnLogo.svelte";
15
+ import { cairnFaviconHref } from "./cairn-favicon.js";
16
+ import { warnIfChromeWrapped } from "./chrome-guard.js";
17
+ import FileTextIcon from "@lucide/svelte/icons/file-text";
18
+ import SignpostIcon from "@lucide/svelte/icons/signpost";
19
+ import SettingsIcon from "@lucide/svelte/icons/settings";
20
+ import UsersIcon from "@lucide/svelte/icons/users";
21
+ import ImageIcon from "@lucide/svelte/icons/image";
22
+ import BlocksIcon from "@lucide/svelte/icons/blocks";
23
+ import ExternalLinkIcon from "@lucide/svelte/icons/external-link";
24
+ import "./cairn-admin.css";
25
+ let { data, children } = $props();
26
+ setContext(CSRF_CONTEXT_KEY, () => data.csrf);
27
+ function writeAdminCookie(name, value) {
28
+ document.cookie = `${name}=${value}; path=/admin; max-age=31536000; samesite=lax`;
29
+ }
30
+ const coreItems = $derived([
31
+ ...data.concepts.map((c) => ({ label: c.label, icon: FileTextIcon, href: `/admin/${c.id}` })),
32
+ // Media is a content peer, immediately after the concepts.
33
+ { label: "Media", icon: ImageIcon, href: "/admin/media" },
34
+ ...data.navLabel ? [{ label: data.navLabel, icon: SignpostIcon, href: "/admin/nav" }] : [],
35
+ { label: "Settings", icon: SettingsIcon, href: "/admin/settings" },
36
+ ...data.canManageEditors ? [{ label: "Editors", icon: UsersIcon, href: "/admin/editors" }] : []
37
+ ]);
38
+ const extensionGroups = [
39
+ { name: "Marketing", items: [
40
+ { label: "Campaigns", icon: BlocksIcon },
41
+ { label: "Audiences", icon: BlocksIcon }
42
+ ] },
43
+ { name: "Shop", items: [
44
+ { label: "Products", icon: BlocksIcon },
45
+ { label: "Orders", icon: BlocksIcon }
46
+ ] }
47
+ ];
48
+ function initialsOf(displayName) {
49
+ const letters = displayName.split(/\s+/).filter(Boolean).slice(0, 2).map((word) => word[0]?.toUpperCase() ?? "").join("");
50
+ return letters || "?";
51
+ }
52
+ const initials = $derived(initialsOf(data.user.displayName));
53
+ function isActive(href) {
54
+ return data.pathname === href || data.pathname.startsWith(`${href}/`);
55
+ }
56
+ let collapsed = $state(new Set(untrack(() => data.collapsedNav)));
57
+ function onToggleSection(label, open) {
58
+ const next = new Set(collapsed);
59
+ if (open) next.delete(label);
60
+ else next.add(label);
61
+ collapsed = next;
62
+ const value = [...next].map((entry) => encodeURIComponent(entry)).join(",");
63
+ writeAdminCookie("cairn-admin-nav-collapsed", value);
64
+ }
65
+ let drawerOpen = $state(false);
66
+ function onKeydown(e) {
67
+ if (e.key.toLowerCase() === "b" && (e.metaKey || e.ctrlKey)) {
68
+ e.preventDefault();
69
+ drawerOpen = !drawerOpen;
122
70
  }
123
-
124
- // Close the mobile drawer and the command palette whenever the active path changes (a nav click
125
- // navigated). Closing the palette here, after the navigation lands, avoids racing a synchronous
126
- // close() against a result link's own navigation, which would cancel it.
127
- $effect(() => {
128
- data.pathname;
129
- drawerOpen = false;
130
- paletteDialog?.close();
131
- publishAllDialog?.close();
132
- });
133
-
134
- // Seed from the SSR'd theme once. The live theme is owned by this state and the toggle, so the
135
- // initial read of data.theme is intentional and untracked to keep it out of any reactive graph.
136
- let theme = $state<'cairn-admin' | 'cairn-admin-dark'>(untrack(() => data.theme));
137
-
138
- // First mount with no persisted choice follows the OS preference. A returning user's cookie was
139
- // already honored by the layout load (data.theme), so this only fires on a first-ever visit.
140
- $effect(() => {
141
- const hasCookie = document.cookie.split('; ').some((c) => c.startsWith('cairn-admin-theme='));
142
- if (!hasCookie && window.matchMedia?.('(prefers-color-scheme: dark)').matches) {
143
- theme = 'cairn-admin-dark';
144
- }
145
- });
146
-
147
- function toggleTheme() {
148
- theme = theme === 'cairn-admin' ? 'cairn-admin-dark' : 'cairn-admin';
149
- writeAdminCookie('cairn-admin-theme', theme);
71
+ if (e.key.toLowerCase() === "k" && (e.metaKey || e.ctrlKey)) {
72
+ e.preventDefault();
73
+ openPalette();
150
74
  }
151
-
152
- // The command palette: a quick jump-to over the admin's destinations plus a couple of actions, so
153
- // the topbar carries something productive. Opened by the topbar trigger or Cmd/Ctrl+K.
154
- interface Command {
155
- label: string;
156
- icon: Component;
157
- href?: string;
158
- external?: boolean;
159
- action?: () => void;
75
+ }
76
+ $effect(() => {
77
+ data.pathname;
78
+ drawerOpen = false;
79
+ paletteDialog?.close();
80
+ publishAllDialog?.close();
81
+ });
82
+ let theme = $state(untrack(() => data.theme));
83
+ $effect(() => {
84
+ const hasCookie = document.cookie.split("; ").some((c) => c.startsWith("cairn-admin-theme="));
85
+ if (!hasCookie && window.matchMedia?.("(prefers-color-scheme: dark)").matches) {
86
+ theme = "cairn-admin-dark";
160
87
  }
161
-
162
- let paletteDialog = $state<HTMLDialogElement>();
163
- let paletteList = $state<HTMLUListElement>();
164
- let paletteQuery = $state('');
165
-
166
- // The site-wide publish action. The trigger and its confirm render only while entries are
167
- // pending; a null pendingEntries (GitHub unreachable) hides them rather than showing a stale count.
168
- let publishAllDialog = $state<HTMLDialogElement>();
169
- const pendingCount = $derived(data.pendingEntries?.length ?? 0);
170
- // The pending ids grouped under their concept's nav label, in first-seen order. A ref whose
171
- // concept is not in the nav (an unconfigured key) falls back to the raw key.
172
- const pendingGroups = $derived.by(() => {
173
- const groups = new Map<string, string[]>();
174
- for (const entry of data.pendingEntries ?? []) {
175
- const label = data.concepts.find((c) => c.id === entry.concept)?.label ?? entry.concept;
176
- groups.set(label, [...(groups.get(label) ?? []), entry.id]);
177
- }
178
- return [...groups.entries()].map(([label, ids]) => ({ label, ids }));
179
- });
180
-
181
- // The bare data-theme wrapper is the admin root the dev chrome-guard measures from.
182
- let rootEl = $state<HTMLElement>();
183
- onMount(() => {
184
- if (rootEl) warnIfChromeWrapped(rootEl);
185
- });
186
-
187
- const paletteCommands = $derived<Command[]>([
188
- ...coreItems.map((item) => ({ label: item.label, icon: item.icon, href: item.href })),
189
- { label: 'View the live site', icon: ExternalLinkIcon, href: '/', external: true },
190
- theme === 'cairn-admin'
191
- ? { label: 'Switch to dark mode', icon: MoonIcon, action: toggleTheme }
192
- : { label: 'Switch to light mode', icon: SunIcon, action: toggleTheme },
193
- ]);
194
- const paletteResults = $derived(
195
- paletteCommands.filter((c) => c.label.toLowerCase().includes(paletteQuery.trim().toLowerCase())),
196
- );
197
-
198
- function openPalette() {
199
- if (paletteDialog?.open) return; // showModal throws on an already-open dialog
200
- paletteQuery = '';
201
- paletteDialog?.showModal();
202
- }
203
- // An action command (theme toggle). Link commands are real <a> elements that navigate on click, so
204
- // the Enter shortcut clicks the first result element and both paths share the one navigation.
205
- function runCommand(cmd: Command) {
206
- paletteDialog?.close();
207
- cmd.action?.();
208
- }
209
- function submitPalette() {
210
- (paletteList?.querySelector('a, button') as HTMLElement | null)?.click();
211
- }
212
-
213
- interface Crumb {
214
- label: string;
215
- href?: string;
88
+ });
89
+ function toggleTheme() {
90
+ theme = theme === "cairn-admin" ? "cairn-admin-dark" : "cairn-admin";
91
+ writeAdminCookie("cairn-admin-theme", theme);
92
+ }
93
+ let paletteDialog = $state();
94
+ let paletteList = $state();
95
+ let paletteQuery = $state("");
96
+ let publishAllDialog = $state();
97
+ const pendingCount = $derived(data.pendingEntries?.length ?? 0);
98
+ const pendingGroups = $derived.by(() => {
99
+ const groups = /* @__PURE__ */ new Map();
100
+ for (const entry of data.pendingEntries ?? []) {
101
+ const label = data.concepts.find((c) => c.id === entry.concept)?.label ?? entry.concept;
102
+ groups.set(label, [...groups.get(label) ?? [], entry.id]);
216
103
  }
217
-
218
- // Path-derived breadcrumbs: the concept label (from the nav) then the entry id segment. Only the
219
- // /admin/<concept>/<id> depth shows a trail; a bare concept list shows just the concept.
220
- const crumbs = $derived.by<Crumb[]>(() => {
221
- const segs = data.pathname.split('/').filter(Boolean); // ['admin', concept, id?]
222
- if (segs.length < 2 || segs[0] !== 'admin') return [];
223
- const conceptId = segs[1];
224
- const concept = data.concepts.find((c) => c.id === conceptId);
225
- const out: Crumb[] = [{ label: concept?.label ?? conceptId, href: `/admin/${conceptId}` }];
226
- if (segs[2]) out.push({ label: decodeURIComponent(segs[2]) });
227
- return out;
228
- });
229
-
230
- // The browser-tab title: the deepest breadcrumb (the active concept or entry), then the brand.
231
- const pageTitle = $derived(crumbs.length ? crumbs[crumbs.length - 1].label : 'Admin');
232
-
233
- // A desk route is an open document (/admin/<concept>/<id>): the third path segment is the entry.
234
- // The band has one job there, so the topbar drops the palette trigger and the site-wide Publish
235
- // button and renders the document's own desk controls instead.
236
- const isDeskRoute = $derived(data.pathname.split('/').filter(Boolean).length > 2);
237
-
238
- // The topbar context portal: a reactive holder a descendant document fills with its desk snippet.
239
- // EditPage registers on mount and nulls it on teardown; the office routes leave it null.
240
- let topbar = $state<TopbarHolder>({ desk: null, zen: false });
241
- provideTopbar(topbar);
104
+ return [...groups.entries()].map(([label, ids]) => ({ label, ids }));
105
+ });
106
+ let rootEl = $state();
107
+ onMount(() => {
108
+ if (rootEl) warnIfChromeWrapped(rootEl);
109
+ });
110
+ const paletteCommands = $derived([
111
+ ...coreItems.map((item) => ({ label: item.label, icon: item.icon, href: item.href })),
112
+ { label: "View the live site", icon: ExternalLinkIcon, href: "/", external: true },
113
+ theme === "cairn-admin" ? { label: "Switch to dark mode", icon: MoonIcon, action: toggleTheme } : { label: "Switch to light mode", icon: SunIcon, action: toggleTheme }
114
+ ]);
115
+ const paletteResults = $derived(
116
+ paletteCommands.filter((c) => c.label.toLowerCase().includes(paletteQuery.trim().toLowerCase()))
117
+ );
118
+ function openPalette() {
119
+ if (paletteDialog?.open) return;
120
+ paletteQuery = "";
121
+ paletteDialog?.showModal();
122
+ }
123
+ function runCommand(cmd) {
124
+ paletteDialog?.close();
125
+ cmd.action?.();
126
+ }
127
+ function submitPalette() {
128
+ paletteList?.querySelector("a, button")?.click();
129
+ }
130
+ const crumbs = $derived.by(() => {
131
+ const segs = data.pathname.split("/").filter(Boolean);
132
+ if (segs.length < 2 || segs[0] !== "admin") return [];
133
+ const conceptId = segs[1];
134
+ const concept = data.concepts.find((c) => c.id === conceptId);
135
+ const out = [{ label: concept?.label ?? conceptId, href: `/admin/${conceptId}` }];
136
+ if (segs[2]) out.push({ label: decodeURIComponent(segs[2]) });
137
+ return out;
138
+ });
139
+ const pageTitle = $derived(crumbs.length ? crumbs[crumbs.length - 1].label : "Admin");
140
+ const isDeskRoute = $derived(data.pathname.split("/").filter(Boolean).length > 2);
141
+ let topbar = $state({ desk: null, zen: false });
142
+ provideTopbar(topbar);
242
143
  </script>
243
144
 
244
145
  <svelte:head>
@@ -5,48 +5,16 @@ component for every admin view, feeding it the discriminated `AdminData` from `c
5
5
  load. It is a pure switcher on `data.view`: the public auth pages mount bare, and the authed views
6
6
  mount inside `AdminLayout`. No styling or wrapper elements of its own.
7
7
  -->
8
- <script lang="ts">
9
- import AdminLayout from './AdminLayout.svelte';
10
- import LoginPage from './LoginPage.svelte';
11
- import ConfirmPage from './ConfirmPage.svelte';
12
- import ConceptList from './ConceptList.svelte';
13
- import EditPage from './EditPage.svelte';
14
- import ManageEditors from './ManageEditors.svelte';
15
- import NavTree from './NavTree.svelte';
16
- import CairnMediaLibrary from './CairnMediaLibrary.svelte';
17
- import CairnTidySettings from './CairnTidySettings.svelte';
18
- import type { AdminData } from '../sveltekit/cairn-admin.js';
19
- import type { ContentFormFailure } from '../sveltekit/content-routes.js';
20
- import type { ComponentRegistry } from '../render/registry.js';
21
- import type { IconSet } from '../render/glyph.js';
22
- import type { LinkResolve } from '../content/links.js';
23
- import type { MediaResolve } from '../render/resolve-media.js';
24
-
25
- interface Props {
26
- /** The discriminated view data from `createCairnAdmin`'s load. */
27
- data: AdminData;
28
- /** The last action's result, forwarded to whichever view rendered: the shared content-action
29
- * failure family (every failure carries `error`), merged with the auth and editors results,
30
- * so the route's one `form` export covers every view. */
31
- form?:
32
- | (ContentFormFailure & {
33
- sent?: boolean;
34
- status?: 'sent' | 'send_error' | 'throttled';
35
- ok?: boolean;
36
- })
37
- | null;
38
- /** The site's design-accurate render pipeline, for the edit view's preview pane. */
39
- render?: (
40
- md: string,
41
- opts?: { stagger?: boolean; resolve?: LinkResolve; resolveMedia?: MediaResolve },
42
- ) => string | Promise<string>;
43
- /** The site's component registry, for the edit view's insert palette. */
44
- registry?: ComponentRegistry;
45
- /** The site's icon set, for the edit view's guided form fields. */
46
- icons?: IconSet;
47
- }
48
-
49
- let { data, form = null, render, registry, icons }: Props = $props();
8
+ <script lang="ts">import AdminLayout from "./AdminLayout.svelte";
9
+ import LoginPage from "./LoginPage.svelte";
10
+ import ConfirmPage from "./ConfirmPage.svelte";
11
+ import ConceptList from "./ConceptList.svelte";
12
+ import EditPage from "./EditPage.svelte";
13
+ import ManageEditors from "./ManageEditors.svelte";
14
+ import NavTree from "./NavTree.svelte";
15
+ import CairnMediaLibrary from "./CairnMediaLibrary.svelte";
16
+ import CairnTidySettings from "./CairnTidySettings.svelte";
17
+ let { data, form = null, render, registry, icons } = $props();
50
18
  </script>
51
19
 
52
20
  {#if data.view === 'login'}
@@ -7,12 +7,7 @@ accessible name.
7
7
  Artwork: the "cairn" icon from the Temaki icon set (https://github.com/ideditor/temaki), released into
8
8
  the public domain under CC0 1.0 (no attribution required). Recorded here as provenance.
9
9
  -->
10
- <script lang="ts">
11
- interface Props {
12
- /** Utility classes for sizing and color, e.g. `h-7 w-7 text-primary`. */
13
- class?: string;
14
- }
15
- let { class: className = '' }: Props = $props();
10
+ <script lang="ts">let { class: className = "" } = $props();
16
11
  </script>
17
12
 
18
13
  <svg