@airalogy/aimd-editor 1.7.1
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 +59 -0
- package/README.zh-CN.md +43 -0
- package/dist/AimdEditorTopBar.vue_vue_type_script_setup_true_lang-gbfMDZSh.js +1131 -0
- package/dist/AimdSourceEditor.vue_vue_type_script_setup_true_lang-t_sUoXky.js +274 -0
- package/dist/AimdWysiwygEditor.vue_vue_type_script_setup_true_lang-B8o1VbUH.js +25012 -0
- package/dist/aimd-editor.css +1 -0
- package/dist/embedded.js +11 -0
- package/dist/index.js +44 -0
- package/dist/monaco.js +16 -0
- package/dist/theme-B8dCnOx-.js +583 -0
- package/dist/vue.js +30 -0
- package/dist/wysiwyg.js +9 -0
- package/package.json +90 -0
- package/src/__tests__/editor.test.ts +296 -0
- package/src/embedded.ts +18 -0
- package/src/index.ts +10 -0
- package/src/language-config.ts +152 -0
- package/src/monaco.ts +19 -0
- package/src/theme.ts +166 -0
- package/src/tokens.ts +120 -0
- package/src/vue/AimdEditor.vue +715 -0
- package/src/vue/AimdEditorToolbar.vue +83 -0
- package/src/vue/AimdEditorTopBar.vue +39 -0
- package/src/vue/AimdFieldDialog.vue +1102 -0
- package/src/vue/AimdSourceEditor.vue +330 -0
- package/src/vue/AimdWysiwygEditor.vue +569 -0
- package/src/vue/aimdInlineMarkdownNormalization.ts +10 -0
- package/src/vue/comparableAimdMarkdown.ts +6 -0
- package/src/vue/env.d.ts +7 -0
- package/src/vue/index.ts +45 -0
- package/src/vue/locales.ts +667 -0
- package/src/vue/milkdown-aimd-plugin.ts +378 -0
- package/src/vue/programmaticMarkdownSyncGuard.ts +66 -0
- package/src/vue/types.ts +449 -0
- package/src/vue/useEditorContent.ts +252 -0
- package/src/wysiwyg.ts +17 -0
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
import { ref, shallowRef, computed, watch, nextTick } from 'vue'
|
|
2
|
+
import { protectAimdInlineTemplates, restoreAimdInlineTemplates } from '@airalogy/aimd-core'
|
|
3
|
+
import { parseAndExtract } from '@airalogy/aimd-renderer'
|
|
4
|
+
import type { Editor } from '@milkdown/kit/core'
|
|
5
|
+
import { replaceAll, getMarkdown, insert } from '@milkdown/kit/utils'
|
|
6
|
+
import { insertTableCommand } from '@milkdown/kit/preset/gfm'
|
|
7
|
+
import { callCommand } from '@milkdown/kit/utils'
|
|
8
|
+
import type { AimdEditorMessages } from './locales'
|
|
9
|
+
import { normalizeAimdInlineTemplateMarkdownEscapes } from './aimdInlineMarkdownNormalization'
|
|
10
|
+
|
|
11
|
+
export interface UseEditorContentOptions {
|
|
12
|
+
initialContent: string
|
|
13
|
+
initialMode: 'source' | 'wysiwyg'
|
|
14
|
+
resolvedMessages: { value: AimdEditorMessages }
|
|
15
|
+
emitModelValue: (value: string) => void
|
|
16
|
+
emitMode: (mode: 'source' | 'wysiwyg') => void
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function useEditorContent(options: UseEditorContentOptions) {
|
|
20
|
+
const { initialContent, initialMode, resolvedMessages, emitModelValue, emitMode } = options
|
|
21
|
+
|
|
22
|
+
const editorMode = ref<'source' | 'wysiwyg'>(initialMode)
|
|
23
|
+
const content = ref(initialContent)
|
|
24
|
+
const monacoEditor = shallowRef<any>(null)
|
|
25
|
+
const monacoInstance = shallowRef<any>(null)
|
|
26
|
+
const monacoLoading = ref(true)
|
|
27
|
+
const milkdownEditorRef = shallowRef<Editor | null>(null)
|
|
28
|
+
|
|
29
|
+
let isSyncing = false
|
|
30
|
+
|
|
31
|
+
function commitUserContent(nextContent: string) {
|
|
32
|
+
if (nextContent === content.value) {
|
|
33
|
+
return
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
content.value = nextContent
|
|
37
|
+
emitModelValue(nextContent)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function toMilkdownMarkdown(markdown: string): string {
|
|
41
|
+
return protectAimdInlineTemplates(markdown).content
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function isLikelyCorruptedMilkdownMarkdown(markdown: string): boolean {
|
|
45
|
+
return /class="ProseMirror milkdown-editor-content editor"/.test(markdown)
|
|
46
|
+
|| /<div class="milkdown">/.test(markdown)
|
|
47
|
+
|| (/<\/?[a-z][^>]*>/i.test(markdown) && /AIMDINLINETEMPLATE[0-9a-f]+TOKEN/.test(markdown))
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function normalizeMilkdownMarkdown(markdown: string, fallback: string): string {
|
|
51
|
+
const restored = normalizeAimdInlineTemplateMarkdownEscapes(restoreAimdInlineTemplates(markdown))
|
|
52
|
+
if (isLikelyCorruptedMilkdownMarkdown(restored)) {
|
|
53
|
+
return fallback
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return restored
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function shouldReparseInsertedText(text: string): boolean {
|
|
60
|
+
return /\{\{(?:var_table|var|quiz|step|check|ref_step|ref_var|ref_fig|cite|fig)\|/.test(text)
|
|
61
|
+
|| /```(?:quiz|fig|assigner)\b/.test(text)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function reparseMilkdownMarkdown() {
|
|
65
|
+
if (!milkdownEditorRef.value) {
|
|
66
|
+
return
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
try {
|
|
70
|
+
const markdown = milkdownEditorRef.value.action(getMarkdown())
|
|
71
|
+
if (typeof markdown !== 'string') {
|
|
72
|
+
return
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const normalizedMarkdown = normalizeMilkdownMarkdown(markdown, content.value)
|
|
76
|
+
milkdownEditorRef.value.action(replaceAll(toMilkdownMarkdown(normalizedMarkdown)))
|
|
77
|
+
} catch {}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// --- Extracted fields for reference suggestions ---
|
|
81
|
+
const extractedFields = computed(() => {
|
|
82
|
+
try { return parseAndExtract(content.value) } catch { return null }
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
// --- Mode switching ---
|
|
86
|
+
async function switchMode(mode: 'source' | 'wysiwyg') {
|
|
87
|
+
if (mode === editorMode.value) return
|
|
88
|
+
|
|
89
|
+
if (editorMode.value === 'source' && monacoEditor.value) {
|
|
90
|
+
content.value = monacoEditor.value.getValue()
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (editorMode.value === 'wysiwyg' && milkdownEditorRef.value) {
|
|
94
|
+
try {
|
|
95
|
+
const md = milkdownEditorRef.value.action(getMarkdown())
|
|
96
|
+
if (typeof md === 'string') {
|
|
97
|
+
content.value = normalizeMilkdownMarkdown(md, content.value)
|
|
98
|
+
}
|
|
99
|
+
} catch {}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
editorMode.value = mode
|
|
103
|
+
emitMode(mode)
|
|
104
|
+
|
|
105
|
+
if (mode === 'source') {
|
|
106
|
+
await nextTick()
|
|
107
|
+
if (monacoEditor.value) {
|
|
108
|
+
isSyncing = true
|
|
109
|
+
monacoEditor.value.setValue(content.value)
|
|
110
|
+
isSyncing = false
|
|
111
|
+
}
|
|
112
|
+
} else if (mode === 'wysiwyg') {
|
|
113
|
+
await nextTick()
|
|
114
|
+
if (milkdownEditorRef.value) {
|
|
115
|
+
try {
|
|
116
|
+
isSyncing = true
|
|
117
|
+
milkdownEditorRef.value.action(replaceAll(toMilkdownMarkdown(content.value)))
|
|
118
|
+
isSyncing = false
|
|
119
|
+
} catch { isSyncing = false }
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Sync external modelValue changes
|
|
125
|
+
function syncFromProp(val: string) {
|
|
126
|
+
if (val !== content.value) {
|
|
127
|
+
isSyncing = true
|
|
128
|
+
content.value = val
|
|
129
|
+
if (editorMode.value === 'source' && monacoEditor.value) {
|
|
130
|
+
monacoEditor.value.setValue(val)
|
|
131
|
+
} else if (editorMode.value === 'wysiwyg' && milkdownEditorRef.value) {
|
|
132
|
+
try { milkdownEditorRef.value.action(replaceAll(toMilkdownMarkdown(val))) } catch {}
|
|
133
|
+
}
|
|
134
|
+
isSyncing = false
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// --- Markdown toolbar actions ---
|
|
139
|
+
function insertTextIntoActiveEditor(text: string) {
|
|
140
|
+
if (editorMode.value === 'source' && monacoEditor.value) {
|
|
141
|
+
const selection = monacoEditor.value.getSelection()
|
|
142
|
+
monacoEditor.value.executeEdits('toolbar', [{
|
|
143
|
+
range: selection,
|
|
144
|
+
text,
|
|
145
|
+
forceMoveMarkers: true,
|
|
146
|
+
}])
|
|
147
|
+
monacoEditor.value.focus()
|
|
148
|
+
} else if (editorMode.value === 'wysiwyg' && milkdownEditorRef.value) {
|
|
149
|
+
try {
|
|
150
|
+
milkdownEditorRef.value.action(insert(text))
|
|
151
|
+
if (shouldReparseInsertedText(text)) {
|
|
152
|
+
reparseMilkdownMarkdown()
|
|
153
|
+
}
|
|
154
|
+
} catch {}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function insertAtCursor(before: string, after: string = '', placeholder: string = '') {
|
|
159
|
+
if (editorMode.value === 'source' && monacoEditor.value) {
|
|
160
|
+
const selection = monacoEditor.value.getSelection()
|
|
161
|
+
const selectedText = monacoEditor.value.getModel().getValueInRange(selection) || placeholder
|
|
162
|
+
const text = before + selectedText + after
|
|
163
|
+
monacoEditor.value.executeEdits('toolbar', [{
|
|
164
|
+
range: selection,
|
|
165
|
+
text,
|
|
166
|
+
forceMoveMarkers: true,
|
|
167
|
+
}])
|
|
168
|
+
monacoEditor.value.focus()
|
|
169
|
+
} else if (editorMode.value === 'wysiwyg' && milkdownEditorRef.value) {
|
|
170
|
+
try { milkdownEditorRef.value.action(insert(before + placeholder + after)) } catch {}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function insertLine(prefix: string, placeholder: string = '') {
|
|
175
|
+
if (editorMode.value === 'source' && monacoEditor.value) {
|
|
176
|
+
const position = monacoEditor.value.getPosition()
|
|
177
|
+
const model = monacoEditor.value.getModel()
|
|
178
|
+
const lineContent = model.getLineContent(position.lineNumber)
|
|
179
|
+
if (lineContent.trim() === '') {
|
|
180
|
+
const range = { startLineNumber: position.lineNumber, startColumn: 1, endLineNumber: position.lineNumber, endColumn: lineContent.length + 1 }
|
|
181
|
+
monacoEditor.value.executeEdits('toolbar', [{ range, text: prefix + placeholder, forceMoveMarkers: true }])
|
|
182
|
+
} else {
|
|
183
|
+
const range = { startLineNumber: position.lineNumber, startColumn: lineContent.length + 1, endLineNumber: position.lineNumber, endColumn: lineContent.length + 1 }
|
|
184
|
+
monacoEditor.value.executeEdits('toolbar', [{ range, text: '\n' + prefix + placeholder, forceMoveMarkers: true }])
|
|
185
|
+
}
|
|
186
|
+
monacoEditor.value.focus()
|
|
187
|
+
} else if (editorMode.value === 'wysiwyg' && milkdownEditorRef.value) {
|
|
188
|
+
try { milkdownEditorRef.value.action(insert('\n' + prefix + placeholder)) } catch {}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function handleMdAction(action: string) {
|
|
193
|
+
const snippets = resolvedMessages.value.snippets
|
|
194
|
+
switch (action) {
|
|
195
|
+
case 'h1': insertLine('# ', snippets.heading); break
|
|
196
|
+
case 'h2': insertLine('## ', snippets.heading); break
|
|
197
|
+
case 'h3': insertLine('### ', snippets.heading); break
|
|
198
|
+
case 'bold': insertAtCursor('**', '**', snippets.boldText); break
|
|
199
|
+
case 'italic': insertAtCursor('*', '*', snippets.italicText); break
|
|
200
|
+
case 'strikethrough': insertAtCursor('~~', '~~', snippets.strikethrough); break
|
|
201
|
+
case 'ul': insertLine('- ', snippets.listItem); break
|
|
202
|
+
case 'ol': insertLine('1. ', snippets.listItem); break
|
|
203
|
+
case 'blockquote': insertLine('> ', snippets.quote); break
|
|
204
|
+
case 'code': insertAtCursor('`', '`', snippets.code); break
|
|
205
|
+
case 'codeblock': insertAtCursor('```\n', '\n```', snippets.codeBlock); break
|
|
206
|
+
case 'link': insertAtCursor('[', '](url)', snippets.linkText); break
|
|
207
|
+
case 'image': insertAtCursor('', snippets.altText); break
|
|
208
|
+
case 'hr': insertLine('---'); break
|
|
209
|
+
case 'math': insertAtCursor('$', '$', snippets.mathFormula); break
|
|
210
|
+
case 'table':
|
|
211
|
+
if (editorMode.value === 'wysiwyg' && milkdownEditorRef.value) {
|
|
212
|
+
try { milkdownEditorRef.value.action(callCommand(insertTableCommand.key, { row: 3, col: 3 })) } catch {}
|
|
213
|
+
} else {
|
|
214
|
+
insertLine(
|
|
215
|
+
`| ${snippets.tableColumnA} | ${snippets.tableColumnB} | ${snippets.tableColumnC} |\n|-------|-------|-------|\n| `,
|
|
216
|
+
' | | |',
|
|
217
|
+
)
|
|
218
|
+
}
|
|
219
|
+
break
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function onMilkdownMarkdownUpdated(_ctx: any, markdown: string, _prev: string) {
|
|
224
|
+
if (isSyncing) {
|
|
225
|
+
return
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
commitUserContent(normalizeMilkdownMarkdown(markdown, content.value))
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function onMilkdownReady(editor: Editor) {
|
|
232
|
+
milkdownEditorRef.value = editor
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
return {
|
|
236
|
+
editorMode,
|
|
237
|
+
content,
|
|
238
|
+
commitUserContent,
|
|
239
|
+
monacoEditor,
|
|
240
|
+
monacoInstance,
|
|
241
|
+
monacoLoading,
|
|
242
|
+
milkdownEditorRef,
|
|
243
|
+
extractedFields,
|
|
244
|
+
toMilkdownMarkdown,
|
|
245
|
+
switchMode,
|
|
246
|
+
syncFromProp,
|
|
247
|
+
insertTextIntoActiveEditor,
|
|
248
|
+
handleMdAction,
|
|
249
|
+
onMilkdownMarkdownUpdated,
|
|
250
|
+
onMilkdownReady,
|
|
251
|
+
}
|
|
252
|
+
}
|
package/src/wysiwyg.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export { default as AimdWysiwygEditor } from './vue/AimdWysiwygEditor.vue'
|
|
2
|
+
export {
|
|
3
|
+
createAimdEditorMessages,
|
|
4
|
+
DEFAULT_AIMD_EDITOR_LOCALE,
|
|
5
|
+
resolveAimdEditorLocale,
|
|
6
|
+
} from './vue/locales'
|
|
7
|
+
export {
|
|
8
|
+
createAimdFieldTypes,
|
|
9
|
+
createAimdVarTypePresets,
|
|
10
|
+
} from './vue/types'
|
|
11
|
+
export type {
|
|
12
|
+
AimdFieldType,
|
|
13
|
+
AimdVarTypePresetOption,
|
|
14
|
+
AimdEditorMessages,
|
|
15
|
+
AimdEditorMessagesInput,
|
|
16
|
+
AimdEditorLocale,
|
|
17
|
+
} from './vue/index'
|