vue-editify 0.1.17 → 0.1.19

Sign up to get free protection for your applications and to get access to all the features.
Files changed (80) hide show
  1. package/README.md +3 -3
  2. package/examples/App.vue +62 -53
  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 +88 -70
  18. package/lib/editify/props.d.ts +11 -3
  19. package/lib/editify.es.js +65 -46
  20. package/lib/editify.umd.js +1 -1
  21. package/lib/index.d.ts +1 -1
  22. package/lib/style.css +1 -1
  23. package/package.json +45 -45
  24. package/src/components/button/button.less +145 -145
  25. package/src/components/button/button.vue +197 -197
  26. package/src/components/button/props.ts +95 -95
  27. package/src/components/checkbox/checkbox.less +84 -84
  28. package/src/components/checkbox/checkbox.vue +68 -68
  29. package/src/components/checkbox/props.ts +49 -49
  30. package/src/components/colors/colors.less +75 -75
  31. package/src/components/colors/colors.vue +36 -36
  32. package/src/components/colors/props.ts +29 -29
  33. package/src/components/icon/icon.less +14 -14
  34. package/src/components/icon/icon.vue +12 -12
  35. package/src/components/icon/props.ts +11 -11
  36. package/src/components/insertImage/insertImage.less +135 -135
  37. package/src/components/insertImage/insertImage.vue +146 -146
  38. package/src/components/insertImage/props.ts +43 -43
  39. package/src/components/insertLink/insertLink.less +64 -64
  40. package/src/components/insertLink/insertLink.vue +58 -58
  41. package/src/components/insertLink/props.ts +16 -16
  42. package/src/components/insertTable/insertTable.less +54 -54
  43. package/src/components/insertTable/insertTable.vue +85 -85
  44. package/src/components/insertTable/props.ts +27 -27
  45. package/src/components/insertVideo/insertVideo.less +135 -135
  46. package/src/components/insertVideo/insertVideo.vue +146 -146
  47. package/src/components/insertVideo/props.ts +43 -43
  48. package/src/components/layer/layer.less +49 -49
  49. package/src/components/layer/layer.vue +598 -598
  50. package/src/components/layer/props.ts +71 -71
  51. package/src/components/menu/menu.less +63 -63
  52. package/src/components/menu/menu.vue +1569 -1569
  53. package/src/components/menu/props.ts +17 -17
  54. package/src/components/toolbar/props.ts +35 -35
  55. package/src/components/toolbar/toolbar.less +89 -89
  56. package/src/components/toolbar/toolbar.vue +1101 -1101
  57. package/src/components/tooltip/props.ts +21 -21
  58. package/src/components/tooltip/tooltip.less +23 -23
  59. package/src/components/tooltip/tooltip.vue +37 -37
  60. package/src/components/triangle/props.ts +26 -26
  61. package/src/components/triangle/triangle.less +79 -79
  62. package/src/components/triangle/triangle.vue +65 -65
  63. package/src/core/function.ts +1144 -1144
  64. package/src/core/rule.ts +259 -259
  65. package/src/core/tool.ts +1137 -1137
  66. package/src/css/base.less +30 -30
  67. package/src/css/hljs.less +54 -54
  68. package/src/editify/editify.less +404 -403
  69. package/src/editify/editify.vue +803 -792
  70. package/src/editify/props.ts +156 -146
  71. package/src/hljs/index.ts +197 -197
  72. package/src/icon/iconfont.css +219 -219
  73. package/src/index.ts +32 -32
  74. package/src/locale/en_US.ts +88 -88
  75. package/src/locale/index.ts +12 -12
  76. package/src/locale/zh_CN.ts +88 -88
  77. package/tsconfig.json +27 -27
  78. package/tsconfig.node.json +11 -11
  79. package/vite-env.d.ts +1 -1
  80. package/vite.config.ts +42 -42
