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.
Files changed (80) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/DOCS.md +1 -1
  3. package/ROADMAP.md +22 -8
  4. package/dist/admin/components/fields/media-field.svelte +54 -2
  5. package/dist/admin/components/media/file/file-details.svelte +65 -0
  6. package/dist/admin/remote/media.remote.d.ts +1 -0
  7. package/dist/admin/remote/media.remote.js +5 -0
  8. package/dist/cmp/types.d.ts +25 -0
  9. package/dist/cmp/types.js +1 -0
  10. package/dist/core/cms.d.ts +2 -0
  11. package/dist/core/cms.js +2 -0
  12. package/dist/core/server/cmp/getCountryFromHeaders.d.ts +10 -0
  13. package/dist/core/server/cmp/getCountryFromHeaders.js +30 -0
  14. package/dist/core/server/cmp/operations/create.d.ts +17 -0
  15. package/dist/core/server/cmp/operations/create.js +38 -0
  16. package/dist/core/server/cmp/operations/get.d.ts +2 -0
  17. package/dist/core/server/cmp/operations/get.js +8 -0
  18. package/dist/core/server/cmp/operations/list.d.ts +3 -0
  19. package/dist/core/server/cmp/operations/list.js +15 -0
  20. package/dist/core/server/cmp/truncateIpAddress.d.ts +7 -0
  21. package/dist/core/server/cmp/truncateIpAddress.js +57 -0
  22. package/dist/core/server/fields/resolveImageFields.d.ts +5 -0
  23. package/dist/core/server/fields/resolveImageFields.js +9 -1
  24. package/dist/core/server/generator/generator.js +22 -6
  25. package/dist/core/server/media/operations/backgroundMaintenance.js +51 -20
  26. package/dist/core/server/media/operations/findMediaReferences.d.ts +16 -0
  27. package/dist/core/server/media/operations/findMediaReferences.js +60 -0
  28. package/dist/db-postgres/index.js +46 -1
  29. package/dist/db-postgres/schema/consentLog.d.ts +17 -0
  30. package/dist/db-postgres/schema/consentLog.js +4 -1
  31. package/dist/paraglide/messages/_index.d.ts +36 -3
  32. package/dist/paraglide/messages/_index.js +71 -3
  33. package/dist/paraglide/messages/en.d.ts +5 -0
  34. package/dist/paraglide/messages/en.js +14 -0
  35. package/dist/paraglide/messages/pl.d.ts +5 -0
  36. package/dist/paraglide/messages/pl.js +14 -0
  37. package/dist/sveltekit/server/handle.js +1 -1
  38. package/dist/types/adapters/db.d.ts +7 -1
  39. package/dist/types/cms.d.ts +3 -0
  40. package/dist/types/consent.d.ts +11 -0
  41. package/dist/updates/0.15.5/index.d.ts +2 -0
  42. package/dist/updates/0.15.5/index.js +15 -0
  43. package/dist/updates/0.16.0/index.d.ts +2 -0
  44. package/dist/updates/0.16.0/index.js +14 -0
  45. package/dist/updates/index.js +3 -1
  46. package/package.json +1 -1
  47. package/dist/demo/seed.d.ts +0 -1
  48. package/dist/demo/seed.js +0 -117
  49. package/dist/inline-edit-proto/ModeToggle.svelte +0 -36
  50. package/dist/inline-edit-proto/ModeToggle.svelte.d.ts +0 -18
  51. package/dist/inline-edit-proto/blocks/AddBlockButton.svelte +0 -47
  52. package/dist/inline-edit-proto/blocks/AddBlockButton.svelte.d.ts +0 -8
  53. package/dist/inline-edit-proto/blocks/BlockToolbar.svelte +0 -80
  54. package/dist/inline-edit-proto/blocks/BlockToolbar.svelte.d.ts +0 -13
  55. package/dist/inline-edit-proto/blocks/BlockWrapper.svelte +0 -83
  56. package/dist/inline-edit-proto/blocks/BlockWrapper.svelte.d.ts +0 -11
  57. package/dist/inline-edit-proto/context.svelte.d.ts +0 -65
  58. package/dist/inline-edit-proto/context.svelte.js +0 -194
  59. package/dist/inline-edit-proto/hybrid/EditableHybrid.svelte +0 -70
  60. package/dist/inline-edit-proto/hybrid/EditableHybrid.svelte.d.ts +0 -11
  61. package/dist/inline-edit-proto/hybrid/FieldRenderer.svelte +0 -94
  62. package/dist/inline-edit-proto/hybrid/FieldRenderer.svelte.d.ts +0 -11
  63. package/dist/inline-edit-proto/hybrid/HybridEditor.svelte +0 -107
  64. package/dist/inline-edit-proto/hybrid/HybridEditor.svelte.d.ts +0 -7
  65. package/dist/inline-edit-proto/hybrid/SyncPanel.svelte +0 -181
  66. package/dist/inline-edit-proto/hybrid/SyncPanel.svelte.d.ts +0 -3
  67. package/dist/inline-edit-proto/inline/EditableInline.svelte +0 -120
  68. package/dist/inline-edit-proto/inline/EditableInline.svelte.d.ts +0 -11
  69. package/dist/inline-edit-proto/inline/InlineToolbar.svelte +0 -71
  70. package/dist/inline-edit-proto/inline/InlineToolbar.svelte.d.ts +0 -6
  71. package/dist/inline-edit-proto/panel/EditSheet.svelte +0 -130
  72. package/dist/inline-edit-proto/panel/EditSheet.svelte.d.ts +0 -3
  73. package/dist/inline-edit-proto/panel/EditablePanel.svelte +0 -44
  74. package/dist/inline-edit-proto/panel/EditablePanel.svelte.d.ts +0 -9
  75. package/dist/paraglide/messages/hello_world.d.ts +0 -5
  76. package/dist/paraglide/messages/hello_world.js +0 -33
  77. package/dist/paraglide/messages/login_hello.d.ts +0 -16
  78. package/dist/paraglide/messages/login_hello.js +0 -34
  79. package/dist/paraglide/messages/login_please_login.d.ts +0 -16
  80. package/dist/paraglide/messages/login_please_login.js +0 -34
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "includio-cms",
3
- "version": "0.15.4",
3
+ "version": "0.16.0",
4
4
  "scripts": {
5
5
  "dev": "vite dev",
6
6
  "build": "vite build && npm run prepack",
@@ -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
- }