@nuasite/cms 0.1.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 +237 -0
- package/dist/src/build-processor.d.ts +20 -0
- package/dist/src/build-processor.d.ts.map +1 -0
- package/dist/src/collection-scanner.d.ts +6 -0
- package/dist/src/collection-scanner.d.ts.map +1 -0
- package/dist/src/component-registry.d.ts +63 -0
- package/dist/src/component-registry.d.ts.map +1 -0
- package/dist/src/config.d.ts +24 -0
- package/dist/src/config.d.ts.map +1 -0
- package/dist/src/dev-middleware.d.ts +20 -0
- package/dist/src/dev-middleware.d.ts.map +1 -0
- package/dist/src/editor/ai.d.ts +60 -0
- package/dist/src/editor/ai.d.ts.map +1 -0
- package/dist/src/editor/api.d.ts +140 -0
- package/dist/src/editor/api.d.ts.map +1 -0
- package/dist/src/editor/color-utils.d.ts +106 -0
- package/dist/src/editor/color-utils.d.ts.map +1 -0
- package/dist/src/editor/components/ai-chat.d.ts +11 -0
- package/dist/src/editor/components/ai-chat.d.ts.map +1 -0
- package/dist/src/editor/components/ai-tooltip.d.ts +12 -0
- package/dist/src/editor/components/ai-tooltip.d.ts.map +1 -0
- package/dist/src/editor/components/attribute-editor.d.ts +5 -0
- package/dist/src/editor/components/attribute-editor.d.ts.map +1 -0
- package/dist/src/editor/components/block-editor.d.ts +12 -0
- package/dist/src/editor/components/block-editor.d.ts.map +1 -0
- package/dist/src/editor/components/collections-browser.d.ts +2 -0
- package/dist/src/editor/components/collections-browser.d.ts.map +1 -0
- package/dist/src/editor/components/color-toolbar.d.ts +12 -0
- package/dist/src/editor/components/color-toolbar.d.ts.map +1 -0
- package/dist/src/editor/components/confirm-dialog.d.ts +2 -0
- package/dist/src/editor/components/confirm-dialog.d.ts.map +1 -0
- package/dist/src/editor/components/create-page-modal.d.ts +2 -0
- package/dist/src/editor/components/create-page-modal.d.ts.map +1 -0
- package/dist/src/editor/components/editable-highlights.d.ts +9 -0
- package/dist/src/editor/components/editable-highlights.d.ts.map +1 -0
- package/dist/src/editor/components/error-boundary.d.ts +32 -0
- package/dist/src/editor/components/error-boundary.d.ts.map +1 -0
- package/dist/src/editor/components/fields.d.ts +75 -0
- package/dist/src/editor/components/fields.d.ts.map +1 -0
- package/dist/src/editor/components/frontmatter-fields.d.ts +29 -0
- package/dist/src/editor/components/frontmatter-fields.d.ts.map +1 -0
- package/dist/src/editor/components/highlight-overlay.d.ts +64 -0
- package/dist/src/editor/components/highlight-overlay.d.ts.map +1 -0
- package/dist/src/editor/components/image-overlay.d.ts +12 -0
- package/dist/src/editor/components/image-overlay.d.ts.map +1 -0
- package/dist/src/editor/components/markdown-editor-overlay.d.ts +6 -0
- package/dist/src/editor/components/markdown-editor-overlay.d.ts.map +1 -0
- package/dist/src/editor/components/markdown-inline-editor.d.ts +10 -0
- package/dist/src/editor/components/markdown-inline-editor.d.ts.map +1 -0
- package/dist/src/editor/components/media-library.d.ts +2 -0
- package/dist/src/editor/components/media-library.d.ts.map +1 -0
- package/dist/src/editor/components/outline.d.ts +21 -0
- package/dist/src/editor/components/outline.d.ts.map +1 -0
- package/dist/src/editor/components/redirect-countdown.d.ts +2 -0
- package/dist/src/editor/components/redirect-countdown.d.ts.map +1 -0
- package/dist/src/editor/components/seo-editor.d.ts +2 -0
- package/dist/src/editor/components/seo-editor.d.ts.map +1 -0
- package/dist/src/editor/components/text-style-toolbar.d.ts +8 -0
- package/dist/src/editor/components/text-style-toolbar.d.ts.map +1 -0
- package/dist/src/editor/components/toast/toast-container.d.ts +7 -0
- package/dist/src/editor/components/toast/toast-container.d.ts.map +1 -0
- package/dist/src/editor/components/toast/toast.d.ts +7 -0
- package/dist/src/editor/components/toast/toast.d.ts.map +1 -0
- package/dist/src/editor/components/toast/types.d.ts +7 -0
- package/dist/src/editor/components/toast/types.d.ts.map +1 -0
- package/dist/src/editor/components/toolbar.d.ts +21 -0
- package/dist/src/editor/components/toolbar.d.ts.map +1 -0
- package/dist/src/editor/config.d.ts +4 -0
- package/dist/src/editor/config.d.ts.map +1 -0
- package/dist/src/editor/constants.d.ts +101 -0
- package/dist/src/editor/constants.d.ts.map +1 -0
- package/dist/src/editor/context.d.ts +14 -0
- package/dist/src/editor/context.d.ts.map +1 -0
- package/dist/src/editor/dom.d.ts +77 -0
- package/dist/src/editor/dom.d.ts.map +1 -0
- package/dist/src/editor/editor.d.ts +64 -0
- package/dist/src/editor/editor.d.ts.map +1 -0
- package/dist/src/editor/history.d.ts +20 -0
- package/dist/src/editor/history.d.ts.map +1 -0
- package/dist/src/editor/hooks/index.d.ts +14 -0
- package/dist/src/editor/hooks/index.d.ts.map +1 -0
- package/dist/src/editor/hooks/useAIHandlers.d.ts +22 -0
- package/dist/src/editor/hooks/useAIHandlers.d.ts.map +1 -0
- package/dist/src/editor/hooks/useBlockEditorHandlers.d.ts +18 -0
- package/dist/src/editor/hooks/useBlockEditorHandlers.d.ts.map +1 -0
- package/dist/src/editor/hooks/useElementDetection.d.ts +26 -0
- package/dist/src/editor/hooks/useElementDetection.d.ts.map +1 -0
- package/dist/src/editor/hooks/useImageHoverDetection.d.ts +12 -0
- package/dist/src/editor/hooks/useImageHoverDetection.d.ts.map +1 -0
- package/dist/src/editor/hooks/useTextSelection.d.ts +23 -0
- package/dist/src/editor/hooks/useTextSelection.d.ts.map +1 -0
- package/dist/src/editor/hooks/useTooltipState.d.ts +19 -0
- package/dist/src/editor/hooks/useTooltipState.d.ts.map +1 -0
- package/dist/src/editor/hooks/utils.d.ts +32 -0
- package/dist/src/editor/hooks/utils.d.ts.map +1 -0
- package/dist/src/editor/index.d.ts +12 -0
- package/dist/src/editor/index.d.ts.map +1 -0
- package/dist/src/editor/lib/cn.d.ts +3 -0
- package/dist/src/editor/lib/cn.d.ts.map +1 -0
- package/dist/src/editor/manifest.d.ts +19 -0
- package/dist/src/editor/manifest.d.ts.map +1 -0
- package/dist/src/editor/markdown-api.d.ts +36 -0
- package/dist/src/editor/markdown-api.d.ts.map +1 -0
- package/dist/src/editor/signals.d.ts +242 -0
- package/dist/src/editor/signals.d.ts.map +1 -0
- package/dist/src/editor/storage.d.ts +27 -0
- package/dist/src/editor/storage.d.ts.map +1 -0
- package/dist/src/editor/text-styling.d.ts +350 -0
- package/dist/src/editor/text-styling.d.ts.map +1 -0
- package/dist/src/editor/themes.d.ts +38 -0
- package/dist/src/editor/themes.d.ts.map +1 -0
- package/dist/src/editor/types.d.ts +454 -0
- package/dist/src/editor/types.d.ts.map +1 -0
- package/dist/src/error-collector.d.ts +56 -0
- package/dist/src/error-collector.d.ts.map +1 -0
- package/dist/src/handlers/component-ops.d.ts +34 -0
- package/dist/src/handlers/component-ops.d.ts.map +1 -0
- package/dist/src/handlers/markdown-ops.d.ts +41 -0
- package/dist/src/handlers/markdown-ops.d.ts.map +1 -0
- package/dist/src/handlers/request-utils.d.ts +20 -0
- package/dist/src/handlers/request-utils.d.ts.map +1 -0
- package/dist/src/handlers/source-writer.d.ts +51 -0
- package/dist/src/handlers/source-writer.d.ts.map +1 -0
- package/dist/src/html-processor.d.ts +63 -0
- package/dist/src/html-processor.d.ts.map +1 -0
- package/dist/src/index.d.ts +41 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/manifest-writer.d.ts +111 -0
- package/dist/src/manifest-writer.d.ts.map +1 -0
- package/dist/src/media/contember.d.ts +15 -0
- package/dist/src/media/contember.d.ts.map +1 -0
- package/dist/src/media/local.d.ts +9 -0
- package/dist/src/media/local.d.ts.map +1 -0
- package/dist/src/media/s3.d.ts +12 -0
- package/dist/src/media/s3.d.ts.map +1 -0
- package/dist/src/media/types.d.ts +40 -0
- package/dist/src/media/types.d.ts.map +1 -0
- package/dist/src/preview-generator.d.ts +19 -0
- package/dist/src/preview-generator.d.ts.map +1 -0
- package/dist/src/seo-processor.d.ts +23 -0
- package/dist/src/seo-processor.d.ts.map +1 -0
- package/dist/src/source-finder/ast-extractors.d.ts +35 -0
- package/dist/src/source-finder/ast-extractors.d.ts.map +1 -0
- package/dist/src/source-finder/ast-parser.d.ts +16 -0
- package/dist/src/source-finder/ast-parser.d.ts.map +1 -0
- package/dist/src/source-finder/cache.d.ts +18 -0
- package/dist/src/source-finder/cache.d.ts.map +1 -0
- package/dist/src/source-finder/collection-finder.d.ts +29 -0
- package/dist/src/source-finder/collection-finder.d.ts.map +1 -0
- package/dist/src/source-finder/cross-file-tracker.d.ts +39 -0
- package/dist/src/source-finder/cross-file-tracker.d.ts.map +1 -0
- package/dist/src/source-finder/element-finder.d.ts +42 -0
- package/dist/src/source-finder/element-finder.d.ts.map +1 -0
- package/dist/src/source-finder/image-finder.d.ts +24 -0
- package/dist/src/source-finder/image-finder.d.ts.map +1 -0
- package/dist/src/source-finder/index.d.ts +9 -0
- package/dist/src/source-finder/index.d.ts.map +1 -0
- package/dist/src/source-finder/search-index.d.ts +27 -0
- package/dist/src/source-finder/search-index.d.ts.map +1 -0
- package/dist/src/source-finder/snippet-utils.d.ts +90 -0
- package/dist/src/source-finder/snippet-utils.d.ts.map +1 -0
- package/dist/src/source-finder/source-lookup.d.ts +16 -0
- package/dist/src/source-finder/source-lookup.d.ts.map +1 -0
- package/dist/src/source-finder/types.d.ts +167 -0
- package/dist/src/source-finder/types.d.ts.map +1 -0
- package/dist/src/source-finder/variable-extraction.d.ts +37 -0
- package/dist/src/source-finder/variable-extraction.d.ts.map +1 -0
- package/dist/src/tailwind-colors.d.ts +54 -0
- package/dist/src/tailwind-colors.d.ts.map +1 -0
- package/dist/src/tsconfig.tsbuildinfo +1 -0
- package/dist/src/types.d.ts +367 -0
- package/dist/src/types.d.ts.map +1 -0
- package/dist/src/utils.d.ts +61 -0
- package/dist/src/utils.d.ts.map +1 -0
- package/dist/src/vite-plugin.d.ts +14 -0
- package/dist/src/vite-plugin.d.ts.map +1 -0
- package/dist/types/tsconfig.tsbuildinfo +1 -0
- package/package.json +80 -0
- package/src/build-processor.ts +784 -0
- package/src/collection-scanner.ts +304 -0
- package/src/component-registry.ts +393 -0
- package/src/config.ts +74 -0
- package/src/dev-middleware.ts +525 -0
- package/src/dist/src/tsconfig.tsbuildinfo +1 -0
- package/src/editor/ai.ts +185 -0
- package/src/editor/api.ts +513 -0
- package/src/editor/color-utils.ts +556 -0
- package/src/editor/components/ai-chat.tsx +632 -0
- package/src/editor/components/ai-tooltip.tsx +179 -0
- package/src/editor/components/attribute-editor.tsx +596 -0
- package/src/editor/components/block-editor.tsx +546 -0
- package/src/editor/components/collections-browser.tsx +248 -0
- package/src/editor/components/color-toolbar.tsx +314 -0
- package/src/editor/components/confirm-dialog.tsx +69 -0
- package/src/editor/components/create-page-modal.tsx +163 -0
- package/src/editor/components/editable-highlights.tsx +260 -0
- package/src/editor/components/error-boundary.tsx +87 -0
- package/src/editor/components/fields.tsx +387 -0
- package/src/editor/components/frontmatter-fields.tsx +469 -0
- package/src/editor/components/highlight-overlay.ts +229 -0
- package/src/editor/components/image-overlay.tsx +230 -0
- package/src/editor/components/markdown-editor-overlay.tsx +505 -0
- package/src/editor/components/markdown-inline-editor.tsx +780 -0
- package/src/editor/components/media-library.tsx +297 -0
- package/src/editor/components/outline.tsx +402 -0
- package/src/editor/components/redirect-countdown.tsx +45 -0
- package/src/editor/components/seo-editor.tsx +498 -0
- package/src/editor/components/text-style-toolbar.tsx +362 -0
- package/src/editor/components/toast/toast-container.tsx +15 -0
- package/src/editor/components/toast/toast.tsx +49 -0
- package/src/editor/components/toast/types.ts +7 -0
- package/src/editor/components/toolbar.tsx +366 -0
- package/src/editor/config.ts +12 -0
- package/src/editor/constants.ts +106 -0
- package/src/editor/context.tsx +38 -0
- package/src/editor/dom.ts +357 -0
- package/src/editor/editor.ts +1510 -0
- package/src/editor/env.d.ts +4 -0
- package/src/editor/history.ts +355 -0
- package/src/editor/hooks/index.ts +19 -0
- package/src/editor/hooks/useAIHandlers.ts +345 -0
- package/src/editor/hooks/useBlockEditorHandlers.ts +206 -0
- package/src/editor/hooks/useElementDetection.ts +284 -0
- package/src/editor/hooks/useImageHoverDetection.ts +102 -0
- package/src/editor/hooks/useTextSelection.ts +187 -0
- package/src/editor/hooks/useTooltipState.ts +126 -0
- package/src/editor/hooks/utils.ts +101 -0
- package/src/editor/index.tsx +481 -0
- package/src/editor/lib/cn.ts +4 -0
- package/src/editor/manifest.ts +25 -0
- package/src/editor/markdown-api.ts +209 -0
- package/src/editor/signals.ts +1351 -0
- package/src/editor/storage.ts +266 -0
- package/src/editor/styles.css +465 -0
- package/src/editor/text-styling.ts +773 -0
- package/src/editor/themes.ts +210 -0
- package/src/editor/types.ts +591 -0
- package/src/error-collector.ts +106 -0
- package/src/handlers/component-ops.ts +463 -0
- package/src/handlers/markdown-ops.ts +202 -0
- package/src/handlers/request-utils.ts +151 -0
- package/src/handlers/source-writer.ts +649 -0
- package/src/html-processor.ts +1108 -0
- package/src/index.ts +284 -0
- package/src/manifest-writer.ts +371 -0
- package/src/media/contember.ts +84 -0
- package/src/media/local.ts +114 -0
- package/src/media/s3.ts +133 -0
- package/src/media/types.ts +33 -0
- package/src/preview-generator.ts +293 -0
- package/src/seo-processor.ts +567 -0
- package/src/source-finder/ast-extractors.ts +185 -0
- package/src/source-finder/ast-parser.ts +150 -0
- package/src/source-finder/cache.ts +76 -0
- package/src/source-finder/collection-finder.ts +335 -0
- package/src/source-finder/cross-file-tracker.ts +741 -0
- package/src/source-finder/element-finder.ts +387 -0
- package/src/source-finder/image-finder.ts +283 -0
- package/src/source-finder/index.ts +37 -0
- package/src/source-finder/search-index.ts +525 -0
- package/src/source-finder/snippet-utils.ts +668 -0
- package/src/source-finder/source-lookup.ts +200 -0
- package/src/source-finder/types.ts +210 -0
- package/src/source-finder/variable-extraction.ts +406 -0
- package/src/tailwind-colors.ts +874 -0
- package/src/tsconfig.json +25 -0
- package/src/types.ts +406 -0
- package/src/utils.ts +186 -0
- package/src/vite-plugin.ts +42 -0
|
@@ -0,0 +1,1351 @@
|
|
|
1
|
+
import { batch, computed, type Signal, signal } from '@preact/signals'
|
|
2
|
+
import { fetchManifest, getMarkdownContent } from './api'
|
|
3
|
+
import type { ToastMessage, ToastType } from './components/toast/types'
|
|
4
|
+
import { getConfig } from './config'
|
|
5
|
+
import type {
|
|
6
|
+
AIState,
|
|
7
|
+
AIStatusType,
|
|
8
|
+
AttributeEditorState,
|
|
9
|
+
BlockEditorState,
|
|
10
|
+
ChatMessage,
|
|
11
|
+
CmsConfig,
|
|
12
|
+
CmsManifest,
|
|
13
|
+
CmsSettings,
|
|
14
|
+
CollectionDefinition,
|
|
15
|
+
CollectionEntry,
|
|
16
|
+
CollectionsBrowserState,
|
|
17
|
+
ColorEditorState,
|
|
18
|
+
ComponentInstance,
|
|
19
|
+
ConfirmDialogState,
|
|
20
|
+
CreatePageState,
|
|
21
|
+
DeploymentState,
|
|
22
|
+
DeploymentStatusType,
|
|
23
|
+
EditorState,
|
|
24
|
+
FieldDefinition,
|
|
25
|
+
MarkdownEditorState,
|
|
26
|
+
MarkdownPageEntry,
|
|
27
|
+
MediaItem,
|
|
28
|
+
MediaLibraryState,
|
|
29
|
+
PendingAttributeChange,
|
|
30
|
+
PendingChange,
|
|
31
|
+
PendingColorChange,
|
|
32
|
+
PendingComponentInsert,
|
|
33
|
+
PendingImageChange,
|
|
34
|
+
PendingSeoChange,
|
|
35
|
+
SeoEditorState,
|
|
36
|
+
} from './types'
|
|
37
|
+
|
|
38
|
+
// ============================================================================
|
|
39
|
+
// Map Signal Helpers - reduces boilerplate for Map-based signals
|
|
40
|
+
// ============================================================================
|
|
41
|
+
|
|
42
|
+
interface MapSignalHelpers<T> {
|
|
43
|
+
set: (id: string, value: T) => void
|
|
44
|
+
update: (id: string, updater: (value: T) => T) => void
|
|
45
|
+
delete: (id: string) => void
|
|
46
|
+
clear: () => void
|
|
47
|
+
get: (id: string) => T | undefined
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Creates helper functions for a Map-based signal.
|
|
52
|
+
* Reduces boilerplate for common Map operations.
|
|
53
|
+
*/
|
|
54
|
+
function createMapHelpers<T>(mapSignal: Signal<Map<string, T>>): MapSignalHelpers<T> {
|
|
55
|
+
return {
|
|
56
|
+
set(id: string, value: T): void {
|
|
57
|
+
const newMap = new Map(mapSignal.value)
|
|
58
|
+
newMap.set(id, value)
|
|
59
|
+
mapSignal.value = newMap
|
|
60
|
+
},
|
|
61
|
+
update(id: string, updater: (value: T) => T): void {
|
|
62
|
+
const current = mapSignal.value.get(id)
|
|
63
|
+
if (current) {
|
|
64
|
+
const newMap = new Map(mapSignal.value)
|
|
65
|
+
newMap.set(id, updater(current))
|
|
66
|
+
mapSignal.value = newMap
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
delete(id: string): void {
|
|
70
|
+
const newMap = new Map(mapSignal.value)
|
|
71
|
+
newMap.delete(id)
|
|
72
|
+
mapSignal.value = newMap
|
|
73
|
+
},
|
|
74
|
+
clear(): void {
|
|
75
|
+
mapSignal.value = new Map()
|
|
76
|
+
},
|
|
77
|
+
get(id: string): T | undefined {
|
|
78
|
+
return mapSignal.value.get(id)
|
|
79
|
+
},
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Creates computed signals for tracking dirty items in a Map signal.
|
|
85
|
+
* Works with any type that has an `isDirty` property.
|
|
86
|
+
*/
|
|
87
|
+
function createDirtyTracking<T extends { isDirty: boolean }>(
|
|
88
|
+
mapSignal: Signal<Map<string, T>>,
|
|
89
|
+
) {
|
|
90
|
+
const dirtyCount = computed(() => {
|
|
91
|
+
return Array.from(mapSignal.value.values()).filter((c) => c.isDirty).length
|
|
92
|
+
})
|
|
93
|
+
const dirtyItems = computed(() => {
|
|
94
|
+
return Array.from(mapSignal.value.entries()).filter(([_, item]) => item.isDirty)
|
|
95
|
+
})
|
|
96
|
+
const hasDirty = computed(() => dirtyCount.value > 0)
|
|
97
|
+
|
|
98
|
+
return { dirtyCount, dirtyItems, hasDirty }
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Initial state factories
|
|
102
|
+
function createInitialAIState(): AIState {
|
|
103
|
+
return {
|
|
104
|
+
isPromptVisible: false,
|
|
105
|
+
isProcessing: false,
|
|
106
|
+
targetElementId: null,
|
|
107
|
+
streamingContent: null,
|
|
108
|
+
error: null,
|
|
109
|
+
isChatOpen: false,
|
|
110
|
+
chatMessages: [],
|
|
111
|
+
chatContextElementId: null,
|
|
112
|
+
currentStatus: null,
|
|
113
|
+
statusMessage: null,
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function createInitialBlockEditorState(): BlockEditorState {
|
|
118
|
+
return {
|
|
119
|
+
isOpen: false,
|
|
120
|
+
currentComponentId: null,
|
|
121
|
+
mode: 'edit',
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function createInitialMarkdownEditorState(): MarkdownEditorState {
|
|
126
|
+
return {
|
|
127
|
+
isOpen: false,
|
|
128
|
+
currentPage: null,
|
|
129
|
+
activeElementId: null,
|
|
130
|
+
mode: 'edit',
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function createInitialMediaLibraryState(): MediaLibraryState {
|
|
135
|
+
return {
|
|
136
|
+
isOpen: false,
|
|
137
|
+
items: [],
|
|
138
|
+
isLoading: false,
|
|
139
|
+
selectedItem: null,
|
|
140
|
+
insertCallback: null,
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function createInitialCreatePageState(): CreatePageState {
|
|
145
|
+
return {
|
|
146
|
+
isOpen: false,
|
|
147
|
+
isCreating: false,
|
|
148
|
+
selectedCollection: null,
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function createInitialCollectionsBrowserState(): CollectionsBrowserState {
|
|
153
|
+
return {
|
|
154
|
+
isOpen: false,
|
|
155
|
+
selectedCollection: null,
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function createInitialDeploymentState(): DeploymentState {
|
|
160
|
+
return {
|
|
161
|
+
status: null,
|
|
162
|
+
lastDeployedAt: null,
|
|
163
|
+
isPolling: false,
|
|
164
|
+
error: null,
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function createInitialColorEditorState(): ColorEditorState {
|
|
169
|
+
return {
|
|
170
|
+
isOpen: false,
|
|
171
|
+
targetElementId: null,
|
|
172
|
+
targetRect: null,
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function createInitialConfirmDialogState(): ConfirmDialogState {
|
|
177
|
+
return {
|
|
178
|
+
isOpen: false,
|
|
179
|
+
title: '',
|
|
180
|
+
message: '',
|
|
181
|
+
confirmLabel: 'Confirm',
|
|
182
|
+
cancelLabel: 'Cancel',
|
|
183
|
+
variant: 'info',
|
|
184
|
+
onConfirm: null,
|
|
185
|
+
onCancel: null,
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function createInitialSettings(): CmsSettings {
|
|
190
|
+
return {
|
|
191
|
+
showEditableHighlights: false,
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function createInitialSeoEditorState(): SeoEditorState {
|
|
196
|
+
return {
|
|
197
|
+
isOpen: false,
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function createInitialAttributeEditorState(): AttributeEditorState {
|
|
202
|
+
return {
|
|
203
|
+
isOpen: false,
|
|
204
|
+
targetElementId: null,
|
|
205
|
+
targetRect: null,
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// ============================================================================
|
|
210
|
+
// Core Editor State Signals
|
|
211
|
+
// ============================================================================
|
|
212
|
+
|
|
213
|
+
export const isEnabled = signal(false)
|
|
214
|
+
export const isEditing = signal(false)
|
|
215
|
+
export const isSaving = signal(false)
|
|
216
|
+
export const showingOriginal = signal(false)
|
|
217
|
+
export const currentEditingId = signal<string | null>(null)
|
|
218
|
+
export const currentComponentId = signal<string | null>(null)
|
|
219
|
+
|
|
220
|
+
// Complex state - use signals wrapping the full object for atomicity
|
|
221
|
+
export const pendingChanges = signal<Map<string, PendingChange>>(new Map())
|
|
222
|
+
export const pendingComponentChanges = signal<Map<string, ComponentInstance>>(
|
|
223
|
+
new Map(),
|
|
224
|
+
)
|
|
225
|
+
export const pendingInserts = signal<Map<string, PendingComponentInsert>>(
|
|
226
|
+
new Map(),
|
|
227
|
+
)
|
|
228
|
+
export const pendingImageChanges = signal<Map<string, PendingImageChange>>(
|
|
229
|
+
new Map(),
|
|
230
|
+
)
|
|
231
|
+
export const pendingColorChanges = signal<Map<string, PendingColorChange>>(
|
|
232
|
+
new Map(),
|
|
233
|
+
)
|
|
234
|
+
export const manifest = signal<CmsManifest>({
|
|
235
|
+
entries: {},
|
|
236
|
+
components: {},
|
|
237
|
+
componentDefinitions: {},
|
|
238
|
+
})
|
|
239
|
+
|
|
240
|
+
// Computed signal to get the current page's collection entry (if any)
|
|
241
|
+
export const currentPageCollection = computed((): CollectionEntry | null => {
|
|
242
|
+
const collections = manifest.value.collections
|
|
243
|
+
if (!collections || Object.keys(collections).length === 0) return null
|
|
244
|
+
// Return the first (and typically only) collection entry for the current page
|
|
245
|
+
const entries = Object.values(collections)
|
|
246
|
+
return entries.length > 0 ? entries[0]! : null
|
|
247
|
+
})
|
|
248
|
+
|
|
249
|
+
// Create helpers for Map signals (internal use)
|
|
250
|
+
const _pendingChangesHelpers = createMapHelpers(pendingChanges)
|
|
251
|
+
const _pendingComponentChangesHelpers = createMapHelpers(pendingComponentChanges)
|
|
252
|
+
const _pendingInsertsHelpers = createMapHelpers(pendingInserts)
|
|
253
|
+
const _pendingImageChangesHelpers = createMapHelpers(pendingImageChanges)
|
|
254
|
+
const _pendingColorChangesHelpers = createMapHelpers(pendingColorChanges)
|
|
255
|
+
|
|
256
|
+
// ============================================================================
|
|
257
|
+
// AI State Signals
|
|
258
|
+
// ============================================================================
|
|
259
|
+
|
|
260
|
+
export const aiState = signal<AIState>(createInitialAIState())
|
|
261
|
+
|
|
262
|
+
// Convenience computed signals for AI state
|
|
263
|
+
export const isAIProcessing = computed(() => aiState.value.isProcessing)
|
|
264
|
+
export const isChatOpen = computed(() => aiState.value.isChatOpen)
|
|
265
|
+
export const chatMessages = computed(() => aiState.value.chatMessages)
|
|
266
|
+
export const chatContextElementId = computed(
|
|
267
|
+
() => aiState.value.chatContextElementId,
|
|
268
|
+
)
|
|
269
|
+
export const currentStatus = computed(() => aiState.value.currentStatus)
|
|
270
|
+
export const statusMessage = computed(() => aiState.value.statusMessage)
|
|
271
|
+
|
|
272
|
+
// ============================================================================
|
|
273
|
+
// Block Editor State Signals
|
|
274
|
+
// ============================================================================
|
|
275
|
+
|
|
276
|
+
export const blockEditorState = signal<BlockEditorState>(
|
|
277
|
+
createInitialBlockEditorState(),
|
|
278
|
+
)
|
|
279
|
+
|
|
280
|
+
// Convenience computed signals for block editor
|
|
281
|
+
export const isBlockEditorOpen = computed(() => blockEditorState.value.isOpen)
|
|
282
|
+
export const blockEditorMode = computed(() => blockEditorState.value.mode)
|
|
283
|
+
|
|
284
|
+
// ============================================================================
|
|
285
|
+
// Markdown Editor State Signals
|
|
286
|
+
// ============================================================================
|
|
287
|
+
|
|
288
|
+
export const markdownEditorState = signal<MarkdownEditorState>(
|
|
289
|
+
createInitialMarkdownEditorState(),
|
|
290
|
+
)
|
|
291
|
+
|
|
292
|
+
// Convenience computed signals for markdown editor
|
|
293
|
+
export const isMarkdownEditorOpen = computed(
|
|
294
|
+
() => markdownEditorState.value.isOpen,
|
|
295
|
+
)
|
|
296
|
+
export const currentMarkdownPage = computed(
|
|
297
|
+
() => markdownEditorState.value.currentPage,
|
|
298
|
+
)
|
|
299
|
+
export const isMarkdownPreview = signal(false)
|
|
300
|
+
|
|
301
|
+
// ============================================================================
|
|
302
|
+
// Media Library State Signals
|
|
303
|
+
// ============================================================================
|
|
304
|
+
|
|
305
|
+
export const mediaLibraryState = signal<MediaLibraryState>(
|
|
306
|
+
createInitialMediaLibraryState(),
|
|
307
|
+
)
|
|
308
|
+
|
|
309
|
+
// Convenience computed signals for media library
|
|
310
|
+
export const isMediaLibraryOpen = computed(() => mediaLibraryState.value.isOpen)
|
|
311
|
+
export const mediaLibraryItems = computed(() => mediaLibraryState.value.items)
|
|
312
|
+
export const isMediaLibraryLoading = computed(
|
|
313
|
+
() => mediaLibraryState.value.isLoading,
|
|
314
|
+
)
|
|
315
|
+
|
|
316
|
+
// ============================================================================
|
|
317
|
+
// Create Page State Signals
|
|
318
|
+
// ============================================================================
|
|
319
|
+
|
|
320
|
+
export const createPageState = signal<CreatePageState>(
|
|
321
|
+
createInitialCreatePageState(),
|
|
322
|
+
)
|
|
323
|
+
|
|
324
|
+
// Convenience computed signals for create page
|
|
325
|
+
export const isCreatePageOpen = computed(() => createPageState.value.isOpen)
|
|
326
|
+
export const isCreatingPage = computed(() => createPageState.value.isCreating)
|
|
327
|
+
export const selectedCollection = computed(() => createPageState.value.selectedCollection)
|
|
328
|
+
|
|
329
|
+
// ============================================================================
|
|
330
|
+
// Collections Browser State Signals
|
|
331
|
+
// ============================================================================
|
|
332
|
+
|
|
333
|
+
export const collectionsBrowserState = signal<CollectionsBrowserState>(
|
|
334
|
+
createInitialCollectionsBrowserState(),
|
|
335
|
+
)
|
|
336
|
+
|
|
337
|
+
// Convenience computed signals for collections browser
|
|
338
|
+
export const isCollectionsBrowserOpen = computed(() => collectionsBrowserState.value.isOpen)
|
|
339
|
+
export const selectedBrowserCollection = computed(() => collectionsBrowserState.value.selectedCollection)
|
|
340
|
+
|
|
341
|
+
// ============================================================================
|
|
342
|
+
// Deployment State Signals
|
|
343
|
+
// ============================================================================
|
|
344
|
+
|
|
345
|
+
export const deploymentState = signal<DeploymentState>(
|
|
346
|
+
createInitialDeploymentState(),
|
|
347
|
+
)
|
|
348
|
+
|
|
349
|
+
// Convenience computed signals for deployment
|
|
350
|
+
export const deploymentStatus = computed(() => deploymentState.value.status)
|
|
351
|
+
export const isDeploymentPolling = computed(() => deploymentState.value.isPolling)
|
|
352
|
+
export const lastDeployedAt = computed(() => deploymentState.value.lastDeployedAt)
|
|
353
|
+
|
|
354
|
+
// ============================================================================
|
|
355
|
+
// Redirect Countdown State
|
|
356
|
+
// ============================================================================
|
|
357
|
+
|
|
358
|
+
export interface RedirectCountdownState {
|
|
359
|
+
url: string
|
|
360
|
+
label: string
|
|
361
|
+
secondsLeft: number
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
export const redirectCountdown = signal<RedirectCountdownState | null>(null)
|
|
365
|
+
|
|
366
|
+
let redirectTimer: ReturnType<typeof setInterval> | null = null
|
|
367
|
+
|
|
368
|
+
export function startRedirectCountdown(url: string, label: string, seconds = 10): void {
|
|
369
|
+
stopRedirectCountdown()
|
|
370
|
+
redirectCountdown.value = { url, label, secondsLeft: seconds }
|
|
371
|
+
redirectTimer = setInterval(() => {
|
|
372
|
+
const current = redirectCountdown.value
|
|
373
|
+
if (!current) {
|
|
374
|
+
stopRedirectCountdown()
|
|
375
|
+
return
|
|
376
|
+
}
|
|
377
|
+
if (current.secondsLeft <= 1) {
|
|
378
|
+
const targetUrl = current.url
|
|
379
|
+
stopRedirectCountdown()
|
|
380
|
+
window.location.href = targetUrl
|
|
381
|
+
} else {
|
|
382
|
+
redirectCountdown.value = { ...current, secondsLeft: current.secondsLeft - 1 }
|
|
383
|
+
}
|
|
384
|
+
}, 1000)
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
export function stopRedirectCountdown(): void {
|
|
388
|
+
if (redirectTimer) {
|
|
389
|
+
clearInterval(redirectTimer)
|
|
390
|
+
redirectTimer = null
|
|
391
|
+
}
|
|
392
|
+
redirectCountdown.value = null
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// ============================================================================
|
|
396
|
+
// Color Editor State Signals
|
|
397
|
+
// ============================================================================
|
|
398
|
+
|
|
399
|
+
export const colorEditorState = signal<ColorEditorState>(
|
|
400
|
+
createInitialColorEditorState(),
|
|
401
|
+
)
|
|
402
|
+
|
|
403
|
+
// Convenience computed signals for color editor
|
|
404
|
+
export const isColorEditorOpen = computed(() => colorEditorState.value.isOpen)
|
|
405
|
+
export const colorEditorTargetId = computed(() => colorEditorState.value.targetElementId)
|
|
406
|
+
|
|
407
|
+
// ============================================================================
|
|
408
|
+
// Confirm Dialog State Signals
|
|
409
|
+
// ============================================================================
|
|
410
|
+
|
|
411
|
+
export const confirmDialogState = signal<ConfirmDialogState>(
|
|
412
|
+
createInitialConfirmDialogState(),
|
|
413
|
+
)
|
|
414
|
+
|
|
415
|
+
// Convenience computed signals for confirm dialog
|
|
416
|
+
export const isConfirmDialogOpen = computed(() => confirmDialogState.value.isOpen)
|
|
417
|
+
|
|
418
|
+
// ============================================================================
|
|
419
|
+
// Settings State Signals
|
|
420
|
+
// ============================================================================
|
|
421
|
+
|
|
422
|
+
export const settings = signal<CmsSettings>(createInitialSettings())
|
|
423
|
+
|
|
424
|
+
// Convenience computed signals for settings
|
|
425
|
+
export const showEditableHighlights = computed(() => settings.value.showEditableHighlights)
|
|
426
|
+
|
|
427
|
+
// ============================================================================
|
|
428
|
+
// SEO Editor State Signals
|
|
429
|
+
// ============================================================================
|
|
430
|
+
|
|
431
|
+
export const seoEditorState = signal<SeoEditorState>(createInitialSeoEditorState())
|
|
432
|
+
export const pendingSeoChanges = signal<Map<string, PendingSeoChange>>(new Map())
|
|
433
|
+
|
|
434
|
+
// Convenience computed signals for SEO editor
|
|
435
|
+
export const isSeoEditorOpen = computed(() => seoEditorState.value.isOpen)
|
|
436
|
+
|
|
437
|
+
// Create helpers for pending SEO changes
|
|
438
|
+
const _pendingSeoChangesHelpers = createMapHelpers(pendingSeoChanges)
|
|
439
|
+
|
|
440
|
+
// ============================================================================
|
|
441
|
+
// Attribute Editor State Signals
|
|
442
|
+
// ============================================================================
|
|
443
|
+
|
|
444
|
+
export const attributeEditorState = signal<AttributeEditorState>(createInitialAttributeEditorState())
|
|
445
|
+
export const pendingAttributeChanges = signal<Map<string, PendingAttributeChange>>(new Map())
|
|
446
|
+
|
|
447
|
+
// Convenience computed signals for attribute editor
|
|
448
|
+
export const isAttributeEditorOpen = computed(() => attributeEditorState.value.isOpen)
|
|
449
|
+
export const attributeEditorTargetId = computed(() => attributeEditorState.value.targetElementId)
|
|
450
|
+
|
|
451
|
+
// Create helpers for pending attribute changes
|
|
452
|
+
const _pendingAttributeChangesHelpers = createMapHelpers(pendingAttributeChanges)
|
|
453
|
+
|
|
454
|
+
// ============================================================================
|
|
455
|
+
// Swatch/Attribute Button Hover State Signals
|
|
456
|
+
// ============================================================================
|
|
457
|
+
|
|
458
|
+
/** True when user is hovering over color swatches */
|
|
459
|
+
export const isHoveringSwatches = signal(false)
|
|
460
|
+
|
|
461
|
+
/** True when user is hovering over attribute edit button */
|
|
462
|
+
export const isHoveringAttributeButton = signal(false)
|
|
463
|
+
|
|
464
|
+
/** Computed: true when hovering over any outline UI element (swatches, attr button) */
|
|
465
|
+
export const isHoveringOutlineUI = computed(() => isHoveringSwatches.value || isHoveringAttributeButton.value)
|
|
466
|
+
|
|
467
|
+
export function setHoveringSwatches(hovering: boolean): void {
|
|
468
|
+
isHoveringSwatches.value = hovering
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
export function setHoveringAttributeButton(hovering: boolean): void {
|
|
472
|
+
isHoveringAttributeButton.value = hovering
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
// ============================================================================
|
|
476
|
+
// Config Signal
|
|
477
|
+
// ============================================================================
|
|
478
|
+
|
|
479
|
+
export const config = signal<CmsConfig>(getConfig())
|
|
480
|
+
|
|
481
|
+
// ============================================================================
|
|
482
|
+
// Toast State
|
|
483
|
+
// ============================================================================
|
|
484
|
+
|
|
485
|
+
export const toasts = signal<ToastMessage[]>([])
|
|
486
|
+
|
|
487
|
+
// Counter for unique toast IDs (more reliable than Date.now())
|
|
488
|
+
let toastIdCounter = 0
|
|
489
|
+
|
|
490
|
+
// ============================================================================
|
|
491
|
+
// Computed Values - Dirty Tracking
|
|
492
|
+
// ============================================================================
|
|
493
|
+
|
|
494
|
+
// Use factory for dirty tracking to reduce duplication
|
|
495
|
+
const _pendingChangesDirty = createDirtyTracking(pendingChanges)
|
|
496
|
+
const _pendingImageChangesDirty = createDirtyTracking(pendingImageChanges)
|
|
497
|
+
const _pendingColorChangesDirty = createDirtyTracking(pendingColorChanges)
|
|
498
|
+
const _pendingSeoChangesDirty = createDirtyTracking(pendingSeoChanges)
|
|
499
|
+
const _pendingAttributeChangesDirty = createDirtyTracking(pendingAttributeChanges)
|
|
500
|
+
|
|
501
|
+
export const dirtyChangesCount = _pendingChangesDirty.dirtyCount
|
|
502
|
+
export const dirtyChanges = _pendingChangesDirty.dirtyItems
|
|
503
|
+
export const hasDirtyChanges = _pendingChangesDirty.hasDirty
|
|
504
|
+
|
|
505
|
+
export const dirtyImageChangesCount = _pendingImageChangesDirty.dirtyCount
|
|
506
|
+
export const dirtyImageChanges = _pendingImageChangesDirty.dirtyItems
|
|
507
|
+
export const hasDirtyImageChanges = _pendingImageChangesDirty.hasDirty
|
|
508
|
+
|
|
509
|
+
export const dirtyColorChangesCount = _pendingColorChangesDirty.dirtyCount
|
|
510
|
+
export const dirtyColorChanges = _pendingColorChangesDirty.dirtyItems
|
|
511
|
+
export const hasDirtyColorChanges = _pendingColorChangesDirty.hasDirty
|
|
512
|
+
|
|
513
|
+
export const dirtySeoChangesCount = _pendingSeoChangesDirty.dirtyCount
|
|
514
|
+
export const dirtySeoChanges = _pendingSeoChangesDirty.dirtyItems
|
|
515
|
+
export const hasDirtySeoChanges = _pendingSeoChangesDirty.hasDirty
|
|
516
|
+
|
|
517
|
+
export const dirtyAttributeChangesCount = _pendingAttributeChangesDirty.dirtyCount
|
|
518
|
+
export const dirtyAttributeChanges = _pendingAttributeChangesDirty.dirtyItems
|
|
519
|
+
export const hasDirtyAttributeChanges = _pendingAttributeChangesDirty.hasDirty
|
|
520
|
+
|
|
521
|
+
export const totalDirtyCount = computed(
|
|
522
|
+
() =>
|
|
523
|
+
dirtyChangesCount.value + dirtyImageChangesCount.value + dirtyColorChangesCount.value + dirtySeoChangesCount.value
|
|
524
|
+
+ dirtyAttributeChangesCount.value,
|
|
525
|
+
)
|
|
526
|
+
|
|
527
|
+
export const hasAnyDirtyChanges = computed(
|
|
528
|
+
() =>
|
|
529
|
+
hasDirtyChanges.value || hasDirtyImageChanges.value || hasDirtyColorChanges.value || hasDirtySeoChanges.value || hasDirtyAttributeChanges.value,
|
|
530
|
+
)
|
|
531
|
+
|
|
532
|
+
// Navigation index for cycling through dirty elements
|
|
533
|
+
export const changeNavigationIndex = signal<number>(0)
|
|
534
|
+
|
|
535
|
+
// Combined list of all dirty elements for navigation
|
|
536
|
+
export const allDirtyElements = computed(() => {
|
|
537
|
+
const elements: Array<{ cmsId: string; element: HTMLElement; type: 'text' | 'image' | 'color' }> = []
|
|
538
|
+
|
|
539
|
+
dirtyChanges.value.forEach(([cmsId, change]) => {
|
|
540
|
+
elements.push({ cmsId, element: change.element, type: 'text' })
|
|
541
|
+
})
|
|
542
|
+
dirtyImageChanges.value.forEach(([cmsId, change]) => {
|
|
543
|
+
elements.push({ cmsId, element: change.element, type: 'image' })
|
|
544
|
+
})
|
|
545
|
+
dirtyColorChanges.value.forEach(([cmsId, change]) => {
|
|
546
|
+
elements.push({ cmsId, element: change.element, type: 'color' })
|
|
547
|
+
})
|
|
548
|
+
|
|
549
|
+
return elements
|
|
550
|
+
})
|
|
551
|
+
|
|
552
|
+
// ============================================================================
|
|
553
|
+
// State Mutation Functions
|
|
554
|
+
// ============================================================================
|
|
555
|
+
|
|
556
|
+
// Editor state mutations
|
|
557
|
+
export function setManifest(newManifest: CmsManifest): void {
|
|
558
|
+
manifest.value = newManifest
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
export function setEnabled(enabled: boolean): void {
|
|
562
|
+
isEnabled.value = enabled
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
export function setEditing(editing: boolean): void {
|
|
566
|
+
isEditing.value = editing
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
export function setShowingOriginal(showing: boolean): void {
|
|
570
|
+
showingOriginal.value = showing
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
export function setCurrentEditingId(id: string | null): void {
|
|
574
|
+
currentEditingId.value = id
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
export function setCurrentComponentId(componentId: string | null): void {
|
|
578
|
+
batch(() => {
|
|
579
|
+
currentComponentId.value = componentId
|
|
580
|
+
blockEditorState.value = {
|
|
581
|
+
...blockEditorState.value,
|
|
582
|
+
currentComponentId: componentId,
|
|
583
|
+
}
|
|
584
|
+
})
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
// Pending changes mutations - using helpers
|
|
588
|
+
export const setPendingChange = _pendingChangesHelpers.set
|
|
589
|
+
export const updatePendingChange = _pendingChangesHelpers.update
|
|
590
|
+
export const deletePendingChange = _pendingChangesHelpers.delete
|
|
591
|
+
export const clearPendingChanges = _pendingChangesHelpers.clear
|
|
592
|
+
export const getPendingChange = _pendingChangesHelpers.get
|
|
593
|
+
|
|
594
|
+
// Component changes mutations - using helpers
|
|
595
|
+
export const setPendingComponentChange = _pendingComponentChangesHelpers.set
|
|
596
|
+
export const deletePendingComponentChange = _pendingComponentChangesHelpers.delete
|
|
597
|
+
export const clearPendingComponentChanges = _pendingComponentChangesHelpers.clear
|
|
598
|
+
|
|
599
|
+
// Insert mutations - using helpers
|
|
600
|
+
export const setPendingInsert = _pendingInsertsHelpers.set
|
|
601
|
+
export const deletePendingInsert = _pendingInsertsHelpers.delete
|
|
602
|
+
export const clearPendingInserts = _pendingInsertsHelpers.clear
|
|
603
|
+
|
|
604
|
+
// Image changes mutations - using helpers
|
|
605
|
+
export const setPendingImageChange = _pendingImageChangesHelpers.set
|
|
606
|
+
export const updatePendingImageChange = _pendingImageChangesHelpers.update
|
|
607
|
+
export const deletePendingImageChange = _pendingImageChangesHelpers.delete
|
|
608
|
+
export const clearPendingImageChanges = _pendingImageChangesHelpers.clear
|
|
609
|
+
export const getPendingImageChange = _pendingImageChangesHelpers.get
|
|
610
|
+
|
|
611
|
+
// Color changes mutations - using helpers
|
|
612
|
+
export const setPendingColorChange = _pendingColorChangesHelpers.set
|
|
613
|
+
export const updatePendingColorChange = _pendingColorChangesHelpers.update
|
|
614
|
+
export const deletePendingColorChange = _pendingColorChangesHelpers.delete
|
|
615
|
+
export const clearPendingColorChanges = _pendingColorChangesHelpers.clear
|
|
616
|
+
export const getPendingColorChange = _pendingColorChangesHelpers.get
|
|
617
|
+
|
|
618
|
+
// SEO changes mutations - using helpers
|
|
619
|
+
export const setPendingSeoChange = _pendingSeoChangesHelpers.set
|
|
620
|
+
export const updatePendingSeoChange = _pendingSeoChangesHelpers.update
|
|
621
|
+
export const deletePendingSeoChange = _pendingSeoChangesHelpers.delete
|
|
622
|
+
export const clearPendingSeoChanges = _pendingSeoChangesHelpers.clear
|
|
623
|
+
export const getPendingSeoChange = _pendingSeoChangesHelpers.get
|
|
624
|
+
|
|
625
|
+
// Attribute changes mutations - using helpers
|
|
626
|
+
export const setPendingAttributeChange = _pendingAttributeChangesHelpers.set
|
|
627
|
+
export const updatePendingAttributeChange = _pendingAttributeChangesHelpers.update
|
|
628
|
+
export const deletePendingAttributeChange = _pendingAttributeChangesHelpers.delete
|
|
629
|
+
export const clearPendingAttributeChanges = _pendingAttributeChangesHelpers.clear
|
|
630
|
+
export const getPendingAttributeChange = _pendingAttributeChangesHelpers.get
|
|
631
|
+
|
|
632
|
+
// ============================================================================
|
|
633
|
+
// AI State Mutations
|
|
634
|
+
// ============================================================================
|
|
635
|
+
|
|
636
|
+
export function setAIPromptVisible(visible: boolean): void {
|
|
637
|
+
aiState.value = { ...aiState.value, isPromptVisible: visible }
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
export function setAIProcessing(processing: boolean): void {
|
|
641
|
+
aiState.value = { ...aiState.value, isProcessing: processing }
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
export function setAIStatus(status: AIStatusType, message?: string): void {
|
|
645
|
+
aiState.value = {
|
|
646
|
+
...aiState.value,
|
|
647
|
+
currentStatus: status,
|
|
648
|
+
statusMessage: message ?? null,
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
export function clearAIStatus(): void {
|
|
653
|
+
aiState.value = {
|
|
654
|
+
...aiState.value,
|
|
655
|
+
currentStatus: null,
|
|
656
|
+
statusMessage: null,
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
export function setAITargetElement(elementId: string | null): void {
|
|
661
|
+
aiState.value = { ...aiState.value, targetElementId: elementId }
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
export function setAIStreamingContent(content: string | null): void {
|
|
665
|
+
aiState.value = { ...aiState.value, streamingContent: content }
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
export function setAIError(error: string | null): void {
|
|
669
|
+
aiState.value = { ...aiState.value, error: error }
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
export function resetAIState(): void {
|
|
673
|
+
aiState.value = createInitialAIState()
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
export function setAIChatOpen(open: boolean): void {
|
|
677
|
+
aiState.value = { ...aiState.value, isChatOpen: open }
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
export function addChatMessage(message: ChatMessage): void {
|
|
681
|
+
aiState.value = {
|
|
682
|
+
...aiState.value,
|
|
683
|
+
chatMessages: [...aiState.value.chatMessages, message],
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
export function setChatMessages(messages: ChatMessage[]): void {
|
|
688
|
+
aiState.value = {
|
|
689
|
+
...aiState.value,
|
|
690
|
+
chatMessages: messages,
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
export function updateChatMessage(messageId: string, content: string): void {
|
|
695
|
+
aiState.value = {
|
|
696
|
+
...aiState.value,
|
|
697
|
+
chatMessages: aiState.value.chatMessages.map((msg) => msg.id === messageId ? { ...msg, content } : msg),
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
export function setChatContextElement(elementId: string | null): void {
|
|
702
|
+
aiState.value = { ...aiState.value, chatContextElementId: elementId }
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
export function clearChatMessages(): void {
|
|
706
|
+
aiState.value = { ...aiState.value, chatMessages: [] }
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
// ============================================================================
|
|
710
|
+
// Block Editor State Mutations
|
|
711
|
+
// ============================================================================
|
|
712
|
+
|
|
713
|
+
export function setBlockEditorOpen(open: boolean): void {
|
|
714
|
+
blockEditorState.value = { ...blockEditorState.value, isOpen: open }
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
export function setBlockEditorMode(mode: 'edit' | 'add' | 'picker'): void {
|
|
718
|
+
blockEditorState.value = { ...blockEditorState.value, mode }
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
export function resetBlockEditorState(): void {
|
|
722
|
+
blockEditorState.value = createInitialBlockEditorState()
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
// ============================================================================
|
|
726
|
+
// Markdown Editor State Mutations
|
|
727
|
+
// ============================================================================
|
|
728
|
+
|
|
729
|
+
export function setMarkdownEditorOpen(open: boolean): void {
|
|
730
|
+
markdownEditorState.value = { ...markdownEditorState.value, isOpen: open }
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
export function setMarkdownPage(page: MarkdownPageEntry | null): void {
|
|
734
|
+
markdownEditorState.value = { ...markdownEditorState.value, currentPage: page }
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
export function updateMarkdownContent(content: string): void {
|
|
738
|
+
if (markdownEditorState.value.currentPage) {
|
|
739
|
+
markdownEditorState.value = {
|
|
740
|
+
...markdownEditorState.value,
|
|
741
|
+
currentPage: {
|
|
742
|
+
...markdownEditorState.value.currentPage,
|
|
743
|
+
content,
|
|
744
|
+
isDirty: true,
|
|
745
|
+
},
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
export function setMarkdownActiveElement(elementId: string | null): void {
|
|
751
|
+
markdownEditorState.value = {
|
|
752
|
+
...markdownEditorState.value,
|
|
753
|
+
activeElementId: elementId,
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
export function updateMarkdownFrontmatter(updates: Partial<import('./types').BlogFrontmatter>): void {
|
|
758
|
+
if (markdownEditorState.value.currentPage) {
|
|
759
|
+
markdownEditorState.value = {
|
|
760
|
+
...markdownEditorState.value,
|
|
761
|
+
currentPage: {
|
|
762
|
+
...markdownEditorState.value.currentPage,
|
|
763
|
+
frontmatter: {
|
|
764
|
+
...markdownEditorState.value.currentPage.frontmatter,
|
|
765
|
+
...updates,
|
|
766
|
+
},
|
|
767
|
+
isDirty: true,
|
|
768
|
+
},
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
export function resetMarkdownEditorState(): void {
|
|
774
|
+
markdownEditorState.value = createInitialMarkdownEditorState()
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
/**
|
|
778
|
+
* Parse a frontmatter value from string to its appropriate type.
|
|
779
|
+
* The manifest stores all values as strings, so we need to convert them back.
|
|
780
|
+
*/
|
|
781
|
+
function parseFrontmatterValue(value: string): unknown {
|
|
782
|
+
// Handle booleans
|
|
783
|
+
if (value === 'true') return true
|
|
784
|
+
if (value === 'false') return false
|
|
785
|
+
|
|
786
|
+
// Handle numbers
|
|
787
|
+
const num = Number(value)
|
|
788
|
+
if (!Number.isNaN(num) && value.trim() !== '') {
|
|
789
|
+
return num
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
// Handle arrays (simple comma-separated for now)
|
|
793
|
+
if (value.startsWith('[') && value.endsWith(']')) {
|
|
794
|
+
try {
|
|
795
|
+
return JSON.parse(value)
|
|
796
|
+
} catch {
|
|
797
|
+
// Not valid JSON, return as string
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
// Return as string (already the default)
|
|
802
|
+
return value
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
/**
|
|
806
|
+
* Open the markdown editor for the current page's collection entry.
|
|
807
|
+
* Refreshes the manifest first to ensure we have the latest content.
|
|
808
|
+
*/
|
|
809
|
+
export async function openMarkdownEditorForCurrentPage(): Promise<boolean> {
|
|
810
|
+
// Refresh manifest to get the latest content
|
|
811
|
+
try {
|
|
812
|
+
const newManifest = await fetchManifest()
|
|
813
|
+
setManifest(newManifest)
|
|
814
|
+
} catch (err) {
|
|
815
|
+
console.error('[CMS] Failed to refresh manifest:', err)
|
|
816
|
+
// Continue with current manifest if refresh fails
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
const collection = currentPageCollection.value
|
|
820
|
+
if (!collection) {
|
|
821
|
+
return false
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
// Fetch the actual markdown content via the API to get properly parsed frontmatter.
|
|
825
|
+
// The manifest's naive YAML parsing corrupts block scalars (e.g. `description: >-`).
|
|
826
|
+
let frontmatter: Record<string, unknown>
|
|
827
|
+
let content: string
|
|
828
|
+
try {
|
|
829
|
+
const result = await getMarkdownContent(config.value.apiBase, collection.sourcePath)
|
|
830
|
+
if (result) {
|
|
831
|
+
frontmatter = result.frontmatter as Record<string, unknown>
|
|
832
|
+
content = result.content
|
|
833
|
+
} else {
|
|
834
|
+
throw new Error('API returned null')
|
|
835
|
+
}
|
|
836
|
+
} catch {
|
|
837
|
+
// Fall back to manifest data if the API call fails
|
|
838
|
+
frontmatter = {}
|
|
839
|
+
for (const [key, data] of Object.entries(collection.frontmatter)) {
|
|
840
|
+
frontmatter[key] = parseFrontmatterValue(data.value)
|
|
841
|
+
}
|
|
842
|
+
content = collection.body
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
// Look up collection definition for schema-aware field rendering
|
|
846
|
+
const collectionDefinition = manifest.value.collectionDefinitions?.[collection.collectionName]
|
|
847
|
+
|
|
848
|
+
markdownEditorState.value = {
|
|
849
|
+
isOpen: true,
|
|
850
|
+
currentPage: {
|
|
851
|
+
filePath: collection.sourcePath,
|
|
852
|
+
slug: collection.collectionSlug,
|
|
853
|
+
frontmatter: frontmatter as import('./types').BlogFrontmatter,
|
|
854
|
+
content,
|
|
855
|
+
isDirty: false,
|
|
856
|
+
},
|
|
857
|
+
activeElementId: collection.wrapperId ?? null,
|
|
858
|
+
mode: 'edit',
|
|
859
|
+
collectionDefinition,
|
|
860
|
+
}
|
|
861
|
+
return true
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
/**
|
|
865
|
+
* Open the markdown editor in "create" mode for a new page in the given collection.
|
|
866
|
+
* Builds initial frontmatter from the collection's field definitions.
|
|
867
|
+
*/
|
|
868
|
+
export function openMarkdownEditorForNewPage(
|
|
869
|
+
collectionName: string,
|
|
870
|
+
collectionDefinition: CollectionDefinition,
|
|
871
|
+
): void {
|
|
872
|
+
// Build initial frontmatter from field definitions
|
|
873
|
+
const initialFrontmatter: Record<string, unknown> = {}
|
|
874
|
+
for (const field of collectionDefinition.fields) {
|
|
875
|
+
if (field.name === 'title') continue // title handled separately via the header
|
|
876
|
+
if (field.defaultValue !== undefined) {
|
|
877
|
+
initialFrontmatter[field.name] = field.defaultValue
|
|
878
|
+
} else {
|
|
879
|
+
initialFrontmatter[field.name] = getDefaultForFieldType(field)
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
markdownEditorState.value = {
|
|
884
|
+
isOpen: true,
|
|
885
|
+
currentPage: {
|
|
886
|
+
filePath: '',
|
|
887
|
+
slug: '',
|
|
888
|
+
frontmatter: { title: '', ...initialFrontmatter },
|
|
889
|
+
content: '',
|
|
890
|
+
isDirty: false,
|
|
891
|
+
},
|
|
892
|
+
activeElementId: null,
|
|
893
|
+
mode: 'create',
|
|
894
|
+
collectionDefinition,
|
|
895
|
+
createOptions: {
|
|
896
|
+
collectionName,
|
|
897
|
+
collectionDefinition,
|
|
898
|
+
},
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
/**
|
|
903
|
+
* Get a sensible default value for a field based on its type definition.
|
|
904
|
+
*/
|
|
905
|
+
function getDefaultForFieldType(field: FieldDefinition): unknown {
|
|
906
|
+
switch (field.type) {
|
|
907
|
+
case 'boolean':
|
|
908
|
+
return false
|
|
909
|
+
case 'number':
|
|
910
|
+
return 0
|
|
911
|
+
case 'array':
|
|
912
|
+
return []
|
|
913
|
+
case 'date':
|
|
914
|
+
return new Date().toISOString().split('T')[0]
|
|
915
|
+
default:
|
|
916
|
+
return ''
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
// ============================================================================
|
|
921
|
+
// Media Library State Mutations
|
|
922
|
+
// ============================================================================
|
|
923
|
+
|
|
924
|
+
export function setMediaLibraryOpen(open: boolean): void {
|
|
925
|
+
mediaLibraryState.value = { ...mediaLibraryState.value, isOpen: open }
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
export function setMediaLibraryItems(items: MediaItem[]): void {
|
|
929
|
+
mediaLibraryState.value = { ...mediaLibraryState.value, items }
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
export function setMediaLibraryLoading(loading: boolean): void {
|
|
933
|
+
mediaLibraryState.value = { ...mediaLibraryState.value, isLoading: loading }
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
export function setMediaLibrarySelectedItem(item: MediaItem | null): void {
|
|
937
|
+
mediaLibraryState.value = { ...mediaLibraryState.value, selectedItem: item }
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
export function setMediaLibraryInsertCallback(
|
|
941
|
+
callback: ((url: string, alt: string) => void) | null,
|
|
942
|
+
): void {
|
|
943
|
+
mediaLibraryState.value = { ...mediaLibraryState.value, insertCallback: callback }
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
export function openMediaLibraryWithCallback(
|
|
947
|
+
callback: (url: string, alt: string) => void,
|
|
948
|
+
): void {
|
|
949
|
+
mediaLibraryState.value = {
|
|
950
|
+
...mediaLibraryState.value,
|
|
951
|
+
isOpen: true,
|
|
952
|
+
insertCallback: callback,
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
export function resetMediaLibraryState(): void {
|
|
957
|
+
mediaLibraryState.value = createInitialMediaLibraryState()
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
// ============================================================================
|
|
961
|
+
// Create Page State Mutations
|
|
962
|
+
// ============================================================================
|
|
963
|
+
|
|
964
|
+
export function setCreatePageOpen(open: boolean): void {
|
|
965
|
+
createPageState.value = { ...createPageState.value, isOpen: open }
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
export function setCreatingPage(creating: boolean): void {
|
|
969
|
+
createPageState.value = { ...createPageState.value, isCreating: creating }
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
export function setSelectedCollection(collection: string | null): void {
|
|
973
|
+
createPageState.value = { ...createPageState.value, selectedCollection: collection }
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
export function resetCreatePageState(): void {
|
|
977
|
+
createPageState.value = createInitialCreatePageState()
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
// ============================================================================
|
|
981
|
+
// Collections Browser State Mutations
|
|
982
|
+
// ============================================================================
|
|
983
|
+
|
|
984
|
+
export function openCollectionsBrowser(): void {
|
|
985
|
+
collectionsBrowserState.value = { isOpen: true, selectedCollection: null }
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
export function selectBrowserCollection(name: string | null): void {
|
|
989
|
+
collectionsBrowserState.value = { ...collectionsBrowserState.value, selectedCollection: name }
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
export function closeCollectionsBrowser(): void {
|
|
993
|
+
collectionsBrowserState.value = createInitialCollectionsBrowserState()
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
/**
|
|
997
|
+
* Open the markdown editor for an existing collection entry.
|
|
998
|
+
* Fetches markdown content via the API and opens in edit mode.
|
|
999
|
+
*/
|
|
1000
|
+
export async function openMarkdownEditorForEntry(
|
|
1001
|
+
collectionName: string,
|
|
1002
|
+
slug: string,
|
|
1003
|
+
sourcePath: string,
|
|
1004
|
+
collectionDefinition: CollectionDefinition,
|
|
1005
|
+
): Promise<void> {
|
|
1006
|
+
let frontmatter: Record<string, unknown> = {}
|
|
1007
|
+
let content = ''
|
|
1008
|
+
|
|
1009
|
+
try {
|
|
1010
|
+
const result = await getMarkdownContent(config.value.apiBase, sourcePath)
|
|
1011
|
+
if (result) {
|
|
1012
|
+
frontmatter = result.frontmatter as Record<string, unknown>
|
|
1013
|
+
content = result.content
|
|
1014
|
+
}
|
|
1015
|
+
} catch (err) {
|
|
1016
|
+
console.error('[CMS] Failed to fetch markdown content for entry:', err)
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
markdownEditorState.value = {
|
|
1020
|
+
isOpen: true,
|
|
1021
|
+
currentPage: {
|
|
1022
|
+
filePath: sourcePath,
|
|
1023
|
+
slug,
|
|
1024
|
+
frontmatter: frontmatter as import('./types').BlogFrontmatter,
|
|
1025
|
+
content,
|
|
1026
|
+
isDirty: false,
|
|
1027
|
+
},
|
|
1028
|
+
activeElementId: null,
|
|
1029
|
+
mode: 'edit',
|
|
1030
|
+
collectionDefinition,
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
// ============================================================================
|
|
1035
|
+
// Deployment State Mutations
|
|
1036
|
+
// ============================================================================
|
|
1037
|
+
|
|
1038
|
+
export function setDeploymentStatus(status: DeploymentStatusType | null): void {
|
|
1039
|
+
deploymentState.value = { ...deploymentState.value, status }
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
export function setDeploymentPolling(isPolling: boolean): void {
|
|
1043
|
+
deploymentState.value = { ...deploymentState.value, isPolling }
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
export function setLastDeployedAt(timestamp: string | null): void {
|
|
1047
|
+
deploymentState.value = { ...deploymentState.value, lastDeployedAt: timestamp }
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1050
|
+
export function setDeploymentError(error: string | null): void {
|
|
1051
|
+
deploymentState.value = { ...deploymentState.value, error }
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
export function updateDeploymentState(update: Partial<DeploymentState>): void {
|
|
1055
|
+
deploymentState.value = { ...deploymentState.value, ...update }
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
export function resetDeploymentState(): void {
|
|
1059
|
+
deploymentState.value = createInitialDeploymentState()
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
// ============================================================================
|
|
1063
|
+
// Color Editor State Mutations
|
|
1064
|
+
// ============================================================================
|
|
1065
|
+
|
|
1066
|
+
export function setColorEditorOpen(open: boolean): void {
|
|
1067
|
+
colorEditorState.value = { ...colorEditorState.value, isOpen: open }
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
export function setColorEditorTarget(elementId: string | null, rect: DOMRect | null): void {
|
|
1071
|
+
colorEditorState.value = {
|
|
1072
|
+
...colorEditorState.value,
|
|
1073
|
+
targetElementId: elementId,
|
|
1074
|
+
targetRect: rect,
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
export function openColorEditor(elementId: string, rect: DOMRect): void {
|
|
1079
|
+
// Close attribute editor when opening color editor
|
|
1080
|
+
attributeEditorState.value = createInitialAttributeEditorState()
|
|
1081
|
+
|
|
1082
|
+
colorEditorState.value = {
|
|
1083
|
+
isOpen: true,
|
|
1084
|
+
targetElementId: elementId,
|
|
1085
|
+
targetRect: rect,
|
|
1086
|
+
}
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1089
|
+
export function closeColorEditor(): void {
|
|
1090
|
+
colorEditorState.value = createInitialColorEditorState()
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
export function resetColorEditorState(): void {
|
|
1094
|
+
colorEditorState.value = createInitialColorEditorState()
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
// ============================================================================
|
|
1098
|
+
// Confirm Dialog State Mutations
|
|
1099
|
+
// ============================================================================
|
|
1100
|
+
|
|
1101
|
+
export interface ShowConfirmOptions {
|
|
1102
|
+
title?: string
|
|
1103
|
+
message: string
|
|
1104
|
+
confirmLabel?: string
|
|
1105
|
+
cancelLabel?: string
|
|
1106
|
+
variant?: 'danger' | 'warning' | 'info'
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
export function showConfirmDialog(
|
|
1110
|
+
options: ShowConfirmOptions,
|
|
1111
|
+
): Promise<boolean> {
|
|
1112
|
+
return new Promise((resolve) => {
|
|
1113
|
+
confirmDialogState.value = {
|
|
1114
|
+
isOpen: true,
|
|
1115
|
+
title: options.title ?? 'Confirm',
|
|
1116
|
+
message: options.message,
|
|
1117
|
+
confirmLabel: options.confirmLabel ?? 'Confirm',
|
|
1118
|
+
cancelLabel: options.cancelLabel ?? 'Cancel',
|
|
1119
|
+
variant: options.variant ?? 'info',
|
|
1120
|
+
onConfirm: () => {
|
|
1121
|
+
closeConfirmDialog()
|
|
1122
|
+
resolve(true)
|
|
1123
|
+
},
|
|
1124
|
+
onCancel: () => {
|
|
1125
|
+
closeConfirmDialog()
|
|
1126
|
+
resolve(false)
|
|
1127
|
+
},
|
|
1128
|
+
}
|
|
1129
|
+
})
|
|
1130
|
+
}
|
|
1131
|
+
|
|
1132
|
+
export function closeConfirmDialog(): void {
|
|
1133
|
+
confirmDialogState.value = createInitialConfirmDialogState()
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1136
|
+
// ============================================================================
|
|
1137
|
+
// Settings State Mutations
|
|
1138
|
+
// ============================================================================
|
|
1139
|
+
|
|
1140
|
+
export function setShowEditableHighlights(show: boolean): void {
|
|
1141
|
+
settings.value = { ...settings.value, showEditableHighlights: show }
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1144
|
+
export function toggleShowEditableHighlights(): void {
|
|
1145
|
+
settings.value = { ...settings.value, showEditableHighlights: !settings.value.showEditableHighlights }
|
|
1146
|
+
}
|
|
1147
|
+
|
|
1148
|
+
export function updateSettings(update: Partial<CmsSettings>): void {
|
|
1149
|
+
settings.value = { ...settings.value, ...update }
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1152
|
+
export function resetSettings(): void {
|
|
1153
|
+
settings.value = createInitialSettings()
|
|
1154
|
+
}
|
|
1155
|
+
|
|
1156
|
+
// ============================================================================
|
|
1157
|
+
// SEO Editor State Mutations
|
|
1158
|
+
// ============================================================================
|
|
1159
|
+
|
|
1160
|
+
export function setSeoEditorOpen(open: boolean): void {
|
|
1161
|
+
seoEditorState.value = { ...seoEditorState.value, isOpen: open }
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1164
|
+
export function openSeoEditor(): void {
|
|
1165
|
+
seoEditorState.value = { isOpen: true }
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1168
|
+
export function closeSeoEditor(): void {
|
|
1169
|
+
seoEditorState.value = createInitialSeoEditorState()
|
|
1170
|
+
}
|
|
1171
|
+
|
|
1172
|
+
export function resetSeoEditorState(): void {
|
|
1173
|
+
seoEditorState.value = createInitialSeoEditorState()
|
|
1174
|
+
pendingSeoChanges.value = new Map()
|
|
1175
|
+
}
|
|
1176
|
+
|
|
1177
|
+
// ============================================================================
|
|
1178
|
+
// Attribute Editor State Mutations
|
|
1179
|
+
// ============================================================================
|
|
1180
|
+
|
|
1181
|
+
export function setAttributeEditorOpen(open: boolean): void {
|
|
1182
|
+
attributeEditorState.value = { ...attributeEditorState.value, isOpen: open }
|
|
1183
|
+
}
|
|
1184
|
+
|
|
1185
|
+
export function setAttributeEditorTarget(elementId: string | null, rect: DOMRect | null): void {
|
|
1186
|
+
attributeEditorState.value = {
|
|
1187
|
+
...attributeEditorState.value,
|
|
1188
|
+
targetElementId: elementId,
|
|
1189
|
+
targetRect: rect,
|
|
1190
|
+
}
|
|
1191
|
+
}
|
|
1192
|
+
|
|
1193
|
+
export function openAttributeEditor(elementId: string, rect: DOMRect): void {
|
|
1194
|
+
// Close color editor when opening attribute editor
|
|
1195
|
+
colorEditorState.value = createInitialColorEditorState()
|
|
1196
|
+
|
|
1197
|
+
// Ensure pending attribute change exists for this element
|
|
1198
|
+
if (!pendingAttributeChanges.value.has(elementId)) {
|
|
1199
|
+
const manifestEntry = manifest.value.entries[elementId]
|
|
1200
|
+
if (manifestEntry?.attributes && Object.keys(manifestEntry.attributes).length > 0) {
|
|
1201
|
+
// Deep copy the flat attributes map
|
|
1202
|
+
const originalAttributes: Record<string, import('./types').Attribute> = {}
|
|
1203
|
+
const newAttributes: Record<string, import('./types').Attribute> = {}
|
|
1204
|
+
for (const [key, attr] of Object.entries(manifestEntry.attributes)) {
|
|
1205
|
+
originalAttributes[key] = { ...attr }
|
|
1206
|
+
newAttributes[key] = { ...attr }
|
|
1207
|
+
}
|
|
1208
|
+
|
|
1209
|
+
// Find the element in the DOM
|
|
1210
|
+
const element = document.querySelector(`[data-cms-id="${elementId}"]`) as HTMLElement | null
|
|
1211
|
+
|
|
1212
|
+
if (element) {
|
|
1213
|
+
const newMap = new Map(pendingAttributeChanges.value)
|
|
1214
|
+
newMap.set(elementId, {
|
|
1215
|
+
element,
|
|
1216
|
+
cmsId: elementId,
|
|
1217
|
+
originalAttributes,
|
|
1218
|
+
newAttributes,
|
|
1219
|
+
isDirty: false,
|
|
1220
|
+
})
|
|
1221
|
+
pendingAttributeChanges.value = newMap
|
|
1222
|
+
}
|
|
1223
|
+
}
|
|
1224
|
+
}
|
|
1225
|
+
|
|
1226
|
+
attributeEditorState.value = {
|
|
1227
|
+
isOpen: true,
|
|
1228
|
+
targetElementId: elementId,
|
|
1229
|
+
targetRect: rect,
|
|
1230
|
+
}
|
|
1231
|
+
}
|
|
1232
|
+
|
|
1233
|
+
export function closeAttributeEditor(): void {
|
|
1234
|
+
attributeEditorState.value = createInitialAttributeEditorState()
|
|
1235
|
+
}
|
|
1236
|
+
|
|
1237
|
+
export function resetAttributeEditorState(): void {
|
|
1238
|
+
attributeEditorState.value = createInitialAttributeEditorState()
|
|
1239
|
+
pendingAttributeChanges.value = new Map()
|
|
1240
|
+
}
|
|
1241
|
+
|
|
1242
|
+
// ============================================================================
|
|
1243
|
+
// Toast Mutations
|
|
1244
|
+
// ============================================================================
|
|
1245
|
+
|
|
1246
|
+
export function showToast(message: string, type: ToastType = 'info'): string {
|
|
1247
|
+
const id = `toast-${++toastIdCounter}`
|
|
1248
|
+
toasts.value = [...toasts.value, { id, message, type }]
|
|
1249
|
+
return id
|
|
1250
|
+
}
|
|
1251
|
+
|
|
1252
|
+
export function removeToast(id: string): void {
|
|
1253
|
+
toasts.value = toasts.value.filter((t) => t.id !== id)
|
|
1254
|
+
}
|
|
1255
|
+
|
|
1256
|
+
// ============================================================================
|
|
1257
|
+
// Config Mutations
|
|
1258
|
+
// ============================================================================
|
|
1259
|
+
|
|
1260
|
+
export function setConfig(newConfig: CmsConfig): void {
|
|
1261
|
+
config.value = newConfig
|
|
1262
|
+
}
|
|
1263
|
+
|
|
1264
|
+
// ============================================================================
|
|
1265
|
+
// Change Navigation Mutations
|
|
1266
|
+
// ============================================================================
|
|
1267
|
+
|
|
1268
|
+
export function navigateToNextChange(): void {
|
|
1269
|
+
const elements = allDirtyElements.value
|
|
1270
|
+
if (elements.length === 0) return
|
|
1271
|
+
|
|
1272
|
+
const nextIndex = (changeNavigationIndex.value + 1) % elements.length
|
|
1273
|
+
changeNavigationIndex.value = nextIndex
|
|
1274
|
+
|
|
1275
|
+
const target = elements[nextIndex]
|
|
1276
|
+
if (!target?.element) return
|
|
1277
|
+
|
|
1278
|
+
target.element.scrollIntoView({ behavior: 'smooth', block: 'center' })
|
|
1279
|
+
|
|
1280
|
+
if (target.type === 'text') {
|
|
1281
|
+
target.element.focus()
|
|
1282
|
+
}
|
|
1283
|
+
setCurrentEditingId(target.cmsId)
|
|
1284
|
+
}
|
|
1285
|
+
|
|
1286
|
+
export function resetChangeNavigationIndex(): void {
|
|
1287
|
+
changeNavigationIndex.value = 0
|
|
1288
|
+
}
|
|
1289
|
+
|
|
1290
|
+
// ============================================================================
|
|
1291
|
+
// Legacy Compatibility Layer
|
|
1292
|
+
// ============================================================================
|
|
1293
|
+
|
|
1294
|
+
/**
|
|
1295
|
+
* Get a snapshot of the current state for legacy code paths.
|
|
1296
|
+
* Prefer using individual signals directly when possible.
|
|
1297
|
+
*/
|
|
1298
|
+
export function getStateSnapshot(): EditorState {
|
|
1299
|
+
return {
|
|
1300
|
+
isEnabled: isEnabled.value,
|
|
1301
|
+
isEditing: isEditing.value,
|
|
1302
|
+
showingOriginal: showingOriginal.value,
|
|
1303
|
+
currentEditingId: currentEditingId.value,
|
|
1304
|
+
currentComponentId: currentComponentId.value,
|
|
1305
|
+
pendingChanges: pendingChanges.value,
|
|
1306
|
+
pendingComponentChanges: pendingComponentChanges.value,
|
|
1307
|
+
pendingInserts: pendingInserts.value,
|
|
1308
|
+
manifest: manifest.value,
|
|
1309
|
+
ai: aiState.value,
|
|
1310
|
+
blockEditor: blockEditorState.value,
|
|
1311
|
+
}
|
|
1312
|
+
}
|
|
1313
|
+
|
|
1314
|
+
/**
|
|
1315
|
+
* Batch multiple state updates for performance.
|
|
1316
|
+
* Use this when updating multiple signals at once.
|
|
1317
|
+
*/
|
|
1318
|
+
export { batch }
|
|
1319
|
+
|
|
1320
|
+
/**
|
|
1321
|
+
* Reset all state to initial values.
|
|
1322
|
+
*/
|
|
1323
|
+
export function resetAllState(): void {
|
|
1324
|
+
batch(() => {
|
|
1325
|
+
isEnabled.value = false
|
|
1326
|
+
isEditing.value = false
|
|
1327
|
+
showingOriginal.value = false
|
|
1328
|
+
currentEditingId.value = null
|
|
1329
|
+
currentComponentId.value = null
|
|
1330
|
+
pendingChanges.value = new Map()
|
|
1331
|
+
pendingComponentChanges.value = new Map()
|
|
1332
|
+
pendingInserts.value = new Map()
|
|
1333
|
+
pendingImageChanges.value = new Map()
|
|
1334
|
+
pendingColorChanges.value = new Map()
|
|
1335
|
+
pendingSeoChanges.value = new Map()
|
|
1336
|
+
pendingAttributeChanges.value = new Map()
|
|
1337
|
+
manifest.value = { entries: {}, components: {}, componentDefinitions: {} }
|
|
1338
|
+
aiState.value = createInitialAIState()
|
|
1339
|
+
blockEditorState.value = createInitialBlockEditorState()
|
|
1340
|
+
markdownEditorState.value = createInitialMarkdownEditorState()
|
|
1341
|
+
mediaLibraryState.value = createInitialMediaLibraryState()
|
|
1342
|
+
createPageState.value = createInitialCreatePageState()
|
|
1343
|
+
collectionsBrowserState.value = createInitialCollectionsBrowserState()
|
|
1344
|
+
deploymentState.value = createInitialDeploymentState()
|
|
1345
|
+
colorEditorState.value = createInitialColorEditorState()
|
|
1346
|
+
confirmDialogState.value = createInitialConfirmDialogState()
|
|
1347
|
+
seoEditorState.value = createInitialSeoEditorState()
|
|
1348
|
+
attributeEditorState.value = createInitialAttributeEditorState()
|
|
1349
|
+
toasts.value = []
|
|
1350
|
+
})
|
|
1351
|
+
}
|