@@ -1,792 +1,803 @@
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
- customImagePaste: props.customImagePaste,
268
- customVideoPaste: props.customVideoPaste,
269
- customFilePaste: props.customFilePaste,
270
- customMerge: handleCustomMerge,
271
- customParseNode: handleCustomParseNode
272
- })
273
- //编辑器渲染后会有一个渲染过程,会改变内容,因此重新获取内容的值来设置value
274
- internalModify(editor.value.value)
275
- //设置监听事件
276
- editor.value.on('change', handleEditorChange)
277
- editor.value.on('focus', handleEditorFocus)
278
- editor.value.on('blur', handleEditorBlur)
279
- editor.value.on('insertParagraph', handleInsertParagraph)
280
- editor.value.on('rangeUpdate', handleRangeUpdate)
281
- editor.value.on('pasteHtml', handlePasteHtml)
282
- editor.value.on('deleteInStart', handleDeleteInStart)
283
- editor.value.on('deleteComplete', handleDeleteComplete)
284
- editor.value.on('afterRender', handleAfterRender)
285
- //格式化和dom渲染
286
- editor.value.formatElementStack()
287
- editor.value.domRender()
288
- //自动获取焦点
289
- if (props.autofocus && !isSourceView.value && !props.disabled) {
290
- collapseToEnd()
291
- }
292
- }
293
- //设定编辑器内的视频高度
294
- const setVideoHeight = () => {
295
- contentRef.value!.querySelectorAll('video').forEach(video => {
296
- video.style.height = video.offsetWidth / props.videoRatio + 'px'
297
- })
298
- }
299
- //鼠标在页面按下:处理表格拖拽改变列宽和菜单栏是否使用判断
300
- const documentMouseDown = (e: Event) => {
301
- if (props.disabled) {
302
- return
303
- }
304
- //鼠标在编辑器内按下
305
- if (DapElement.isContains(contentRef.value!, <HTMLElement>e.target)) {
306
- const elm = <HTMLElement>e.target
307
- const key = DapData.get(elm, 'data-alex-editor-key')
308
- if (key) {
309
- const element = editor.value!.getElementByKey(key)
310
- if (element && element.parsedom == 'td') {
311
- const length = element.parent!.children!.length
312
- //最后一个td不设置
313
- if (element.parent!.children![length - 1].isEqual(element)) {
314
- return
315
- }
316
- const rect = DapElement.getElementBounding(elm)
317
- //在可拖拽范围内
318
- if ((<MouseEvent>e).pageX >= Math.abs(rect.left + elm.offsetWidth - 5) && (<MouseEvent>e).pageX <= Math.abs(rect.left + elm.offsetWidth + 5)) {
319
- tableColumnResizeParams.value.element = element
320
- tableColumnResizeParams.value.start = (<MouseEvent>e).pageX
321
- }
322
- }
323
- }
324
- }
325
- //如果点击了除编辑器外的地方,菜单栏不可使用
326
- if (!DapElement.isContains(elRef.value!, <HTMLElement>e.target) && !isSourceView.value) {
327
- canUseMenu.value = false
328
- }
329
- }
330
- //鼠标在页面移动:处理表格拖拽改变列宽
331
- const documentMouseMove = (e: Event) => {
332
- if (props.disabled) {
333
- return
334
- }
335
- if (!tableColumnResizeParams.value.element) {
336
- return
337
- }
338
- const table = getCurrentParsedomElement(editor.value!, dataRangeCaches.value, 'table')
339
- if (!table) {
340
- return
341
- }
342
- const colgroup = table.children!.find(item => {
343
- return item.parsedom == 'colgroup'
344
- })!
345
- const index = tableColumnResizeParams.value.element.parent!.children!.findIndex(el => {
346
- return el.isEqual(tableColumnResizeParams.value.element!)
347
- })
348
- const width = `${tableColumnResizeParams.value.element.elm!.offsetWidth + (<MouseEvent>e).pageX - tableColumnResizeParams.value.start}`
349
- colgroup.children![index].marks!['width'] = width
350
- colgroup.children![index].elm!.setAttribute('width', width)
351
- tableColumnResizeParams.value.start = (<MouseEvent>e).pageX
352
- }
353
- //鼠标在页面松开:处理表格拖拽改变列宽
354
- const documentMouseUp = () => {
355
- if (props.disabled) {
356
- return
357
- }
358
- if (!tableColumnResizeParams.value.element) {
359
- return
360
- }
361
- const table = getCurrentParsedomElement(editor.value!, dataRangeCaches.value, 'table')
362
- if (!table) {
363
- return
364
- }
365
- const colgroup = table.children!.find(item => {
366
- return item.parsedom == 'colgroup'
367
- })!
368
- const index = tableColumnResizeParams.value.element.parent!.children!.findIndex(el => {
369
- return el.isEqual(tableColumnResizeParams.value.element!)
370
- })
371
- const width = Number(colgroup.children![index].marks!['width'])
372
- if (!isNaN(width)) {
373
- colgroup.children![index].marks!['width'] = `${Number(((width / tableColumnResizeParams.value.element.parent!.elm!.offsetWidth) * 100).toFixed(2))}%`
374
- editor.value!.formatElementStack()
375
- editor.value!.domRender()
376
- editor.value!.rangeRender()
377
- }
378
- tableColumnResizeParams.value.element = null
379
- tableColumnResizeParams.value.start = 0
380
- }
381
- //鼠标点击页面:处理任务列表复选框勾选
382
- const documentClick = (e: Event) => {
383
- if (props.disabled) {
384
- return
385
- }
386
- //鼠标在编辑器内点击
387
- if (DapElement.isContains(contentRef.value!, <HTMLElement>e.target)) {
388
- const elm = <HTMLElement>e.target
389
- const key = DapData.get(elm, 'data-alex-editor-key')
390
- if (key) {
391
- const element = editor.value!.getElementByKey(key)!
392
- //如果是任务列表元素
393
- if (isTask(element)) {
394
- const rect = DapElement.getElementBounding(elm)
395
- //在复选框范围内
396
- if ((<MouseEvent>e).pageX >= Math.abs(rect.left) && (<MouseEvent>e).pageX <= Math.abs(rect.left + 16) && (<MouseEvent>e).pageY >= Math.abs(rect.top + 2) && (<MouseEvent>e).pageY <= Math.abs(rect.top + 18)) {
397
- //取消勾选
398
- if (element.marks!['data-editify-task'] == 'checked') {
399
- element.marks!['data-editify-task'] = 'uncheck'
400
- }
401
- //勾选
402
- else {
403
- element.marks!['data-editify-task'] = 'checked'
404
- }
405
- if (!editor.value!.range) {
406
- editor.value!.initRange()
407
- }
408
- editor.value!.range!.anchor.moveToEnd(element)
409
- editor.value!.range!.focus.moveToEnd(element)
410
- editor.value!.formatElementStack()
411
- editor.value!.domRender()
412
- editor.value!.rangeRender()
413
- }
414
- }
415
- }
416
- }
417
- }
418
- //重新定义编辑器合并元素的逻辑
419
- const handleCustomMerge = (ele: AlexElement, preEle: AlexElement) => {
420
- const uneditable = preEle.getUneditableElement()
421
- if (uneditable) {
422
- uneditable.toEmpty()
423
- } else {
424
- preEle.children!.push(...ele.children!)
425
- preEle.children!.forEach(item => {
426
- item.parent = preEle
427
- })
428
- ele.children = null
429
- }
430
- }
431
- //针对node转为元素进行额外的处理
432
- const handleCustomParseNode = (ele: AlexElement) => {
433
- if (ele.parsedom == 'code') {
434
- ele.parsedom = 'span'
435
- const marks = {
436
- 'data-editify-code': true
437
- }
438
- if (ele.hasMarks()) {
439
- Object.assign(ele.marks!, marks)
440
- } else {
441
- ele.marks = marks
442
- }
443
- }
444
- if (typeof props.customParseNode == 'function') {
445
- ele = props.customParseNode.apply(instance.proxy, [ele])
446
- }
447
- return ele
448
- }
449
- //编辑区域键盘按下:设置缩进快捷键
450
- const handleEditorKeydown = (e: Event) => {
451
- if (props.disabled) {
452
- return
453
- }
454
- //单独按下tab键
455
- if ((<KeyboardEvent>e).key.toLocaleLowerCase() == 'tab' && !(<KeyboardEvent>e).metaKey && !(<KeyboardEvent>e).shiftKey && !(<KeyboardEvent>e).ctrlKey && !(<KeyboardEvent>e).altKey && props.tab) {
456
- e.preventDefault()
457
- editor.value!.insertText(' ')
458
- editor.value!.formatElementStack()
459
- editor.value!.domRender()
460
- editor.value!.rangeRender()
461
- }
462
- //自定义键盘按下操作
463
- emits('keydown', e)
464
- }
465
- //点击编辑器:处理图片和视频的光标聚集
466
- const handleEditorClick = (e: Event) => {
467
- if (props.disabled || isSourceView.value) {
468
- return
469
- }
470
- const node = <HTMLElement>e.target
471
- //点击的是图片或者视频
472
- if (node.nodeName.toLocaleLowerCase() == 'img' || node.nodeName.toLocaleLowerCase() == 'video') {
473
- const key = Number(node.getAttribute('data-editify-element'))
474
- if (DapNumber.isNumber(key)) {
475
- const element = editor.value!.getElementByKey(key)!
476
- if (!editor.value!.range) {
477
- editor.value!.initRange()
478
- }
479
- editor.value!.range!.anchor.moveToStart(element)
480
- editor.value!.range!.focus.moveToEnd(element)
481
- editor.value!.rangeRender()
482
- }
483
- }
484
- }
485
- //编辑器的值更新
486
- const handleEditorChange = (newVal: string, oldVal: string) => {
487
- if (props.disabled) {
488
- return
489
- }
490
- //内部修改
491
- internalModify(newVal)
492
- //触发change事件
493
- emits('change', newVal, oldVal)
494
- }
495
- //编辑器失去焦点
496
- const handleEditorBlur = (val: string) => {
497
- if (props.disabled) {
498
- return
499
- }
500
- if (props.border && props.color && !isFullScreen.value) {
501
- //恢复编辑区域边框颜色
502
- bodyRef.value!.style.borderColor = ''
503
- //恢复编辑区域阴影颜色
504
- bodyRef.value!.style.boxShadow = ''
505
- //使用菜单栏的情况下恢复菜单栏的样式
506
- if (menuConfig.value.use) {
507
- menuRef.value!.$el.style.borderColor = ''
508
- menuRef.value!.$el.style.boxShadow = ''
509
- }
510
- }
511
- emits('blur', val)
512
- }
513
- //编辑器获取焦点
514
- const handleEditorFocus = (val: string) => {
515
- if (props.disabled) {
516
- return
517
- }
518
- if (props.border && props.color && !isFullScreen.value) {
519
- //编辑区域边框颜色
520
- bodyRef.value!.style.borderColor = props.color
521
- //转换颜色值
522
- const rgb = DapColor.hex2rgb(props.color)
523
- //菜单栏模式为inner
524
- if (menuConfig.value.use && menuConfig.value.mode == 'inner') {
525
- //编辑区域除顶部边框的阴影
526
- 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)`
527
- //菜单栏的边框颜色
528
- menuRef.value!.$el.style.borderColor = props.color
529
- //菜单栏除底部边框的阴影
530
- 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)`
531
- }
532
- //其他菜单栏模式
533
- else if (menuConfig.value.use) {
534
- //编辑区域四边阴影
535
- bodyRef.value!.style.boxShadow = `0 0 8px rgba(${rgb[0]},${rgb[1]},${rgb[2]},0.5)`
536
- }
537
- //不使用菜单栏
538
- else {
539
- //编辑区域四边阴影
540
- bodyRef.value!.style.boxShadow = `0 0 8px rgba(${rgb[0]},${rgb[1]},${rgb[2]},0.5)`
541
- }
542
- }
543
- //获取焦点时可以使用菜单栏
544
- setTimeout(() => {
545
- canUseMenu.value = true
546
- emits('focus', val)
547
- }, 0)
548
- }
549
- //编辑器换行
550
- const handleInsertParagraph = (element: AlexElement, previousElement: AlexElement) => {
551
- //前一个块元素如果是只包含换行符的元素,并且当前块元素也是包含换行符的元素,则当前块元素转为段落
552
- if (previousElement.isOnlyHasBreak() && element.isOnlyHasBreak()) {
553
- if (previousElement.parsedom != AlexElement.BLOCK_NODE) {
554
- elementToParagraph(previousElement)
555
- editor.value!.range!.anchor.moveToStart(previousElement)
556
- editor.value!.range!.focus.moveToStart(previousElement)
557
- element.toEmpty()
558
- }
559
- }
560
- emits('insertparagraph', value.value)
561
- }
562
- //编辑器焦点更新
563
- const handleRangeUpdate = () => {
564
- if (props.disabled) {
565
- return
566
- }
567
- //如果没有range禁用菜单栏
568
- canUseMenu.value = !!editor.value!.range
569
- //没有range直接返回
570
- if (!editor.value!.range) {
571
- return
572
- }
573
- //获取光标选取范围内的元素数据,并且进行缓存
574
- dataRangeCaches.value = editor.value!.getElementsByRange()
575
-
576
- //节流写法
577
- if (rangeUpdateTimer.value) {
578
- clearTimeout(rangeUpdateTimer.value)
579
- rangeUpdateTimer.value = null
580
- }
581
- //延时200ms进行判断
582
- rangeUpdateTimer.value = setTimeout(() => {
583
- //如果使用工具条或者菜单栏
584
- if (toolbarConfig.value.use || menuConfig.value.use) {
585
- //如果使用工具条
586
- if (toolbarConfig.value.use) {
587
- handleToolbar()
588
- }
589
- //如果使用菜单栏
590
- if (menuConfig.value.use) {
591
- menuRef.value!.handleRangeUpdate()
592
- }
593
- }
594
- }, 200)
595
- emits('rangeupdate')
596
- }
597
- //编辑器粘贴html
598
- const handlePasteHtml = (elements: AlexElement[]) => {
599
- const keepStyles = Object.assign(pasteKeepData.styles, props.pasteKeepStyles || {})
600
- const keepMarks = Object.assign(pasteKeepData.marks, props.pasteKeepMarks || {})
601
- //粘贴html时过滤元素的样式和属性
602
- AlexElement.flatElements(elements).forEach(el => {
603
- let marks: ObjectType = {}
604
- let styles: ObjectType = {}
605
- if (el.hasMarks()) {
606
- for (let key in keepMarks) {
607
- if (el.marks!.hasOwnProperty(key) && ((Array.isArray(keepMarks[key]) && keepMarks[key].includes(el.parsedom)) || keepMarks[key] == '*')) {
608
- marks[key] = el.marks![key]
609
- }
610
- }
611
- el.marks = marks
612
- }
613
- if (el.hasStyles() && !el.isText()) {
614
- for (let key in keepStyles) {
615
- if (el.styles!.hasOwnProperty(key) && ((Array.isArray(keepStyles[key]) && keepStyles[key].includes(el.parsedom)) || keepStyles[key] == '*')) {
616
- styles[key] = el.styles![key]
617
- }
618
- }
619
- el.styles = styles
620
- }
621
- })
622
- }
623
- //编辑器部分删除情景(在编辑器起始处)
624
- const handleDeleteInStart = (element: AlexElement) => {
625
- if (element.isBlock()) {
626
- elementToParagraph(element)
627
- }
628
- }
629
- //编辑器删除完成后事件
630
- const handleDeleteComplete = () => {
631
- const uneditable = editor.value!.range!.anchor.element.getUneditableElement()
632
- if (uneditable) {
633
- uneditable.toEmpty()
634
- }
635
- }
636
- //编辑器dom渲染
637
- const handleAfterRender = () => {
638
- //设定视频高度
639
- setVideoHeight()
640
- emits('updateview')
641
- }
642
- //api:光标设置到文档底部
643
- const collapseToEnd = () => {
644
- if (props.disabled) {
645
- return
646
- }
647
- editor.value!.collapseToEnd()
648
- editor.value!.rangeRender()
649
- DapElement.setScrollTop({
650
- el: contentRef.value!,
651
- number: 1000000,
652
- time: 0
653
- })
654
- }
655
- //api:光标设置到文档头部
656
- const collapseToStart = () => {
657
- if (props.disabled) {
658
- return
659
- }
660
- editor.value!.collapseToStart()
661
- editor.value!.rangeRender()
662
- nextTick(() => {
663
- DapElement.setScrollTop({
664
- el: contentRef.value!,
665
- number: 0,
666
- time: 0
667
- })
668
- })
669
- }
670
- //api:撤销
671
- const undo = () => {
672
- if (props.disabled) {
673
- return
674
- }
675
- const historyRecord = editor.value!.history.get(-1)
676
- if (historyRecord) {
677
- editor.value!.history.current = historyRecord.current
678
- editor.value!.stack = historyRecord.stack
679
- editor.value!.range = historyRecord.range
680
- editor.value!.formatElementStack()
681
- editor.value!.domRender(true)
682
- editor.value!.rangeRender()
683
- }
684
- }
685
- //api:重做
686
- const redo = () => {
687
- if (props.disabled) {
688
- return
689
- }
690
- const historyRecord = editor.value!.history.get(1)
691
- if (historyRecord) {
692
- editor.value!.history.current = historyRecord.current
693
- editor.value!.stack = historyRecord.stack
694
- editor.value!.range = historyRecord.range
695
- editor.value!.formatElementStack()
696
- editor.value!.domRender(true)
697
- editor.value!.rangeRender()
698
- }
699
- }
700
-
701
- //监听编辑的值变更
702
- watch(
703
- () => value.value,
704
- newVal => {
705
- //内部修改不处理
706
- if (isModelChange.value) {
707
- return
708
- }
709
- //如果是外部修改,需要重新渲染编辑器
710
- editor.value!.stack = editor.value!.parseHtml(newVal)
711
- editor.value!.range = null
712
- editor.value!.formatElementStack()
713
- editor.value!.domRender()
714
- editor.value!.rangeRender()
715
- contentRef.value!.blur()
716
- }
717
- )
718
- //代码视图切换
719
- watch(
720
- () => isSourceView.value,
721
- newVal => {
722
- if (toolbarConfig.value.use) {
723
- if (newVal) {
724
- hideToolbar()
725
- } else {
726
- handleToolbar()
727
- }
728
- }
729
- }
730
- )
731
- //监听disabled
732
- watch(
733
- () => props.disabled,
734
- newVal => {
735
- if (newVal) {
736
- editor.value!.setDisabled()
737
- } else {
738
- editor.value!.setEnabled()
739
- }
740
- }
741
- )
742
-
743
- onMounted(() => {
744
- //创建编辑器
745
- createEditor()
746
- //监听滚动隐藏工具条
747
- handleScroll()
748
- //鼠标按下监听
749
- DapEvent.on(document.documentElement, `mousedown.editify_${instance.uid}`, documentMouseDown)
750
- //鼠标移动监听
751
- DapEvent.on(document.documentElement, `mousemove.editify_${instance.uid}`, documentMouseMove)
752
- //鼠标松开监听
753
- DapEvent.on(document.documentElement, `mouseup.editify_${instance.uid}`, documentMouseUp)
754
- //鼠标点击箭头
755
- DapEvent.on(document.documentElement, `click.editify_${instance.uid}`, documentClick)
756
- //监听窗口改变
757
- DapEvent.on(window, `resize.editify_${instance.uid}`, setVideoHeight)
758
- })
759
-
760
- onBeforeUnmount(() => {
761
- //卸载绑定在滚动元素上的事件
762
- removeScrollHandle()
763
- //卸载绑定在document.documentElement上的事件
764
- DapEvent.off(document.documentElement, `mousedown.editify_${instance.uid} mousemove.editify_${instance.uid} mouseup.editify_${instance.uid} click.editify_${instance.uid}`)
765
- //卸载绑定在window上的事件
766
- DapEvent.off(window, `resize.editify_${instance.uid}`)
767
- //销毁编辑器
768
- editor.value!.destroy()
769
- })
770
-
771
- provide('editify', instance)
772
- provide('isSourceView', isSourceView)
773
- provide('isFullScreen', isFullScreen)
774
- provide('canUseMenu', canUseMenu)
775
- provide('editor', editor)
776
- provide('dataRangeCaches', dataRangeCaches)
777
- provide('showBorder', showBorder)
778
-
779
- defineExpose({
780
- editor,
781
- isSourceView,
782
- isFullScreen,
783
- canUseMenu,
784
- dataRangeCaches,
785
- textValue,
786
- collapseToEnd,
787
- collapseToStart,
788
- undo,
789
- redo
790
- })
791
- </script>
792
- <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 (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>