@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.
Files changed (36) hide show
  1. package/README.md +59 -0
  2. package/README.zh-CN.md +43 -0
  3. package/dist/AimdEditorTopBar.vue_vue_type_script_setup_true_lang-gbfMDZSh.js +1131 -0
  4. package/dist/AimdSourceEditor.vue_vue_type_script_setup_true_lang-t_sUoXky.js +274 -0
  5. package/dist/AimdWysiwygEditor.vue_vue_type_script_setup_true_lang-B8o1VbUH.js +25012 -0
  6. package/dist/aimd-editor.css +1 -0
  7. package/dist/embedded.js +11 -0
  8. package/dist/index.js +44 -0
  9. package/dist/monaco.js +16 -0
  10. package/dist/theme-B8dCnOx-.js +583 -0
  11. package/dist/vue.js +30 -0
  12. package/dist/wysiwyg.js +9 -0
  13. package/package.json +90 -0
  14. package/src/__tests__/editor.test.ts +296 -0
  15. package/src/embedded.ts +18 -0
  16. package/src/index.ts +10 -0
  17. package/src/language-config.ts +152 -0
  18. package/src/monaco.ts +19 -0
  19. package/src/theme.ts +166 -0
  20. package/src/tokens.ts +120 -0
  21. package/src/vue/AimdEditor.vue +715 -0
  22. package/src/vue/AimdEditorToolbar.vue +83 -0
  23. package/src/vue/AimdEditorTopBar.vue +39 -0
  24. package/src/vue/AimdFieldDialog.vue +1102 -0
  25. package/src/vue/AimdSourceEditor.vue +330 -0
  26. package/src/vue/AimdWysiwygEditor.vue +569 -0
  27. package/src/vue/aimdInlineMarkdownNormalization.ts +10 -0
  28. package/src/vue/comparableAimdMarkdown.ts +6 -0
  29. package/src/vue/env.d.ts +7 -0
  30. package/src/vue/index.ts +45 -0
  31. package/src/vue/locales.ts +667 -0
  32. package/src/vue/milkdown-aimd-plugin.ts +378 -0
  33. package/src/vue/programmaticMarkdownSyncGuard.ts +66 -0
  34. package/src/vue/types.ts +449 -0
  35. package/src/vue/useEditorContent.ts +252 -0
  36. 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('![', '](url)', 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'