@nuasite/cms 0.8.3 → 0.9.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 +76 -0
- package/dist/editor.js +7948 -8356
- package/package.json +1 -1
- package/src/editor/index.tsx +103 -26
- package/src/editor/post-message.ts +136 -0
- package/src/index.ts +9 -0
- package/src/types.ts +145 -0
package/package.json
CHANGED
package/src/editor/index.tsx
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
import { render } from 'preact'
|
|
2
|
-
import { useCallback, useEffect } from 'preact/hooks'
|
|
2
|
+
import { useCallback, useEffect, useRef } from 'preact/hooks'
|
|
3
|
+
import type { CmsElementDeselectedMessage, CmsElementSelectedMessage } from '../types'
|
|
4
|
+
import {
|
|
5
|
+
buildEditorState,
|
|
6
|
+
buildPageNavigatedMessage,
|
|
7
|
+
buildReadyMessage,
|
|
8
|
+
buildSelectedElement,
|
|
9
|
+
buildStateChangedMessage,
|
|
10
|
+
postToParent,
|
|
11
|
+
} from './post-message'
|
|
3
12
|
import { fetchManifest } from './api'
|
|
4
13
|
import { AIChat } from './components/ai-chat'
|
|
5
14
|
import { AITooltip } from './components/ai-tooltip'
|
|
@@ -32,7 +41,7 @@ import {
|
|
|
32
41
|
stopEditMode,
|
|
33
42
|
toggleShowOriginal,
|
|
34
43
|
} from './editor'
|
|
35
|
-
import { performRedo, performUndo } from './history'
|
|
44
|
+
import { canRedo, canUndo, performRedo, performUndo } from './history'
|
|
36
45
|
import {
|
|
37
46
|
useAIHandlers,
|
|
38
47
|
useBgImageHoverDetection,
|
|
@@ -103,6 +112,17 @@ const CmsUI = () => {
|
|
|
103
112
|
}).catch(() => {})
|
|
104
113
|
}, [])
|
|
105
114
|
|
|
115
|
+
// Re-fetch manifest on View Transitions navigation (astro:after-swap)
|
|
116
|
+
useEffect(() => {
|
|
117
|
+
const onNavigation = () => {
|
|
118
|
+
fetchManifest().then((manifest) => {
|
|
119
|
+
signals.setManifest(manifest)
|
|
120
|
+
}).catch(() => {})
|
|
121
|
+
}
|
|
122
|
+
document.addEventListener('astro:after-swap', onNavigation)
|
|
123
|
+
return () => document.removeEventListener('astro:after-swap', onNavigation)
|
|
124
|
+
}, [])
|
|
125
|
+
|
|
106
126
|
// Auto-restore edit mode if it was active before a page refresh (e.g. after save triggers HMR)
|
|
107
127
|
useEffect(() => {
|
|
108
128
|
if (loadEditingState() && !signals.isEditing.value) {
|
|
@@ -117,6 +137,87 @@ const CmsUI = () => {
|
|
|
117
137
|
}
|
|
118
138
|
}, [])
|
|
119
139
|
|
|
140
|
+
// Send selected element info to parent window via postMessage (when inside an iframe)
|
|
141
|
+
const prevOutlineRef = useRef<{ cmsId: string | null; isComponent: boolean }>({ cmsId: null, isComponent: false })
|
|
142
|
+
useEffect(() => {
|
|
143
|
+
const prev = prevOutlineRef.current
|
|
144
|
+
const changed = outlineState.cmsId !== prev.cmsId
|
|
145
|
+
|| outlineState.isComponent !== prev.isComponent
|
|
146
|
+
|| (!outlineState.visible && (prev.cmsId !== null || prev.isComponent))
|
|
147
|
+
|
|
148
|
+
if (!changed) return
|
|
149
|
+
prevOutlineRef.current = { cmsId: outlineState.cmsId, isComponent: outlineState.isComponent }
|
|
150
|
+
|
|
151
|
+
if (outlineState.visible && (outlineState.cmsId || outlineState.isComponent)) {
|
|
152
|
+
const manifestData = signals.manifest.value
|
|
153
|
+
const entry = outlineState.cmsId ? manifestData.entries[outlineState.cmsId] : undefined
|
|
154
|
+
const componentEl = outlineState.element
|
|
155
|
+
const componentId = componentEl?.getAttribute('data-cms-component-id') ?? undefined
|
|
156
|
+
const instance = componentId ? manifestData.components?.[componentId] : undefined
|
|
157
|
+
const rect = outlineState.rect
|
|
158
|
+
|
|
159
|
+
const msg: CmsElementSelectedMessage = {
|
|
160
|
+
type: 'cms-element-selected',
|
|
161
|
+
element: buildSelectedElement({
|
|
162
|
+
cmsId: outlineState.cmsId,
|
|
163
|
+
isComponent: outlineState.isComponent,
|
|
164
|
+
componentName: outlineState.componentName,
|
|
165
|
+
componentId,
|
|
166
|
+
tagName: outlineState.tagName,
|
|
167
|
+
rect: rect ? { x: rect.x, y: rect.y, width: rect.width, height: rect.height } : null,
|
|
168
|
+
entry,
|
|
169
|
+
instance,
|
|
170
|
+
}),
|
|
171
|
+
}
|
|
172
|
+
postToParent(msg)
|
|
173
|
+
} else {
|
|
174
|
+
const msg: CmsElementDeselectedMessage = { type: 'cms-element-deselected' }
|
|
175
|
+
postToParent(msg)
|
|
176
|
+
}
|
|
177
|
+
}, [outlineState])
|
|
178
|
+
|
|
179
|
+
// Send cms-ready + cms-page-navigated when manifest loads
|
|
180
|
+
const prevManifestRef = useRef<boolean>(false)
|
|
181
|
+
useEffect(() => {
|
|
182
|
+
const m = signals.manifest.value
|
|
183
|
+
// Only fire when manifest has entries (i.e. actually loaded)
|
|
184
|
+
if (Object.keys(m.entries).length === 0) return
|
|
185
|
+
|
|
186
|
+
if (!prevManifestRef.current) {
|
|
187
|
+
prevManifestRef.current = true
|
|
188
|
+
postToParent(buildReadyMessage(m, window.location.pathname))
|
|
189
|
+
} else {
|
|
190
|
+
postToParent(buildPageNavigatedMessage(m, window.location.pathname))
|
|
191
|
+
}
|
|
192
|
+
})
|
|
193
|
+
|
|
194
|
+
// Send cms-state-changed when editor state changes
|
|
195
|
+
const prevStateRef = useRef<string>('')
|
|
196
|
+
useEffect(() => {
|
|
197
|
+
const state = buildEditorState({
|
|
198
|
+
isEditing: signals.isEditing.value,
|
|
199
|
+
dirtyCount: {
|
|
200
|
+
text: signals.dirtyChangesCount.value,
|
|
201
|
+
image: signals.dirtyImageChangesCount.value,
|
|
202
|
+
color: signals.dirtyColorChangesCount.value,
|
|
203
|
+
bgImage: signals.dirtyBgImageChangesCount.value,
|
|
204
|
+
attribute: signals.dirtyAttributeChangesCount.value,
|
|
205
|
+
seo: signals.dirtySeoChangesCount.value,
|
|
206
|
+
total: signals.totalDirtyCount.value,
|
|
207
|
+
},
|
|
208
|
+
deploymentStatus: signals.deploymentStatus.value,
|
|
209
|
+
lastDeployedAt: signals.lastDeployedAt.value,
|
|
210
|
+
canUndo: canUndo.value,
|
|
211
|
+
canRedo: canRedo.value,
|
|
212
|
+
})
|
|
213
|
+
|
|
214
|
+
const key = JSON.stringify(state)
|
|
215
|
+
if (key === prevStateRef.current) return
|
|
216
|
+
prevStateRef.current = key
|
|
217
|
+
|
|
218
|
+
postToParent(buildStateChangedMessage(state))
|
|
219
|
+
})
|
|
220
|
+
|
|
120
221
|
const {
|
|
121
222
|
handleAIChatToggle,
|
|
122
223
|
handleChatClose,
|
|
@@ -384,7 +485,6 @@ const CmsUI = () => {
|
|
|
384
485
|
onCompare: handleCompare,
|
|
385
486
|
onSave: handleSave,
|
|
386
487
|
onDiscard: handleDiscard,
|
|
387
|
-
onAIChat: handleAIChatToggle,
|
|
388
488
|
onMediaLibrary: handleMediaLibrary,
|
|
389
489
|
onDismissDeployment: handleDismissDeployment,
|
|
390
490
|
onNavigateChange: () => {
|
|
@@ -400,18 +500,6 @@ const CmsUI = () => {
|
|
|
400
500
|
/>
|
|
401
501
|
</ErrorBoundary>
|
|
402
502
|
|
|
403
|
-
<ErrorBoundary componentName="AI Tooltip">
|
|
404
|
-
<AITooltip
|
|
405
|
-
callbacks={{
|
|
406
|
-
onPromptSubmit: handleTooltipPromptSubmit,
|
|
407
|
-
}}
|
|
408
|
-
visible={!!tooltipState.elementId && isEditing && !isAIProcessing && !textSelectionState.hasSelection}
|
|
409
|
-
elementId={tooltipState.elementId}
|
|
410
|
-
rect={tooltipState.rect}
|
|
411
|
-
processing={isAIProcessing}
|
|
412
|
-
/>
|
|
413
|
-
</ErrorBoundary>
|
|
414
|
-
|
|
415
503
|
<ErrorBoundary componentName="Text Style Toolbar">
|
|
416
504
|
<TextStyleToolbar
|
|
417
505
|
visible={textSelectionState.hasSelection && isEditing && !isAIProcessing && isTextStylingAllowed}
|
|
@@ -439,17 +527,6 @@ const CmsUI = () => {
|
|
|
439
527
|
/>
|
|
440
528
|
</ErrorBoundary>
|
|
441
529
|
|
|
442
|
-
<ErrorBoundary componentName="AI Chat">
|
|
443
|
-
<AIChat
|
|
444
|
-
callbacks={{
|
|
445
|
-
onSend: handleChatSend,
|
|
446
|
-
onClose: handleChatClose,
|
|
447
|
-
onCancel: handleChatCancel,
|
|
448
|
-
onApplyToElement: handleApplyToElement,
|
|
449
|
-
}}
|
|
450
|
-
/>
|
|
451
|
-
</ErrorBoundary>
|
|
452
|
-
|
|
453
530
|
<ErrorBoundary componentName="Block Editor">
|
|
454
531
|
<BlockEditor
|
|
455
532
|
visible={blockEditorState.isOpen && isEditing}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
CmsEditorState,
|
|
3
|
+
CmsManifest,
|
|
4
|
+
CmsPageNavigatedMessage,
|
|
5
|
+
CmsPostMessage,
|
|
6
|
+
CmsReadyMessage,
|
|
7
|
+
CmsSelectedElement,
|
|
8
|
+
CmsStateChangedMessage,
|
|
9
|
+
ManifestEntry,
|
|
10
|
+
PageSeoData,
|
|
11
|
+
} from '../types'
|
|
12
|
+
import type { ComponentInstance } from '../types'
|
|
13
|
+
|
|
14
|
+
/** Send a postMessage to the parent window (no-op when not in an iframe) */
|
|
15
|
+
export function postToParent(msg: CmsPostMessage): void {
|
|
16
|
+
if (window.parent !== window) {
|
|
17
|
+
window.parent.postMessage(msg, '*')
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** Build a CmsSelectedElement from manifest data and outline state */
|
|
22
|
+
export function buildSelectedElement(opts: {
|
|
23
|
+
cmsId: string | null
|
|
24
|
+
isComponent: boolean
|
|
25
|
+
componentName?: string
|
|
26
|
+
componentId?: string
|
|
27
|
+
tagName?: string
|
|
28
|
+
rect: { x: number; y: number; width: number; height: number } | null
|
|
29
|
+
entry?: ManifestEntry
|
|
30
|
+
instance?: ComponentInstance
|
|
31
|
+
}): CmsSelectedElement {
|
|
32
|
+
const { cmsId, isComponent, componentName, componentId, tagName, rect, entry, instance } = opts
|
|
33
|
+
return {
|
|
34
|
+
cmsId,
|
|
35
|
+
isComponent,
|
|
36
|
+
componentName: componentName ?? instance?.componentName,
|
|
37
|
+
componentId,
|
|
38
|
+
tagName: tagName ?? entry?.tag,
|
|
39
|
+
rect,
|
|
40
|
+
...(entry && {
|
|
41
|
+
text: entry.text,
|
|
42
|
+
html: entry.html,
|
|
43
|
+
sourcePath: entry.sourcePath,
|
|
44
|
+
sourceLine: entry.sourceLine,
|
|
45
|
+
sourceSnippet: entry.sourceSnippet,
|
|
46
|
+
sourceHash: entry.sourceHash,
|
|
47
|
+
stableId: entry.stableId,
|
|
48
|
+
contentPath: entry.contentPath,
|
|
49
|
+
parentComponentId: entry.parentComponentId,
|
|
50
|
+
childCmsIds: entry.childCmsIds,
|
|
51
|
+
imageMetadata: entry.imageMetadata,
|
|
52
|
+
backgroundImage: entry.backgroundImage,
|
|
53
|
+
colorClasses: entry.colorClasses,
|
|
54
|
+
attributes: entry.attributes,
|
|
55
|
+
constraints: entry.constraints,
|
|
56
|
+
allowStyling: entry.allowStyling,
|
|
57
|
+
collectionName: entry.collectionName,
|
|
58
|
+
collectionSlug: entry.collectionSlug,
|
|
59
|
+
}),
|
|
60
|
+
...(instance && {
|
|
61
|
+
component: {
|
|
62
|
+
name: instance.componentName,
|
|
63
|
+
file: instance.file,
|
|
64
|
+
sourcePath: instance.sourcePath,
|
|
65
|
+
sourceLine: instance.sourceLine,
|
|
66
|
+
props: instance.props,
|
|
67
|
+
slots: instance.slots,
|
|
68
|
+
},
|
|
69
|
+
}),
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** Build a CmsReadyMessage from the loaded manifest */
|
|
74
|
+
export function buildReadyMessage(manifest: CmsManifest, pathname: string): CmsReadyMessage {
|
|
75
|
+
const seo = (manifest as any).seo as PageSeoData | undefined
|
|
76
|
+
const pageTitle = seo?.title?.content ?? manifest.pages?.find(p => p.pathname === pathname)?.title
|
|
77
|
+
|
|
78
|
+
return {
|
|
79
|
+
type: 'cms-ready',
|
|
80
|
+
data: {
|
|
81
|
+
pathname,
|
|
82
|
+
pageTitle,
|
|
83
|
+
seo,
|
|
84
|
+
pages: manifest.pages,
|
|
85
|
+
collectionDefinitions: manifest.collectionDefinitions,
|
|
86
|
+
componentDefinitions: manifest.componentDefinitions,
|
|
87
|
+
availableColors: manifest.availableColors,
|
|
88
|
+
availableTextStyles: manifest.availableTextStyles,
|
|
89
|
+
metadata: manifest.metadata,
|
|
90
|
+
},
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** Build a CmsPageNavigatedMessage */
|
|
95
|
+
export function buildPageNavigatedMessage(manifest: CmsManifest, pathname: string): CmsPageNavigatedMessage {
|
|
96
|
+
const seo = (manifest as any).seo as PageSeoData | undefined
|
|
97
|
+
const pageTitle = seo?.title?.content ?? manifest.pages?.find(p => p.pathname === pathname)?.title
|
|
98
|
+
|
|
99
|
+
return {
|
|
100
|
+
type: 'cms-page-navigated',
|
|
101
|
+
page: {
|
|
102
|
+
pathname,
|
|
103
|
+
title: pageTitle,
|
|
104
|
+
},
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** Build a CmsEditorState snapshot from current signal values */
|
|
109
|
+
export function buildEditorState(opts: {
|
|
110
|
+
isEditing: boolean
|
|
111
|
+
dirtyCount: CmsEditorState['dirtyCount']
|
|
112
|
+
deploymentStatus: CmsEditorState['deployment']['status']
|
|
113
|
+
lastDeployedAt: string | null
|
|
114
|
+
canUndo: boolean
|
|
115
|
+
canRedo: boolean
|
|
116
|
+
}): CmsEditorState {
|
|
117
|
+
return {
|
|
118
|
+
isEditing: opts.isEditing,
|
|
119
|
+
hasChanges: opts.dirtyCount.total > 0,
|
|
120
|
+
dirtyCount: opts.dirtyCount,
|
|
121
|
+
deployment: {
|
|
122
|
+
status: opts.deploymentStatus,
|
|
123
|
+
lastDeployedAt: opts.lastDeployedAt,
|
|
124
|
+
},
|
|
125
|
+
canUndo: opts.canUndo,
|
|
126
|
+
canRedo: opts.canRedo,
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** Build a CmsStateChangedMessage */
|
|
131
|
+
export function buildStateChangedMessage(state: CmsEditorState): CmsStateChangedMessage {
|
|
132
|
+
return {
|
|
133
|
+
type: 'cms-state-changed',
|
|
134
|
+
state,
|
|
135
|
+
}
|
|
136
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -287,8 +287,17 @@ export type {
|
|
|
287
287
|
AvailableColors,
|
|
288
288
|
AvailableTextStyles,
|
|
289
289
|
CanonicalUrl,
|
|
290
|
+
CmsEditorState,
|
|
291
|
+
CmsElementDeselectedMessage,
|
|
292
|
+
CmsElementSelectedMessage,
|
|
290
293
|
CmsManifest,
|
|
291
294
|
CmsMarkerOptions,
|
|
295
|
+
CmsPageNavigatedMessage,
|
|
296
|
+
CmsPostMessage,
|
|
297
|
+
CmsReadyData,
|
|
298
|
+
CmsReadyMessage,
|
|
299
|
+
CmsSelectedElement,
|
|
300
|
+
CmsStateChangedMessage,
|
|
292
301
|
CollectionDefinition,
|
|
293
302
|
CollectionEntry,
|
|
294
303
|
ComponentDefinition,
|
package/src/types.ts
CHANGED
|
@@ -423,3 +423,148 @@ export interface PageSeoData {
|
|
|
423
423
|
/** JSON-LD structured data blocks */
|
|
424
424
|
jsonLd?: JsonLdEntry[]
|
|
425
425
|
}
|
|
426
|
+
|
|
427
|
+
// ============================================================================
|
|
428
|
+
// PostMessage Types (iframe communication)
|
|
429
|
+
// ============================================================================
|
|
430
|
+
|
|
431
|
+
/** Element data sent to parent when a CMS element is hovered/selected */
|
|
432
|
+
export interface CmsSelectedElement {
|
|
433
|
+
/** CMS element ID (null for component-only selections) */
|
|
434
|
+
cmsId: string | null
|
|
435
|
+
/** Whether the selected element is a component root */
|
|
436
|
+
isComponent: boolean
|
|
437
|
+
/** Component name if applicable */
|
|
438
|
+
componentName?: string
|
|
439
|
+
/** Component instance ID */
|
|
440
|
+
componentId?: string
|
|
441
|
+
/** HTML tag name */
|
|
442
|
+
tagName?: string
|
|
443
|
+
/** Bounding rect relative to the iframe viewport */
|
|
444
|
+
rect: { x: number; y: number; width: number; height: number } | null
|
|
445
|
+
|
|
446
|
+
// --- Manifest entry data (text/image elements) ---
|
|
447
|
+
|
|
448
|
+
/** Plain text content */
|
|
449
|
+
text?: string
|
|
450
|
+
/** HTML content with inline styling */
|
|
451
|
+
html?: string
|
|
452
|
+
/** Source file path */
|
|
453
|
+
sourcePath?: string
|
|
454
|
+
/** Line number in source file */
|
|
455
|
+
sourceLine?: number
|
|
456
|
+
/** Parent component ID */
|
|
457
|
+
parentComponentId?: string
|
|
458
|
+
/** Nested CMS element IDs */
|
|
459
|
+
childCmsIds?: string[]
|
|
460
|
+
/** Image metadata for img elements */
|
|
461
|
+
imageMetadata?: ImageMetadata
|
|
462
|
+
/** Background image metadata */
|
|
463
|
+
backgroundImage?: BackgroundImageMetadata
|
|
464
|
+
/** Color classes (bg, text, border, etc.) */
|
|
465
|
+
colorClasses?: Record<string, Attribute>
|
|
466
|
+
/** HTML attributes with source info */
|
|
467
|
+
attributes?: Record<string, Attribute>
|
|
468
|
+
/** Content validation constraints */
|
|
469
|
+
constraints?: ContentConstraints
|
|
470
|
+
/** Whether inline text styling is allowed */
|
|
471
|
+
allowStyling?: boolean
|
|
472
|
+
/** Collection name if from a content collection */
|
|
473
|
+
collectionName?: string
|
|
474
|
+
/** Collection entry slug */
|
|
475
|
+
collectionSlug?: string
|
|
476
|
+
/** Full element snippet from source */
|
|
477
|
+
sourceSnippet?: string
|
|
478
|
+
/** SHA256 hash of sourceSnippet for conflict detection */
|
|
479
|
+
sourceHash?: string
|
|
480
|
+
/** Stable ID derived from content + context hash */
|
|
481
|
+
stableId?: string
|
|
482
|
+
/** Path to the markdown content file */
|
|
483
|
+
contentPath?: string
|
|
484
|
+
|
|
485
|
+
// --- Component instance data ---
|
|
486
|
+
|
|
487
|
+
/** Full component instance info (when isComponent is true) */
|
|
488
|
+
component?: {
|
|
489
|
+
name: string
|
|
490
|
+
file: string
|
|
491
|
+
sourcePath: string
|
|
492
|
+
sourceLine: number
|
|
493
|
+
props: Record<string, unknown>
|
|
494
|
+
slots?: Record<string, string>
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
/** Message sent when a CMS element is hovered/selected */
|
|
499
|
+
export interface CmsElementSelectedMessage {
|
|
500
|
+
type: 'cms-element-selected'
|
|
501
|
+
element: CmsSelectedElement
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
/** Message sent when no element is hovered */
|
|
505
|
+
export interface CmsElementDeselectedMessage {
|
|
506
|
+
type: 'cms-element-deselected'
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
/** Data sent with the cms-ready message when the manifest first loads */
|
|
510
|
+
export interface CmsReadyData {
|
|
511
|
+
pathname: string
|
|
512
|
+
pageTitle?: string
|
|
513
|
+
seo?: PageSeoData
|
|
514
|
+
pages?: PageEntry[]
|
|
515
|
+
collectionDefinitions?: Record<string, CollectionDefinition>
|
|
516
|
+
componentDefinitions?: Record<string, ComponentDefinition>
|
|
517
|
+
availableColors?: AvailableColors
|
|
518
|
+
availableTextStyles?: AvailableTextStyles
|
|
519
|
+
metadata?: ManifestMetadata
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
/** Message sent when the CMS manifest has loaded and the editor is ready */
|
|
523
|
+
export interface CmsReadyMessage {
|
|
524
|
+
type: 'cms-ready'
|
|
525
|
+
data: CmsReadyData
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
/** Snapshot of editor state sent on every meaningful change */
|
|
529
|
+
export interface CmsEditorState {
|
|
530
|
+
isEditing: boolean
|
|
531
|
+
hasChanges: boolean
|
|
532
|
+
dirtyCount: {
|
|
533
|
+
text: number
|
|
534
|
+
image: number
|
|
535
|
+
color: number
|
|
536
|
+
bgImage: number
|
|
537
|
+
attribute: number
|
|
538
|
+
seo: number
|
|
539
|
+
total: number
|
|
540
|
+
}
|
|
541
|
+
deployment: {
|
|
542
|
+
status: 'pending' | 'queued' | 'running' | 'completed' | 'failed' | 'cancelled' | null
|
|
543
|
+
lastDeployedAt: string | null
|
|
544
|
+
}
|
|
545
|
+
canUndo: boolean
|
|
546
|
+
canRedo: boolean
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
/** Message sent when editor state changes (dirty counts, deployment, editing mode, undo/redo) */
|
|
550
|
+
export interface CmsStateChangedMessage {
|
|
551
|
+
type: 'cms-state-changed'
|
|
552
|
+
state: CmsEditorState
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
/** Message sent when the user navigates to a different page (manifest reload) */
|
|
556
|
+
export interface CmsPageNavigatedMessage {
|
|
557
|
+
type: 'cms-page-navigated'
|
|
558
|
+
page: {
|
|
559
|
+
pathname: string
|
|
560
|
+
title?: string
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
/** All possible CMS postMessage types sent from the editor iframe to the parent */
|
|
565
|
+
export type CmsPostMessage =
|
|
566
|
+
| CmsElementSelectedMessage
|
|
567
|
+
| CmsElementDeselectedMessage
|
|
568
|
+
| CmsReadyMessage
|
|
569
|
+
| CmsStateChangedMessage
|
|
570
|
+
| CmsPageNavigatedMessage
|