vue-editify 0.1.19 → 0.1.20

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 (79) hide show
  1. package/README.md +3 -3
  2. package/examples/App.vue +62 -62
  3. package/examples/main.ts +4 -4
  4. package/lib/components/button/button.vue.d.ts +11 -11
  5. package/lib/components/checkbox/checkbox.vue.d.ts +8 -8
  6. package/lib/components/colors/colors.vue.d.ts +4 -4
  7. package/lib/components/icon/icon.vue.d.ts +1 -1
  8. package/lib/components/insertImage/insertImage.vue.d.ts +9 -9
  9. package/lib/components/insertLink/insertLink.vue.d.ts +2 -2
  10. package/lib/components/insertTable/insertTable.vue.d.ts +2 -2
  11. package/lib/components/insertVideo/insertVideo.vue.d.ts +9 -9
  12. package/lib/components/layer/layer.vue.d.ts +9 -9
  13. package/lib/components/menu/menu.vue.d.ts +4 -4
  14. package/lib/components/toolbar/toolbar.vue.d.ts +9 -9
  15. package/lib/components/tooltip/tooltip.vue.d.ts +1 -1
  16. package/lib/components/triangle/triangle.vue.d.ts +4 -4
  17. package/lib/editify/editify.vue.d.ts +68 -68
  18. package/lib/editify.es.js +35 -24
  19. package/lib/editify.umd.js +1 -1
  20. package/lib/index.d.ts +1 -1
  21. package/lib/style.css +1 -1
  22. package/package.json +45 -45
  23. package/src/components/button/button.less +145 -145
  24. package/src/components/button/button.vue +197 -197
  25. package/src/components/button/props.ts +95 -95
  26. package/src/components/checkbox/checkbox.less +84 -84
  27. package/src/components/checkbox/checkbox.vue +68 -68
  28. package/src/components/checkbox/props.ts +49 -49
  29. package/src/components/colors/colors.less +75 -75
  30. package/src/components/colors/colors.vue +36 -36
  31. package/src/components/colors/props.ts +29 -29
  32. package/src/components/icon/icon.less +14 -14
  33. package/src/components/icon/icon.vue +12 -12
  34. package/src/components/icon/props.ts +11 -11
  35. package/src/components/insertImage/insertImage.less +135 -135
  36. package/src/components/insertImage/insertImage.vue +146 -146
  37. package/src/components/insertImage/props.ts +43 -43
  38. package/src/components/insertLink/insertLink.less +64 -64
  39. package/src/components/insertLink/insertLink.vue +58 -58
  40. package/src/components/insertLink/props.ts +16 -16
  41. package/src/components/insertTable/insertTable.less +54 -54
  42. package/src/components/insertTable/insertTable.vue +85 -85
  43. package/src/components/insertTable/props.ts +27 -27
  44. package/src/components/insertVideo/insertVideo.less +135 -135
  45. package/src/components/insertVideo/insertVideo.vue +146 -146
  46. package/src/components/insertVideo/props.ts +43 -43
  47. package/src/components/layer/layer.less +49 -49
  48. package/src/components/layer/layer.vue +598 -598
  49. package/src/components/layer/props.ts +71 -71
  50. package/src/components/menu/menu.less +63 -63
  51. package/src/components/menu/menu.vue +1569 -1569
  52. package/src/components/menu/props.ts +17 -17
  53. package/src/components/toolbar/props.ts +35 -35
  54. package/src/components/toolbar/toolbar.less +89 -89
  55. package/src/components/toolbar/toolbar.vue +1101 -1101
  56. package/src/components/tooltip/props.ts +21 -21
  57. package/src/components/tooltip/tooltip.less +23 -23
  58. package/src/components/tooltip/tooltip.vue +37 -37
  59. package/src/components/triangle/props.ts +26 -26
  60. package/src/components/triangle/triangle.less +79 -79
  61. package/src/components/triangle/triangle.vue +65 -65
  62. package/src/core/function.ts +1150 -1144
  63. package/src/core/rule.ts +259 -259
  64. package/src/core/tool.ts +1137 -1137
  65. package/src/css/base.less +30 -30
  66. package/src/css/hljs.less +54 -54
  67. package/src/editify/editify.less +404 -404
  68. package/src/editify/editify.vue +810 -803
  69. package/src/editify/props.ts +156 -156
  70. package/src/hljs/index.ts +197 -197
  71. package/src/icon/iconfont.css +219 -219
  72. package/src/index.ts +32 -32
  73. package/src/locale/en_US.ts +88 -88
  74. package/src/locale/index.ts +12 -12
  75. package/src/locale/zh_CN.ts +88 -88
  76. package/tsconfig.json +27 -27
  77. package/tsconfig.node.json +11 -11
  78. package/vite-env.d.ts +1 -1
  79. package/vite.config.ts +42 -42
