@velastack/cms 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +107 -18
- package/dist/backend/index.d.ts +1 -1
- package/dist/backend/routes/seed.d.ts +6 -0
- package/dist/backend/routes/seed.js +96 -0
- package/dist/backend/routes/table.js +3 -0
- package/dist/backend/store/queries.d.ts +6 -1
- package/dist/backend/store/queries.js +89 -0
- package/dist/cms.js +13 -16
- package/dist/components/admin-bar/admin-bar-internal.svelte +4 -4
- package/dist/components/admin-bar/admin-bar.css +1 -1
- package/dist/components/admin-bar/field-input.svelte +220 -0
- package/dist/components/admin-bar/field-input.svelte.d.ts +29 -0
- package/dist/components/admin-bar/icons/pencil.svelte +19 -0
- package/dist/components/admin-bar/icons/pencil.svelte.d.ts +3 -0
- package/dist/components/admin-bar/keyboard-shortcuts-dialog.svelte +1 -1
- package/dist/components/admin-bar/locales-panel.svelte +42 -1
- package/dist/components/admin-bar/page-config.d.ts +8 -1
- package/dist/components/admin-bar/page-config.js +15 -2
- package/dist/components/admin-bar/reorder-buttons.svelte +52 -0
- package/dist/components/admin-bar/reorder-buttons.svelte.d.ts +10 -0
- package/dist/components/admin-bar/seo-panel.svelte +144 -189
- package/dist/components/admin-bar/site-options-panel.svelte +115 -0
- package/dist/components/admin-bar/site-options-panel.svelte.d.ts +7 -0
- package/dist/components/admin-bar/structured-editor-popover.svelte +250 -0
- package/dist/components/admin-bar/structured-editor-popover.svelte.d.ts +48 -0
- package/dist/components/admin-bar/ui/checkbox/checkbox.svelte +36 -0
- package/dist/components/admin-bar/ui/checkbox/checkbox.svelte.d.ts +5 -0
- package/dist/components/admin-bar/ui/checkbox/index.d.ts +2 -0
- package/dist/components/admin-bar/ui/checkbox/index.js +2 -0
- package/dist/components/admin-bar/ui/popover/index.d.ts +5 -0
- package/dist/components/admin-bar/ui/popover/index.js +7 -0
- package/dist/components/admin-bar/ui/popover/popover-close.svelte +7 -0
- package/dist/components/admin-bar/ui/popover/popover-close.svelte.d.ts +4 -0
- package/dist/components/admin-bar/ui/popover/popover-content.svelte +35 -0
- package/dist/components/admin-bar/ui/popover/popover-content.svelte.d.ts +10 -0
- package/dist/components/admin-bar/ui/popover/popover-trigger.svelte +7 -0
- package/dist/components/admin-bar/ui/popover/popover-trigger.svelte.d.ts +4 -0
- package/dist/components/admin-bar/ui/popover/popover.svelte +7 -0
- package/dist/components/admin-bar/ui/popover/popover.svelte.d.ts +3 -0
- package/dist/components/admin-bar/ui/sheet/index.d.ts +10 -0
- package/dist/components/admin-bar/ui/sheet/index.js +12 -0
- package/dist/components/admin-bar/ui/sheet/sheet-close.svelte +7 -0
- package/dist/components/admin-bar/ui/sheet/sheet-close.svelte.d.ts +4 -0
- package/dist/components/admin-bar/ui/sheet/sheet-content.svelte +64 -0
- package/dist/components/admin-bar/ui/sheet/sheet-content.svelte.d.ts +17 -0
- package/dist/components/admin-bar/ui/sheet/sheet-description.svelte +17 -0
- package/dist/components/admin-bar/ui/sheet/sheet-description.svelte.d.ts +4 -0
- package/dist/components/admin-bar/ui/sheet/sheet-footer.svelte +23 -0
- package/dist/components/admin-bar/ui/sheet/sheet-footer.svelte.d.ts +5 -0
- package/dist/components/admin-bar/ui/sheet/sheet-header.svelte +20 -0
- package/dist/components/admin-bar/ui/sheet/sheet-header.svelte.d.ts +5 -0
- package/dist/components/admin-bar/ui/sheet/sheet-overlay.svelte +20 -0
- package/dist/components/admin-bar/ui/sheet/sheet-overlay.svelte.d.ts +4 -0
- package/dist/components/admin-bar/ui/sheet/sheet-title.svelte +17 -0
- package/dist/components/admin-bar/ui/sheet/sheet-title.svelte.d.ts +4 -0
- package/dist/components/admin-bar/ui/sheet/sheet-trigger.svelte +7 -0
- package/dist/components/admin-bar/ui/sheet/sheet-trigger.svelte.d.ts +4 -0
- package/dist/components/admin-bar/ui/sheet/sheet.svelte +7 -0
- package/dist/components/admin-bar/ui/sheet/sheet.svelte.d.ts +3 -0
- package/dist/components/admin-bar/ui/switch/index.d.ts +2 -0
- package/dist/components/admin-bar/ui/switch/index.js +2 -0
- package/dist/components/admin-bar/ui/switch/switch.svelte +27 -0
- package/dist/components/admin-bar/ui/switch/switch.svelte.d.ts +5 -0
- package/dist/components/admin-bar/ui/tabs/index.d.ts +5 -0
- package/dist/components/admin-bar/ui/tabs/index.js +7 -0
- package/dist/components/admin-bar/ui/tabs/tabs-content.svelte +17 -0
- package/dist/components/admin-bar/ui/tabs/tabs-content.svelte.d.ts +4 -0
- package/dist/components/admin-bar/ui/tabs/tabs-list.svelte +16 -0
- package/dist/components/admin-bar/ui/tabs/tabs-list.svelte.d.ts +4 -0
- package/dist/components/admin-bar/ui/tabs/tabs-trigger.svelte +20 -0
- package/dist/components/admin-bar/ui/tabs/tabs-trigger.svelte.d.ts +4 -0
- package/dist/components/admin-bar/ui/tabs/tabs.svelte +19 -0
- package/dist/components/admin-bar/ui/tabs/tabs.svelte.d.ts +4 -0
- package/dist/components/cms/cms-boolean.svelte +9 -18
- package/dist/components/cms/cms-boolean.svelte.d.ts +1 -0
- package/dist/components/cms/cms-date-time-editable.svelte +3 -3
- package/dist/components/cms/cms-date-time.svelte +17 -17
- package/dist/components/cms/cms-date-time.svelte.d.ts +2 -1
- package/dist/components/cms/cms-image-editable.svelte +13 -4
- package/dist/components/cms/cms-image.svelte +37 -30
- package/dist/components/cms/cms-image.svelte.d.ts +11 -4
- package/dist/components/cms/cms-link.svelte +26 -19
- package/dist/components/cms/cms-link.svelte.d.ts +2 -0
- package/dist/components/cms/cms-markdown.svelte +15 -16
- package/dist/components/cms/cms-markdown.svelte.d.ts +1 -0
- package/dist/components/cms/cms-number-editable.svelte +4 -2
- package/dist/components/cms/cms-number.svelte +18 -17
- package/dist/components/cms/cms-number.svelte.d.ts +2 -1
- package/dist/components/cms/cms-repeater.svelte +15 -18
- package/dist/components/cms/cms-repeater.svelte.d.ts +1 -0
- package/dist/components/cms/cms-rich-text.svelte +17 -22
- package/dist/components/cms/cms-rich-text.svelte.d.ts +1 -0
- package/dist/components/cms/cms-store.svelte.d.ts +14 -0
- package/dist/components/cms/cms-store.svelte.js +39 -2
- package/dist/components/cms/cms-text.svelte +23 -30
- package/dist/components/cms/cms-text.svelte.d.ts +3 -1
- package/dist/components/cms/scope.d.ts +25 -5
- package/dist/components/cms/structured-registry.d.ts +10 -0
- package/dist/components/cms/structured-registry.js +6 -0
- package/dist/components/cms/use-cms-field.svelte.d.ts +54 -0
- package/dist/components/cms/use-cms-field.svelte.js +83 -0
- package/dist/core/metadata.d.ts +44 -0
- package/dist/core/metadata.js +59 -0
- package/dist/core/structured.d.ts +108 -0
- package/dist/core/structured.js +184 -0
- package/dist/core/wire.d.ts +31 -1
- package/dist/editor.d.ts +22 -0
- package/dist/editor.js +21 -0
- package/dist/index.d.ts +12 -4
- package/dist/index.js +13 -3
- package/dist/server/load-cms.js +1 -0
- package/package.json +12 -3
- package/plugin/README.md +22 -2
- package/plugin/dist/build.d.ts +74 -0
- package/plugin/dist/build.js +210 -0
- package/plugin/dist/build.js.map +1 -0
- package/plugin/dist/index.d.ts +10 -1
- package/plugin/dist/index.js +63 -4
- package/plugin/dist/index.js.map +1 -1
- package/plugin/dist/manifest.d.ts +31 -0
- package/plugin/dist/manifest.js +146 -41
- package/plugin/dist/manifest.js.map +1 -1
- package/plugin/dist/parse-svelte.d.ts +15 -0
- package/plugin/dist/parse-svelte.js +26 -16
- package/plugin/dist/parse-svelte.js.map +1 -1
- package/dist/components/admin-bar/site-settings-panel.svelte +0 -225
- package/dist/components/admin-bar/site-settings-panel.svelte.d.ts +0 -7
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
2
|
+
import { type WithElementRef } from '../../utils.js';
|
|
3
|
+
declare const SheetHeader: import("svelte").Component<WithElementRef<HTMLAttributes<HTMLDivElement>>, {}, "ref">;
|
|
4
|
+
type SheetHeader = ReturnType<typeof SheetHeader>;
|
|
5
|
+
export default SheetHeader;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { Dialog as SheetPrimitive } from 'bits-ui';
|
|
3
|
+
import { cn } from '../../utils.js';
|
|
4
|
+
|
|
5
|
+
let {
|
|
6
|
+
ref = $bindable(null),
|
|
7
|
+
class: className,
|
|
8
|
+
...restProps
|
|
9
|
+
}: SheetPrimitive.OverlayProps = $props();
|
|
10
|
+
</script>
|
|
11
|
+
|
|
12
|
+
<SheetPrimitive.Overlay
|
|
13
|
+
bind:ref
|
|
14
|
+
data-slot="sheet-overlay"
|
|
15
|
+
class={cn(
|
|
16
|
+
'vela:fixed vela:inset-0 vela:z-[9998] vela:bg-black/50 vela:data-[state=open]:animate-in vela:data-[state=closed]:animate-out vela:data-[state=closed]:fade-out-0 vela:data-[state=open]:fade-in-0',
|
|
17
|
+
className
|
|
18
|
+
)}
|
|
19
|
+
{...restProps}
|
|
20
|
+
/>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { Dialog as SheetPrimitive } from 'bits-ui';
|
|
3
|
+
import { cn } from '../../utils.js';
|
|
4
|
+
|
|
5
|
+
let {
|
|
6
|
+
ref = $bindable(null),
|
|
7
|
+
class: className,
|
|
8
|
+
...restProps
|
|
9
|
+
}: SheetPrimitive.TitleProps = $props();
|
|
10
|
+
</script>
|
|
11
|
+
|
|
12
|
+
<SheetPrimitive.Title
|
|
13
|
+
bind:ref
|
|
14
|
+
data-slot="sheet-title"
|
|
15
|
+
class={cn('vela:text-[15px] vela:font-semibold vela:m-0', className)}
|
|
16
|
+
{...restProps}
|
|
17
|
+
/>
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { Switch as SwitchPrimitive } from 'bits-ui';
|
|
3
|
+
import { cn, type WithoutChildrenOrChild } from '../../utils.js';
|
|
4
|
+
|
|
5
|
+
let {
|
|
6
|
+
ref = $bindable(null),
|
|
7
|
+
checked = $bindable(false),
|
|
8
|
+
class: className,
|
|
9
|
+
...restProps
|
|
10
|
+
}: WithoutChildrenOrChild<SwitchPrimitive.RootProps> = $props();
|
|
11
|
+
</script>
|
|
12
|
+
|
|
13
|
+
<SwitchPrimitive.Root
|
|
14
|
+
bind:ref
|
|
15
|
+
bind:checked
|
|
16
|
+
data-slot="switch"
|
|
17
|
+
class={cn(
|
|
18
|
+
'vela:peer vela:inline-flex vela:h-5 vela:w-9 vela:shrink-0 vela:items-center vela:rounded-full vela:border vela:border-transparent vela:transition-colors vela:cursor-pointer vela:focus-visible:outline-none vela:focus-visible:ring-2 vela:focus-visible:ring-[var(--cms-accent)] vela:disabled:cursor-not-allowed vela:disabled:opacity-50 vela:data-[state=checked]:bg-[var(--cms-accent)] vela:data-[state=unchecked]:bg-[var(--cms-bar-divider)]',
|
|
19
|
+
className
|
|
20
|
+
)}
|
|
21
|
+
{...restProps}
|
|
22
|
+
>
|
|
23
|
+
<SwitchPrimitive.Thumb
|
|
24
|
+
data-slot="switch-thumb"
|
|
25
|
+
class="vela:pointer-events-none vela:block vela:size-4 vela:rounded-full vela:bg-white vela:shadow vela:transition-transform vela:data-[state=checked]:translate-x-4 vela:data-[state=unchecked]:translate-x-0.5"
|
|
26
|
+
/>
|
|
27
|
+
</SwitchPrimitive.Root>
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { Switch as SwitchPrimitive } from 'bits-ui';
|
|
2
|
+
import { type WithoutChildrenOrChild } from '../../utils.js';
|
|
3
|
+
declare const Switch: import("svelte").Component<WithoutChildrenOrChild<SwitchPrimitive.RootProps>, {}, "ref" | "checked">;
|
|
4
|
+
type Switch = ReturnType<typeof Switch>;
|
|
5
|
+
export default Switch;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import Root from './tabs.svelte';
|
|
2
|
+
import Content from './tabs-content.svelte';
|
|
3
|
+
import List from './tabs-list.svelte';
|
|
4
|
+
import Trigger from './tabs-trigger.svelte';
|
|
5
|
+
export { Root, Content, List, Trigger, Root as Tabs, Content as TabsContent, List as TabsList, Trigger as TabsTrigger };
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import Root from './tabs.svelte';
|
|
2
|
+
import Content from './tabs-content.svelte';
|
|
3
|
+
import List from './tabs-list.svelte';
|
|
4
|
+
import Trigger from './tabs-trigger.svelte';
|
|
5
|
+
export { Root, Content, List, Trigger,
|
|
6
|
+
//
|
|
7
|
+
Root as Tabs, Content as TabsContent, List as TabsList, Trigger as TabsTrigger };
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { Tabs as TabsPrimitive } from 'bits-ui';
|
|
3
|
+
import { cn } from '../../utils.js';
|
|
4
|
+
|
|
5
|
+
let {
|
|
6
|
+
ref = $bindable(null),
|
|
7
|
+
class: className,
|
|
8
|
+
...restProps
|
|
9
|
+
}: TabsPrimitive.ContentProps = $props();
|
|
10
|
+
</script>
|
|
11
|
+
|
|
12
|
+
<TabsPrimitive.Content
|
|
13
|
+
bind:ref
|
|
14
|
+
data-slot="tabs-content"
|
|
15
|
+
class={cn('vela:flex-1 vela:outline-none', className)}
|
|
16
|
+
{...restProps}
|
|
17
|
+
/>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { Tabs as TabsPrimitive } from 'bits-ui';
|
|
3
|
+
import { cn } from '../../utils.js';
|
|
4
|
+
|
|
5
|
+
let { ref = $bindable(null), class: className, ...restProps }: TabsPrimitive.ListProps = $props();
|
|
6
|
+
</script>
|
|
7
|
+
|
|
8
|
+
<TabsPrimitive.List
|
|
9
|
+
bind:ref
|
|
10
|
+
data-slot="tabs-list"
|
|
11
|
+
class={cn(
|
|
12
|
+
'vela:inline-flex vela:self-start vela:items-center vela:justify-center vela:p-0.5 vela:rounded-md vela:bg-[var(--cms-bar-bg-hover)] vela:border vela:border-[var(--cms-bar-divider)]',
|
|
13
|
+
className
|
|
14
|
+
)}
|
|
15
|
+
{...restProps}
|
|
16
|
+
/>
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { Tabs as TabsPrimitive } from 'bits-ui';
|
|
3
|
+
import { cn } from '../../utils.js';
|
|
4
|
+
|
|
5
|
+
let {
|
|
6
|
+
ref = $bindable(null),
|
|
7
|
+
class: className,
|
|
8
|
+
...restProps
|
|
9
|
+
}: TabsPrimitive.TriggerProps = $props();
|
|
10
|
+
</script>
|
|
11
|
+
|
|
12
|
+
<TabsPrimitive.Trigger
|
|
13
|
+
bind:ref
|
|
14
|
+
data-slot="tabs-trigger"
|
|
15
|
+
class={cn(
|
|
16
|
+
'vela:inline-flex vela:items-center vela:justify-center vela:gap-1.5 vela:px-2.5 vela:py-1 vela:rounded vela:text-[11px] vela:font-medium vela:whitespace-nowrap vela:cursor-pointer vela:transition-colors vela:text-bar-text-secondary vela:hover:text-bar-text vela:data-[state=active]:bg-bar-bg vela:data-[state=active]:text-bar-text vela:focus-visible:outline-none vela:focus-visible:ring-2 vela:focus-visible:ring-[var(--cms-accent)] vela:disabled:pointer-events-none vela:disabled:opacity-50',
|
|
17
|
+
className
|
|
18
|
+
)}
|
|
19
|
+
{...restProps}
|
|
20
|
+
/>
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { Tabs as TabsPrimitive } from 'bits-ui';
|
|
3
|
+
import { cn } from '../../utils.js';
|
|
4
|
+
|
|
5
|
+
let {
|
|
6
|
+
ref = $bindable(null),
|
|
7
|
+
value = $bindable(),
|
|
8
|
+
class: className,
|
|
9
|
+
...restProps
|
|
10
|
+
}: TabsPrimitive.RootProps = $props();
|
|
11
|
+
</script>
|
|
12
|
+
|
|
13
|
+
<TabsPrimitive.Root
|
|
14
|
+
bind:ref
|
|
15
|
+
bind:value
|
|
16
|
+
data-slot="tabs"
|
|
17
|
+
class={cn('vela:flex vela:flex-col vela:gap-2', className)}
|
|
18
|
+
{...restProps}
|
|
19
|
+
/>
|
|
@@ -1,35 +1,26 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import type { Snippet } from 'svelte';
|
|
3
|
-
import {
|
|
4
|
-
import { cmsStore, type CmsScopeRef } from './cms-store.svelte.js';
|
|
3
|
+
import { useCmsField } from './use-cms-field.svelte.js';
|
|
5
4
|
|
|
6
5
|
type Props = {
|
|
7
6
|
name: string;
|
|
7
|
+
scope?: string;
|
|
8
8
|
initial?: boolean;
|
|
9
9
|
value?: unknown;
|
|
10
10
|
children?: Snippet<[boolean]>;
|
|
11
11
|
};
|
|
12
|
-
let { name, initial = false, value, children }: Props = $props();
|
|
12
|
+
let { name, scope, initial = false, value, children }: Props = $props();
|
|
13
13
|
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
const field = useCmsField(
|
|
15
|
+
() => ({ name, scope, value }),
|
|
16
|
+
(raw): boolean => (typeof raw === 'boolean' ? raw : raw === null ? false : initial)
|
|
17
17
|
);
|
|
18
18
|
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
return ref ? cmsStore.getValue(ref, name) : undefined;
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
const current = $derived(typeof resolved === 'boolean' ? resolved : initial);
|
|
25
|
-
const editable = $derived(cmsStore.isEditing && value === undefined && !!ref);
|
|
26
|
-
|
|
27
|
-
const toggle = () => {
|
|
28
|
-
if (ref) cmsStore.setValue(ref, name, !current);
|
|
29
|
-
};
|
|
19
|
+
const current = $derived(field.current);
|
|
20
|
+
const toggle = () => field.set(!current);
|
|
30
21
|
</script>
|
|
31
22
|
|
|
32
|
-
{#if editable && ref}
|
|
23
|
+
{#if field.editable && field.ref}
|
|
33
24
|
<span class="cms-boolean-edit">
|
|
34
25
|
{@render children?.(current)}
|
|
35
26
|
<button
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
);
|
|
17
17
|
|
|
18
18
|
const live = $derived(cmsStore.getValue(scope, name));
|
|
19
|
-
const display = $derived(typeof live === 'string' ? live : (initial ?? ''));
|
|
19
|
+
const display = $derived(typeof live === 'string' ? live : live === null ? '' : (initial ?? ''));
|
|
20
20
|
|
|
21
21
|
let el = $state<HTMLInputElement>();
|
|
22
22
|
|
|
@@ -29,8 +29,8 @@
|
|
|
29
29
|
|
|
30
30
|
const onInput = (e: Event) => {
|
|
31
31
|
const raw = (e.currentTarget as HTMLInputElement).value;
|
|
32
|
-
|
|
33
|
-
cmsStore.setValue(scope, name, raw);
|
|
32
|
+
// An emptied input clears the field explicitly (`null`).
|
|
33
|
+
cmsStore.setValue(scope, name, raw === '' ? null : raw);
|
|
34
34
|
};
|
|
35
35
|
</script>
|
|
36
36
|
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
export type CmsDateTimeProps = {
|
|
7
7
|
name: string;
|
|
8
|
+
scope?: string;
|
|
8
9
|
/**
|
|
9
10
|
* `'datetime'` (default) → `<input type="datetime-local">`, stores
|
|
10
11
|
* `YYYY-MM-DDTHH:MM`. `'date'` → `<input type="date">`, stores
|
|
@@ -13,44 +14,43 @@
|
|
|
13
14
|
*/
|
|
14
15
|
mode?: CmsDateTimeMode;
|
|
15
16
|
fallback?: string;
|
|
16
|
-
/** Per-item override used by
|
|
17
|
+
/** Per-item override used by structured components. */
|
|
17
18
|
value?: unknown;
|
|
18
19
|
children?: Snippet<[string]>;
|
|
19
20
|
};
|
|
20
21
|
</script>
|
|
21
22
|
|
|
22
23
|
<script lang="ts">
|
|
23
|
-
import {
|
|
24
|
-
import { cmsStore, type CmsScopeRef } from './cms-store.svelte.js';
|
|
24
|
+
import { useCmsField } from './use-cms-field.svelte.js';
|
|
25
25
|
|
|
26
|
-
let { name, mode = 'datetime', fallback, value, children }: CmsDateTimeProps = $props();
|
|
26
|
+
let { name, scope, mode = 'datetime', fallback, value, children }: CmsDateTimeProps = $props();
|
|
27
27
|
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
28
|
+
const field = useCmsField(
|
|
29
|
+
() => ({ name, scope, value }),
|
|
30
|
+
(raw): string | undefined => {
|
|
31
|
+
if (raw === undefined) return fallback;
|
|
32
|
+
return typeof raw === 'string' ? raw : '';
|
|
33
|
+
}
|
|
31
34
|
);
|
|
32
35
|
|
|
33
|
-
const
|
|
34
|
-
if (value !== undefined) return value;
|
|
35
|
-
return ref ? cmsStore.getValue(ref, name) : undefined;
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
const current = $derived(typeof resolved === 'string' ? resolved : fallback);
|
|
39
|
-
const editable = $derived(cmsStore.isEditing && value === undefined && !!ref);
|
|
36
|
+
const current = $derived(field.current);
|
|
40
37
|
</script>
|
|
41
38
|
|
|
42
|
-
{#if editable && ref}
|
|
39
|
+
{#if field.editable && field.ref}
|
|
43
40
|
{#await import('./cms-date-time-editable.svelte') then { default: Editable }}
|
|
44
|
-
<Editable scope={ref} {name} {mode} initial={current} />
|
|
41
|
+
<Editable scope={field.ref} {name} {mode} initial={current} />
|
|
45
42
|
{/await}
|
|
46
43
|
{:else if current && children}
|
|
47
44
|
{@render children(current)}
|
|
48
45
|
{:else if current}
|
|
49
46
|
{current}
|
|
47
|
+
{:else if field.raw === null}
|
|
48
|
+
<!-- cleared by the editor -->
|
|
50
49
|
{:else if children}
|
|
51
50
|
{@render children('')}
|
|
52
51
|
{:else}
|
|
53
|
-
<span class="cms-missing" data-cms-name={name} data-cms-scope={
|
|
52
|
+
<span class="cms-missing" data-cms-name={name} data-cms-scope={field.ref?.scopeId ?? '?'}
|
|
53
|
+
>{name}</span
|
|
54
54
|
>
|
|
55
55
|
{/if}
|
|
56
56
|
|
|
@@ -2,6 +2,7 @@ import type { Snippet } from 'svelte';
|
|
|
2
2
|
export type CmsDateTimeMode = 'datetime' | 'date' | 'time';
|
|
3
3
|
export type CmsDateTimeProps = {
|
|
4
4
|
name: string;
|
|
5
|
+
scope?: string;
|
|
5
6
|
/**
|
|
6
7
|
* `'datetime'` (default) → `<input type="datetime-local">`, stores
|
|
7
8
|
* `YYYY-MM-DDTHH:MM`. `'date'` → `<input type="date">`, stores
|
|
@@ -10,7 +11,7 @@ export type CmsDateTimeProps = {
|
|
|
10
11
|
*/
|
|
11
12
|
mode?: CmsDateTimeMode;
|
|
12
13
|
fallback?: string;
|
|
13
|
-
/** Per-item override used by
|
|
14
|
+
/** Per-item override used by structured components. */
|
|
14
15
|
value?: unknown;
|
|
15
16
|
children?: Snippet<[string]>;
|
|
16
17
|
};
|
|
@@ -29,13 +29,17 @@
|
|
|
29
29
|
// Read leaves through `getValue` so live drafts reflect immediately. Fall
|
|
30
30
|
// back to the snapshot the display passed in when nothing's been written.
|
|
31
31
|
const currentUrl = $derived.by(() => {
|
|
32
|
-
const
|
|
32
|
+
const stored = cmsStore.getValue(scope, name);
|
|
33
|
+
const v = pathGet(stored, 'url');
|
|
33
34
|
if (typeof v === 'string') return v;
|
|
35
|
+
if (v === null || stored === null) return '';
|
|
34
36
|
return initial.url ?? '';
|
|
35
37
|
});
|
|
36
38
|
const currentAlt = $derived.by(() => {
|
|
37
|
-
const
|
|
39
|
+
const stored = cmsStore.getValue(scope, name);
|
|
40
|
+
const v = pathGet(stored, 'alt');
|
|
38
41
|
if (typeof v === 'string') return v;
|
|
42
|
+
if (v === null || stored === null) return '';
|
|
39
43
|
return initial.alt ?? '';
|
|
40
44
|
});
|
|
41
45
|
|
|
@@ -55,8 +59,13 @@
|
|
|
55
59
|
let uploading = $state(false);
|
|
56
60
|
let error: string | null = $state(null);
|
|
57
61
|
|
|
58
|
-
|
|
59
|
-
|
|
62
|
+
// Whole-object writes (see `core/structured.ts`): a cleared image is
|
|
63
|
+
// `{ url: null }`, which merges over the published tree as "cleared"
|
|
64
|
+
// instead of falling back to the template's demo image.
|
|
65
|
+
const writeUrl = (v: string) =>
|
|
66
|
+
cmsStore.setValue(scope, name, { url: v || null, alt: currentAlt || null });
|
|
67
|
+
const writeAlt = (v: string) =>
|
|
68
|
+
cmsStore.setValue(scope, name, { url: currentUrl || null, alt: v || null });
|
|
60
69
|
|
|
61
70
|
const upload = async (file: File) => {
|
|
62
71
|
if (!file.type.startsWith('image/')) {
|
|
@@ -1,62 +1,69 @@
|
|
|
1
1
|
<script lang="ts" module>
|
|
2
|
+
/**
|
|
3
|
+
* Stored shape of an image slot. Seeds and fallbacks may pass a bare URL
|
|
4
|
+
* string; it is normalized to `{ url }`. A cleared slot stores `url: null`.
|
|
5
|
+
*/
|
|
2
6
|
export type CmsImageValue = {
|
|
3
|
-
url?: string;
|
|
4
|
-
alt?: string;
|
|
7
|
+
url?: string | null;
|
|
8
|
+
alt?: string | null;
|
|
5
9
|
width?: number;
|
|
6
10
|
height?: number;
|
|
7
11
|
};
|
|
12
|
+
|
|
13
|
+
export const normalizeImage = (raw: unknown): CmsImageValue => {
|
|
14
|
+
if (raw == null) return {};
|
|
15
|
+
if (typeof raw === 'string') return { url: raw };
|
|
16
|
+
if (typeof raw === 'object' && !Array.isArray(raw)) {
|
|
17
|
+
const r = raw as Record<string, unknown>;
|
|
18
|
+
const out: CmsImageValue = {};
|
|
19
|
+
if (typeof r.url === 'string') out.url = r.url;
|
|
20
|
+
if (typeof r.alt === 'string') out.alt = r.alt;
|
|
21
|
+
if (typeof r.width === 'number') out.width = r.width;
|
|
22
|
+
if (typeof r.height === 'number') out.height = r.height;
|
|
23
|
+
return out;
|
|
24
|
+
}
|
|
25
|
+
return {};
|
|
26
|
+
};
|
|
8
27
|
</script>
|
|
9
28
|
|
|
10
29
|
<script lang="ts">
|
|
11
30
|
import type { Snippet } from 'svelte';
|
|
12
|
-
import {
|
|
13
|
-
import { cmsStore, type CmsScopeRef } from './cms-store.svelte.js';
|
|
31
|
+
import { useCmsField } from './use-cms-field.svelte.js';
|
|
14
32
|
|
|
15
33
|
type Props = {
|
|
16
34
|
name: string;
|
|
17
|
-
|
|
35
|
+
scope?: string;
|
|
36
|
+
/** Fallback alt text used when the stored value has no `alt`. */
|
|
18
37
|
alt?: string;
|
|
19
|
-
|
|
38
|
+
/** A URL, or a full `{ url, alt }` value. */
|
|
39
|
+
fallback?: string | CmsImageValue;
|
|
20
40
|
value?: unknown;
|
|
21
41
|
children?: Snippet;
|
|
22
42
|
};
|
|
23
43
|
|
|
24
|
-
let { name, alt = '', fallback, value, children }: Props = $props();
|
|
44
|
+
let { name, scope, alt = '', fallback, value, children }: Props = $props();
|
|
25
45
|
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
46
|
+
const field = useCmsField(
|
|
47
|
+
() => ({ name, scope, value }),
|
|
48
|
+
(raw): CmsImageValue => (raw === undefined ? normalizeImage(fallback) : normalizeImage(raw))
|
|
29
49
|
);
|
|
30
50
|
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
return ref ? cmsStore.getValue(ref, name) : undefined;
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
// `<CmsImage>` owns a `{ url, alt, width?, height? }` branch. Accept a bare
|
|
37
|
-
// string as the URL leaf for legacy/value-prop callers.
|
|
38
|
-
const branch = $derived.by((): CmsImageValue | null => {
|
|
39
|
-
if (resolved == null) return null;
|
|
40
|
-
if (typeof resolved === 'string') return { url: resolved };
|
|
41
|
-
if (typeof resolved === 'object' && !Array.isArray(resolved)) return resolved as CmsImageValue;
|
|
42
|
-
return null;
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
const src = $derived(branch?.url ?? fallback ?? '');
|
|
46
|
-
const altText = $derived(branch?.alt ?? alt ?? '');
|
|
47
|
-
const editable = $derived(cmsStore.isEditing && value === undefined && !!ref);
|
|
51
|
+
const src = $derived(field.current.url ?? '');
|
|
52
|
+
const altText = $derived(field.current.alt ?? alt);
|
|
48
53
|
</script>
|
|
49
54
|
|
|
50
|
-
{#if editable && ref}
|
|
55
|
+
{#if field.editable && field.ref}
|
|
51
56
|
{#await import('./cms-image-editable.svelte') then { default: Editable }}
|
|
52
|
-
<Editable scope={ref} {name} initial={
|
|
57
|
+
<Editable scope={field.ref} {name} initial={field.current} fallbackAlt={alt} />
|
|
53
58
|
{/await}
|
|
54
59
|
{:else if src}
|
|
55
60
|
<img class="cms-image" {src} alt={altText} />
|
|
61
|
+
{:else if field.raw === null}
|
|
62
|
+
<!-- cleared by the editor -->
|
|
56
63
|
{:else if children}
|
|
57
64
|
{@render children()}
|
|
58
65
|
{:else}
|
|
59
|
-
<span class="cms-missing" data-cms-name={name} data-cms-scope={
|
|
66
|
+
<span class="cms-missing" data-cms-name={name} data-cms-scope={field.ref?.scopeId ?? '?'}>
|
|
60
67
|
{name}
|
|
61
68
|
</span>
|
|
62
69
|
{/if}
|
|
@@ -1,15 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stored shape of an image slot. Seeds and fallbacks may pass a bare URL
|
|
3
|
+
* string; it is normalized to `{ url }`. A cleared slot stores `url: null`.
|
|
4
|
+
*/
|
|
1
5
|
export type CmsImageValue = {
|
|
2
|
-
url?: string;
|
|
3
|
-
alt?: string;
|
|
6
|
+
url?: string | null;
|
|
7
|
+
alt?: string | null;
|
|
4
8
|
width?: number;
|
|
5
9
|
height?: number;
|
|
6
10
|
};
|
|
11
|
+
export declare const normalizeImage: (raw: unknown) => CmsImageValue;
|
|
7
12
|
import type { Snippet } from 'svelte';
|
|
8
13
|
type Props = {
|
|
9
14
|
name: string;
|
|
10
|
-
|
|
15
|
+
scope?: string;
|
|
16
|
+
/** Fallback alt text used when the stored value has no `alt`. */
|
|
11
17
|
alt?: string;
|
|
12
|
-
|
|
18
|
+
/** A URL, or a full `{ url, alt }` value. */
|
|
19
|
+
fallback?: string | CmsImageValue;
|
|
13
20
|
value?: unknown;
|
|
14
21
|
children?: Snippet;
|
|
15
22
|
};
|