includio-cms 0.15.4 → 0.16.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/CHANGELOG.md +27 -0
- package/DOCS.md +1 -1
- package/ROADMAP.md +22 -8
- package/dist/admin/components/fields/media-field.svelte +54 -2
- package/dist/admin/components/media/file/file-details.svelte +65 -0
- package/dist/admin/remote/media.remote.d.ts +1 -0
- package/dist/admin/remote/media.remote.js +5 -0
- package/dist/cmp/types.d.ts +25 -0
- package/dist/cmp/types.js +1 -0
- package/dist/core/cms.d.ts +2 -0
- package/dist/core/cms.js +2 -0
- package/dist/core/server/cmp/getCountryFromHeaders.d.ts +10 -0
- package/dist/core/server/cmp/getCountryFromHeaders.js +30 -0
- package/dist/core/server/cmp/operations/create.d.ts +17 -0
- package/dist/core/server/cmp/operations/create.js +38 -0
- package/dist/core/server/cmp/operations/get.d.ts +2 -0
- package/dist/core/server/cmp/operations/get.js +8 -0
- package/dist/core/server/cmp/operations/list.d.ts +3 -0
- package/dist/core/server/cmp/operations/list.js +15 -0
- package/dist/core/server/cmp/truncateIpAddress.d.ts +7 -0
- package/dist/core/server/cmp/truncateIpAddress.js +57 -0
- package/dist/core/server/fields/resolveImageFields.d.ts +5 -0
- package/dist/core/server/fields/resolveImageFields.js +9 -1
- package/dist/core/server/generator/generator.js +22 -6
- package/dist/core/server/media/operations/backgroundMaintenance.js +51 -20
- package/dist/core/server/media/operations/findMediaReferences.d.ts +16 -0
- package/dist/core/server/media/operations/findMediaReferences.js +60 -0
- package/dist/db-postgres/index.js +46 -1
- package/dist/db-postgres/schema/consentLog.d.ts +17 -0
- package/dist/db-postgres/schema/consentLog.js +4 -1
- package/dist/paraglide/messages/_index.d.ts +36 -3
- package/dist/paraglide/messages/_index.js +71 -3
- package/dist/paraglide/messages/en.d.ts +5 -0
- package/dist/paraglide/messages/en.js +14 -0
- package/dist/paraglide/messages/pl.d.ts +5 -0
- package/dist/paraglide/messages/pl.js +14 -0
- package/dist/sveltekit/server/handle.js +1 -1
- package/dist/types/adapters/db.d.ts +7 -1
- package/dist/types/cms.d.ts +3 -0
- package/dist/types/consent.d.ts +11 -0
- package/dist/updates/0.15.5/index.d.ts +2 -0
- package/dist/updates/0.15.5/index.js +15 -0
- package/dist/updates/0.16.0/index.d.ts +2 -0
- package/dist/updates/0.16.0/index.js +14 -0
- package/dist/updates/index.js +3 -1
- package/package.json +1 -1
- package/dist/demo/seed.d.ts +0 -1
- package/dist/demo/seed.js +0 -117
- package/dist/inline-edit-proto/ModeToggle.svelte +0 -36
- package/dist/inline-edit-proto/ModeToggle.svelte.d.ts +0 -18
- package/dist/inline-edit-proto/blocks/AddBlockButton.svelte +0 -47
- package/dist/inline-edit-proto/blocks/AddBlockButton.svelte.d.ts +0 -8
- package/dist/inline-edit-proto/blocks/BlockToolbar.svelte +0 -80
- package/dist/inline-edit-proto/blocks/BlockToolbar.svelte.d.ts +0 -13
- package/dist/inline-edit-proto/blocks/BlockWrapper.svelte +0 -83
- package/dist/inline-edit-proto/blocks/BlockWrapper.svelte.d.ts +0 -11
- package/dist/inline-edit-proto/context.svelte.d.ts +0 -65
- package/dist/inline-edit-proto/context.svelte.js +0 -194
- package/dist/inline-edit-proto/hybrid/EditableHybrid.svelte +0 -70
- package/dist/inline-edit-proto/hybrid/EditableHybrid.svelte.d.ts +0 -11
- package/dist/inline-edit-proto/hybrid/FieldRenderer.svelte +0 -94
- package/dist/inline-edit-proto/hybrid/FieldRenderer.svelte.d.ts +0 -11
- package/dist/inline-edit-proto/hybrid/HybridEditor.svelte +0 -107
- package/dist/inline-edit-proto/hybrid/HybridEditor.svelte.d.ts +0 -7
- package/dist/inline-edit-proto/hybrid/SyncPanel.svelte +0 -181
- package/dist/inline-edit-proto/hybrid/SyncPanel.svelte.d.ts +0 -3
- package/dist/inline-edit-proto/inline/EditableInline.svelte +0 -120
- package/dist/inline-edit-proto/inline/EditableInline.svelte.d.ts +0 -11
- package/dist/inline-edit-proto/inline/InlineToolbar.svelte +0 -71
- package/dist/inline-edit-proto/inline/InlineToolbar.svelte.d.ts +0 -6
- package/dist/inline-edit-proto/panel/EditSheet.svelte +0 -130
- package/dist/inline-edit-proto/panel/EditSheet.svelte.d.ts +0 -3
- package/dist/inline-edit-proto/panel/EditablePanel.svelte +0 -44
- package/dist/inline-edit-proto/panel/EditablePanel.svelte.d.ts +0 -9
- package/dist/paraglide/messages/hello_world.d.ts +0 -5
- package/dist/paraglide/messages/hello_world.js +0 -33
- package/dist/paraglide/messages/login_hello.d.ts +0 -16
- package/dist/paraglide/messages/login_hello.js +0 -34
- package/dist/paraglide/messages/login_please_login.d.ts +0 -16
- package/dist/paraglide/messages/login_please_login.js +0 -34
package/package.json
CHANGED
package/dist/demo/seed.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare function seedDemoData(): Promise<void>;
|
package/dist/demo/seed.js
DELETED
|
@@ -1,117 +0,0 @@
|
|
|
1
|
-
import { getCMS } from '../core/cms.js';
|
|
2
|
-
const blogPosts = [
|
|
3
|
-
{
|
|
4
|
-
title: 'Getting Started with Includio CMS',
|
|
5
|
-
slug: 'getting-started',
|
|
6
|
-
category: 'tutorial',
|
|
7
|
-
content: '<p>Includio CMS is a modern, headless content management system built with SvelteKit. It provides a flexible and developer-friendly way to manage content for your web applications.</p><p>In this tutorial, we will walk through the basic setup and configuration of Includio CMS.</p>',
|
|
8
|
-
publishedAt: '2024-12-01'
|
|
9
|
-
},
|
|
10
|
-
{
|
|
11
|
-
title: 'Building Modern Web Applications',
|
|
12
|
-
slug: 'modern-web-apps',
|
|
13
|
-
category: 'technology',
|
|
14
|
-
content: '<p>The landscape of web development is constantly evolving. With frameworks like SvelteKit, developers can build fast, modern applications with great developer experience.</p><p>This post explores the key principles behind modern web application architecture.</p>',
|
|
15
|
-
publishedAt: '2024-12-15'
|
|
16
|
-
},
|
|
17
|
-
{
|
|
18
|
-
title: 'Design Systems for Content Management',
|
|
19
|
-
slug: 'design-systems-cms',
|
|
20
|
-
category: 'design',
|
|
21
|
-
content: '<p>A well-designed content management interface can make all the difference in editorial productivity. Learn how design systems help create consistent, usable admin panels.</p>',
|
|
22
|
-
publishedAt: '2025-01-05'
|
|
23
|
-
},
|
|
24
|
-
{
|
|
25
|
-
title: 'Scaling Your Content Strategy',
|
|
26
|
-
slug: 'scaling-content-strategy',
|
|
27
|
-
category: 'business',
|
|
28
|
-
content: '<p>As your business grows, so does your content needs. This post discusses strategies for scaling content operations while maintaining quality and consistency.</p>',
|
|
29
|
-
publishedAt: '2025-01-20'
|
|
30
|
-
}
|
|
31
|
-
];
|
|
32
|
-
const projects = [
|
|
33
|
-
{
|
|
34
|
-
title: 'E-commerce Platform',
|
|
35
|
-
description: 'A full-featured online store built with SvelteKit and Includio CMS.',
|
|
36
|
-
url: 'https://example.com/ecommerce',
|
|
37
|
-
techStack: [{ name: 'SvelteKit' }, { name: 'PostgreSQL' }, { name: 'Stripe' }]
|
|
38
|
-
},
|
|
39
|
-
{
|
|
40
|
-
title: 'Corporate Website',
|
|
41
|
-
description: 'Multi-language corporate site with dynamic content management.',
|
|
42
|
-
url: 'https://example.com/corporate',
|
|
43
|
-
techStack: [{ name: 'SvelteKit' }, { name: 'Includio CMS' }, { name: 'Tailwind CSS' }]
|
|
44
|
-
},
|
|
45
|
-
{
|
|
46
|
-
title: 'Blog Platform',
|
|
47
|
-
description: 'A performant blogging platform with SEO optimization.',
|
|
48
|
-
url: 'https://example.com/blog',
|
|
49
|
-
techStack: [{ name: 'SvelteKit' }, { name: 'MDsveX' }, { name: 'Vercel' }]
|
|
50
|
-
}
|
|
51
|
-
];
|
|
52
|
-
const settingsData = {
|
|
53
|
-
siteName: 'Includio Demo',
|
|
54
|
-
description: 'A demo instance of Includio CMS showcasing its features.',
|
|
55
|
-
socialLinks: [
|
|
56
|
-
{ platform: 'GitHub', url: 'https://github.com/includio' },
|
|
57
|
-
{ platform: 'Twitter', url: 'https://twitter.com/includio' }
|
|
58
|
-
]
|
|
59
|
-
};
|
|
60
|
-
export async function seedDemoData() {
|
|
61
|
-
const cms = getCMS();
|
|
62
|
-
const db = cms.databaseAdapter;
|
|
63
|
-
// Seed blog posts
|
|
64
|
-
for (const post of blogPosts) {
|
|
65
|
-
const entry = await db.createEntry({
|
|
66
|
-
slug: 'blog-post',
|
|
67
|
-
type: 'collection'
|
|
68
|
-
});
|
|
69
|
-
await db.createEntryVersion({
|
|
70
|
-
entryId: entry.id,
|
|
71
|
-
versionNumber: 1,
|
|
72
|
-
data: {
|
|
73
|
-
en: {
|
|
74
|
-
title: post.title,
|
|
75
|
-
slug: post.slug,
|
|
76
|
-
category: post.category,
|
|
77
|
-
content: post.content,
|
|
78
|
-
publishedAt: post.publishedAt
|
|
79
|
-
}
|
|
80
|
-
},
|
|
81
|
-
publishedAt: new Date(post.publishedAt)
|
|
82
|
-
});
|
|
83
|
-
}
|
|
84
|
-
// Seed projects
|
|
85
|
-
for (const project of projects) {
|
|
86
|
-
const entry = await db.createEntry({
|
|
87
|
-
slug: 'project',
|
|
88
|
-
type: 'collection'
|
|
89
|
-
});
|
|
90
|
-
await db.createEntryVersion({
|
|
91
|
-
entryId: entry.id,
|
|
92
|
-
versionNumber: 1,
|
|
93
|
-
data: {
|
|
94
|
-
en: {
|
|
95
|
-
title: project.title,
|
|
96
|
-
description: project.description,
|
|
97
|
-
url: project.url,
|
|
98
|
-
techStack: project.techStack
|
|
99
|
-
}
|
|
100
|
-
},
|
|
101
|
-
publishedAt: new Date()
|
|
102
|
-
});
|
|
103
|
-
}
|
|
104
|
-
// Seed settings single
|
|
105
|
-
const settingsEntry = await db.createEntry({
|
|
106
|
-
slug: 'settings',
|
|
107
|
-
type: 'singleton'
|
|
108
|
-
});
|
|
109
|
-
await db.createEntryVersion({
|
|
110
|
-
entryId: settingsEntry.id,
|
|
111
|
-
versionNumber: 1,
|
|
112
|
-
data: {
|
|
113
|
-
en: settingsData
|
|
114
|
-
},
|
|
115
|
-
publishedAt: new Date()
|
|
116
|
-
});
|
|
117
|
-
}
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
import { getEditContext, type EditMode } from './context.svelte.js';
|
|
3
|
-
|
|
4
|
-
const ctx = getEditContext();
|
|
5
|
-
|
|
6
|
-
const modes: { value: EditMode; label: string; desc: string }[] = [
|
|
7
|
-
{ value: 'panel', label: 'A', desc: 'Panel' },
|
|
8
|
-
{ value: 'inline', label: 'B', desc: 'Inline' },
|
|
9
|
-
{ value: 'blocks', label: 'C', desc: 'Blocks' },
|
|
10
|
-
{ value: 'hybrid', label: 'D', desc: 'Hybrid' }
|
|
11
|
-
];
|
|
12
|
-
</script>
|
|
13
|
-
|
|
14
|
-
<div class="fixed bottom-6 left-1/2 z-50 -translate-x-1/2">
|
|
15
|
-
<div class="flex items-center gap-1 rounded-full bg-slate-900 p-1.5 shadow-xl">
|
|
16
|
-
{#each modes as m}
|
|
17
|
-
<button
|
|
18
|
-
onclick={() => (ctx.mode = m.value)}
|
|
19
|
-
class="flex items-center gap-2 rounded-full px-4 py-2 text-sm font-medium transition-all {ctx.mode ===
|
|
20
|
-
m.value
|
|
21
|
-
? 'bg-white text-slate-900'
|
|
22
|
-
: 'text-slate-400 hover:text-white'}"
|
|
23
|
-
>
|
|
24
|
-
<span
|
|
25
|
-
class="flex h-5 w-5 items-center justify-center rounded-full text-xs font-bold {ctx.mode ===
|
|
26
|
-
m.value
|
|
27
|
-
? 'bg-slate-900 text-white'
|
|
28
|
-
: 'bg-slate-700 text-slate-400'}"
|
|
29
|
-
>
|
|
30
|
-
{m.label}
|
|
31
|
-
</span>
|
|
32
|
-
<span class="hidden sm:inline">{m.desc}</span>
|
|
33
|
-
</button>
|
|
34
|
-
{/each}
|
|
35
|
-
</div>
|
|
36
|
-
</div>
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> = any, Events extends Record<string, any> = any, Slots extends Record<string, any> = any, Exports = {}, Bindings = string> {
|
|
2
|
-
new (options: import('svelte').ComponentConstructorOptions<Props>): import('svelte').SvelteComponent<Props, Events, Slots> & {
|
|
3
|
-
$$bindings?: Bindings;
|
|
4
|
-
} & Exports;
|
|
5
|
-
(internal: unknown, props: {
|
|
6
|
-
$$events?: Events;
|
|
7
|
-
$$slots?: Slots;
|
|
8
|
-
}): Exports & {
|
|
9
|
-
$set?: any;
|
|
10
|
-
$on?: any;
|
|
11
|
-
};
|
|
12
|
-
z_$$bindings?: Bindings;
|
|
13
|
-
}
|
|
14
|
-
declare const ModeToggle: $$__sveltets_2_IsomorphicComponent<Record<string, never>, {
|
|
15
|
-
[evt: string]: CustomEvent<any>;
|
|
16
|
-
}, {}, {}, string>;
|
|
17
|
-
type ModeToggle = InstanceType<typeof ModeToggle>;
|
|
18
|
-
export default ModeToggle;
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
import Plus from '@tabler/icons-svelte/icons/plus';
|
|
3
|
-
import { getEditContext } from '../context.svelte.js';
|
|
4
|
-
|
|
5
|
-
type Props = {
|
|
6
|
-
arrayPath: string;
|
|
7
|
-
index: number;
|
|
8
|
-
itemTemplate: () => Record<string, unknown>;
|
|
9
|
-
};
|
|
10
|
-
|
|
11
|
-
let { arrayPath, index, itemTemplate }: Props = $props();
|
|
12
|
-
|
|
13
|
-
const ctx = getEditContext();
|
|
14
|
-
|
|
15
|
-
let isHovered = $state(false);
|
|
16
|
-
|
|
17
|
-
function addBlock() {
|
|
18
|
-
const arr = ctx.getData(arrayPath) as unknown[];
|
|
19
|
-
if (Array.isArray(arr)) {
|
|
20
|
-
const newItem = { ...itemTemplate(), _id: crypto.randomUUID() };
|
|
21
|
-
arr.splice(index, 0, newItem);
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
</script>
|
|
25
|
-
|
|
26
|
-
{#if ctx.mode === 'blocks'}
|
|
27
|
-
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
28
|
-
<div
|
|
29
|
-
class="add-block-zone relative my-2 flex h-8 items-center justify-center"
|
|
30
|
-
onmouseenter={() => (isHovered = true)}
|
|
31
|
-
onmouseleave={() => (isHovered = false)}
|
|
32
|
-
>
|
|
33
|
-
{#if isHovered}
|
|
34
|
-
<div class="absolute inset-x-0 top-1/2 h-px bg-[#1e3a5f]/30"></div>
|
|
35
|
-
<button
|
|
36
|
-
type="button"
|
|
37
|
-
onclick={addBlock}
|
|
38
|
-
class="relative z-10 flex h-8 w-8 items-center justify-center rounded-full bg-[#1e3a5f] text-white shadow-lg transition-transform hover:scale-110"
|
|
39
|
-
title="Add block"
|
|
40
|
-
>
|
|
41
|
-
<Plus class="h-4 w-4" />
|
|
42
|
-
</button>
|
|
43
|
-
{:else}
|
|
44
|
-
<div class="h-1 w-full cursor-pointer rounded bg-transparent transition-colors hover:bg-[#1e3a5f]/10"></div>
|
|
45
|
-
{/if}
|
|
46
|
-
</div>
|
|
47
|
-
{/if}
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
type Props = {
|
|
2
|
-
arrayPath: string;
|
|
3
|
-
index: number;
|
|
4
|
-
itemTemplate: () => Record<string, unknown>;
|
|
5
|
-
};
|
|
6
|
-
declare const AddBlockButton: import("svelte").Component<Props, {}, "">;
|
|
7
|
-
type AddBlockButton = ReturnType<typeof AddBlockButton>;
|
|
8
|
-
export default AddBlockButton;
|
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
import ArrowUp from '@tabler/icons-svelte/icons/arrow-up';
|
|
3
|
-
import ArrowDown from '@tabler/icons-svelte/icons/arrow-down';
|
|
4
|
-
import Trash from '@tabler/icons-svelte/icons/trash';
|
|
5
|
-
import Settings from '@tabler/icons-svelte/icons/settings';
|
|
6
|
-
import GripVertical from '@tabler/icons-svelte/icons/grip-vertical';
|
|
7
|
-
|
|
8
|
-
type Props = {
|
|
9
|
-
label: string;
|
|
10
|
-
canMoveUp: boolean;
|
|
11
|
-
canMoveDown: boolean;
|
|
12
|
-
canDelete: boolean;
|
|
13
|
-
onMoveUp: () => void;
|
|
14
|
-
onMoveDown: () => void;
|
|
15
|
-
onDelete: () => void;
|
|
16
|
-
onSettings: () => void;
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
let { label, canMoveUp, canMoveDown, canDelete, onMoveUp, onMoveDown, onDelete, onSettings }: Props = $props();
|
|
20
|
-
</script>
|
|
21
|
-
|
|
22
|
-
<div class="absolute -left-12 top-1/2 z-20 flex -translate-y-1/2 flex-col items-center gap-1">
|
|
23
|
-
<div class="flex flex-col rounded-lg bg-slate-900 p-1 shadow-xl">
|
|
24
|
-
<button
|
|
25
|
-
type="button"
|
|
26
|
-
class="cursor-grab rounded p-1.5 text-slate-400 transition-colors hover:bg-slate-700 hover:text-white"
|
|
27
|
-
title="Drag to reorder"
|
|
28
|
-
>
|
|
29
|
-
<GripVertical class="h-4 w-4" />
|
|
30
|
-
</button>
|
|
31
|
-
|
|
32
|
-
<div class="my-1 h-px bg-slate-700"></div>
|
|
33
|
-
|
|
34
|
-
<button
|
|
35
|
-
type="button"
|
|
36
|
-
onclick={onMoveUp}
|
|
37
|
-
disabled={!canMoveUp}
|
|
38
|
-
class="rounded p-1.5 text-slate-400 transition-colors hover:bg-slate-700 hover:text-white disabled:cursor-not-allowed disabled:opacity-30"
|
|
39
|
-
title="Move up"
|
|
40
|
-
>
|
|
41
|
-
<ArrowUp class="h-4 w-4" />
|
|
42
|
-
</button>
|
|
43
|
-
|
|
44
|
-
<button
|
|
45
|
-
type="button"
|
|
46
|
-
onclick={onMoveDown}
|
|
47
|
-
disabled={!canMoveDown}
|
|
48
|
-
class="rounded p-1.5 text-slate-400 transition-colors hover:bg-slate-700 hover:text-white disabled:cursor-not-allowed disabled:opacity-30"
|
|
49
|
-
title="Move down"
|
|
50
|
-
>
|
|
51
|
-
<ArrowDown class="h-4 w-4" />
|
|
52
|
-
</button>
|
|
53
|
-
|
|
54
|
-
<div class="my-1 h-px bg-slate-700"></div>
|
|
55
|
-
|
|
56
|
-
<button
|
|
57
|
-
type="button"
|
|
58
|
-
onclick={onSettings}
|
|
59
|
-
class="rounded p-1.5 text-slate-400 transition-colors hover:bg-slate-700 hover:text-white"
|
|
60
|
-
title="Edit settings"
|
|
61
|
-
>
|
|
62
|
-
<Settings class="h-4 w-4" />
|
|
63
|
-
</button>
|
|
64
|
-
|
|
65
|
-
{#if canDelete}
|
|
66
|
-
<button
|
|
67
|
-
type="button"
|
|
68
|
-
onclick={onDelete}
|
|
69
|
-
class="rounded p-1.5 text-red-400 transition-colors hover:bg-red-900/50 hover:text-red-300"
|
|
70
|
-
title="Delete"
|
|
71
|
-
>
|
|
72
|
-
<Trash class="h-4 w-4" />
|
|
73
|
-
</button>
|
|
74
|
-
{/if}
|
|
75
|
-
</div>
|
|
76
|
-
</div>
|
|
77
|
-
|
|
78
|
-
<div class="absolute -top-3 left-4 z-20 rounded bg-slate-900 px-2 py-0.5 text-xs font-medium text-slate-300 shadow">
|
|
79
|
-
{label}
|
|
80
|
-
</div>
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
type Props = {
|
|
2
|
-
label: string;
|
|
3
|
-
canMoveUp: boolean;
|
|
4
|
-
canMoveDown: boolean;
|
|
5
|
-
canDelete: boolean;
|
|
6
|
-
onMoveUp: () => void;
|
|
7
|
-
onMoveDown: () => void;
|
|
8
|
-
onDelete: () => void;
|
|
9
|
-
onSettings: () => void;
|
|
10
|
-
};
|
|
11
|
-
declare const BlockToolbar: import("svelte").Component<Props, {}, "">;
|
|
12
|
-
type BlockToolbar = ReturnType<typeof BlockToolbar>;
|
|
13
|
-
export default BlockToolbar;
|
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
import type { Snippet } from 'svelte';
|
|
3
|
-
import { getEditContext } from '../context.svelte.js';
|
|
4
|
-
import BlockToolbar from './BlockToolbar.svelte';
|
|
5
|
-
|
|
6
|
-
type Props = {
|
|
7
|
-
path: string;
|
|
8
|
-
index?: number;
|
|
9
|
-
arrayPath?: string;
|
|
10
|
-
label?: string;
|
|
11
|
-
children: Snippet;
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
let { path, index, arrayPath, label = 'Block', children }: Props = $props();
|
|
15
|
-
|
|
16
|
-
const ctx = getEditContext();
|
|
17
|
-
|
|
18
|
-
let isHovered = $state(false);
|
|
19
|
-
let isExpanded = $state(false);
|
|
20
|
-
|
|
21
|
-
function handleMoveUp() {
|
|
22
|
-
if (arrayPath !== undefined && index !== undefined && index > 0) {
|
|
23
|
-
ctx.moveArrayItem(arrayPath, index, index - 1);
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
function handleMoveDown() {
|
|
28
|
-
if (arrayPath !== undefined && index !== undefined) {
|
|
29
|
-
const arr = ctx.getData(arrayPath) as unknown[];
|
|
30
|
-
if (arr && index < arr.length - 1) {
|
|
31
|
-
ctx.moveArrayItem(arrayPath, index, index + 1);
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
function handleDelete() {
|
|
37
|
-
if (arrayPath !== undefined && index !== undefined) {
|
|
38
|
-
ctx.removeArrayItem(arrayPath, index);
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
function handleSettings() {
|
|
43
|
-
// Open panel edit for this block
|
|
44
|
-
ctx.editingPath = path;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
let canMoveUp = $derived(index !== undefined && index > 0);
|
|
48
|
-
let canMoveDown = $derived.by(() => {
|
|
49
|
-
if (index === undefined || !arrayPath) return false;
|
|
50
|
-
const arr = ctx.getData(arrayPath) as unknown[];
|
|
51
|
-
return arr && index < arr.length - 1;
|
|
52
|
-
});
|
|
53
|
-
let canDelete = $derived(arrayPath !== undefined && index !== undefined);
|
|
54
|
-
</script>
|
|
55
|
-
|
|
56
|
-
{#if ctx.mode === 'blocks'}
|
|
57
|
-
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
58
|
-
<div
|
|
59
|
-
class="block-wrapper group relative"
|
|
60
|
-
onmouseenter={() => (isHovered = true)}
|
|
61
|
-
onmouseleave={() => (isHovered = false)}
|
|
62
|
-
>
|
|
63
|
-
{#if isHovered}
|
|
64
|
-
<BlockToolbar
|
|
65
|
-
{label}
|
|
66
|
-
{canMoveUp}
|
|
67
|
-
{canMoveDown}
|
|
68
|
-
{canDelete}
|
|
69
|
-
onMoveUp={handleMoveUp}
|
|
70
|
-
onMoveDown={handleMoveDown}
|
|
71
|
-
onDelete={handleDelete}
|
|
72
|
-
onSettings={handleSettings}
|
|
73
|
-
/>
|
|
74
|
-
<div class="pointer-events-none absolute inset-0 rounded-xl ring-2 ring-[#1e3a5f]/30"></div>
|
|
75
|
-
{/if}
|
|
76
|
-
|
|
77
|
-
<div class="relative">
|
|
78
|
-
{@render children()}
|
|
79
|
-
</div>
|
|
80
|
-
</div>
|
|
81
|
-
{:else}
|
|
82
|
-
{@render children()}
|
|
83
|
-
{/if}
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import type { Snippet } from 'svelte';
|
|
2
|
-
type Props = {
|
|
3
|
-
path: string;
|
|
4
|
-
index?: number;
|
|
5
|
-
arrayPath?: string;
|
|
6
|
-
label?: string;
|
|
7
|
-
children: Snippet;
|
|
8
|
-
};
|
|
9
|
-
declare const BlockWrapper: import("svelte").Component<Props, {}, "">;
|
|
10
|
-
type BlockWrapper = ReturnType<typeof BlockWrapper>;
|
|
11
|
-
export default BlockWrapper;
|
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
export type EditMode = 'panel' | 'inline' | 'blocks' | 'hybrid';
|
|
2
|
-
export type SaveStatus = 'idle' | 'saving' | 'saved' | 'error';
|
|
3
|
-
export type ContentBlock = {
|
|
4
|
-
_id: string;
|
|
5
|
-
type: 'hero' | 'features' | 'text' | 'image' | 'cta';
|
|
6
|
-
data: Record<string, unknown>;
|
|
7
|
-
};
|
|
8
|
-
export type DemoData = {
|
|
9
|
-
hero: {
|
|
10
|
-
title: string;
|
|
11
|
-
subtitle: string;
|
|
12
|
-
cta: string;
|
|
13
|
-
};
|
|
14
|
-
features: Array<{
|
|
15
|
-
_id: string;
|
|
16
|
-
title: string;
|
|
17
|
-
description: string;
|
|
18
|
-
icon: string;
|
|
19
|
-
}>;
|
|
20
|
-
content: {
|
|
21
|
-
title: string;
|
|
22
|
-
body: string;
|
|
23
|
-
};
|
|
24
|
-
image: {
|
|
25
|
-
src: string;
|
|
26
|
-
alt: string;
|
|
27
|
-
caption: string;
|
|
28
|
-
};
|
|
29
|
-
};
|
|
30
|
-
export declare function createEditContext(initialData: DemoData): {
|
|
31
|
-
mode: EditMode;
|
|
32
|
-
readonly data: DemoData;
|
|
33
|
-
editingPath: string | null;
|
|
34
|
-
readonly isDirty: boolean;
|
|
35
|
-
focusedPath: string | null;
|
|
36
|
-
readonly saveStatus: SaveStatus;
|
|
37
|
-
readonly canUndo: boolean;
|
|
38
|
-
readonly canRedo: boolean;
|
|
39
|
-
updateData(path: string, value: unknown, recordHistory?: boolean): void;
|
|
40
|
-
undo(): void;
|
|
41
|
-
redo(): void;
|
|
42
|
-
getData(path: string): unknown;
|
|
43
|
-
reset(): void;
|
|
44
|
-
addArrayItem(path: string, item: unknown): void;
|
|
45
|
-
removeArrayItem(path: string, index: number): void;
|
|
46
|
-
moveArrayItem(path: string, fromIndex: number, toIndex: number): void;
|
|
47
|
-
};
|
|
48
|
-
export declare function getEditContext(): {
|
|
49
|
-
mode: EditMode;
|
|
50
|
-
readonly data: DemoData;
|
|
51
|
-
editingPath: string | null;
|
|
52
|
-
readonly isDirty: boolean;
|
|
53
|
-
focusedPath: string | null;
|
|
54
|
-
readonly saveStatus: SaveStatus;
|
|
55
|
-
readonly canUndo: boolean;
|
|
56
|
-
readonly canRedo: boolean;
|
|
57
|
-
updateData(path: string, value: unknown, recordHistory?: boolean): void;
|
|
58
|
-
undo(): void;
|
|
59
|
-
redo(): void;
|
|
60
|
-
getData(path: string): unknown;
|
|
61
|
-
reset(): void;
|
|
62
|
-
addArrayItem(path: string, item: unknown): void;
|
|
63
|
-
removeArrayItem(path: string, index: number): void;
|
|
64
|
-
moveArrayItem(path: string, fromIndex: number, toIndex: number): void;
|
|
65
|
-
};
|
|
@@ -1,194 +0,0 @@
|
|
|
1
|
-
import { getContext, setContext } from 'svelte';
|
|
2
|
-
const CONTEXT_KEY = Symbol('inline-edit-context');
|
|
3
|
-
export function createEditContext(initialData) {
|
|
4
|
-
let mode = $state('panel');
|
|
5
|
-
let data = $state(structuredClone(initialData));
|
|
6
|
-
let editingPath = $state(null);
|
|
7
|
-
let isDirty = $state(false);
|
|
8
|
-
// Hybrid mode state
|
|
9
|
-
let focusedPath = $state(null);
|
|
10
|
-
let saveStatus = $state('idle');
|
|
11
|
-
// Undo/redo stack
|
|
12
|
-
let undoStack = $state([]);
|
|
13
|
-
let redoStack = $state([]);
|
|
14
|
-
const MAX_HISTORY = 50;
|
|
15
|
-
// Auto-save timer
|
|
16
|
-
let saveTimeout = null;
|
|
17
|
-
const AUTO_SAVE_DELAY = 2000;
|
|
18
|
-
function pushHistory() {
|
|
19
|
-
undoStack.push({
|
|
20
|
-
data: structuredClone(data),
|
|
21
|
-
timestamp: Date.now()
|
|
22
|
-
});
|
|
23
|
-
if (undoStack.length > MAX_HISTORY) {
|
|
24
|
-
undoStack.shift();
|
|
25
|
-
}
|
|
26
|
-
redoStack = [];
|
|
27
|
-
}
|
|
28
|
-
function triggerAutoSave() {
|
|
29
|
-
if (saveTimeout)
|
|
30
|
-
clearTimeout(saveTimeout);
|
|
31
|
-
saveStatus = 'idle';
|
|
32
|
-
saveTimeout = setTimeout(async () => {
|
|
33
|
-
saveStatus = 'saving';
|
|
34
|
-
// Simulate save (replace with real API call)
|
|
35
|
-
await new Promise((r) => setTimeout(r, 500));
|
|
36
|
-
saveStatus = 'saved';
|
|
37
|
-
setTimeout(() => {
|
|
38
|
-
if (saveStatus === 'saved')
|
|
39
|
-
saveStatus = 'idle';
|
|
40
|
-
}, 2000);
|
|
41
|
-
}, AUTO_SAVE_DELAY);
|
|
42
|
-
}
|
|
43
|
-
const ctx = {
|
|
44
|
-
get mode() {
|
|
45
|
-
return mode;
|
|
46
|
-
},
|
|
47
|
-
set mode(value) {
|
|
48
|
-
mode = value;
|
|
49
|
-
// Reset focus when changing modes
|
|
50
|
-
if (value !== 'hybrid') {
|
|
51
|
-
focusedPath = null;
|
|
52
|
-
}
|
|
53
|
-
},
|
|
54
|
-
get data() {
|
|
55
|
-
return data;
|
|
56
|
-
},
|
|
57
|
-
get editingPath() {
|
|
58
|
-
return editingPath;
|
|
59
|
-
},
|
|
60
|
-
set editingPath(value) {
|
|
61
|
-
editingPath = value;
|
|
62
|
-
},
|
|
63
|
-
get isDirty() {
|
|
64
|
-
return isDirty;
|
|
65
|
-
},
|
|
66
|
-
// Hybrid mode getters/setters
|
|
67
|
-
get focusedPath() {
|
|
68
|
-
return focusedPath;
|
|
69
|
-
},
|
|
70
|
-
set focusedPath(value) {
|
|
71
|
-
focusedPath = value;
|
|
72
|
-
},
|
|
73
|
-
get saveStatus() {
|
|
74
|
-
return saveStatus;
|
|
75
|
-
},
|
|
76
|
-
get canUndo() {
|
|
77
|
-
return undoStack.length > 0;
|
|
78
|
-
},
|
|
79
|
-
get canRedo() {
|
|
80
|
-
return redoStack.length > 0;
|
|
81
|
-
},
|
|
82
|
-
// Update data at path
|
|
83
|
-
updateData(path, value, recordHistory = true) {
|
|
84
|
-
if (recordHistory) {
|
|
85
|
-
pushHistory();
|
|
86
|
-
}
|
|
87
|
-
const parts = path.split('.');
|
|
88
|
-
let obj = data;
|
|
89
|
-
for (let i = 0; i < parts.length - 1; i++) {
|
|
90
|
-
const part = parts[i];
|
|
91
|
-
if (part.match(/^\d+$/)) {
|
|
92
|
-
obj = obj[parseInt(part)];
|
|
93
|
-
}
|
|
94
|
-
else {
|
|
95
|
-
obj = obj[part];
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
const lastKey = parts[parts.length - 1];
|
|
99
|
-
if (lastKey.match(/^\d+$/)) {
|
|
100
|
-
obj[parseInt(lastKey)] = value;
|
|
101
|
-
}
|
|
102
|
-
else {
|
|
103
|
-
obj[lastKey] = value;
|
|
104
|
-
}
|
|
105
|
-
isDirty = true;
|
|
106
|
-
if (mode === 'hybrid') {
|
|
107
|
-
triggerAutoSave();
|
|
108
|
-
}
|
|
109
|
-
},
|
|
110
|
-
// Undo last change
|
|
111
|
-
undo() {
|
|
112
|
-
if (undoStack.length === 0)
|
|
113
|
-
return;
|
|
114
|
-
const entry = undoStack.pop();
|
|
115
|
-
redoStack.push({
|
|
116
|
-
data: structuredClone(data),
|
|
117
|
-
timestamp: Date.now()
|
|
118
|
-
});
|
|
119
|
-
data = entry.data;
|
|
120
|
-
},
|
|
121
|
-
// Redo last undone change
|
|
122
|
-
redo() {
|
|
123
|
-
if (redoStack.length === 0)
|
|
124
|
-
return;
|
|
125
|
-
const entry = redoStack.pop();
|
|
126
|
-
undoStack.push({
|
|
127
|
-
data: structuredClone(data),
|
|
128
|
-
timestamp: Date.now()
|
|
129
|
-
});
|
|
130
|
-
data = entry.data;
|
|
131
|
-
},
|
|
132
|
-
// Get data at path
|
|
133
|
-
getData(path) {
|
|
134
|
-
const parts = path.split('.');
|
|
135
|
-
let obj = data;
|
|
136
|
-
for (const part of parts) {
|
|
137
|
-
if (obj === undefined || obj === null)
|
|
138
|
-
return undefined;
|
|
139
|
-
if (part.match(/^\d+$/)) {
|
|
140
|
-
obj = obj[parseInt(part)];
|
|
141
|
-
}
|
|
142
|
-
else {
|
|
143
|
-
obj = obj[part];
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
return obj;
|
|
147
|
-
},
|
|
148
|
-
// Reset to initial
|
|
149
|
-
reset() {
|
|
150
|
-
pushHistory();
|
|
151
|
-
data = structuredClone(initialData);
|
|
152
|
-
isDirty = false;
|
|
153
|
-
editingPath = null;
|
|
154
|
-
focusedPath = null;
|
|
155
|
-
},
|
|
156
|
-
// Array operations
|
|
157
|
-
addArrayItem(path, item) {
|
|
158
|
-
pushHistory();
|
|
159
|
-
const arr = this.getData(path);
|
|
160
|
-
if (Array.isArray(arr)) {
|
|
161
|
-
arr.push(item);
|
|
162
|
-
isDirty = true;
|
|
163
|
-
if (mode === 'hybrid')
|
|
164
|
-
triggerAutoSave();
|
|
165
|
-
}
|
|
166
|
-
},
|
|
167
|
-
removeArrayItem(path, index) {
|
|
168
|
-
pushHistory();
|
|
169
|
-
const arr = this.getData(path);
|
|
170
|
-
if (Array.isArray(arr)) {
|
|
171
|
-
arr.splice(index, 1);
|
|
172
|
-
isDirty = true;
|
|
173
|
-
if (mode === 'hybrid')
|
|
174
|
-
triggerAutoSave();
|
|
175
|
-
}
|
|
176
|
-
},
|
|
177
|
-
moveArrayItem(path, fromIndex, toIndex) {
|
|
178
|
-
pushHistory();
|
|
179
|
-
const arr = this.getData(path);
|
|
180
|
-
if (Array.isArray(arr) && fromIndex >= 0 && toIndex >= 0 && fromIndex < arr.length && toIndex < arr.length) {
|
|
181
|
-
const [item] = arr.splice(fromIndex, 1);
|
|
182
|
-
arr.splice(toIndex, 0, item);
|
|
183
|
-
isDirty = true;
|
|
184
|
-
if (mode === 'hybrid')
|
|
185
|
-
triggerAutoSave();
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
};
|
|
189
|
-
setContext(CONTEXT_KEY, ctx);
|
|
190
|
-
return ctx;
|
|
191
|
-
}
|
|
192
|
-
export function getEditContext() {
|
|
193
|
-
return getContext(CONTEXT_KEY);
|
|
194
|
-
}
|