@@ -1,803 +1,810 @@
1
- <template>
2
- <div class="editify" :class="{ fullscreen: isFullScreen, autoheight: !isFullScreen && autoheight }" ref="elRef">
3
- <!-- 菜单区域 -->
4
- <Menu v-if="menuConfig.use" :config="menuConfig" :color="color" ref="menuRef"></Menu>
5
- <!-- 编辑层,与编辑区域宽高相同必须适配 -->
6
- <div ref="bodyRef" class="editify-body" :class="{ border: showBorder, menu_inner: menuConfig.use && menuConfig.mode == 'inner' }" :data-editify-uid="instance.uid">
7
- <!-- 编辑器 -->
8
- <div ref="contentRef" class="editify-content" :class="{ placeholder: showPlaceholder, disabled: disabled }" @keydown="handleEditorKeydown" @click="handleEditorClick" @compositionstart="isInputChinese = true" @compositionend="isInputChinese = false" :data-editify-placeholder="placeholder"></div>
9
- <!-- 代码视图 -->
10
- <textarea v-if="isSourceView" :value="value" readonly class="editify-source" />
11
- <!-- 工具条 -->
12
- <Toolbar ref="toolbarRef" v-model="toolbarOptions.show" :node="toolbarOptions.node!" :type="toolbarOptions.type" :config="toolbarConfig" :color="color"></Toolbar>
13
- </div>
14
- <!-- 编辑器尾部 -->
15
- <div v-if="showWordLength" class="editify-footer" :class="{ fullscreen: isFullScreen && !isSourceView }" ref="footerRef">
16
- <!-- 字数统计 -->
17
- <div class="editify-footer-words">{{ $editTrans('totalWordCount') }}{{ textValue.length }}</div>
18
- </div>
19
- </div>
20
- </template>
21
- <script setup lang="ts">
22
- import { computed, getCurrentInstance, nextTick, onBeforeUnmount, onMounted, provide, ref, watch } from 'vue'
23
- import { AlexEditor, AlexElement, AlexElementRangeType, AlexElementsRangeType } from 'alex-editor'
24
- import { element as DapElement, event as DapEvent, data as DapData, number as DapNumber, color as DapColor } from 'dap-util'
25
- import { pasteKeepData, mergeObject, getToolbarConfig, getMenuConfig, MenuConfigType, ObjectType, ToolbarConfigType } from '../core/tool'
26
- import { parseList, orderdListHandle, mediaHandle, tableHandle, preHandle, specialInblockHandle } from '../core/rule'
27
- import { isTask, elementToParagraph, getCurrentParsedomElement, hasTableInRange, hasLinkInRange, hasPreInRange, hasImageInRange, hasVideoInRange } from '../core/function'
28
- import Toolbar from '../components/toolbar/toolbar.vue'
29
- import Menu from '../components/menu/menu.vue'
30
- import Layer from '../components/layer/layer.vue'
31
- import { EditifyProps, EditifyTableColumnResizeParamsType, EditifyToolbarOptionsType } from './props'
32
- import { trans } from '../locale'
33
- import { LanguagesItemType } from '../hljs'
34
-
35
- //定义组件名称
36
- defineOptions({
37
- name: 'editify'
38
- })
39
- //获取实例
40
- const instance = getCurrentInstance()!
41
- //属性
42
- const props = defineProps(EditifyProps)
43
- //事件
44
- const emits = defineEmits(['update:modelValue', 'focus', 'blur', 'change', 'keydown', 'insertparagraph', 'rangeupdate', 'updateview'])
45
-
46
- //设置国际化方法
47
- const $editTrans = trans(props.locale || 'zh_CN')
48
- //对子孙后代组件提供国际化方法
49
- provide('$editTrans', $editTrans)
50
-
51
- //是否编辑器内部修改值
52
- const isModelChange = ref<boolean>(false)
53
- //是否正在输入中文
54
- const isInputChinese = ref<boolean>(false)
55
- //工具条和菜单栏判定延时器
56
- const rangeUpdateTimer = ref<any>(null)
57
- //表格列宽拖拽记录数据
58
- const tableColumnResizeParams = ref<EditifyTableColumnResizeParamsType>({
59
- element: null, //被拖拽的td
60
- start: 0 //水平方向起点位置
61
- })
62
- //工具条参数配置
63
- const toolbarOptions = ref<EditifyToolbarOptionsType>({
64
- //是否显示工具条
65
- show: false,
66
- //关联元素
67
- node: null,
68
- //类型
69
- type: 'text'
70
- })
71
-
72
- const menuRef = ref<InstanceType<typeof Menu> | null>(null)
73
- const bodyRef = ref<HTMLElement | null>(null)
74
- const contentRef = ref<HTMLElement | null>(null)
75
- const toolbarRef = ref<InstanceType<typeof Toolbar> | null>(null)
76
- const footerRef = ref<HTMLElement | null>(null)
77
- const elRef = ref<HTMLElement | null>(null)
78
-
79
- //编辑器对象
80
- const editor = ref<AlexEditor | null>(null)
81
- //是否代码视图
82
- const isSourceView = ref<boolean>(false)
83
- //是否全屏
84
- const isFullScreen = ref<boolean>(false)
85
- //菜单栏是否可以使用标识
86
- const canUseMenu = ref<boolean>(false)
87
- //光标选取范围内的元素数组
88
- const dataRangeCaches = ref<AlexElementsRangeType>({
89
- flatList: [],
90
- list: []
91
- })
92
-
93
- //编辑器的值
94
- const value = computed<string>({
95
- set(val) {
96
- emits('update:modelValue', val)
97
- },
98
- get() {
99
- return props.modelValue || '<p><br></p>'
100
- }
101
- })
102
- //编辑器的纯文本值
103
- const textValue = computed<string>(() => {
104
- return (<HTMLElement>DapElement.string2dom(`<div>${value.value}</div>`)).innerText
105
- })
106
- //是否显示占位符
107
- const showPlaceholder = computed<boolean>(() => {
108
- if (editor.value) {
109
- if (value.value && editor.value.stack.length == 1 && editor.value.stack[0].type == 'block' && editor.value.stack[0].parsedom == AlexElement.BLOCK_NODE && editor.value.stack[0].isOnlyHasBreak() && !editor.value.stack[0].hasStyles() && !editor.value.stack[0].hasMarks()) {
110
- return !isInputChinese.value
111
- }
112
- }
113
- return false
114
- })
115
- //是否显示边框
116
- const showBorder = computed<boolean>(() => {
117
- //全屏模式下不显示边框
118
- if (isFullScreen.value) {
119
- return false
120
- }
121
- return props.border
122
- })
123
- //最终生效的工具栏配置
124
- const toolbarConfig = computed<ToolbarConfigType>(() => {
125
- return <ToolbarConfigType>mergeObject(getToolbarConfig($editTrans, props.locale), props.toolbar || {})
126
- })
127
- //最终生效的菜单栏配置
128
- const menuConfig = computed<MenuConfigType>(() => {
129
- return <MenuConfigType>mergeObject(getMenuConfig($editTrans, props.locale), props.menu || {})
130
- })
131
-
132
- //编辑器内部修改值的方法
133
- const internalModify = (val: string) => {
134
- isModelChange.value = true
135
- value.value = val
136
- nextTick(() => {
137
- isModelChange.value = false
138
- })
139
- }
140
- //隐藏工具条
141
- const hideToolbar = () => {
142
- toolbarOptions.value.show = false
143
- toolbarOptions.value.node = null
144
- }
145
- //监听滚动隐藏工具条
146
- const handleScroll = () => {
147
- const setScroll = (el: HTMLElement) => {
148
- DapEvent.on(el, `scroll.editify_${instance.uid}`, () => {
149
- if (toolbarConfig.value.use && toolbarOptions.value.show) {
150
- hideToolbar()
151
- }
152
- })
153
- if (el.parentNode) {
154
- setScroll(<HTMLElement>el.parentNode)
155
- }
156
- }
157
- setScroll(contentRef.value!)
158
- }
159
- //移除上述滚动事件的监听
160
- const removeScrollHandle = () => {
161
- const removeScroll = (el: HTMLElement) => {
162
- DapEvent.off(el, `scroll.editify_${instance.uid}`)
163
- if (el.parentNode) {
164
- removeScroll(<HTMLElement>el.parentNode)
165
- }
166
- }
167
- removeScroll(contentRef.value!)
168
- }
169
- //工具条显示判断
170
- const handleToolbar = () => {
171
- if (props.disabled || isSourceView.value) {
172
- return
173
- }
174
- hideToolbar()
175
- nextTick(() => {
176
- const table = getCurrentParsedomElement(editor.value!, dataRangeCaches.value, 'table')
177
- const pre = getCurrentParsedomElement(editor.value!, dataRangeCaches.value, 'pre')
178
- const link = getCurrentParsedomElement(editor.value!, dataRangeCaches.value, 'a')
179
- const image = getCurrentParsedomElement(editor.value!, dataRangeCaches.value, 'img')
180
- const video = getCurrentParsedomElement(editor.value!, dataRangeCaches.value, 'video')
181
- if (link) {
182
- toolbarOptions.value.type = 'link'
183
- toolbarOptions.value.node = `[data-editify-uid="${instance.uid}"] [data-editify-element="${link.key}"]`
184
- if (toolbarOptions.value.show) {
185
- ;(<InstanceType<typeof Layer>>toolbarRef.value!.$refs.layerRef).setPosition()
186
- } else {
187
- toolbarOptions.value.show = true
188
- }
189
- } else if (image) {
190
- toolbarOptions.value.type = 'image'
191
- toolbarOptions.value.node = `[data-editify-uid="${instance.uid}"] [data-editify-element="${image.key}"]`
192
- if (toolbarOptions.value.show) {
193
- ;(<InstanceType<typeof Layer>>toolbarRef.value!.$refs.layerRef).setPosition()
194
- } else {
195
- toolbarOptions.value.show = true
196
- }
197
- } else if (video) {
198
- toolbarOptions.value.type = 'video'
199
- toolbarOptions.value.node = `[data-editify-uid="${instance.uid}"] [data-editify-element="${video.key}"]`
200
- if (toolbarOptions.value.show) {
201
- ;(<InstanceType<typeof Layer>>toolbarRef.value!.$refs.layerRef).setPosition()
202
- } else {
203
- toolbarOptions.value.show = true
204
- }
205
- } else if (table) {
206
- toolbarOptions.value.type = 'table'
207
- toolbarOptions.value.node = `[data-editify-uid="${instance.uid}"] [data-editify-element="${table.key}"]`
208
- if (toolbarOptions.value.show) {
209
- ;(<InstanceType<typeof Layer>>toolbarRef.value!.$refs.layerRef).setPosition()
210
- } else {
211
- toolbarOptions.value.show = true
212
- }
213
- } else if (pre) {
214
- toolbarOptions.value.type = 'codeBlock'
215
- toolbarOptions.value.node = `[data-editify-uid="${instance.uid}"] [data-editify-element="${pre.key}"]`
216
- if (toolbarOptions.value.show) {
217
- ;(<InstanceType<typeof Layer>>toolbarRef.value!.$refs.layerRef).setPosition()
218
- } else {
219
- toolbarOptions.value.show = true
220
- }
221
- } else {
222
- const result = dataRangeCaches.value.flatList.filter((item: AlexElementRangeType) => {
223
- return item.element.isText()
224
- })
225
- if (result.length && !hasTableInRange(editor.value!, dataRangeCaches.value) && !hasPreInRange(editor.value!, dataRangeCaches.value) && !hasLinkInRange(editor.value!, dataRangeCaches.value) && !hasImageInRange(editor.value!, dataRangeCaches.value) && !hasVideoInRange(editor.value!, dataRangeCaches.value)) {
226
- toolbarOptions.value.type = 'text'
227
- if (toolbarOptions.value.show) {
228
- ;(<InstanceType<typeof Layer>>toolbarRef.value!.$refs.layerRef).setPosition()
229
- } else {
230
- toolbarOptions.value.show = true
231
- }
232
- }
233
- }
234
- })
235
- }
236
- //初始创建编辑器
237
- const createEditor = () => {
238
- //创建编辑器
239
- editor.value = new AlexEditor(contentRef.value!, {
240
- value: value.value,
241
- disabled: props.disabled,
242
- renderRules: [
243
- el => {
244
- parseList(editor.value!, el)
245
- },
246
- el => {
247
- orderdListHandle(editor.value!, el)
248
- },
249
- el => {
250
- mediaHandle(editor.value!, el)
251
- },
252
- el => {
253
- tableHandle(editor.value!, el)
254
- },
255
- el => {
256
- preHandle(editor.value!, el, !!(toolbarConfig.value?.use && toolbarConfig.value?.codeBlock?.languages?.show), <(string | LanguagesItemType)[]>toolbarConfig.value?.codeBlock?.languages?.options)
257
- },
258
- el => {
259
- specialInblockHandle(editor.value!, el)
260
- },
261
- ...props.renderRules
262
- ],
263
- allowCopy: props.allowCopy,
264
- allowPaste: props.allowPaste,
265
- allowCut: props.allowCut,
266
- allowPasteHtml: props.allowPasteHtml,
267
- customTextPaste: props.customTextPaste,
268
- customImagePaste: props.customImagePaste,
269
- customVideoPaste: props.customVideoPaste,
270
- customFilePaste: props.customFilePaste,
271
- customHtmlPaste: handleCustomHtmlPaste,
272
- customMerge: handleCustomMerge,
273
- customParseNode: handleCustomParseNode
274
- })
275
- //编辑器渲染后会有一个渲染过程,会改变内容,因此重新获取内容的值来设置value
276
- internalModify(editor.value.value)
277
- //设置监听事件
278
- editor.value.on('change', handleEditorChange)
279
- editor.value.on('focus', handleEditorFocus)
280
- editor.value.on('blur', handleEditorBlur)
281
- editor.value.on('insertParagraph', handleInsertParagraph)
282
- editor.value.on('rangeUpdate', handleRangeUpdate)
283
- editor.value.on('deleteInStart', handleDeleteInStart)
284
- editor.value.on('deleteComplete', handleDeleteComplete)
285
- editor.value.on('afterRender', handleAfterRender)
286
- //格式化和dom渲染
287
- editor.value.formatElementStack()
288
- editor.value.domRender()
289
- //自动获取焦点
290
- if (props.autofocus && !isSourceView.value && !props.disabled) {
291
- collapseToEnd()
292
- }
293
- }
294
- //设定编辑器内的视频高度
295
- const setVideoHeight = () => {
296
- contentRef.value!.querySelectorAll('video').forEach(video => {
297
- video.style.height = video.offsetWidth / props.videoRatio + 'px'
298
- })
299
- }
300
- //鼠标在页面按下:处理表格拖拽改变列宽和菜单栏是否使用判断
301
- const documentMouseDown = (e: Event) => {
302
- if (props.disabled) {
303
- return
304
- }
305
- //鼠标在编辑器内按下
306
- if (DapElement.isContains(contentRef.value!, <HTMLElement>e.target)) {
307
- const elm = <HTMLElement>e.target
308
- const key = DapData.get(elm, 'data-alex-editor-key')
309
- if (key) {
310
- const element = editor.value!.getElementByKey(key)
311
- if (element && element.parsedom == 'td') {
312
- const length = element.parent!.children!.length
313
- //最后一个td不设置
314
- if (element.parent!.children![length - 1].isEqual(element)) {
315
- return
316
- }
317
- const rect = DapElement.getElementBounding(elm)
318
- //在可拖拽范围内
319
- if ((<MouseEvent>e).pageX >= Math.abs(rect.left + elm.offsetWidth - 5) && (<MouseEvent>e).pageX <= Math.abs(rect.left + elm.offsetWidth + 5)) {
320
- tableColumnResizeParams.value.element = element
321
- tableColumnResizeParams.value.start = (<MouseEvent>e).pageX
322
- }
323
- }
324
- }
325
- }
326
- //如果点击了除编辑器外的地方,菜单栏不可使用
327
- if (!DapElement.isContains(elRef.value!, <HTMLElement>e.target) && !isSourceView.value) {
328
- canUseMenu.value = false
329
- }
330
- }
331
- //鼠标在页面移动:处理表格拖拽改变列宽
332
- const documentMouseMove = (e: Event) => {
333
- if (props.disabled) {
334
- return
335
- }
336
- if (!tableColumnResizeParams.value.element) {
337
- return
338
- }
339
- const table = getCurrentParsedomElement(editor.value!, dataRangeCaches.value, 'table')
340
- if (!table) {
341
- return
342
- }
343
- const colgroup = table.children!.find(item => {
344
- return item.parsedom == 'colgroup'
345
- })!
346
- const index = tableColumnResizeParams.value.element.parent!.children!.findIndex(el => {
347
- return el.isEqual(tableColumnResizeParams.value.element!)
348
- })
349
- const width = `${tableColumnResizeParams.value.element.elm!.offsetWidth + (<MouseEvent>e).pageX - tableColumnResizeParams.value.start}`
350
- colgroup.children![index].marks!['width'] = width
351
- colgroup.children![index].elm!.setAttribute('width', width)
352
- tableColumnResizeParams.value.start = (<MouseEvent>e).pageX
353
- }
354
- //鼠标在页面松开:处理表格拖拽改变列宽
355
- const documentMouseUp = () => {
356
- if (props.disabled) {
357
- return
358
- }
359
- if (!tableColumnResizeParams.value.element) {
360
- return
361
- }
362
- const table = getCurrentParsedomElement(editor.value!, dataRangeCaches.value, 'table')
363
- if (!table) {
364
- return
365
- }
366
- const colgroup = table.children!.find(item => {
367
- return item.parsedom == 'colgroup'
368
- })!
369
- const index = tableColumnResizeParams.value.element.parent!.children!.findIndex(el => {
370
- return el.isEqual(tableColumnResizeParams.value.element!)
371
- })
372
- const width = Number(colgroup.children![index].marks!['width'])
373
- if (!isNaN(width)) {
374
- colgroup.children![index].marks!['width'] = `${Number(((width / tableColumnResizeParams.value.element.parent!.elm!.offsetWidth) * 100).toFixed(2))}%`
375
- editor.value!.formatElementStack()
376
- editor.value!.domRender()
377
- editor.value!.rangeRender()
378
- }
379
- tableColumnResizeParams.value.element = null
380
- tableColumnResizeParams.value.start = 0
381
- }
382
- //鼠标点击页面:处理任务列表复选框勾选
383
- const documentClick = (e: Event) => {
384
- if (props.disabled) {
385
- return
386
- }
387
- //鼠标在编辑器内点击
388
- if (DapElement.isContains(contentRef.value!, <HTMLElement>e.target)) {
389
- const elm = <HTMLElement>e.target
390
- const key = DapData.get(elm, 'data-alex-editor-key')
391
- if (key) {
392
- const element = editor.value!.getElementByKey(key)!
393
- //如果是任务列表元素
394
- if (isTask(element)) {
395
- const rect = DapElement.getElementBounding(elm)
396
- //在复选框范围内
397
- if ((<MouseEvent>e).pageX >= Math.abs(rect.left) && (<MouseEvent>e).pageX <= Math.abs(rect.left + 16) && (<MouseEvent>e).pageY >= Math.abs(rect.top + elm.offsetHeight / 2 - 8) && (<MouseEvent>e).pageY <= Math.abs(rect.top + elm.offsetHeight / 2 + 8)) {
398
- //取消勾选
399
- if (element.marks!['data-editify-task'] == 'checked') {
400
- element.marks!['data-editify-task'] = 'uncheck'
401
- }
402
- //勾选
403
- else {
404
- element.marks!['data-editify-task'] = 'checked'
405
- }
406
- if (!editor.value!.range) {
407
- editor.value!.initRange()
408
- }
409
- editor.value!.range!.anchor.moveToEnd(element)
410
- editor.value!.range!.focus.moveToEnd(element)
411
- editor.value!.formatElementStack()
412
- editor.value!.domRender()
413
- editor.value!.rangeRender()
414
- }
415
- }
416
- }
417
- }
418
- }
419
- //重新定义编辑器粘贴html
420
- const handleCustomHtmlPaste = async (elements: AlexElement[]) => {
421
- const keepStyles = Object.assign(pasteKeepData.styles, props.pasteKeepStyles || {})
422
- const keepMarks = Object.assign(pasteKeepData.marks, props.pasteKeepMarks || {})
423
- //粘贴html时过滤元素的样式和属性
424
- AlexElement.flatElements(elements).forEach(el => {
425
- let marks: ObjectType = {}
426
- let styles: ObjectType = {}
427
- if (el.hasMarks()) {
428
- for (let key in keepMarks) {
429
- if (el.marks!.hasOwnProperty(key) && ((Array.isArray(keepMarks[key]) && keepMarks[key].includes(el.parsedom)) || keepMarks[key] == '*')) {
430
- marks[key] = el.marks![key]
431
- }
432
- }
433
- el.marks = marks
434
- }
435
- if (el.hasStyles() && !el.isText()) {
436
- for (let key in keepStyles) {
437
- if (el.styles!.hasOwnProperty(key) && ((Array.isArray(keepStyles[key]) && keepStyles[key].includes(el.parsedom)) || keepStyles[key] == '*')) {
438
- styles[key] = el.styles![key]
439
- }
440
- }
441
- el.styles = styles
442
- }
443
- })
444
- //如果使用了自定义粘贴html的功能
445
- if (typeof props.customHtmlPaste == 'function') {
446
- await props.customHtmlPaste.apply(this, [elements])
447
- }
448
- //默认粘贴html
449
- else {
450
- for (let i = 0; i < elements.length; i++) {
451
- editor.value!.insertElement(elements[i], false)
452
- }
453
- }
454
- }
455
- //重新定义编辑器合并元素的逻辑
456
- const handleCustomMerge = (ele: AlexElement, preEle: AlexElement) => {
457
- const uneditable = preEle.getUneditableElement()
458
- if (uneditable) {
459
- uneditable.toEmpty()
460
- } else {
461
- preEle.children!.push(...ele.children!)
462
- preEle.children!.forEach(item => {
463
- item.parent = preEle
464
- })
465
- ele.children = null
466
- }
467
- }
468
- //针对node转为元素进行额外的处理
469
- const handleCustomParseNode = (ele: AlexElement) => {
470
- if (ele.parsedom == 'code') {
471
- ele.parsedom = 'span'
472
- const marks = {
473
- 'data-editify-code': true
474
- }
475
- if (ele.hasMarks()) {
476
- Object.assign(ele.marks!, marks)
477
- } else {
478
- ele.marks = marks
479
- }
480
- }
481
- if (typeof props.customParseNode == 'function') {
482
- ele = props.customParseNode.apply(instance.proxy, [ele])
483
- }
484
- return ele
485
- }
486
- //编辑区域键盘按下:设置缩进快捷键
487
- const handleEditorKeydown = (e: Event) => {
488
- if (props.disabled) {
489
- return
490
- }
491
- //单独按下tab键
492
- if ((<KeyboardEvent>e).key.toLocaleLowerCase() == 'tab' && !(<KeyboardEvent>e).metaKey && !(<KeyboardEvent>e).shiftKey && !(<KeyboardEvent>e).ctrlKey && !(<KeyboardEvent>e).altKey && props.tab) {
493
- e.preventDefault()
494
- editor.value!.insertText(' ')
495
- editor.value!.formatElementStack()
496
- editor.value!.domRender()
497
- editor.value!.rangeRender()
498
- }
499
- //自定义键盘按下操作
500
- emits('keydown', e)
501
- }
502
- //点击编辑器:处理图片和视频的光标聚集
503
- const handleEditorClick = (e: Event) => {
504
- if (props.disabled || isSourceView.value) {
505
- return
506
- }
507
- const node = <HTMLElement>e.target
508
- //点击的是图片或者视频
509
- if (node.nodeName.toLocaleLowerCase() == 'img' || node.nodeName.toLocaleLowerCase() == 'video') {
510
- const key = Number(node.getAttribute('data-editify-element'))
511
- if (DapNumber.isNumber(key)) {
512
- const element = editor.value!.getElementByKey(key)!
513
- if (!editor.value!.range) {
514
- editor.value!.initRange()
515
- }
516
- editor.value!.range!.anchor.moveToStart(element)
517
- editor.value!.range!.focus.moveToEnd(element)
518
- editor.value!.rangeRender()
519
- }
520
- }
521
- }
522
- //编辑器的值更新
523
- const handleEditorChange = (newVal: string, oldVal: string) => {
524
- if (props.disabled) {
525
- return
526
- }
527
- //内部修改
528
- internalModify(newVal)
529
- //触发change事件
530
- emits('change', newVal, oldVal)
531
- }
532
- //编辑器失去焦点
533
- const handleEditorBlur = (val: string) => {
534
- if (props.disabled) {
535
- return
536
- }
537
- if (props.border && props.color && !isFullScreen.value) {
538
- //恢复编辑区域边框颜色
539
- bodyRef.value!.style.borderColor = ''
540
- //恢复编辑区域阴影颜色
541
- bodyRef.value!.style.boxShadow = ''
542
- //使用菜单栏的情况下恢复菜单栏的样式
543
- if (menuConfig.value.use) {
544
- menuRef.value!.$el.style.borderColor = ''
545
- menuRef.value!.$el.style.boxShadow = ''
546
- }
547
- }
548
- emits('blur', val)
549
- }
550
- //编辑器获取焦点
551
- const handleEditorFocus = (val: string) => {
552
- if (props.disabled) {
553
- return
554
- }
555
- if (props.border && props.color && !isFullScreen.value) {
556
- //编辑区域边框颜色
557
- bodyRef.value!.style.borderColor = props.color
558
- //转换颜色值
559
- const rgb = DapColor.hex2rgb(props.color)
560
- //菜单栏模式为inner
561
- if (menuConfig.value.use && menuConfig.value.mode == 'inner') {
562
- //编辑区域除顶部边框的阴影
563
- bodyRef.value!.style.boxShadow = `0 8px 8px -8px rgba(${rgb[0]},${rgb[1]},${rgb[2]},0.5),8px 0 8px -8px rgba(${rgb[0]},${rgb[1]},${rgb[2]},0.5), -8px 0 8px -8px rgba(${rgb[0]},${rgb[1]},${rgb[2]},0.5)`
564
- //菜单栏的边框颜色
565
- menuRef.value!.$el.style.borderColor = props.color
566
- //菜单栏除底部边框的阴影
567
- menuRef.value!.$el.style.boxShadow = `0 -8px 8px -8px rgba(${rgb[0]},${rgb[1]},${rgb[2]},0.5),8px 0 8px -8px rgba(${rgb[0]},${rgb[1]},${rgb[2]},0.5), -8px 0 8px -8px rgba(${rgb[0]},${rgb[1]},${rgb[2]},0.5)`
568
- }
569
- //其他菜单栏模式
570
- else if (menuConfig.value.use) {
571
- //编辑区域四边阴影
572
- bodyRef.value!.style.boxShadow = `0 0 8px rgba(${rgb[0]},${rgb[1]},${rgb[2]},0.5)`
573
- }
574
- //不使用菜单栏
575
- else {
576
- //编辑区域四边阴影
577
- bodyRef.value!.style.boxShadow = `0 0 8px rgba(${rgb[0]},${rgb[1]},${rgb[2]},0.5)`
578
- }
579
- }
580
- //获取焦点时可以使用菜单栏
581
- setTimeout(() => {
582
- canUseMenu.value = true
583
- emits('focus', val)
584
- }, 0)
585
- }
586
- //编辑器换行
587
- const handleInsertParagraph = (element: AlexElement, previousElement: AlexElement) => {
588
- //前一个块元素如果是只包含换行符的元素,并且当前块元素也是包含换行符的元素,则当前块元素转为段落
589
- if (previousElement.isOnlyHasBreak() && element.isOnlyHasBreak()) {
590
- if (previousElement.parsedom != AlexElement.BLOCK_NODE) {
591
- elementToParagraph(previousElement)
592
- editor.value!.range!.anchor.moveToStart(previousElement)
593
- editor.value!.range!.focus.moveToStart(previousElement)
594
- element.toEmpty()
595
- }
596
- }
597
- emits('insertparagraph', value.value)
598
- }
599
- //编辑器焦点更新
600
- const handleRangeUpdate = () => {
601
- if (props.disabled) {
602
- return
603
- }
604
- //如果没有range禁用菜单栏
605
- canUseMenu.value = !!editor.value!.range
606
- //没有range直接返回
607
- if (!editor.value!.range) {
608
- return
609
- }
610
- //获取光标选取范围内的元素数据,并且进行缓存
611
- dataRangeCaches.value = editor.value!.getElementsByRange()
612
-
613
- //节流写法
614
- if (rangeUpdateTimer.value) {
615
- clearTimeout(rangeUpdateTimer.value)
616
- rangeUpdateTimer.value = null
617
- }
618
- //延时200ms进行判断
619
- rangeUpdateTimer.value = setTimeout(() => {
620
- //如果使用工具条或者菜单栏
621
- if (toolbarConfig.value.use || menuConfig.value.use) {
622
- //如果使用工具条
623
- if (toolbarConfig.value.use) {
624
- handleToolbar()
625
- }
626
- //如果使用菜单栏
627
- if (menuConfig.value.use) {
628
- menuRef.value!.handleRangeUpdate()
629
- }
630
- }
631
- }, 200)
632
- emits('rangeupdate')
633
- }
634
- //编辑器部分删除情景(在编辑器起始处)
635
- const handleDeleteInStart = (element: AlexElement) => {
636
- if (element.isBlock()) {
637
- elementToParagraph(element)
638
- }
639
- }
640
- //编辑器删除完成后事件
641
- const handleDeleteComplete = () => {
642
- const uneditable = editor.value!.range!.anchor.element.getUneditableElement()
643
- if (uneditable) {
644
- uneditable.toEmpty()
645
- }
646
- }
647
- //编辑器dom渲染
648
- const handleAfterRender = () => {
649
- //设定视频高度
650
- setVideoHeight()
651
- emits('updateview')
652
- }
653
- //api:光标设置到文档底部
654
- const collapseToEnd = () => {
655
- if (props.disabled) {
656
- return
657
- }
658
- editor.value!.collapseToEnd()
659
- editor.value!.rangeRender()
660
- DapElement.setScrollTop({
661
- el: contentRef.value!,
662
- number: 1000000,
663
- time: 0
664
- })
665
- }
666
- //api:光标设置到文档头部
667
- const collapseToStart = () => {
668
- if (props.disabled) {
669
- return
670
- }
671
- editor.value!.collapseToStart()
672
- editor.value!.rangeRender()
673
- nextTick(() => {
674
- DapElement.setScrollTop({
675
- el: contentRef.value!,
676
- number: 0,
677
- time: 0
678
- })
679
- })
680
- }
681
- //api:撤销
682
- const undo = () => {
683
- if (props.disabled) {
684
- return
685
- }
686
- const historyRecord = editor.value!.history.get(-1)
687
- if (historyRecord) {
688
- editor.value!.history.current = historyRecord.current
689
- editor.value!.stack = historyRecord.stack
690
- editor.value!.range = historyRecord.range
691
- editor.value!.formatElementStack()
692
- editor.value!.domRender(true)
693
- editor.value!.rangeRender()
694
- }
695
- }
696
- //api:重做
697
- const redo = () => {
698
- if (props.disabled) {
699
- return
700
- }
701
- const historyRecord = editor.value!.history.get(1)
702
- if (historyRecord) {
703
- editor.value!.history.current = historyRecord.current
704
- editor.value!.stack = historyRecord.stack
705
- editor.value!.range = historyRecord.range
706
- editor.value!.formatElementStack()
707
- editor.value!.domRender(true)
708
- editor.value!.rangeRender()
709
- }
710
- }
711
-
712
- //监听编辑的值变更
713
- watch(
714
- () => value.value,
715
- newVal => {
716
- //内部修改不处理
717
- if (isModelChange.value) {
718
- return
719
- }
720
- //如果是外部修改,需要重新渲染编辑器
721
- editor.value!.stack = editor.value!.parseHtml(newVal)
722
- editor.value!.range = null
723
- editor.value!.formatElementStack()
724
- editor.value!.domRender()
725
- editor.value!.rangeRender()
726
- contentRef.value!.blur()
727
- }
728
- )
729
- //代码视图切换
730
- watch(
731
- () => isSourceView.value,
732
- newVal => {
733
- if (toolbarConfig.value.use) {
734
- if (newVal) {
735
- hideToolbar()
736
- } else {
737
- handleToolbar()
738
- }
739
- }
740
- }
741
- )
742
- //监听disabled
743
- watch(
744
- () => props.disabled,
745
- newVal => {
746
- if (newVal) {
747
- editor.value!.setDisabled()
748
- } else {
749
- editor.value!.setEnabled()
750
- }
751
- }
752
- )
753
-
754
- onMounted(() => {
755
- //创建编辑器
756
- createEditor()
757
- //监听滚动隐藏工具条
758
- handleScroll()
759
- //鼠标按下监听
760
- DapEvent.on(document.documentElement, `mousedown.editify_${instance.uid}`, documentMouseDown)
761
- //鼠标移动监听
762
- DapEvent.on(document.documentElement, `mousemove.editify_${instance.uid}`, documentMouseMove)
763
- //鼠标松开监听
764
- DapEvent.on(document.documentElement, `mouseup.editify_${instance.uid}`, documentMouseUp)
765
- //鼠标点击箭头
766
- DapEvent.on(document.documentElement, `click.editify_${instance.uid}`, documentClick)
767
- //监听窗口改变
768
- DapEvent.on(window, `resize.editify_${instance.uid}`, setVideoHeight)
769
- })
770
-
771
- onBeforeUnmount(() => {
772
- //卸载绑定在滚动元素上的事件
773
- removeScrollHandle()
774
- //卸载绑定在document.documentElement上的事件
775
- DapEvent.off(document.documentElement, `mousedown.editify_${instance.uid} mousemove.editify_${instance.uid} mouseup.editify_${instance.uid} click.editify_${instance.uid}`)
776
- //卸载绑定在window上的事件
777
- DapEvent.off(window, `resize.editify_${instance.uid}`)
778
- //销毁编辑器
779
- editor.value!.destroy()
780
- })
781
-
782
- provide('editify', instance)
783
- provide('isSourceView', isSourceView)
784
- provide('isFullScreen', isFullScreen)
785
- provide('canUseMenu', canUseMenu)
786
- provide('editor', editor)
787
- provide('dataRangeCaches', dataRangeCaches)
788
- provide('showBorder', showBorder)
789
-
790
- defineExpose({
791
- editor,
792
- isSourceView,
793
- isFullScreen,
794
- canUseMenu,
795
- dataRangeCaches,
796
- textValue,
797
- collapseToEnd,
798
- collapseToStart,
799
- undo,
800
- redo
801
- })
802
- </script>
803
- <style scoped src="./editify.less"></style>
1
+ <template>
2
+ <div class="editify" :class="{ fullscreen: isFullScreen, autoheight: !isFullScreen && autoheight }" ref="elRef">
3
+ <!-- 菜单区域 -->
4
+ <Menu v-if="menuConfig.use" :config="menuConfig" :color="color" ref="menuRef"></Menu>
5
+ <!-- 编辑层,与编辑区域宽高相同必须适配 -->
6
+ <div ref="bodyRef" class="editify-body" :class="{ border: showBorder, menu_inner: menuConfig.use && menuConfig.mode == 'inner' }" :data-editify-uid="instance.uid">
7
+ <!-- 编辑器 -->
8
+ <div ref="contentRef" class="editify-content" :class="{ placeholder: showPlaceholder, disabled: disabled }" @keydown="handleEditorKeydown" @click="handleEditorClick" @compositionstart="isInputChinese = true" @compositionend="isInputChinese = false" :data-editify-placeholder="placeholder"></div>
9
+ <!-- 代码视图 -->
10
+ <textarea v-if="isSourceView" :value="value" readonly class="editify-source" />
11
+ <!-- 工具条 -->
12
+ <Toolbar ref="toolbarRef" v-model="toolbarOptions.show" :node="toolbarOptions.node!" :type="toolbarOptions.type" :config="toolbarConfig" :color="color"></Toolbar>
13
+ </div>
14
+ <!-- 编辑器尾部 -->
15
+ <div v-if="showWordLength" class="editify-footer" :class="{ fullscreen: isFullScreen && !isSourceView }" ref="footerRef">
16
+ <!-- 字数统计 -->
17
+ <div class="editify-footer-words">{{ $editTrans('totalWordCount') }}{{ textValue.length }}</div>
18
+ </div>
19
+ </div>
20
+ </template>
21
+ <script setup lang="ts">
22
+ import { computed, getCurrentInstance, nextTick, onBeforeUnmount, onMounted, provide, ref, watch } from 'vue'
23
+ import { AlexEditor, AlexElement, AlexElementRangeType, AlexElementsRangeType } from 'alex-editor'
24
+ import { element as DapElement, event as DapEvent, data as DapData, number as DapNumber, color as DapColor } from 'dap-util'
25
+ import { pasteKeepData, mergeObject, getToolbarConfig, getMenuConfig, MenuConfigType, ObjectType, ToolbarConfigType } from '../core/tool'
26
+ import { parseList, orderdListHandle, mediaHandle, tableHandle, preHandle, specialInblockHandle } from '../core/rule'
27
+ import { isTask, elementToParagraph, getCurrentParsedomElement, hasTableInRange, hasLinkInRange, hasPreInRange, hasImageInRange, hasVideoInRange } from '../core/function'
28
+ import Toolbar from '../components/toolbar/toolbar.vue'
29
+ import Menu from '../components/menu/menu.vue'
30
+ import Layer from '../components/layer/layer.vue'
31
+ import { EditifyProps, EditifyTableColumnResizeParamsType, EditifyToolbarOptionsType } from './props'
32
+ import { trans } from '../locale'
33
+ import { LanguagesItemType } from '../hljs'
34
+
35
+ //定义组件名称
36
+ defineOptions({
37
+ name: 'editify'
38
+ })
39
+ //获取实例
40
+ const instance = getCurrentInstance()!
41
+ //属性
42
+ const props = defineProps(EditifyProps)
43
+ //事件
44
+ const emits = defineEmits(['update:modelValue', 'focus', 'blur', 'change', 'keydown', 'insertparagraph', 'rangeupdate', 'updateview'])
45
+
46
+ //设置国际化方法
47
+ const $editTrans = trans(props.locale || 'zh_CN')
48
+ //对子孙后代组件提供国际化方法
49
+ provide('$editTrans', $editTrans)
50
+
51
+ //是否编辑器内部修改值
52
+ const isModelChange = ref<boolean>(false)
53
+ //是否正在输入中文
54
+ const isInputChinese = ref<boolean>(false)
55
+ //工具条和菜单栏判定延时器
56
+ const rangeUpdateTimer = ref<any>(null)
57
+ //表格列宽拖拽记录数据
58
+ const tableColumnResizeParams = ref<EditifyTableColumnResizeParamsType>({
59
+ element: null, //被拖拽的td
60
+ start: 0 //水平方向起点位置
61
+ })
62
+ //工具条参数配置
63
+ const toolbarOptions = ref<EditifyToolbarOptionsType>({
64
+ //是否显示工具条
65
+ show: false,
66
+ //关联元素
67
+ node: null,
68
+ //类型
69
+ type: 'text'
70
+ })
71
+
72
+ const menuRef = ref<InstanceType<typeof Menu> | null>(null)
73
+ const bodyRef = ref<HTMLElement | null>(null)
74
+ const contentRef = ref<HTMLElement | null>(null)
75
+ const toolbarRef = ref<InstanceType<typeof Toolbar> | null>(null)
76
+ const footerRef = ref<HTMLElement | null>(null)
77
+ const elRef = ref<HTMLElement | null>(null)
78
+
79
+ //编辑器对象
80
+ const editor = ref<AlexEditor | null>(null)
81
+ //是否代码视图
82
+ const isSourceView = ref<boolean>(false)
83
+ //是否全屏
84
+ const isFullScreen = ref<boolean>(false)
85
+ //菜单栏是否可以使用标识
86
+ const canUseMenu = ref<boolean>(false)
87
+ //光标选取范围内的元素数组
88
+ const dataRangeCaches = ref<AlexElementsRangeType>({
89
+ flatList: [],
90
+ list: []
91
+ })
92
+
93
+ //编辑器的值
94
+ const value = computed<string>({
95
+ set(val) {
96
+ emits('update:modelValue', val)
97
+ },
98
+ get() {
99
+ return props.modelValue || '<p><br></p>'
100
+ }
101
+ })
102
+ //编辑器的纯文本值
103
+ const textValue = computed<string>(() => {
104
+ return (<HTMLElement>DapElement.string2dom(`<div>${value.value}</div>`)).innerText
105
+ })
106
+ //是否显示占位符
107
+ const showPlaceholder = computed<boolean>(() => {
108
+ if (editor.value) {
109
+ if (value.value && editor.value.stack.length == 1 && editor.value.stack[0].type == 'block' && editor.value.stack[0].parsedom == AlexElement.BLOCK_NODE && editor.value.stack[0].isOnlyHasBreak() && !editor.value.stack[0].hasStyles() && !editor.value.stack[0].hasMarks()) {
110
+ return !isInputChinese.value
111
+ }
112
+ }
113
+ return false
114
+ })
115
+ //是否显示边框
116
+ const showBorder = computed<boolean>(() => {
117
+ //全屏模式下不显示边框
118
+ if (isFullScreen.value) {
119
+ return false
120
+ }
121
+ return props.border
122
+ })
123
+ //最终生效的工具栏配置
124
+ const toolbarConfig = computed<ToolbarConfigType>(() => {
125
+ return <ToolbarConfigType>mergeObject(getToolbarConfig($editTrans, props.locale), props.toolbar || {})
126
+ })
127
+ //最终生效的菜单栏配置
128
+ const menuConfig = computed<MenuConfigType>(() => {
129
+ return <MenuConfigType>mergeObject(getMenuConfig($editTrans, props.locale), props.menu || {})
130
+ })
131
+
132
+ //编辑器内部修改值的方法
133
+ const internalModify = (val: string) => {
134
+ isModelChange.value = true
135
+ value.value = val
136
+ nextTick(() => {
137
+ isModelChange.value = false
138
+ })
139
+ }
140
+ //隐藏工具条
141
+ const hideToolbar = () => {
142
+ toolbarOptions.value.show = false
143
+ toolbarOptions.value.node = null
144
+ }
145
+ //监听滚动隐藏工具条
146
+ const handleScroll = () => {
147
+ const setScroll = (el: HTMLElement) => {
148
+ DapEvent.on(el, `scroll.editify_${instance.uid}`, () => {
149
+ if (toolbarConfig.value.use && toolbarOptions.value.show) {
150
+ hideToolbar()
151
+ }
152
+ })
153
+ if (el.parentNode) {
154
+ setScroll(<HTMLElement>el.parentNode)
155
+ }
156
+ }
157
+ setScroll(contentRef.value!)
158
+ }
159
+ //移除上述滚动事件的监听
160
+ const removeScrollHandle = () => {
161
+ const removeScroll = (el: HTMLElement) => {
162
+ DapEvent.off(el, `scroll.editify_${instance.uid}`)
163
+ if (el.parentNode) {
164
+ removeScroll(<HTMLElement>el.parentNode)
165
+ }
166
+ }
167
+ removeScroll(contentRef.value!)
168
+ }
169
+ //工具条显示判断
170
+ const handleToolbar = () => {
171
+ if (props.disabled || isSourceView.value) {
172
+ return
173
+ }
174
+ hideToolbar()
175
+ nextTick(() => {
176
+ const table = getCurrentParsedomElement(editor.value!, dataRangeCaches.value, 'table')
177
+ const pre = getCurrentParsedomElement(editor.value!, dataRangeCaches.value, 'pre')
178
+ const link = getCurrentParsedomElement(editor.value!, dataRangeCaches.value, 'a')
179
+ const image = getCurrentParsedomElement(editor.value!, dataRangeCaches.value, 'img')
180
+ const video = getCurrentParsedomElement(editor.value!, dataRangeCaches.value, 'video')
181
+ if (link) {
182
+ toolbarOptions.value.type = 'link'
183
+ toolbarOptions.value.node = `[data-editify-uid="${instance.uid}"] [data-editify-element="${link.key}"]`
184
+ if (toolbarOptions.value.show) {
185
+ ;(<InstanceType<typeof Layer>>toolbarRef.value!.$refs.layerRef).setPosition()
186
+ } else {
187
+ toolbarOptions.value.show = true
188
+ }
189
+ } else if (image) {
190
+ toolbarOptions.value.type = 'image'
191
+ toolbarOptions.value.node = `[data-editify-uid="${instance.uid}"] [data-editify-element="${image.key}"]`
192
+ if (toolbarOptions.value.show) {
193
+ ;(<InstanceType<typeof Layer>>toolbarRef.value!.$refs.layerRef).setPosition()
194
+ } else {
195
+ toolbarOptions.value.show = true
196
+ }
197
+ } else if (video) {
198
+ toolbarOptions.value.type = 'video'
199
+ toolbarOptions.value.node = `[data-editify-uid="${instance.uid}"] [data-editify-element="${video.key}"]`
200
+ if (toolbarOptions.value.show) {
201
+ ;(<InstanceType<typeof Layer>>toolbarRef.value!.$refs.layerRef).setPosition()
202
+ } else {
203
+ toolbarOptions.value.show = true
204
+ }
205
+ } else if (table) {
206
+ toolbarOptions.value.type = 'table'
207
+ toolbarOptions.value.node = `[data-editify-uid="${instance.uid}"] [data-editify-element="${table.key}"]`
208
+ if (toolbarOptions.value.show) {
209
+ ;(<InstanceType<typeof Layer>>toolbarRef.value!.$refs.layerRef).setPosition()
210
+ } else {
211
+ toolbarOptions.value.show = true
212
+ }
213
+ } else if (pre) {
214
+ toolbarOptions.value.type = 'codeBlock'
215
+ toolbarOptions.value.node = `[data-editify-uid="${instance.uid}"] [data-editify-element="${pre.key}"]`
216
+ if (toolbarOptions.value.show) {
217
+ ;(<InstanceType<typeof Layer>>toolbarRef.value!.$refs.layerRef).setPosition()
218
+ } else {
219
+ toolbarOptions.value.show = true
220
+ }
221
+ } else {
222
+ const result = dataRangeCaches.value.flatList.filter((item: AlexElementRangeType) => {
223
+ return item.element.isText()
224
+ })
225
+ if (result.length && !hasTableInRange(editor.value!, dataRangeCaches.value) && !hasPreInRange(editor.value!, dataRangeCaches.value) && !hasLinkInRange(editor.value!, dataRangeCaches.value) && !hasImageInRange(editor.value!, dataRangeCaches.value) && !hasVideoInRange(editor.value!, dataRangeCaches.value)) {
226
+ toolbarOptions.value.type = 'text'
227
+ if (toolbarOptions.value.show) {
228
+ ;(<InstanceType<typeof Layer>>toolbarRef.value!.$refs.layerRef).setPosition()
229
+ } else {
230
+ toolbarOptions.value.show = true
231
+ }
232
+ }
233
+ }
234
+ })
235
+ }
236
+ //初始创建编辑器
237
+ const createEditor = () => {
238
+ //创建编辑器
239
+ editor.value = new AlexEditor(contentRef.value!, {
240
+ value: value.value,
241
+ disabled: props.disabled,
242
+ renderRules: [
243
+ el => {
244
+ parseList(editor.value!, el)
245
+ },
246
+ el => {
247
+ orderdListHandle(editor.value!, el)
248
+ },
249
+ el => {
250
+ mediaHandle(editor.value!, el)
251
+ },
252
+ el => {
253
+ tableHandle(editor.value!, el)
254
+ },
255
+ el => {
256
+ preHandle(editor.value!, el, !!(toolbarConfig.value?.use && toolbarConfig.value?.codeBlock?.languages?.show), <(string | LanguagesItemType)[]>toolbarConfig.value?.codeBlock?.languages?.options)
257
+ },
258
+ el => {
259
+ specialInblockHandle(editor.value!, el)
260
+ },
261
+ ...props.renderRules
262
+ ],
263
+ allowCopy: props.allowCopy,
264
+ allowPaste: props.allowPaste,
265
+ allowCut: props.allowCut,
266
+ allowPasteHtml: props.allowPasteHtml,
267
+ customTextPaste: props.customTextPaste,
268
+ customImagePaste: props.customImagePaste,
269
+ customVideoPaste: props.customVideoPaste,
270
+ customFilePaste: props.customFilePaste,
271
+ customHtmlPaste: handleCustomHtmlPaste,
272
+ customMerge: handleCustomMerge,
273
+ customParseNode: handleCustomParseNode
274
+ })
275
+ //编辑器渲染后会有一个渲染过程,会改变内容,因此重新获取内容的值来设置value
276
+ internalModify(editor.value.value)
277
+ //设置监听事件
278
+ editor.value.on('change', handleEditorChange)
279
+ editor.value.on('focus', handleEditorFocus)
280
+ editor.value.on('blur', handleEditorBlur)
281
+ editor.value.on('insertParagraph', handleInsertParagraph)
282
+ editor.value.on('rangeUpdate', handleRangeUpdate)
283
+ editor.value.on('deleteInStart', handleDeleteInStart)
284
+ editor.value.on('deleteComplete', handleDeleteComplete)
285
+ editor.value.on('afterRender', handleAfterRender)
286
+ //格式化和dom渲染
287
+ editor.value.formatElementStack()
288
+ editor.value.domRender()
289
+ //自动获取焦点
290
+ if (props.autofocus && !isSourceView.value && !props.disabled) {
291
+ collapseToEnd()
292
+ }
293
+ }
294
+ //设定编辑器内的视频高度
295
+ const setVideoHeight = () => {
296
+ contentRef.value!.querySelectorAll('video').forEach(video => {
297
+ video.style.height = video.offsetWidth / props.videoRatio + 'px'
298
+ })
299
+ }
300
+ //鼠标在页面按下:处理表格拖拽改变列宽和菜单栏是否使用判断
301
+ const documentMouseDown = (e: Event) => {
302
+ if (props.disabled) {
303
+ return
304
+ }
305
+ //鼠标在编辑器内按下
306
+ if (DapElement.isContains(contentRef.value!, <HTMLElement>e.target)) {
307
+ const elm = <HTMLElement>e.target
308
+ const key = DapData.get(elm, 'data-alex-editor-key')
309
+ if (key) {
310
+ const element = editor.value!.getElementByKey(key)
311
+ if (element && element.parsedom == 'td') {
312
+ const length = element.parent!.children!.length
313
+ //最后一个td不设置
314
+ if (element.parent!.children![length - 1].isEqual(element)) {
315
+ return
316
+ }
317
+ const rect = DapElement.getElementBounding(elm)
318
+ //在可拖拽范围内
319
+ if ((<MouseEvent>e).pageX >= Math.abs(rect.left + elm.offsetWidth - 5) && (<MouseEvent>e).pageX <= Math.abs(rect.left + elm.offsetWidth + 5)) {
320
+ tableColumnResizeParams.value.element = element
321
+ tableColumnResizeParams.value.start = (<MouseEvent>e).pageX
322
+ }
323
+ }
324
+ }
325
+ }
326
+ //如果点击了除编辑器外的地方,菜单栏不可使用
327
+ if (!DapElement.isContains(elRef.value!, <HTMLElement>e.target) && !isSourceView.value) {
328
+ canUseMenu.value = false
329
+ }
330
+ }
331
+ //鼠标在页面移动:处理表格拖拽改变列宽
332
+ const documentMouseMove = (e: Event) => {
333
+ if (props.disabled) {
334
+ return
335
+ }
336
+ if (!tableColumnResizeParams.value.element) {
337
+ return
338
+ }
339
+ const table = getCurrentParsedomElement(editor.value!, dataRangeCaches.value, 'table')
340
+ if (!table) {
341
+ return
342
+ }
343
+ const colgroup = table.children!.find(item => {
344
+ return item.parsedom == 'colgroup'
345
+ })!
346
+ const index = tableColumnResizeParams.value.element.parent!.children!.findIndex(el => {
347
+ return el.isEqual(tableColumnResizeParams.value.element!)
348
+ })
349
+ const width = `${tableColumnResizeParams.value.element.elm!.offsetWidth + (<MouseEvent>e).pageX - tableColumnResizeParams.value.start}`
350
+ colgroup.children![index].marks!['width'] = width
351
+ colgroup.children![index].elm!.setAttribute('width', width)
352
+ tableColumnResizeParams.value.start = (<MouseEvent>e).pageX
353
+ }
354
+ //鼠标在页面松开:处理表格拖拽改变列宽
355
+ const documentMouseUp = () => {
356
+ if (props.disabled) {
357
+ return
358
+ }
359
+ if (!tableColumnResizeParams.value.element) {
360
+ return
361
+ }
362
+ const table = getCurrentParsedomElement(editor.value!, dataRangeCaches.value, 'table')
363
+ if (!table) {
364
+ return
365
+ }
366
+ const colgroup = table.children!.find(item => {
367
+ return item.parsedom == 'colgroup'
368
+ })!
369
+ const index = tableColumnResizeParams.value.element.parent!.children!.findIndex(el => {
370
+ return el.isEqual(tableColumnResizeParams.value.element!)
371
+ })
372
+ const width = Number(colgroup.children![index].marks!['width'])
373
+ if (!isNaN(width)) {
374
+ colgroup.children![index].marks!['width'] = `${Number(((width / tableColumnResizeParams.value.element.parent!.elm!.offsetWidth) * 100).toFixed(2))}%`
375
+ editor.value!.formatElementStack()
376
+ editor.value!.domRender()
377
+ editor.value!.rangeRender()
378
+ }
379
+ tableColumnResizeParams.value.element = null
380
+ tableColumnResizeParams.value.start = 0
381
+ }
382
+ //鼠标点击页面:处理任务列表复选框勾选
383
+ const documentClick = (e: Event) => {
384
+ if (props.disabled) {
385
+ return
386
+ }
387
+ //鼠标在编辑器内点击
388
+ if (DapElement.isContains(contentRef.value!, <HTMLElement>e.target)) {
389
+ const elm = <HTMLElement>e.target
390
+ const key = DapData.get(elm, 'data-alex-editor-key')
391
+ if (key) {
392
+ const element = editor.value!.getElementByKey(key)!
393
+ //如果是任务列表元素
394
+ if (isTask(element)) {
395
+ const rect = DapElement.getElementBounding(elm)
396
+ //在复选框范围内
397
+ if ((<MouseEvent>e).pageX >= Math.abs(rect.left) && (<MouseEvent>e).pageX <= Math.abs(rect.left + 16) && (<MouseEvent>e).pageY >= Math.abs(rect.top + elm.offsetHeight / 2 - 8) && (<MouseEvent>e).pageY <= Math.abs(rect.top + elm.offsetHeight / 2 + 8)) {
398
+ //取消勾选
399
+ if (element.marks!['data-editify-task'] == 'checked') {
400
+ element.marks!['data-editify-task'] = 'uncheck'
401
+ }
402
+ //勾选
403
+ else {
404
+ element.marks!['data-editify-task'] = 'checked'
405
+ }
406
+ if (!editor.value!.range) {
407
+ editor.value!.initRange()
408
+ }
409
+ editor.value!.range!.anchor.moveToEnd(element)
410
+ editor.value!.range!.focus.moveToEnd(element)
411
+ editor.value!.formatElementStack()
412
+ editor.value!.domRender()
413
+ editor.value!.rangeRender()
414
+ }
415
+ }
416
+ }
417
+ }
418
+ }
419
+ //重新定义编辑器粘贴html
420
+ const handleCustomHtmlPaste = async (elements: AlexElement[]) => {
421
+ const keepStyles = Object.assign(pasteKeepData.styles, props.pasteKeepStyles || {})
422
+ const keepMarks = Object.assign(pasteKeepData.marks, props.pasteKeepMarks || {})
423
+ //粘贴html时过滤元素的样式和属性
424
+ AlexElement.flatElements(elements).forEach(el => {
425
+ let marks: ObjectType = {}
426
+ let styles: ObjectType = {}
427
+ if (el.hasMarks()) {
428
+ for (let key in keepMarks) {
429
+ if (el.marks!.hasOwnProperty(key) && ((Array.isArray(keepMarks[key]) && keepMarks[key].includes(el.parsedom)) || keepMarks[key] == '*')) {
430
+ marks[key] = el.marks![key]
431
+ }
432
+ }
433
+ el.marks = marks
434
+ }
435
+ if (el.hasStyles() && !el.isText()) {
436
+ for (let key in keepStyles) {
437
+ if (el.styles!.hasOwnProperty(key) && ((Array.isArray(keepStyles[key]) && keepStyles[key].includes(el.parsedom)) || keepStyles[key] == '*')) {
438
+ styles[key] = el.styles![key]
439
+ }
440
+ }
441
+ el.styles = styles
442
+ }
443
+ })
444
+ //如果使用了自定义粘贴html的功能
445
+ if (typeof props.customHtmlPaste == 'function') {
446
+ await props.customHtmlPaste.apply(this, [elements])
447
+ }
448
+ //默认粘贴html
449
+ else {
450
+ for (let i = 0; i < elements.length; i++) {
451
+ editor.value!.insertElement(elements[i], false)
452
+ }
453
+ }
454
+ }
455
+ //重新定义编辑器合并元素的逻辑
456
+ const handleCustomMerge = (ele: AlexElement, preEle: AlexElement) => {
457
+ const uneditable = preEle.getUneditableElement()
458
+ if (uneditable) {
459
+ uneditable.toEmpty()
460
+ } else {
461
+ preEle.children!.push(...ele.children!)
462
+ preEle.children!.forEach(item => {
463
+ item.parent = preEle
464
+ })
465
+ ele.children = null
466
+ }
467
+ }
468
+ //针对node转为元素进行额外的处理
469
+ const handleCustomParseNode = (ele: AlexElement) => {
470
+ if (ele.parsedom == 'code') {
471
+ ele.parsedom = 'span'
472
+ const marks = {
473
+ 'data-editify-code': true
474
+ }
475
+ if (ele.hasMarks()) {
476
+ Object.assign(ele.marks!, marks)
477
+ } else {
478
+ ele.marks = marks
479
+ }
480
+ }
481
+ if (typeof props.customParseNode == 'function') {
482
+ ele = props.customParseNode.apply(instance.proxy, [ele])
483
+ }
484
+ return ele
485
+ }
486
+ //编辑区域键盘按下:设置缩进快捷键
487
+ const handleEditorKeydown = (e: Event) => {
488
+ if (props.disabled) {
489
+ return
490
+ }
491
+ //单独按下tab键
492
+ if ((<KeyboardEvent>e).key.toLocaleLowerCase() == 'tab' && !(<KeyboardEvent>e).metaKey && !(<KeyboardEvent>e).shiftKey && !(<KeyboardEvent>e).ctrlKey && !(<KeyboardEvent>e).altKey && props.tab) {
493
+ e.preventDefault()
494
+ editor.value!.insertText(' ')
495
+ editor.value!.formatElementStack()
496
+ editor.value!.domRender()
497
+ editor.value!.rangeRender()
498
+ }
499
+ //自定义键盘按下操作
500
+ emits('keydown', e)
501
+ }
502
+ //点击编辑器:处理图片和视频的光标聚集
503
+ const handleEditorClick = (e: Event) => {
504
+ if (props.disabled || isSourceView.value) {
505
+ return
506
+ }
507
+ const node = <HTMLElement>e.target
508
+ //点击的是图片或者视频
509
+ if (node.nodeName.toLocaleLowerCase() == 'img' || node.nodeName.toLocaleLowerCase() == 'video') {
510
+ const key = Number(node.getAttribute('data-editify-element'))
511
+ if (DapNumber.isNumber(key)) {
512
+ const element = editor.value!.getElementByKey(key)!
513
+ if (!editor.value!.range) {
514
+ editor.value!.initRange()
515
+ }
516
+ editor.value!.range!.anchor.moveToStart(element)
517
+ editor.value!.range!.focus.moveToEnd(element)
518
+ editor.value!.rangeRender()
519
+ }
520
+ }
521
+ }
522
+ //编辑器的值更新
523
+ const handleEditorChange = (newVal: string, oldVal: string) => {
524
+ if (props.disabled) {
525
+ return
526
+ }
527
+ //内部修改
528
+ internalModify(newVal)
529
+ //触发change事件
530
+ emits('change', newVal, oldVal)
531
+ }
532
+ //编辑器失去焦点
533
+ const handleEditorBlur = (val: string) => {
534
+ if (props.disabled) {
535
+ return
536
+ }
537
+ if (props.border && props.color && !isFullScreen.value) {
538
+ //恢复编辑区域边框颜色
539
+ bodyRef.value!.style.borderColor = ''
540
+ //恢复编辑区域阴影颜色
541
+ bodyRef.value!.style.boxShadow = ''
542
+ //使用菜单栏的情况下恢复菜单栏的样式
543
+ if (menuConfig.value.use) {
544
+ menuRef.value!.$el.style.borderColor = ''
545
+ menuRef.value!.$el.style.boxShadow = ''
546
+ }
547
+ }
548
+ emits('blur', val)
549
+ }
550
+ //编辑器获取焦点
551
+ const handleEditorFocus = (val: string) => {
552
+ if (props.disabled) {
553
+ return
554
+ }
555
+ if (props.border && props.color && !isFullScreen.value) {
556
+ //编辑区域边框颜色
557
+ bodyRef.value!.style.borderColor = props.color
558
+ //转换颜色值
559
+ const rgb = DapColor.hex2rgb(props.color)
560
+ //菜单栏模式为inner
561
+ if (menuConfig.value.use && menuConfig.value.mode == 'inner') {
562
+ //编辑区域除顶部边框的阴影
563
+ bodyRef.value!.style.boxShadow = `0 8px 8px -8px rgba(${rgb[0]},${rgb[1]},${rgb[2]},0.5),8px 0 8px -8px rgba(${rgb[0]},${rgb[1]},${rgb[2]},0.5), -8px 0 8px -8px rgba(${rgb[0]},${rgb[1]},${rgb[2]},0.5)`
564
+ //菜单栏的边框颜色
565
+ menuRef.value!.$el.style.borderColor = props.color
566
+ //菜单栏除底部边框的阴影
567
+ menuRef.value!.$el.style.boxShadow = `0 -8px 8px -8px rgba(${rgb[0]},${rgb[1]},${rgb[2]},0.5),8px 0 8px -8px rgba(${rgb[0]},${rgb[1]},${rgb[2]},0.5), -8px 0 8px -8px rgba(${rgb[0]},${rgb[1]},${rgb[2]},0.5)`
568
+ }
569
+ //其他菜单栏模式
570
+ else if (menuConfig.value.use) {
571
+ //编辑区域四边阴影
572
+ bodyRef.value!.style.boxShadow = `0 0 8px rgba(${rgb[0]},${rgb[1]},${rgb[2]},0.5)`
573
+ }
574
+ //不使用菜单栏
575
+ else {
576
+ //编辑区域四边阴影
577
+ bodyRef.value!.style.boxShadow = `0 0 8px rgba(${rgb[0]},${rgb[1]},${rgb[2]},0.5)`
578
+ }
579
+ }
580
+ //获取焦点时可以使用菜单栏
581
+ setTimeout(() => {
582
+ canUseMenu.value = true
583
+ emits('focus', val)
584
+ }, 0)
585
+ }
586
+ //编辑器换行
587
+ const handleInsertParagraph = (element: AlexElement, previousElement: AlexElement) => {
588
+ //两个元素不一致,则表示不在代码块样式内
589
+ if (!element.isEqual(previousElement)) {
590
+ //前一个块元素如果是只包含换行符的元素,并且当前块元素也是包含换行符的元素,则当前块元素转为段落
591
+ if (previousElement.isOnlyHasBreak() && element.isOnlyHasBreak()) {
592
+ if (previousElement.parsedom != AlexElement.BLOCK_NODE) {
593
+ elementToParagraph(previousElement)
594
+ editor.value!.range!.anchor.moveToStart(previousElement)
595
+ editor.value!.range!.focus.moveToStart(previousElement)
596
+ element.toEmpty()
597
+ }
598
+ }
599
+ //如果当前换行元素是任务列表则改为不勾选状态
600
+ if (isTask(element)) {
601
+ element.marks!['data-editify-task'] = 'uncheck'
602
+ }
603
+ }
604
+ emits('insertparagraph', value.value)
605
+ }
606
+ //编辑器焦点更新
607
+ const handleRangeUpdate = () => {
608
+ if (props.disabled) {
609
+ return
610
+ }
611
+ //如果没有range禁用菜单栏
612
+ canUseMenu.value = !!editor.value!.range
613
+ //没有range直接返回
614
+ if (!editor.value!.range) {
615
+ return
616
+ }
617
+ //获取光标选取范围内的元素数据,并且进行缓存
618
+ dataRangeCaches.value = editor.value!.getElementsByRange()
619
+
620
+ //节流写法
621
+ if (rangeUpdateTimer.value) {
622
+ clearTimeout(rangeUpdateTimer.value)
623
+ rangeUpdateTimer.value = null
624
+ }
625
+ //延时200ms进行判断
626
+ rangeUpdateTimer.value = setTimeout(() => {
627
+ //如果使用工具条或者菜单栏
628
+ if (toolbarConfig.value.use || menuConfig.value.use) {
629
+ //如果使用工具条
630
+ if (toolbarConfig.value.use) {
631
+ handleToolbar()
632
+ }
633
+ //如果使用菜单栏
634
+ if (menuConfig.value.use) {
635
+ menuRef.value!.handleRangeUpdate()
636
+ }
637
+ }
638
+ }, 200)
639
+ emits('rangeupdate')
640
+ }
641
+ //编辑器部分删除情景(在编辑器起始处)
642
+ const handleDeleteInStart = (element: AlexElement) => {
643
+ if (element.isBlock()) {
644
+ elementToParagraph(element)
645
+ }
646
+ }
647
+ //编辑器删除完成后事件
648
+ const handleDeleteComplete = () => {
649
+ const uneditable = editor.value!.range!.anchor.element.getUneditableElement()
650
+ if (uneditable) {
651
+ uneditable.toEmpty()
652
+ }
653
+ }
654
+ //编辑器dom渲染
655
+ const handleAfterRender = () => {
656
+ //设定视频高度
657
+ setVideoHeight()
658
+ emits('updateview')
659
+ }
660
+ //api:光标设置到文档底部
661
+ const collapseToEnd = () => {
662
+ if (props.disabled) {
663
+ return
664
+ }
665
+ editor.value!.collapseToEnd()
666
+ editor.value!.rangeRender()
667
+ DapElement.setScrollTop({
668
+ el: contentRef.value!,
669
+ number: 1000000,
670
+ time: 0
671
+ })
672
+ }
673
+ //api:光标设置到文档头部
674
+ const collapseToStart = () => {
675
+ if (props.disabled) {
676
+ return
677
+ }
678
+ editor.value!.collapseToStart()
679
+ editor.value!.rangeRender()
680
+ nextTick(() => {
681
+ DapElement.setScrollTop({
682
+ el: contentRef.value!,
683
+ number: 0,
684
+ time: 0
685
+ })
686
+ })
687
+ }
688
+ //api:撤销
689
+ const undo = () => {
690
+ if (props.disabled) {
691
+ return
692
+ }
693
+ const historyRecord = editor.value!.history.get(-1)
694
+ if (historyRecord) {
695
+ editor.value!.history.current = historyRecord.current
696
+ editor.value!.stack = historyRecord.stack
697
+ editor.value!.range = historyRecord.range
698
+ editor.value!.formatElementStack()
699
+ editor.value!.domRender(true)
700
+ editor.value!.rangeRender()
701
+ }
702
+ }
703
+ //api:重做
704
+ const redo = () => {
705
+ if (props.disabled) {
706
+ return
707
+ }
708
+ const historyRecord = editor.value!.history.get(1)
709
+ if (historyRecord) {
710
+ editor.value!.history.current = historyRecord.current
711
+ editor.value!.stack = historyRecord.stack
712
+ editor.value!.range = historyRecord.range
713
+ editor.value!.formatElementStack()
714
+ editor.value!.domRender(true)
715
+ editor.value!.rangeRender()
716
+ }
717
+ }
718
+
719
+ //监听编辑的值变更
720
+ watch(
721
+ () => value.value,
722
+ newVal => {
723
+ //内部修改不处理
724
+ if (isModelChange.value) {
725
+ return
726
+ }
727
+ //如果是外部修改,需要重新渲染编辑器
728
+ editor.value!.stack = editor.value!.parseHtml(newVal)
729
+ editor.value!.range = null
730
+ editor.value!.formatElementStack()
731
+ editor.value!.domRender()
732
+ editor.value!.rangeRender()
733
+ contentRef.value!.blur()
734
+ }
735
+ )
736
+ //代码视图切换
737
+ watch(
738
+ () => isSourceView.value,
739
+ newVal => {
740
+ if (toolbarConfig.value.use) {
741
+ if (newVal) {
742
+ hideToolbar()
743
+ } else {
744
+ handleToolbar()
745
+ }
746
+ }
747
+ }
748
+ )
749
+ //监听disabled
750
+ watch(
751
+ () => props.disabled,
752
+ newVal => {
753
+ if (newVal) {
754
+ editor.value!.setDisabled()
755
+ } else {
756
+ editor.value!.setEnabled()
757
+ }
758
+ }
759
+ )
760
+
761
+ onMounted(() => {
762
+ //创建编辑器
763
+ createEditor()
764
+ //监听滚动隐藏工具条
765
+ handleScroll()
766
+ //鼠标按下监听
767
+ DapEvent.on(document.documentElement, `mousedown.editify_${instance.uid}`, documentMouseDown)
768
+ //鼠标移动监听
769
+ DapEvent.on(document.documentElement, `mousemove.editify_${instance.uid}`, documentMouseMove)
770
+ //鼠标松开监听
771
+ DapEvent.on(document.documentElement, `mouseup.editify_${instance.uid}`, documentMouseUp)
772
+ //鼠标点击箭头
773
+ DapEvent.on(document.documentElement, `click.editify_${instance.uid}`, documentClick)
774
+ //监听窗口改变
775
+ DapEvent.on(window, `resize.editify_${instance.uid}`, setVideoHeight)
776
+ })
777
+
778
+ onBeforeUnmount(() => {
779
+ //卸载绑定在滚动元素上的事件
780
+ removeScrollHandle()
781
+ //卸载绑定在document.documentElement上的事件
782
+ DapEvent.off(document.documentElement, `mousedown.editify_${instance.uid} mousemove.editify_${instance.uid} mouseup.editify_${instance.uid} click.editify_${instance.uid}`)
783
+ //卸载绑定在window上的事件
784
+ DapEvent.off(window, `resize.editify_${instance.uid}`)
785
+ //销毁编辑器
786
+ editor.value!.destroy()
787
+ })
788
+
789
+ provide('editify', instance)
790
+ provide('isSourceView', isSourceView)
791
+ provide('isFullScreen', isFullScreen)
792
+ provide('canUseMenu', canUseMenu)
793
+ provide('editor', editor)
794
+ provide('dataRangeCaches', dataRangeCaches)
795
+ provide('showBorder', showBorder)
796
+
797
+ defineExpose({
798
+ editor,
799
+ isSourceView,
800
+ isFullScreen,
801
+ canUseMenu,
802
+ dataRangeCaches,
803
+ textValue,
804
+ collapseToEnd,
805
+ collapseToStart,
806
+ undo,
807
+ redo
808
+ })
809
+ </script>
810
+ <style scoped src="./editify.less"></style>