@nuasite/cms 0.19.1 → 0.20.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/dist/editor.js +12615 -12689
- package/package.json +3 -3
- package/src/build-processor.ts +4 -4
- package/src/dev-middleware.ts +185 -189
- package/src/editor/api.ts +0 -251
- package/src/editor/components/fields.tsx +6 -6
- package/src/editor/components/markdown-editor-overlay.tsx +46 -70
- package/src/editor/components/markdown-inline-editor.tsx +34 -165
- package/src/editor/components/mdx-block-view.tsx +351 -47
- package/src/editor/components/mdx-component-picker.tsx +35 -11
- package/src/editor/components/media-library.tsx +1 -15
- package/src/editor/components/modal-shell.tsx +1 -1
- package/src/editor/components/toolbar.tsx +0 -75
- package/src/editor/constants.ts +0 -4
- package/src/editor/editor.ts +2 -192
- package/src/editor/hooks/index.ts +0 -3
- package/src/editor/hooks/useBlockEditorHandlers.ts +1 -8
- package/src/editor/hooks/useTooltipState.ts +1 -2
- package/src/editor/index.tsx +2 -18
- package/src/editor/milkdown-mdx-plugin.tsx +116 -19
- package/src/editor/milkdown-utils.ts +174 -0
- package/src/editor/post-message.ts +0 -6
- package/src/editor/signals.ts +0 -183
- package/src/editor/styles.css +0 -108
- package/src/editor/types.ts +0 -76
- package/src/html-processor.ts +9 -7
- package/src/source-finder/cache.ts +47 -0
- package/src/source-finder/collection-finder.ts +181 -0
- package/src/source-finder/index.ts +5 -2
- package/src/source-finder/search-index.ts +79 -0
- package/src/source-finder/snippet-utils.ts +36 -61
- package/src/types.ts +0 -4
- package/src/utils.ts +10 -0
- package/src/vite-plugin.ts +24 -4
- package/src/editor/ai.ts +0 -185
- package/src/editor/components/ai-chat.tsx +0 -631
- package/src/editor/components/ai-tooltip.tsx +0 -180
- package/src/editor/components/mdx-props-editor.tsx +0 -94
- package/src/editor/hooks/useAIHandlers.ts +0 -345
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { defaultValueCtx, Editor, editorViewCtx, rootCtx } from '@milkdown/core'
|
|
2
2
|
import { listener, listenerCtx } from '@milkdown/plugin-listener'
|
|
3
3
|
import {
|
|
4
4
|
commonmark,
|
|
@@ -15,10 +15,10 @@ import { callCommand, insert, replaceAll } from '@milkdown/utils'
|
|
|
15
15
|
import { useCallback, useEffect, useRef, useState } from 'preact/hooks'
|
|
16
16
|
import { uploadMedia } from '../markdown-api'
|
|
17
17
|
import { insertMdxComponentCommand, mdxComponentPlugin } from '../milkdown-mdx-plugin'
|
|
18
|
+
import { type ActiveFormats, defaultActiveFormats, isInListType, setupFormatTracking, toggleHeading } from '../milkdown-utils'
|
|
18
19
|
import { config, mdxComponentPickerOpen, openMediaLibraryWithCallback, resetMarkdownEditorState, showToast, updateMarkdownContent } from '../signals'
|
|
19
20
|
import { MdxComponentIcon } from './mdx-block-view'
|
|
20
21
|
import { MdxComponentPicker } from './mdx-component-picker'
|
|
21
|
-
import { MdxPropsEditor } from './mdx-props-editor'
|
|
22
22
|
|
|
23
23
|
export interface MarkdownInlineEditorProps {
|
|
24
24
|
elementId: string
|
|
@@ -45,27 +45,7 @@ export function MarkdownInlineEditor({
|
|
|
45
45
|
const [uploadProgress, setUploadProgress] = useState<number | null>(null)
|
|
46
46
|
|
|
47
47
|
// Track active formatting for toolbar highlighting
|
|
48
|
-
const [activeFormats, setActiveFormats] = useState<
|
|
49
|
-
bold: boolean
|
|
50
|
-
italic: boolean
|
|
51
|
-
strikethrough: boolean
|
|
52
|
-
link: boolean
|
|
53
|
-
linkHref: string | null
|
|
54
|
-
bulletList: boolean
|
|
55
|
-
orderedList: boolean
|
|
56
|
-
blockquote: boolean
|
|
57
|
-
heading: number | null
|
|
58
|
-
}>({
|
|
59
|
-
bold: false,
|
|
60
|
-
italic: false,
|
|
61
|
-
strikethrough: false,
|
|
62
|
-
link: false,
|
|
63
|
-
linkHref: null,
|
|
64
|
-
bulletList: false,
|
|
65
|
-
orderedList: false,
|
|
66
|
-
blockquote: false,
|
|
67
|
-
heading: null,
|
|
68
|
-
})
|
|
48
|
+
const [activeFormats, setActiveFormats] = useState<ActiveFormats>(defaultActiveFormats)
|
|
69
49
|
|
|
70
50
|
// Store initial content in ref to avoid stale closure issues
|
|
71
51
|
const initialContentRef = useRef(initialContent)
|
|
@@ -79,91 +59,11 @@ export function MarkdownInlineEditor({
|
|
|
79
59
|
const isMdxRef = useRef(isMdx ?? false)
|
|
80
60
|
isMdxRef.current = isMdx ?? false
|
|
81
61
|
|
|
82
|
-
// Check active formatting at current selection
|
|
83
|
-
const updateActiveFormats = useCallback(() => {
|
|
84
|
-
if (!editorInstanceRef.current) return
|
|
85
|
-
|
|
86
|
-
try {
|
|
87
|
-
const view = editorInstanceRef.current.ctx.get(editorViewCtx)
|
|
88
|
-
const { state } = view
|
|
89
|
-
const { $from, from, to } = state.selection
|
|
90
|
-
|
|
91
|
-
// Check marks (inline formatting)
|
|
92
|
-
let bold = false
|
|
93
|
-
let italic = false
|
|
94
|
-
let strikethrough = false
|
|
95
|
-
let link = false
|
|
96
|
-
let linkHref: string | null = null
|
|
97
|
-
|
|
98
|
-
// Check if marks are active in the selection
|
|
99
|
-
const marks = state.storedMarks || $from.marks()
|
|
100
|
-
for (const mark of marks) {
|
|
101
|
-
if (mark.type.name === 'strong') bold = true
|
|
102
|
-
if (mark.type.name === 'emphasis') italic = true
|
|
103
|
-
if (mark.type.name === 'strikethrough') strikethrough = true
|
|
104
|
-
if (mark.type.name === 'link') {
|
|
105
|
-
link = true
|
|
106
|
-
linkHref = mark.attrs.href as string
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
// Also check marks in the selection range
|
|
111
|
-
if (from !== to) {
|
|
112
|
-
state.doc.nodesBetween(from, to, (node) => {
|
|
113
|
-
if (node.marks) {
|
|
114
|
-
for (const mark of node.marks) {
|
|
115
|
-
if (mark.type.name === 'strong') bold = true
|
|
116
|
-
if (mark.type.name === 'emphasis') italic = true
|
|
117
|
-
if (mark.type.name === 'strikethrough') strikethrough = true
|
|
118
|
-
if (mark.type.name === 'link') {
|
|
119
|
-
link = true
|
|
120
|
-
linkHref = mark.attrs.href as string
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
})
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
// Check block types (lists, blockquote, heading)
|
|
128
|
-
let bulletList = false
|
|
129
|
-
let orderedList = false
|
|
130
|
-
let blockquote = false
|
|
131
|
-
let heading: number | null = null
|
|
132
|
-
|
|
133
|
-
for (let depth = $from.depth; depth > 0; depth--) {
|
|
134
|
-
const node = $from.node(depth)
|
|
135
|
-
if (node.type.name === 'bullet_list') bulletList = true
|
|
136
|
-
if (node.type.name === 'ordered_list') orderedList = true
|
|
137
|
-
if (node.type.name === 'blockquote') blockquote = true
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
// Check heading at current position
|
|
141
|
-
const parentNode = $from.parent
|
|
142
|
-
if (parentNode.type.name === 'heading') {
|
|
143
|
-
heading = parentNode.attrs.level as number
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
setActiveFormats({
|
|
147
|
-
bold,
|
|
148
|
-
italic,
|
|
149
|
-
strikethrough,
|
|
150
|
-
link,
|
|
151
|
-
linkHref,
|
|
152
|
-
bulletList,
|
|
153
|
-
orderedList,
|
|
154
|
-
blockquote,
|
|
155
|
-
heading,
|
|
156
|
-
})
|
|
157
|
-
} catch {
|
|
158
|
-
// Ignore errors during format checking
|
|
159
|
-
}
|
|
160
|
-
}, [])
|
|
161
|
-
|
|
162
62
|
// Initialize Milkdown editor
|
|
163
63
|
useEffect(() => {
|
|
164
64
|
if (!editorRef.current) return
|
|
165
65
|
|
|
166
|
-
let
|
|
66
|
+
let cleanupTracking: (() => void) | undefined
|
|
167
67
|
|
|
168
68
|
const initEditor = async () => {
|
|
169
69
|
try {
|
|
@@ -193,20 +93,8 @@ export function MarkdownInlineEditor({
|
|
|
193
93
|
setIsReady(true)
|
|
194
94
|
onEditorReadyRef.current?.(editor)
|
|
195
95
|
|
|
196
|
-
// Set up selection change listener
|
|
197
|
-
|
|
198
|
-
const view = editor.ctx.get(editorViewCtx)
|
|
199
|
-
const originalDispatch = view.dispatch.bind(view)
|
|
200
|
-
view.dispatch = (tr) => {
|
|
201
|
-
originalDispatch(tr)
|
|
202
|
-
if (tr.selectionSet || tr.docChanged) {
|
|
203
|
-
cancelAnimationFrame(formatRaf)
|
|
204
|
-
formatRaf = requestAnimationFrame(updateActiveFormats)
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
// Initial format check
|
|
209
|
-
updateActiveFormats()
|
|
96
|
+
// Set up selection change listener with shallow equality check
|
|
97
|
+
cleanupTracking = setupFormatTracking(editor, setActiveFormats)
|
|
210
98
|
} catch (error) {
|
|
211
99
|
console.error('Milkdown editor initialization failed:', error)
|
|
212
100
|
showToast('Failed to initialize markdown editor', 'error')
|
|
@@ -216,11 +104,11 @@ export function MarkdownInlineEditor({
|
|
|
216
104
|
initEditor()
|
|
217
105
|
|
|
218
106
|
return () => {
|
|
219
|
-
|
|
107
|
+
cleanupTracking?.()
|
|
220
108
|
editorInstanceRef.current?.destroy()
|
|
221
109
|
editorInstanceRef.current = null
|
|
222
110
|
}
|
|
223
|
-
}, [
|
|
111
|
+
}, [])
|
|
224
112
|
|
|
225
113
|
const handleSave = useCallback(() => {
|
|
226
114
|
onSave(content)
|
|
@@ -283,18 +171,12 @@ export function MarkdownInlineEditor({
|
|
|
283
171
|
)
|
|
284
172
|
|
|
285
173
|
// Check if selection is inside a list of given type
|
|
286
|
-
const
|
|
174
|
+
const checkInList = useCallback(
|
|
287
175
|
(listType: 'bullet_list' | 'ordered_list'): boolean => {
|
|
288
176
|
if (!editorInstanceRef.current) return false
|
|
289
177
|
try {
|
|
290
178
|
const view = editorInstanceRef.current.ctx.get(editorViewCtx)
|
|
291
|
-
|
|
292
|
-
const { $from } = state.selection
|
|
293
|
-
for (let depth = $from.depth; depth > 0; depth--) {
|
|
294
|
-
const node = $from.node(depth)
|
|
295
|
-
if (node.type.name === listType) return true
|
|
296
|
-
}
|
|
297
|
-
return false
|
|
179
|
+
return isInListType(view, listType)
|
|
298
180
|
} catch {
|
|
299
181
|
return false
|
|
300
182
|
}
|
|
@@ -304,21 +186,21 @@ export function MarkdownInlineEditor({
|
|
|
304
186
|
|
|
305
187
|
// Toggle bullet list - if in bullet list, remove it; otherwise add it
|
|
306
188
|
const handleBulletList = useCallback(() => {
|
|
307
|
-
if (
|
|
189
|
+
if (checkInList('bullet_list')) {
|
|
308
190
|
runCommand(liftListItemCommand.key)
|
|
309
191
|
} else {
|
|
310
192
|
runCommand(wrapInBulletListCommand.key)
|
|
311
193
|
}
|
|
312
|
-
}, [runCommand,
|
|
194
|
+
}, [runCommand, checkInList])
|
|
313
195
|
|
|
314
196
|
// Toggle ordered list - if in ordered list, remove it; otherwise add it
|
|
315
197
|
const handleOrderedList = useCallback(() => {
|
|
316
|
-
if (
|
|
198
|
+
if (checkInList('ordered_list')) {
|
|
317
199
|
runCommand(liftListItemCommand.key)
|
|
318
200
|
} else {
|
|
319
201
|
runCommand(wrapInOrderedListCommand.key)
|
|
320
202
|
}
|
|
321
|
-
}, [runCommand,
|
|
203
|
+
}, [runCommand, checkInList])
|
|
322
204
|
|
|
323
205
|
const handleInsertLink = useCallback(() => {
|
|
324
206
|
if (!editorInstanceRef.current) return
|
|
@@ -369,24 +251,20 @@ export function MarkdownInlineEditor({
|
|
|
369
251
|
}, [activeFormats.link, activeFormats.linkHref])
|
|
370
252
|
|
|
371
253
|
const handleInsertHeading = useCallback((level: number) => {
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
editorInstanceRef.current.action(insert(headingMarkdown))
|
|
379
|
-
} catch (error) {
|
|
380
|
-
console.error('Failed to insert heading:', error)
|
|
381
|
-
}
|
|
254
|
+
if (!editorInstanceRef.current) return
|
|
255
|
+
try {
|
|
256
|
+
const view = editorInstanceRef.current.ctx.get(editorViewCtx)
|
|
257
|
+
toggleHeading(view, level)
|
|
258
|
+
} catch (error) {
|
|
259
|
+
console.error('Failed to toggle heading:', error)
|
|
382
260
|
}
|
|
383
261
|
}, [])
|
|
384
262
|
|
|
385
263
|
// MDX component insertion
|
|
386
|
-
const handleInsertMdxComponent = useCallback((componentName: string, props: Record<string, string
|
|
264
|
+
const handleInsertMdxComponent = useCallback((componentName: string, props: Record<string, string>, children?: string) => {
|
|
387
265
|
if (editorInstanceRef.current) {
|
|
388
266
|
try {
|
|
389
|
-
editorInstanceRef.current.action(callCommand(insertMdxComponentCommand.key, { componentName, props }))
|
|
267
|
+
editorInstanceRef.current.action(callCommand(insertMdxComponentCommand.key, { componentName, props, children }))
|
|
390
268
|
} catch (error) {
|
|
391
269
|
console.error('Failed to insert MDX component:', error)
|
|
392
270
|
}
|
|
@@ -397,42 +275,36 @@ export function MarkdownInlineEditor({
|
|
|
397
275
|
mdxComponentPickerOpen.value = true
|
|
398
276
|
}, [])
|
|
399
277
|
|
|
400
|
-
const handleUpdateMdxProps = useCallback((nodePos: number, props: Record<string, string>) => {
|
|
401
|
-
if (!editorInstanceRef.current) return
|
|
402
|
-
try {
|
|
403
|
-
const view = editorInstanceRef.current.ctx.get(editorViewCtx)
|
|
404
|
-
const node = view.state.doc.nodeAt(nodePos)
|
|
405
|
-
if (node && node.type.name === 'mdx_component') {
|
|
406
|
-
const tr = view.state.tr.setNodeMarkup(nodePos, undefined, {
|
|
407
|
-
...node.attrs,
|
|
408
|
-
props: JSON.stringify(props),
|
|
409
|
-
})
|
|
410
|
-
view.dispatch(tr)
|
|
411
|
-
}
|
|
412
|
-
} catch (error) {
|
|
413
|
-
console.error('Failed to update MDX component props:', error)
|
|
414
|
-
}
|
|
415
|
-
}, [])
|
|
416
|
-
|
|
417
278
|
// Drag and drop handlers for direct image upload
|
|
279
|
+
// Only intercept external file drags — let ProseMirror handle internal drags (node reorder)
|
|
280
|
+
const hasFiles = (e: DragEvent) => e.dataTransfer?.types?.includes('Files') ?? false
|
|
281
|
+
|
|
418
282
|
const handleDragOver = useCallback((e: DragEvent) => {
|
|
283
|
+
if (!hasFiles(e)) return
|
|
419
284
|
e.preventDefault()
|
|
420
285
|
e.stopPropagation()
|
|
421
286
|
setIsDragging(true)
|
|
422
287
|
}, [])
|
|
423
288
|
|
|
424
289
|
const handleDragLeave = useCallback((e: DragEvent) => {
|
|
290
|
+
if (!hasFiles(e)) return
|
|
425
291
|
e.preventDefault()
|
|
426
292
|
e.stopPropagation()
|
|
427
293
|
setIsDragging(false)
|
|
428
294
|
}, [])
|
|
429
295
|
|
|
430
296
|
const handleDrop = useCallback(async (e: DragEvent) => {
|
|
297
|
+
// Only handle external file drops — let ProseMirror handle internal drags (e.g. node reorder)
|
|
298
|
+
if (!hasFiles(e)) return
|
|
299
|
+
|
|
431
300
|
e.preventDefault()
|
|
432
301
|
e.stopPropagation()
|
|
433
302
|
setIsDragging(false)
|
|
434
303
|
|
|
435
|
-
const
|
|
304
|
+
const files = e.dataTransfer?.files
|
|
305
|
+
if (!files || files.length === 0) return
|
|
306
|
+
|
|
307
|
+
const file = files[0]
|
|
436
308
|
if (!file || !file.type.startsWith('image/')) {
|
|
437
309
|
showToast('Please drop an image file', 'error')
|
|
438
310
|
return
|
|
@@ -811,9 +683,6 @@ export function MarkdownInlineEditor({
|
|
|
811
683
|
|
|
812
684
|
{/* MDX Component Picker */}
|
|
813
685
|
{isMdx && <MdxComponentPicker onInsert={handleInsertMdxComponent} />}
|
|
814
|
-
|
|
815
|
-
{/* MDX Props Editor */}
|
|
816
|
-
{isMdx && <MdxPropsEditor onUpdateProps={handleUpdateMdxProps} />}
|
|
817
686
|
</div>
|
|
818
687
|
)
|
|
819
688
|
}
|