@nuasite/cms 0.9.0 → 0.9.2
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 +45 -36
- package/dist/editor.js +8488 -10082
- package/package.json +1 -1
- package/src/editor/components/toolbar.tsx +11 -11
- package/src/editor/hooks/useElementDetection.ts +4 -46
- package/src/editor/index.tsx +14 -26
- package/src/editor/signals.ts +1 -0
package/package.json
CHANGED
|
@@ -10,7 +10,7 @@ export interface ToolbarCallbacks {
|
|
|
10
10
|
onCompare: () => void
|
|
11
11
|
onSave: () => void
|
|
12
12
|
onDiscard: () => void
|
|
13
|
-
|
|
13
|
+
onSelectElement?: () => void
|
|
14
14
|
onMediaLibrary?: () => void
|
|
15
15
|
onDismissDeployment?: () => void
|
|
16
16
|
onNavigateChange?: () => void
|
|
@@ -97,7 +97,6 @@ const DeploymentStatusIndicator = ({ onDismiss }: { onDismiss?: () => void }) =>
|
|
|
97
97
|
export const Toolbar = ({ callbacks, collectionDefinitions }: ToolbarProps) => {
|
|
98
98
|
const isEditing = signals.isEditing.value
|
|
99
99
|
const showingOriginal = signals.showingOriginal.value
|
|
100
|
-
const isChatOpen = signals.isChatOpen.value
|
|
101
100
|
const dirtyCount = signals.totalDirtyCount.value
|
|
102
101
|
const isSaving = signals.isSaving.value
|
|
103
102
|
const deploymentStatus = signals.deploymentStatus.value
|
|
@@ -107,7 +106,6 @@ export const Toolbar = ({ callbacks, collectionDefinitions }: ToolbarProps) => {
|
|
|
107
106
|
const [isMenuOpen, setIsMenuOpen] = useState(false)
|
|
108
107
|
|
|
109
108
|
if (isPreviewingMarkdown) return null
|
|
110
|
-
if (isChatOpen && !isEditing) return null
|
|
111
109
|
|
|
112
110
|
const showDeploymentStatus = deploymentStatus !== null
|
|
113
111
|
|
|
@@ -126,21 +124,23 @@ export const Toolbar = ({ callbacks, collectionDefinitions }: ToolbarProps) => {
|
|
|
126
124
|
}
|
|
127
125
|
}
|
|
128
126
|
|
|
129
|
-
const
|
|
127
|
+
const isSelectMode = signals.isSelectMode.value
|
|
128
|
+
const isToolbarOpen = isEditing || isSelectMode
|
|
130
129
|
|
|
131
130
|
// Build menu items dynamically
|
|
132
131
|
const menuItems: Array<{ label: string; icon: ComponentChildren; onClick: () => void; isActive?: boolean }> = []
|
|
133
132
|
|
|
134
|
-
if (callbacks.
|
|
133
|
+
if (callbacks.onSelectElement) {
|
|
135
134
|
menuItems.push({
|
|
136
|
-
label: '
|
|
135
|
+
label: 'Select Element',
|
|
137
136
|
icon: (
|
|
138
137
|
<svg class="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
139
|
-
<path d="
|
|
138
|
+
<path d="M3 3l7.07 16.97 2.51-7.39 7.39-2.51L3 3z" />
|
|
139
|
+
<path d="M13 13l6 6" />
|
|
140
140
|
</svg>
|
|
141
141
|
),
|
|
142
|
-
onClick: () => callbacks.
|
|
143
|
-
isActive:
|
|
142
|
+
onClick: () => callbacks.onSelectElement?.(),
|
|
143
|
+
isActive: isSelectMode,
|
|
144
144
|
})
|
|
145
145
|
}
|
|
146
146
|
|
|
@@ -217,8 +217,8 @@ export const Toolbar = ({ callbacks, collectionDefinitions }: ToolbarProps) => {
|
|
|
217
217
|
onClick={stopPropagation}
|
|
218
218
|
>
|
|
219
219
|
<div class="flex items-center justify-between sm:justify-start gap-2 sm:gap-1.5 px-2 sm:px-2 py-2 sm:py-2 bg-cms-dark rounded-cms-xl shadow-[0_8px_32px_rgba(0,0,0,0.3)] border border-white/10">
|
|
220
|
-
{/* Outlines toggle - visible in toolbar when editing */}
|
|
221
|
-
{
|
|
220
|
+
{/* Outlines toggle - visible in toolbar when editing or selecting */}
|
|
221
|
+
{isToolbarOpen && !showingOriginal && callbacks.onToggleHighlights && (
|
|
222
222
|
<ToolbarButton
|
|
223
223
|
onClick={() => callbacks.onToggleHighlights?.()}
|
|
224
224
|
class={'flex gap-2.5 bg-white/10 text-white/80 hover:bg-white/20 hover:text-white py-2! pr-1.5!'}
|
|
@@ -58,9 +58,9 @@ export function useElementDetection(): OutlineState {
|
|
|
58
58
|
useEffect(() => {
|
|
59
59
|
const handleMouseMove = (ev: MouseEvent) => {
|
|
60
60
|
const isEditing = signals.isEditing.value
|
|
61
|
-
const
|
|
61
|
+
const selectMode = signals.isSelectMode.value
|
|
62
62
|
|
|
63
|
-
if (!isEditing && !
|
|
63
|
+
if (!isEditing && !selectMode) {
|
|
64
64
|
if (hideTimeoutRef.current) {
|
|
65
65
|
clearTimeout(hideTimeoutRef.current)
|
|
66
66
|
hideTimeoutRef.current = null
|
|
@@ -89,34 +89,6 @@ export function useElementDetection(): OutlineState {
|
|
|
89
89
|
const manifest = signals.manifest.value
|
|
90
90
|
const entries = manifest.entries
|
|
91
91
|
|
|
92
|
-
// When chat is open, only detect components (not text/image elements)
|
|
93
|
-
if (chatOpen) {
|
|
94
|
-
const componentEl = getComponentAtPosition(ev.clientX, ev.clientY)
|
|
95
|
-
if (componentEl) {
|
|
96
|
-
if (hideTimeoutRef.current) {
|
|
97
|
-
clearTimeout(hideTimeoutRef.current)
|
|
98
|
-
hideTimeoutRef.current = null
|
|
99
|
-
}
|
|
100
|
-
const rect = componentEl.getBoundingClientRect()
|
|
101
|
-
const componentId = componentEl.getAttribute(CSS.COMPONENT_ID_ATTRIBUTE)
|
|
102
|
-
const instance = componentId ? getComponentInstance(manifest, componentId) : null
|
|
103
|
-
|
|
104
|
-
setOutlineState({
|
|
105
|
-
visible: true,
|
|
106
|
-
rect,
|
|
107
|
-
isComponent: true,
|
|
108
|
-
componentName: instance?.componentName,
|
|
109
|
-
tagName: componentEl.tagName.toLowerCase(),
|
|
110
|
-
element: componentEl,
|
|
111
|
-
cmsId: null,
|
|
112
|
-
})
|
|
113
|
-
return
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
setOutlineState(INITIAL_STATE)
|
|
117
|
-
return
|
|
118
|
-
}
|
|
119
|
-
|
|
120
92
|
// Use the improved elementsFromPoint-based detection
|
|
121
93
|
const cmsEl = getCmsElementAtPosition(ev.clientX, ev.clientY, entries)
|
|
122
94
|
|
|
@@ -222,8 +194,8 @@ export function useComponentClickHandler({
|
|
|
222
194
|
useEffect(() => {
|
|
223
195
|
const handleClick = (ev: MouseEvent) => {
|
|
224
196
|
const isEditing = signals.isEditing.value
|
|
225
|
-
const
|
|
226
|
-
if (!isEditing && !
|
|
197
|
+
const selectMode = signals.isSelectMode.value
|
|
198
|
+
if (!isEditing && !selectMode) return
|
|
227
199
|
|
|
228
200
|
// Ignore clicks on CMS UI elements
|
|
229
201
|
if (isEventOnCmsUI(ev)) return
|
|
@@ -231,20 +203,6 @@ export function useComponentClickHandler({
|
|
|
231
203
|
const manifest = signals.manifest.value
|
|
232
204
|
const entries = manifest.entries
|
|
233
205
|
|
|
234
|
-
if (chatOpen) {
|
|
235
|
-
// When chat is open, only select components
|
|
236
|
-
const componentEl = getComponentAtPosition(ev.clientX, ev.clientY)
|
|
237
|
-
if (componentEl) {
|
|
238
|
-
const componentId = componentEl.getAttribute(CSS.COMPONENT_ID_ATTRIBUTE)
|
|
239
|
-
if (componentId) {
|
|
240
|
-
ev.preventDefault()
|
|
241
|
-
ev.stopPropagation()
|
|
242
|
-
signals.setChatContextElement(componentId)
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
return
|
|
246
|
-
}
|
|
247
|
-
|
|
248
206
|
// Normal editing mode behavior
|
|
249
207
|
// Check for text element first
|
|
250
208
|
const textEl = getCmsElementAtPosition(ev.clientX, ev.clientY, entries)
|
package/src/editor/index.tsx
CHANGED
|
@@ -1,17 +1,7 @@
|
|
|
1
1
|
import { render } from 'preact'
|
|
2
2
|
import { useCallback, useEffect, useRef } from 'preact/hooks'
|
|
3
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'
|
|
12
4
|
import { fetchManifest } from './api'
|
|
13
|
-
import { AIChat } from './components/ai-chat'
|
|
14
|
-
import { AITooltip } from './components/ai-tooltip'
|
|
15
5
|
import { AttributeEditor } from './components/attribute-editor'
|
|
16
6
|
import { BgImageOverlay } from './components/bg-image-overlay'
|
|
17
7
|
import { BlockEditor } from './components/block-editor'
|
|
@@ -43,7 +33,6 @@ import {
|
|
|
43
33
|
} from './editor'
|
|
44
34
|
import { canRedo, canUndo, performRedo, performUndo } from './history'
|
|
45
35
|
import {
|
|
46
|
-
useAIHandlers,
|
|
47
36
|
useBgImageHoverDetection,
|
|
48
37
|
useBlockEditorHandlers,
|
|
49
38
|
useComponentClickHandler,
|
|
@@ -52,6 +41,14 @@ import {
|
|
|
52
41
|
useTextSelection,
|
|
53
42
|
useTooltipState,
|
|
54
43
|
} from './hooks'
|
|
44
|
+
import {
|
|
45
|
+
buildEditorState,
|
|
46
|
+
buildPageNavigatedMessage,
|
|
47
|
+
buildReadyMessage,
|
|
48
|
+
buildSelectedElement,
|
|
49
|
+
buildStateChangedMessage,
|
|
50
|
+
postToParent,
|
|
51
|
+
} from './post-message'
|
|
55
52
|
import {
|
|
56
53
|
openCollectionsBrowser,
|
|
57
54
|
openMarkdownEditorForCurrentPage,
|
|
@@ -218,20 +215,6 @@ const CmsUI = () => {
|
|
|
218
215
|
postToParent(buildStateChangedMessage(state))
|
|
219
216
|
})
|
|
220
217
|
|
|
221
|
-
const {
|
|
222
|
-
handleAIChatToggle,
|
|
223
|
-
handleChatClose,
|
|
224
|
-
handleChatCancel,
|
|
225
|
-
handleTooltipPromptSubmit,
|
|
226
|
-
handleChatSend,
|
|
227
|
-
handleApplyToElement,
|
|
228
|
-
} = useAIHandlers({
|
|
229
|
-
config,
|
|
230
|
-
showToast: signals.showToast,
|
|
231
|
-
onTooltipHide: hideTooltip,
|
|
232
|
-
onUIUpdate: updateUI,
|
|
233
|
-
})
|
|
234
|
-
|
|
235
218
|
const {
|
|
236
219
|
blockEditorCursor,
|
|
237
220
|
handleComponentSelect,
|
|
@@ -252,6 +235,7 @@ const CmsUI = () => {
|
|
|
252
235
|
hideTooltip()
|
|
253
236
|
stopEditMode(updateUI)
|
|
254
237
|
} else {
|
|
238
|
+
signals.isSelectMode.value = false
|
|
255
239
|
await startEditMode(config, updateUI)
|
|
256
240
|
}
|
|
257
241
|
}, [config, updateUI, hideTooltip])
|
|
@@ -310,6 +294,10 @@ const CmsUI = () => {
|
|
|
310
294
|
openSeoEditor()
|
|
311
295
|
}, [])
|
|
312
296
|
|
|
297
|
+
const handleSelectElementToggle = useCallback(() => {
|
|
298
|
+
signals.isSelectMode.value = !signals.isSelectMode.value
|
|
299
|
+
}, [])
|
|
300
|
+
|
|
313
301
|
// Color toolbar handlers
|
|
314
302
|
const handleColorToolbarChange = useCallback(
|
|
315
303
|
(
|
|
@@ -409,7 +397,6 @@ const CmsUI = () => {
|
|
|
409
397
|
// Get reactive values from signals
|
|
410
398
|
const isEditing = signals.isEditing.value
|
|
411
399
|
const isAIProcessing = signals.isAIProcessing.value
|
|
412
|
-
const isChatOpen = signals.isChatOpen.value
|
|
413
400
|
const blockEditorState = signals.blockEditorState.value
|
|
414
401
|
const colorEditorState = signals.colorEditorState.value
|
|
415
402
|
const manifest = signals.manifest.value
|
|
@@ -485,6 +472,7 @@ const CmsUI = () => {
|
|
|
485
472
|
onCompare: handleCompare,
|
|
486
473
|
onSave: handleSave,
|
|
487
474
|
onDiscard: handleDiscard,
|
|
475
|
+
onSelectElement: handleSelectElementToggle,
|
|
488
476
|
onMediaLibrary: handleMediaLibrary,
|
|
489
477
|
onDismissDeployment: handleDismissDeployment,
|
|
490
478
|
onNavigateChange: () => {
|
package/src/editor/signals.ts
CHANGED
|
@@ -213,6 +213,7 @@ function createInitialAttributeEditorState(): AttributeEditorState {
|
|
|
213
213
|
|
|
214
214
|
export const isEnabled = signal(false)
|
|
215
215
|
export const isEditing = signal(false)
|
|
216
|
+
export const isSelectMode = signal(false)
|
|
216
217
|
export const isSaving = signal(false)
|
|
217
218
|
export const showingOriginal = signal(false)
|
|
218
219
|
export const currentEditingId = signal<string | null>(null)
|