vue-editify 0.1.19 → 0.1.20

Sign up to get free protection for your applications and to get access to all the features.
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>