adtec-core-package 3.0.3 → 3.0.5

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.
@@ -1,6 +1,9 @@
1
1
  <!-- RichTextEditor:基于预编译 Umo Editor(prebuilt/umo-editor),对外 API 与原 AiEditor 封装保持一致 -->
2
2
  <template>
3
- <div ref="rootRef" class="umo-rich-text-editor-root">
3
+ <div
4
+ ref="rootRef"
5
+ class="umo-rich-text-editor-root"
6
+ :class="{ 'umo-rich-text-editor-root--readonly': !editable }">
4
7
  <UmoEditor
5
8
  v-if="umoMountOptions"
6
9
  :key="umoRuntimeKey"
@@ -36,9 +39,44 @@ export type RichTextEditorConfig = {
36
39
  }
37
40
 
38
41
  /** 始终保留 BlockImage NodeView;只读由 document.readOnly + node-view 静态分支控制 */
39
- const DISABLED_EXTENSIONS = ['mermaid', 'diagrams'] as const
40
-
41
- const OFFICIAL_TOOLBAR_MENUS = ['base', 'insert', 'table', 'tools', 'page', 'view', 'export'] as const
42
+ const DISABLED_EXTENSIONS = [
43
+ 'locale', // 视图 Tab:隐藏语言设置下拉(locale 固定 zh-CN;状态栏指示器另用 CSS 隐藏)
44
+ 'theme', // 视图 Tab:隐藏主题设置(light/dark,theme 固定 light)
45
+ 'mermaid',
46
+ 'diagrams',
47
+ // Block 菜单(+ 插入新内容):仅保留表格与图片
48
+ 'qrcode',
49
+ 'barcode',
50
+ 'signature',
51
+ 'math',
52
+ 'echarts',
53
+ 'print', // 开始 Tab:隐藏打印
54
+ // 插入 Tab:仅保留图片
55
+ 'link',
56
+ 'video',
57
+ 'audio',
58
+ 'file',
59
+ 'text-box',
60
+ 'details',
61
+ 'code-block',
62
+ 'symbol',
63
+ 'chinese-date',
64
+ 'emoji',
65
+ 'tag',
66
+ 'columns',
67
+ 'callout',
68
+ 'mention',
69
+ 'option-box',
70
+ 'hard-break',
71
+ 'hr',
72
+ 'bookmark',
73
+ 'footnote',
74
+ 'toc',
75
+ 'template',
76
+ 'web-page',
77
+ ] as const
78
+
79
+ const OFFICIAL_TOOLBAR_MENUS = ['base', 'insert', 'table', 'page', 'view'] as const
42
80
 
