@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,330 @@
1
+ <script setup lang="ts">
2
+ import { ref, onMounted, onBeforeUnmount, watch } from 'vue'
3
+ import type { AimdEditorMessages } from './locales'
4
+
5
+ const props = defineProps<{
6
+ content: string
7
+ theme: string
8
+ minHeight: number
9
+ readonly: boolean
10
+ monacoOptions: Record<string, any>
11
+ resolvedMessages: AimdEditorMessages
12
+ }>()
13
+
14
+ const emit = defineEmits<{
15
+ (e: 'content-change', value: string): void
16
+ (e: 'ready', editor: any): void
17
+ (e: 'monaco-loaded', monaco: any, editor: any): void
18
+ }>()
19
+
20
+ const CLIENT_ASSIGNER_FENCE = /^\s*(```|~~~)\s*assigner(?:\s+.*\bruntime\s*=\s*(?:"client"|'client'|client)\b.*)\s*$/
21
+ const SERVER_ASSIGNER_FENCE = /^\s*(```|~~~)\s*assigner(?:\s+.*)?\s*$/
22
+ const QUIZ_FENCE = /^\s*(```|~~~)\s*quiz(?:\s+.*)?\s*$/
23
+ const GENERIC_CODE_FENCE = /^\s*(```|~~~)\s*((?:\w|[/#-])+)(?:\s+.*)?\s*$/
24
+ const EMPTY_CODE_FENCE = /^\s*(```|~~~)\s*$/
25
+
26
+ const editorContainer = ref<HTMLElement | null>(null)
27
+
28
+ let monacoEditorInstance: any = null
29
+ let monacoModule: any = null
30
+ let isSyncing = false
31
+ const loading = ref(true)
32
+
33
+ function registerAimdLanguage(monaco: any) {
34
+ const messages = props.resolvedMessages
35
+ const langs = monaco.languages.getLanguages()
36
+ if (langs.some((l: any) => l.id === 'aimd')) return
37
+
38
+ monaco.languages.register({
39
+ id: 'aimd',
40
+ extensions: ['.aimd'],
41
+ aliases: ['AIMD', 'aimd'],
42
+ mimetypes: ['text/x-aimd'],
43
+ })
44
+
45
+ monaco.languages.setLanguageConfiguration('aimd', {
46
+ brackets: [
47
+ ['{', '}'],
48
+ ['[', ']'],
49
+ ['(', ')'],
50
+ ],
51
+ autoClosingPairs: [
52
+ { open: '{', close: '}' },
53
+ { open: '[', close: ']' },
54
+ { open: '(', close: ')' },
55
+ { open: '"', close: '"' },
56
+ { open: "'", close: "'" },
57
+ { open: '`', close: '`' },
58
+ { open: '{{', close: '}}' },
59
+ ],
60
+ surroundingPairs: [
61
+ { open: '{', close: '}' },
62
+ { open: '[', close: ']' },
63
+ { open: '(', close: ')' },
64
+ { open: '"', close: '"' },
65
+ { open: "'", close: "'" },
66
+ { open: '`', close: '`' },
67
+ ],
68
+ })
69
+
70
+ monaco.languages.setMonarchTokensProvider('aimd', {
71
+ defaultToken: '',
72
+ tokenPostfix: '.aimd',
73
+ tokenizer: {
74
+ root: [
75
+ [/\{\{/, { token: 'delimiter.bracket.aimd', next: '@aimdField' }],
76
+ [/^#{1,6}\s.*$/, 'keyword.md'],
77
+ [QUIZ_FENCE, { token: 'string.code', next: '@embeddedCodeblock', nextEmbedded: 'yaml' }],
78
+ [CLIENT_ASSIGNER_FENCE, { token: 'string.code', next: '@embeddedCodeblock', nextEmbedded: 'javascript' }],
79
+ [SERVER_ASSIGNER_FENCE, { token: 'string.code', next: '@embeddedCodeblock', nextEmbedded: 'python' }],
80
+ [GENERIC_CODE_FENCE, { token: 'string.code', next: '@embeddedCodeblock', nextEmbedded: '$2' }],
81
+ [EMPTY_CODE_FENCE, { token: 'string.code', next: '@codeblock' }],
82
+ [/`[^`]+`/, 'string.code'],
83
+ [/\*\*[^*]+\*\*/, 'strong'],
84
+ [/__[^_]+__/, 'strong'],
85
+ [/\*[^*]+\*/, 'emphasis'],
86
+ [/_[^_]+_/, 'emphasis'],
87
+ [/\[[^\]]+\]\([^)]+\)/, 'string.link'],
88
+ [/!\[[^\]]*\]\([^)]+\)/, 'string.link'],
89
+ [/^>.*$/, 'comment.quote'],
90
+ [/^\s*[-*+]\s/, 'keyword.list'],
91
+ [/^\s*\d+\.\s/, 'keyword.list'],
92
+ [/^[-*_]{3,}\s*$/, 'keyword.hr'],
93
+ [/<\/?[\w-][^>]*>/, 'tag'],
94
+ ],
95
+ aimdField: [
96
+ [/\}\}/, { token: 'delimiter.bracket.aimd', next: '@pop' }],
97
+ [/\b(var_table|var|step|check|ref_step|ref_var|ref_fig|cite)\b/, 'keyword.aimd'],
98
+ [/\|/, 'delimiter.aimd'],
99
+ [/:/, 'delimiter'],
100
+ [/\b(str|int|float|bool|list|dict|any)\b/, 'type.aimd'],
101
+ [/\b[A-Z][A-Za-z0-9_]*(?:\[[A-Za-z0-9_,\s]+\])?\b/, 'type.aimd'],
102
+ [/[[\]()]/, 'delimiter.bracket'],
103
+ [/=/, 'delimiter'],
104
+ [/"[^"]*"/, 'string'],
105
+ [/'[^']*'/, 'string'],
106
+ [/-?\d+\.?\d*/, 'number'],
107
+ [/\b(true|false|True|False|null|None)\b/, 'constant'],
108
+ [/\bsubvars\b/, 'keyword'],
109
+ [/,/, 'delimiter'],
110
+ [/[A-Za-z_]\w*/, 'variable.aimd'],
111
+ [/\s+/, ''],
112
+ ],
113
+ codeblock: [
114
+ [/^```\s*$/, { token: 'string.code', next: '@pop' }],
115
+ [/^~~~\s*$/, { token: 'string.code', next: '@pop' }],
116
+ [/.*$/, 'string.code'],
117
+ ],
118
+ embeddedCodeblock: [
119
+ [/^```\s*$/, { token: 'string.code', next: '@pop', nextEmbedded: '@pop' }],
120
+ [/^~~~\s*$/, { token: 'string.code', next: '@pop', nextEmbedded: '@pop' }],
121
+ [/.*$/, ''],
122
+ ],
123
+ },
124
+ } as any)
125
+
126
+ monaco.languages.registerCompletionItemProvider('aimd', {
127
+ provideCompletionItems: () => {
128
+ const inlineKeywords = ['var', 'var_table', 'step', 'check', 'ref_step', 'ref_var', 'ref_fig', 'cite']
129
+ const placeholderByKeyword: Record<string, string> = {
130
+ var: 'var_id',
131
+ var_table: 'table_id',
132
+ step: 'step_id',
133
+ check: 'check_id',
134
+ ref_step: 'step_id',
135
+ ref_var: 'var_id',
136
+ ref_fig: 'fig_id',
137
+ cite: 'ref_id',
138
+ }
139
+ const suggestions = inlineKeywords.map(keyword => ({
140
+ label: `{{${keyword}|}}`,
141
+ kind: monaco.languages.CompletionItemKind.Snippet,
142
+ insertText: `{{${keyword}|\${1:${placeholderByKeyword[keyword] ?? 'id'}}}}`,
143
+ insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
144
+ documentation: messages.completions.insertAimdField(keyword),
145
+ }))
146
+ suggestions.push({
147
+ label: messages.completions.quizBlockLabel,
148
+ kind: monaco.languages.CompletionItemKind.Snippet,
149
+ insertText: [
150
+ '```quiz',
151
+ 'id: ${1:quiz_choice_1}',
152
+ 'type: choice',
153
+ 'mode: single',
154
+ 'stem: |',
155
+ ` \${2:${messages.defaults.questionStem}}`,
156
+ 'options:',
157
+ ' - key: A',
158
+ ` text: ${messages.defaults.optionText('A')}`,
159
+ ' - key: B',
160
+ ` text: ${messages.defaults.optionText('B')}`,
161
+ 'answer: A',
162
+ '```',
163
+ ].join('\n'),
164
+ insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
165
+ documentation: messages.completions.quizBlock,
166
+ } as any)
167
+ return { suggestions } as any
168
+ },
169
+ })
170
+
171
+ monaco.editor.defineTheme('aimd-light', {
172
+ base: 'vs',
173
+ inherit: true,
174
+ rules: [
175
+ { token: 'delimiter.bracket.aimd', foreground: '2563eb' },
176
+ { token: 'keyword.aimd', foreground: '2563eb', fontStyle: 'bold' },
177
+ { token: 'delimiter.aimd', foreground: '6b7280' },
178
+ { token: 'type.aimd', foreground: '7c3aed' },
179
+ { token: 'variable.aimd', foreground: '059669' },
180
+ { token: 'keyword.md', foreground: '1e40af' },
181
+ { token: 'string.code', foreground: 'be185d' },
182
+ { token: 'strong', fontStyle: 'bold' },
183
+ { token: 'emphasis', fontStyle: 'italic' },
184
+ { token: 'string.link', foreground: '2563eb' },
185
+ { token: 'comment.quote', foreground: '6b7280', fontStyle: 'italic' },
186
+ ],
187
+ colors: {},
188
+ })
189
+
190
+ monaco.editor.defineTheme('aimd-dark', {
191
+ base: 'vs-dark',
192
+ inherit: true,
193
+ rules: [
194
+ { token: 'delimiter.bracket.aimd', foreground: '60a5fa' },
195
+ { token: 'keyword.aimd', foreground: '60a5fa', fontStyle: 'bold' },
196
+ { token: 'delimiter.aimd', foreground: '9ca3af' },
197
+ { token: 'type.aimd', foreground: 'a78bfa' },
198
+ { token: 'variable.aimd', foreground: '34d399' },
199
+ { token: 'keyword.md', foreground: '93c5fd' },
200
+ { token: 'string.code', foreground: 'f472b6' },
201
+ { token: 'strong', fontStyle: 'bold' },
202
+ { token: 'emphasis', fontStyle: 'italic' },
203
+ { token: 'string.link', foreground: '60a5fa' },
204
+ { token: 'comment.quote', foreground: '9ca3af', fontStyle: 'italic' },
205
+ ],
206
+ colors: {},
207
+ })
208
+ }
209
+
210
+ function createEditor(monaco: any) {
211
+ if (!editorContainer.value || monacoEditorInstance) return
212
+ const MONACO_OPTIONS_BLACKLIST = new Set(['value', 'language', 'model'])
213
+ const safeMonacoOptions = Object.fromEntries(
214
+ Object.entries(props.monacoOptions).filter(([key]) => !MONACO_OPTIONS_BLACKLIST.has(key)),
215
+ )
216
+
217
+ monacoEditorInstance = monaco.editor.create(editorContainer.value, {
218
+ value: props.content,
219
+ language: 'aimd',
220
+ theme: props.theme,
221
+ minimap: { enabled: false },
222
+ fontSize: 14,
223
+ lineNumbers: 'on',
224
+ wordWrap: 'on',
225
+ scrollBeyondLastLine: false,
226
+ automaticLayout: true,
227
+ tabSize: 2,
228
+ padding: { top: 12 },
229
+ readOnly: props.readonly,
230
+ ...safeMonacoOptions,
231
+ })
232
+ monacoEditorInstance.onDidChangeModelContent(() => {
233
+ if (!isSyncing) {
234
+ emit('content-change', monacoEditorInstance.getValue())
235
+ }
236
+ })
237
+ emit('ready', monacoEditorInstance)
238
+ emit('monaco-loaded', monaco, monacoEditorInstance)
239
+ }
240
+
241
+ onMounted(async () => {
242
+ try {
243
+ loading.value = true
244
+ const monaco = await import('monaco-editor')
245
+ monacoModule = monaco
246
+ registerAimdLanguage(monaco)
247
+ createEditor(monaco)
248
+ } finally {
249
+ loading.value = false
250
+ }
251
+ })
252
+
253
+ onBeforeUnmount(() => {
254
+ monacoEditorInstance?.dispose()
255
+ })
256
+
257
+ watch(() => props.content, (content) => {
258
+ if (!monacoEditorInstance || content === monacoEditorInstance.getValue()) {
259
+ return
260
+ }
261
+
262
+ isSyncing = true
263
+ monacoEditorInstance.setValue(content)
264
+ isSyncing = false
265
+ })
266
+
267
+ watch(() => props.theme, (theme) => {
268
+ if (monacoModule) monacoModule.editor.setTheme(theme)
269
+ })
270
+
271
+ watch(() => props.readonly, (readonly) => {
272
+ monacoEditorInstance?.updateOptions({ readOnly: readonly })
273
+ })
274
+
275
+ // Expose internal references for parent to set values, etc.
276
+ function getEditor() { return monacoEditorInstance }
277
+ function getMonaco() { return monacoModule }
278
+
279
+ function setValue(val: string) {
280
+ if (monacoEditorInstance) {
281
+ isSyncing = true
282
+ monacoEditorInstance.setValue(val)
283
+ isSyncing = false
284
+ }
285
+ }
286
+
287
+ function getSelection() {
288
+ return monacoEditorInstance?.getSelection()
289
+ }
290
+
291
+ function executeEdits(source: string, edits: any[]) {
292
+ monacoEditorInstance?.executeEdits(source, edits)
293
+ }
294
+
295
+ function focus() {
296
+ monacoEditorInstance?.focus()
297
+ }
298
+
299
+ function getModel() {
300
+ return monacoEditorInstance?.getModel()
301
+ }
302
+
303
+ function getPosition() {
304
+ return monacoEditorInstance?.getPosition()
305
+ }
306
+
307
+ function getValue() {
308
+ return monacoEditorInstance?.getValue()
309
+ }
310
+
311
+ defineExpose({
312
+ getEditor,
313
+ getMonaco,
314
+ setValue,
315
+ getSelection,
316
+ executeEdits,
317
+ focus,
318
+ getModel,
319
+ getPosition,
320
+ getValue,
321
+ loading,
322
+ })
323
+ </script>
324
+
325
+ <template>
326
+ <div class="aimd-editor-source-mode" :style="{ height: minHeight + 'px' }">
327
+ <div v-if="loading" class="aimd-editor-loading">{{ resolvedMessages.common.loadingEditor }}</div>
328
+ <div ref="editorContainer" class="aimd-editor-container" />
329
+ </div>
330
+ </template>