43
81
  const emit = defineEmits<{
44
82
  onBlur: [payload: { html: string; text: string }]
@@ -211,7 +249,7 @@ function seedUmoLayoutStorage() {
211
249
  localStorage.setItem(`umo-editor:${umoRuntimeKey.value}:layout`, JSON.stringify('web'))
212
250
  localStorage.setItem(
213
251
  `umo-editor:${umoRuntimeKey.value}:toolbar`,
214
- JSON.stringify({ mode: 'ribbon', show: showToolbar }),
252
+ JSON.stringify({ mode: 'classic', show: showToolbar }),
215
253
  )
216
254
  } catch {
217
255
  // ignore quota / private mode
@@ -280,11 +318,12 @@ function buildUmoMountOptions(content: string) {
280
318
  fullscreenZIndex: 2500,
281
319
  toolbar: {
282
320
  showSaveLabel: false,
283
- defaultMode: 'ribbon',
321
+ defaultMode: 'classic',
322
+ // Umo schema 要求 menus 至少含 base;只读时 readOnly=true 会隐藏工具栏 UI
284
323
  menus:
285
324
  props.showToolbar && editableNow
286
325
  ? [...OFFICIAL_TOOLBAR_MENUS]
287
- : [],
326
+ : (['base'] as unknown as string[]),
288
327
  },
289
328
  page: {
290
329
  layouts: ['page', 'web'],
@@ -296,7 +335,7 @@ function buildUmoMountOptions(content: string) {
296
335
  zh_CN: placeholder,
297
336
  },
298
337
  readOnly: !editableNow,
299
- autofocus: editableNow,
338
+ autofocus: false,
300
339
  enableMarkdown: false,
301
340
  enableSpellcheck: false,
302
341
  autoSave: {
@@ -367,7 +406,7 @@ async function applyEditableState() {
367
406
  editor.setReadOnly?.(!canEdit)
368
407
  ensureTipTapEditable(canEdit)
369
408
  editor.setToolbar?.({
370
- mode: 'ribbon',
409
+ mode: 'classic',
371
410
  show: props.showToolbar && canEdit,
372
411
  })
373
412
  const tipTap = editor.getEditor?.()?.value
@@ -647,4 +686,18 @@ onUnmounted(() => {
647
686
  cursor: text;
648
687
  min-height: 120px;
649
688
  }
689
+
690
+ .umo-rich-text-editor-root--readonly {
691
+ :deep(.umo-loading),
692
+ :deep(.umo-loading--visible),
693
+ :deep(.umo-loading__overlay),
694
+ :deep(.umo-loading--center),
695
+ :deep(.umo-icon-loading) {
696
+ display: none !important;
697
+ }
698
+
699
+ :deep(.umo-editor-content .ProseMirror) {
700
+ cursor: default;
701
+ }
702
+ }
650
703
  </style>
@@ -5,6 +5,26 @@ import { umoGlobalComponents } from '../../../prebuilt/umo-editor/umo-editor.js'
5
5
  const registeredApps = new WeakSet<App>()
6
6
  const tdesignApps = new WeakSet<App>()
7
7
 
8
+ /** 嵌入 Umo 的全局样式覆盖(须注入 document.head,全屏 Teleport 到 body 时组件 scoped :deep 无法命中) */
9
+ function injectUmoEmbedStyles() {
10
+ if (typeof document === 'undefined') return
11
+ if (document.getElementById('umo-embed-overrides')) return
12
+
13
+ const style = document.createElement('style')
14
+ style.id = 'umo-embed-overrides'
15
+ style.textContent = `
16
+ svg.umo-icon:not(.t-icon) {
17
+ fill: none;
18
+ }
19
+ .umo-status-bar-button.umo-lang-button {
20
+ display: none !important;
21
+ }
22
+ `
23
+ document.head.appendChild(style)
24
+ }
25
+
26
+ injectUmoEmbedStyles()
27
+
8
28
  /** 预编译 Umo 内部用 resolveComponent,须在宿主 Vue App 上注册 umoGlobalComponents */
9
29
  export function installUmoEditorApp(app?: App | null) {
10
30
  if (!app) return false
@@ -81,7 +81,7 @@ import {
81
81
  } from '../utils/history-record'
82
82
  import { getOpitons } from '../utils/options'
83
83
  import { getSelectionNode, getSelectionText } from '../utils/selection'
84
- import { syncCacheOnEditorSelectionUpdate } from '../composables/toolbarSelection.js'
84
+ import { syncCacheOnEditorSelectionUpdate, bumpToolbarEditorState } from '../composables/toolbarSelection.js'
85
85
  import { shortId } from '../utils/short-id'
86
86
  import { getCurrentInstance } from 'vue'
87
87
 
@@ -332,6 +332,7 @@ watch(
332
332
  editor.value.on('update', ({ editor }) => {
333
333
  emits('changed', { editor })
334
334
  contentUpdated.value = true
335
+ bumpToolbarEditorState()
335
336
  })
336
337
  editor.value.on('selectionUpdate', ({ editor }) => {
337
338
  syncCacheOnEditorSelectionUpdate(editor)
@@ -205,6 +205,7 @@
205
205
  :disabled="
206
206
  !forceEnabled && (disabled || editor?.isEditable === false)
207
207
  "
208
+ @mousedown.prevent="cacheToolbarSelection"
208
209
  >
209
210
  <div class="umo-button-content" @click="menuClick">
210
211
  <slot />
@@ -341,7 +342,7 @@ import { isString } from '@tool-belt/type-predicates'
341
342
  import { getShortcut } from '../../utils/shortcut'
342
343
 
343
344
 
344
- import { useAttrs, inject, watch, ref } from 'vue';
345
+ import { useAttrs, inject, watch, ref, onBeforeUnmount } from 'vue';
345
346
  import { onClickOutside } from '@vueuse/core';
346
347
  const { selectVisible } = useSelect()
347
348
 
@@ -442,24 +443,50 @@ const cacheToolbarSelection = () => {
442
443
  }
443
444
  }
444
445
 
446
+ /** 打开下拉时同一次点击可能落在首项上(幽灵 click),短暂忽略 @change */
447
+ let suppressSelectChange = false
448
+ let suppressSelectChangeTimer = null
449
+
450
+ const clearSuppressSelectChange = () => {
451
+ suppressSelectChange = false
452
+ if (suppressSelectChangeTimer != null) {
453
+ clearTimeout(suppressSelectChangeTimer)
454
+ suppressSelectChangeTimer = null
455
+ }
456
+ }
457
+
458
+ const scheduleSuppressSelectChange = () => {
459
+ clearSuppressSelectChange()
460
+ suppressSelectChange = true
461
+ suppressSelectChangeTimer = setTimeout(clearSuppressSelectChange, 250)
462
+ }
463
+
464
+ onBeforeUnmount(clearSuppressSelectChange)
465
+
445
466
  const onSelectTriggerPress = () => {
446
467
  beginSelectMenuInteraction(editor?.value)
468
+ scheduleSuppressSelectChange()
447
469
  }
448
470
 
449
471
  const selectPopupVisibleChange = (visible) => {
450
472
  popupVisileChange(visible)
451
473
  if (visible) {
452
474
  beginSelectMenuInteraction(editor?.value)
475
+ scheduleSuppressSelectChange()
453
476
  } else {
454
477
  endSelectMenuInteraction()
478
+ clearSuppressSelectChange()
455
479
  }
456
480
  }
457
481
 
458
- const selectMenuClick = (...args) => {
482
+ const selectMenuClick = (value, context, ...rest) => {
483
+ if (suppressSelectChange || context?.trigger === 'default') {
484
+ return
485
+ }
459
486
  if (editor?.value?.state.selection.empty) {
460
487
  restorePendingSelectMenuRange(editor.value)
461
488
  }
462
- menuClick(...args)
489
+ menuClick(value, context, ...rest)
463
490
  }
464
491
 
465
492
  const tooltipVisible = _ref(false)
@@ -28,6 +28,10 @@ import { ref as _ref } from 'vue';
28
28
 
29
29
 
30
30
  import { inject } from 'vue';
31
+ import {
32
+ FOCUS_WITHOUT_SCROLL,
33
+ runPreservingScroll,
34
+ } from '../../../../utils/editor-scroll.js';
31
35
  const props = defineProps({
32
36
  text: {
33
37
  type: String,
@@ -46,6 +50,7 @@ const emits = defineEmits(['change'])
46
50
 
47
51
  const { popupVisible, togglePopup } = usePopup()
48
52
  const editor = inject('editor')
53
+ const container = inject('container')
49
54
 
50
55
  let currentColor = _ref()
51
56
  const colorChange = (color) => {
@@ -57,11 +62,13 @@ const colorChange = (color) => {
57
62
  return
58
63
  }
59
64
 
60
- if (color === '') {
61
- editor.value?.chain().focus().unsetColor().run()
62
- } else {
63
- editor.value?.chain().focus().setColor(color).run()
64
- }
65
+ runPreservingScroll(container, () => {
66
+ if (color === '') {
67
+ editor.value?.chain().focus(undefined, FOCUS_WITHOUT_SCROLL).unsetColor().run()
68
+ } else {
69
+ editor.value?.chain().focus(undefined, FOCUS_WITHOUT_SCROLL).setColor(color).run()
70
+ }
71
+ })
65
72
  }
66
73
  </script>
67
74
 
@@ -57,6 +57,10 @@
57
57
 
58
58
  <script setup>
59
59
  import { t } from '../../../../composables/i18n.js';
60
+ import {
61
+ getToolbarFontFamily,
62
+ toolbarEditorTick,
63
+ } from '../../../../composables/toolbarSelection.js';
60
64
 
61
65
  import { useState } from '../../../../composables/state.js';
62
66
  import { ref as _ref } from 'vue';
@@ -81,13 +85,11 @@ let autoDownloadRunning = _ref(false)
81
85
  let restoringDownloadedFonts = _ref(true)
82
86
 
83
87
  const selectedFont = computed(() => {
88
+ toolbarEditorTick.value
84
89
  if (!editor.value || typeWriterIsRunning.value) {
85
90
  return null
86
91
  }
87
- if (!editor.value?.getAttributes('textStyle').fontFamily) {
88
- return null
89
- }
90
- return editor.value.getAttributes('textStyle').fontFamily.replace(/"/g, '')
92
+ return getToolbarFontFamily(editor.value)
91
93
  })
92
94
 
93
95
  const ensureRecentState = () => {
@@ -6,11 +6,7 @@
6
6
  hide-text
7
7
  style="width: 80px"
8
8
  :select-options="fontSizes"
9
- :select-value="
10
- typeWriterIsRunning
11
- ? null
12
- : editor?.getAttributes('textStyle').fontSize || '14px'
13
- "
9
+ :select-value="selectFontSize"
14
10
  v-bind="$attrs"
15
11
  :placeholder="t('base.fontSize.text')"
16
12
  filterable
@@ -40,10 +36,16 @@
40
36
  import { t } from '../../../../composables/i18n.js';
41
37
  import {
42
38
  applyCachedTextSelection,
39
+ getToolbarFontSize,
43
40
  refreshCachedTextSelection,
44
41
  restorePendingSelectMenuRange,
42
+ toolbarEditorTick,
45
43
  } from '../../../../composables/toolbarSelection.js';
46
- import { inject } from 'vue';
44
+ import { computed, inject } from 'vue';
45
+ import {
46
+ FOCUS_WITHOUT_SCROLL,
47
+ runPreservingScroll,
48
+ } from '../../../../utils/editor-scroll.js';
47
49
 
48
50
  const props = defineProps({
49
51
  select: {
@@ -53,6 +55,7 @@ const props = defineProps({
53
55
  })
54
56
 
55
57
  const editor = inject('editor')
58
+ const container = inject('container')
56
59
  const options = inject('options')
57
60
  const typeWriterIsRunning = inject('typeWriterIsRunning')
58
61
 
@@ -60,6 +63,12 @@ const disableMenu = (name) => {
60
63
  return options.value.disableExtensions.includes(name)
61
64
  }
62
65
 
66
+ const selectFontSize = computed(() => {
67
+ toolbarEditorTick.value
68
+ if (typeWriterIsRunning.value) return null
69
+ return getToolbarFontSize(editor.value)
70
+ })
71
+
63
72
  const fontSizes = [
64
73
  { label: t('base.fontSize.default'), value: '14px', order: 4 },
65
74
  { label: t('base.fontSize.42pt'), value: '42pt', order: 20 }, // 56
@@ -94,36 +103,44 @@ const fontSizes = [
94
103
  { label: '96', value: '96px', order: 22 },
95
104
  ]
96
105
 
97
- // 设置字体大小(连续改字号时保持选区)
106
+ // 设置字体大小(连续改字号时保持选区,且不触发外层页面滚动)
98
107
  const applyFontSize = (fontSize) => {
99
108
  const ed = editor.value
100
109
  if (!ed) return
101
110
 
102
- const { empty, from, to } = ed.state.selection
103
- let chain = ed.chain().focus()
111
+ runPreservingScroll(container, () => {
112
+ const { empty, from, to } = ed.state.selection
113
+ let chain = ed.chain().focus(undefined, FOCUS_WITHOUT_SCROLL)
104
114
 
105
- if (!empty && from !== to) {
106
- chain = chain.setTextSelection({ from, to })
107
- } else if (
108
- !restorePendingSelectMenuRange(ed) &&
109
- !applyCachedTextSelection(ed)
110
- ) {
111
- return
112
- } else {
113
- const sel = ed.state.selection
114
- if (!sel.empty && sel.from !== sel.to) {
115
- chain = ed.chain().focus().setTextSelection({ from: sel.from, to: sel.to })
116
- } else {
115
+ if (!empty && from !== to) {
116
+ chain = chain.setTextSelection({ from, to })
117
+ } else if (
118
+ !restorePendingSelectMenuRange(ed) &&
119
+ !applyCachedTextSelection(ed)
120
+ ) {
117
121
  return
122
+ } else {
123
+ const sel = ed.state.selection
124
+ if (!sel.empty && sel.from !== sel.to) {
125
+ chain = ed
126
+ .chain()
127
+ .focus(undefined, FOCUS_WITHOUT_SCROLL)
128
+ .setTextSelection({ from: sel.from, to: sel.to })
129
+ } else {
130
+ return
131
+ }
118
132
  }
119
- }
120
133
 
121
- if (chain.setFontSize(fontSize).run()) {
122
- refreshCachedTextSelection(ed)
123
- }
134
+ if (chain.setFontSize(fontSize).run()) {
135
+ refreshCachedTextSelection(ed)
136
+ }
137
+ })
124
138
  }
125
139
 
126
140
  const setFontSize = (fontSize) => {
141
+ if (!fontSize) return
142
+ const current = getToolbarFontSize(editor.value)
143
+ if (current === fontSize) return
127
144
  applyFontSize(fontSize)
128
145
  }
129
146
 
@@ -84,8 +84,8 @@ import { inject, ref, computed } from 'vue';
84
84
  import { onClickOutside } from '@vueuse/core';
85
85
  import {
86
86
  applyHeadingWithoutScroll,
87
- captureScrollPositions,
88
- restoreScrollPositions,
87
+ FOCUS_WITHOUT_SCROLL,
88
+ runPreservingScroll,
89
89
  } from '../../../../utils/editor-scroll.js';
90
90
  const { popupVisible } = usePopup()
91
91
  const container = inject('container')
@@ -133,16 +133,14 @@ const currentValue = computed(() => {
133
133
  return ''
134
134
  })
135
135
 
136
- const FOCUS_WITHOUT_SCROLL = { scrollIntoView: false }
137
-
138
136
  const setHeading = (value) => {
139
137
  const ed = editor.value
140
138
  if (!ed) return
141
139
 
142
- const scrollPositions = captureScrollPositions(container)
143
- ed.commands.focus(undefined, FOCUS_WITHOUT_SCROLL)
144
- applyHeadingWithoutScroll(ed, value)
145
- restoreScrollPositions(scrollPositions)
140
+ runPreservingScroll(container, () => {
141
+ ed.commands.focus(undefined, FOCUS_WITHOUT_SCROLL)
142
+ applyHeadingWithoutScroll(ed, value)
143
+ })
146
144
  popupVisible.value = false
147
145
  }
148
146