vue-editify 0.1.10 → 0.1.13

Sign up to get free protection for your applications and to get access to all the features.
Files changed (113) hide show
  1. package/examples/App.vue +21 -96
  2. package/examples/main.ts +4 -0
  3. package/lib/components/button/button.vue.d.ts +143 -0
  4. package/lib/components/button/props.d.ts +73 -0
  5. package/lib/components/checkbox/checkbox.vue.d.ts +80 -0
  6. package/lib/components/checkbox/props.d.ts +36 -0
  7. package/lib/components/colors/colors.vue.d.ts +47 -0
  8. package/lib/components/colors/props.d.ts +22 -0
  9. package/lib/components/icon/icon.vue.d.ts +14 -0
  10. package/lib/components/icon/props.d.ts +9 -0
  11. package/lib/components/insertImage/insertImage.vue.d.ts +74 -0
  12. package/lib/components/insertImage/props.d.ts +34 -0
  13. package/lib/components/insertLink/insertLink.vue.d.ts +27 -0
  14. package/lib/components/insertLink/props.d.ts +13 -0
  15. package/lib/components/insertTable/insertTable.vue.d.ts +36 -0
  16. package/lib/components/insertTable/props.d.ts +22 -0
  17. package/lib/components/insertVideo/insertVideo.vue.d.ts +74 -0
  18. package/lib/components/insertVideo/props.d.ts +34 -0
  19. package/lib/components/layer/layer.vue.d.ts +129 -0
  20. package/lib/components/layer/props.d.ts +53 -0
  21. package/lib/components/menu/menu.vue.d.ts +25 -0
  22. package/lib/components/menu/props.d.ts +14 -0
  23. package/lib/components/toolbar/props.d.ts +27 -0
  24. package/lib/components/toolbar/toolbar.vue.d.ts +56 -0
  25. package/lib/components/tooltip/props.d.ts +17 -0
  26. package/lib/components/tooltip/tooltip.vue.d.ts +39 -0
  27. package/lib/components/triangle/props.d.ts +19 -0
  28. package/lib/components/triangle/triangle.vue.d.ts +34 -0
  29. package/lib/core/function.d.ts +45 -0
  30. package/lib/core/rule.d.ts +9 -0
  31. package/lib/core/tool.d.ts +185 -0
  32. package/lib/editify/editify.vue.d.ts +676 -0
  33. package/lib/editify/props.d.ts +110 -0
  34. package/lib/editify.es.js +5706 -5727
  35. package/lib/editify.umd.js +1 -1
  36. package/lib/hljs/index.d.ts +7 -0
  37. package/lib/index.d.ts +17 -0
  38. package/lib/locale/en_US.d.ts +3 -0
  39. package/lib/locale/index.d.ts +2 -0
  40. package/lib/locale/zh_CN.d.ts +3 -0
  41. package/lib/style.css +1 -1
  42. package/package.json +16 -8
  43. package/src/components/button/button.less +145 -0
  44. package/src/components/button/button.vue +197 -0
  45. package/src/components/button/props.ts +95 -0
  46. package/src/components/checkbox/checkbox.less +84 -0
  47. package/src/components/checkbox/checkbox.vue +68 -0
  48. package/src/components/checkbox/props.ts +49 -0
  49. package/src/components/colors/colors.less +75 -0
  50. package/src/components/colors/colors.vue +36 -0
  51. package/src/components/colors/props.ts +29 -0
  52. package/src/components/{base/Icon.vue → icon/icon.less} +0 -17
  53. package/src/components/icon/icon.vue +12 -0
  54. package/src/components/icon/props.ts +11 -0
  55. package/src/components/insertImage/insertImage.less +135 -0
  56. package/src/components/insertImage/insertImage.vue +146 -0
  57. package/src/components/insertImage/props.ts +43 -0
  58. package/src/components/insertLink/insertLink.less +64 -0
  59. package/src/components/insertLink/insertLink.vue +58 -0
  60. package/src/components/insertLink/props.ts +16 -0
  61. package/src/components/insertTable/insertTable.less +54 -0
  62. package/src/components/insertTable/insertTable.vue +85 -0
  63. package/src/components/insertTable/props.ts +27 -0
  64. package/src/components/insertVideo/insertVideo.less +135 -0
  65. package/src/components/insertVideo/insertVideo.vue +146 -0
  66. package/src/components/insertVideo/props.ts +43 -0
  67. package/src/components/layer/layer.less +49 -0
  68. package/src/components/layer/layer.vue +598 -0
  69. package/src/components/layer/props.ts +71 -0
  70. package/src/components/menu/menu.less +64 -0
  71. package/src/components/menu/menu.vue +1570 -0
  72. package/src/components/menu/props.ts +17 -0
  73. package/src/components/toolbar/props.ts +35 -0
  74. package/src/components/toolbar/toolbar.less +89 -0
  75. package/src/components/toolbar/toolbar.vue +1101 -0
  76. package/src/components/tooltip/props.ts +21 -0
  77. package/src/components/tooltip/tooltip.less +23 -0
  78. package/src/components/tooltip/tooltip.vue +37 -0
  79. package/src/components/triangle/props.ts +26 -0
  80. package/src/components/triangle/triangle.less +79 -0
  81. package/src/components/triangle/triangle.vue +65 -0
  82. package/src/core/function.ts +1144 -0
  83. package/src/core/{rule.js → rule.ts} +33 -33
  84. package/src/core/{tool.js → tool.ts} +221 -145
  85. package/src/editify/editify.less +404 -0
  86. package/src/editify/editify.vue +805 -0
  87. package/src/editify/props.ts +141 -0
  88. package/src/hljs/{index.js → index.ts} +7 -4
  89. package/src/index.ts +32 -0
  90. package/src/locale/{en_US.js → en_US.ts} +3 -1
  91. package/src/locale/index.ts +12 -0
  92. package/src/locale/{zh_CN.js → zh_CN.ts} +3 -1
  93. package/tsconfig.json +27 -0
  94. package/tsconfig.node.json +11 -0
  95. package/vite-env.d.ts +1 -0
  96. package/vite.config.ts +39 -0
  97. package/examples/main.js +0 -4
  98. package/src/Editify.vue +0 -1184
  99. package/src/components/Menu.vue +0 -1623
  100. package/src/components/Toolbar.vue +0 -1215
  101. package/src/components/base/Button.vue +0 -450
  102. package/src/components/base/Checkbox.vue +0 -196
  103. package/src/components/base/Layer.vue +0 -713
  104. package/src/components/base/Tooltip.vue +0 -82
  105. package/src/components/base/Triangle.vue +0 -159
  106. package/src/components/common/Colors.vue +0 -138
  107. package/src/components/common/InsertImage.vue +0 -316
  108. package/src/components/common/InsertLink.vue +0 -136
  109. package/src/components/common/InsertTable.vue +0 -157
  110. package/src/components/common/InsertVideo.vue +0 -316
  111. package/src/core/function.js +0 -1044
  112. package/src/index.js +0 -24
  113. package/src/locale/index.js +0 -14
package/src/Editify.vue DELETED
@@ -1,1184 +0,0 @@
1
- <template>
2
- <div class="editify" :class="{ fullscreen: isFullScreen, autoheight: autoheight }">
3
- <!-- 菜单区域 -->
4
- <Menu v-if="menuConfig.use" :config="menuConfig" :color="color" ref="menu"></Menu>
5
- <!-- 编辑层,与编辑区域宽高相同必须适配 -->
6
- <div ref="body" class="editify-body" :class="{ border: showBorder, menu_inner: menuConfig.use && menuConfig.mode == 'inner' }" :data-editify-uid="uid">
7
- <!-- 编辑器 -->
8
- <div ref="content" 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="toolbar" v-model="toolbarOptions.show" :node="toolbarOptions.node" :type="toolbarOptions.type" :config="toolbarConfig"></Toolbar>
13
- </div>
14
- <!-- 编辑器尾部 -->
15
- <div v-if="showWordLength" class="editify-footer" :class="{ fullscreen: isFullScreen && !isSourceView }" ref="footer">
16
- <!-- 字数统计 -->
17
- <div class="editify-footer-words">{{ $editTrans('totalWordCount') }}{{ textValue.length }}</div>
18
- </div>
19
- </div>
20
- </template>
21
- <script>
22
- import { getCurrentInstance } from 'vue'
23
- import { AlexEditor, AlexElement } 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, editorProps, mergeObject, getToolbarConfig, getMenuConfig } from './core/tool'
26
- import { parseList, orderdListHandle, mediaHandle, tableHandle, preHandle, specialInblockHandle } from './core/rule'
27
- import { isTask, elementToParagraph, getCurrentParsedomElement, hasTableInRange, hasLinkInRange, hasPreInRange, hasImageInRange, hasVideoInRange, setIndentIncrease, setIndentDecrease, insertImage, insertVideo } from './core/function'
28
- import Tooltip from './components/base/Tooltip'
29
- import Toolbar from './components/Toolbar'
30
- import Menu from './components/Menu'
31
-
32
- export default {
33
- name: 'editify',
34
- props: { ...editorProps },
35
- emits: ['update:modelValue', 'focus', 'blur', 'change', 'keydown', 'insertparagraph', 'rangeupdate', 'updateview'],
36
- setup() {
37
- const instance = getCurrentInstance()
38
- return {
39
- uid: instance.uid
40
- }
41
- },
42
- data() {
43
- return {
44
- //是否编辑器内部修改值
45
- isModelChange: false,
46
- //是否正在输入中文
47
- isInputChinese: false,
48
- //工具条和菜单栏判定延时器
49
- rangeUpdateTimer: null,
50
- //表格列宽拖拽记录数据
51
- tableColumnResizeParams: {
52
- element: null, //被拖拽的td
53
- start: 0 //水平方向起点位置
54
- },
55
- //工具条参数配置
56
- toolbarOptions: {
57
- //是否显示工具条
58
- show: false,
59
- //关联元素
60
- node: null,
61
- //类型
62
- type: 'text'
63
- },
64
-
65
- /** 以下是可对外使用的属性 */
66
-
67
- //编辑器对象
68
- editor: null,
69
- //是否代码视图
70
- isSourceView: false,
71
- //是否全屏
72
- isFullScreen: false,
73
- //菜单栏是否可以使用标识
74
- canUseMenu: false,
75
- //光标选取范围内的元素数组
76
- dataRangeCaches: {
77
- flatList: [],
78
- list: []
79
- }
80
- }
81
- },
82
- computed: {
83
- //编辑器的值
84
- value: {
85
- set(val) {
86
- this.$emit('update:modelValue', val)
87
- },
88
- get() {
89
- return this.modelValue || '<p><br></p>'
90
- }
91
- },
92
- //编辑器的纯文本值
93
- textValue() {
94
- return DapElement.string2dom(`<div>${this.value}</div>`).innerText
95
- },
96
- //是否显示占位符
97
- showPlaceholder() {
98
- if (this.editor) {
99
- if (this.value && this.editor.stack.length == 1 && this.editor.stack[0].type == 'block' && this.editor.stack[0].parsedom == AlexElement.BLOCK_NODE && this.editor.stack[0].isOnlyHasBreak() && !this.editor.stack[0].hasStyles() && !this.editor.stack[0].hasMarks()) {
100
- return !this.isInputChinese
101
- }
102
- }
103
- return false
104
- },
105
- //是否显示边框
106
- showBorder() {
107
- //全屏模式下不显示边框
108
- if (this.isFullScreen) {
109
- return false
110
- }
111
- return this.border
112
- },
113
- //最终生效的工具栏配置
114
- toolbarConfig() {
115
- return mergeObject(getToolbarConfig(this.$editTrans, this.$editLocale), this.toolbar || {})
116
- },
117
- //最终生效的菜单栏配置
118
- menuConfig() {
119
- return mergeObject(getMenuConfig(this.$editTrans, this.$editLocale), this.menu || {})
120
- }
121
- },
122
- components: {
123
- Toolbar,
124
- Tooltip,
125
- Menu
126
- },
127
- inject: ['$editTrans', '$editLocale'],
128
- watch: {
129
- //监听编辑的值变更
130
- value(newVal) {
131
- //内部修改不处理
132
- if (this.isModelChange) {
133
- return
134
- }
135
- //如果是外部修改,需要重新渲染编辑器
136
- this.editor.stack = this.editor.parseHtml(newVal)
137
- this.editor.range = null
138
- this.editor.formatElementStack()
139
- this.editor.domRender()
140
- this.editor.rangeRender()
141
- this.$refs.content.blur()
142
- },
143
- //代码视图切换
144
- isSourceView(newValue) {
145
- if (this.toolbarConfig.use) {
146
- if (newValue) {
147
- this.hideToolbar()
148
- } else {
149
- this.handleToolbar()
150
- }
151
- }
152
- },
153
- //监听disabled
154
- disabled(newValue) {
155
- if (newValue) {
156
- this.editor.setDisabled()
157
- } else {
158
- this.editor.setEnabled()
159
- }
160
- }
161
- },
162
- mounted() {
163
- //创建编辑器
164
- this.createEditor()
165
- //监听滚动隐藏工具条
166
- this.handleScroll()
167
- //鼠标按下监听
168
- DapEvent.on(document.documentElement, `mousedown.editify_${this.uid}`, this.documentMouseDown)
169
- //鼠标移动监听
170
- DapEvent.on(document.documentElement, `mousemove.editify_${this.uid}`, this.documentMouseMove)
171
- //鼠标松开监听
172
- DapEvent.on(document.documentElement, `mouseup.editify_${this.uid}`, this.documentMouseUp)
173
- //鼠标点击箭头
174
- DapEvent.on(document.documentElement, `click.editify_${this.uid}`, this.documentClick)
175
- //监听窗口改变
176
- DapEvent.on(window, `resize.editify_${this.uid}`, this.setVideoHeight)
177
- },
178
- methods: {
179
- //编辑器内部修改值的方法
180
- internalModify(val) {
181
- this.isModelChange = true
182
- this.value = val
183
- this.$nextTick(() => {
184
- this.isModelChange = false
185
- })
186
- },
187
- //隐藏工具条
188
- hideToolbar() {
189
- this.toolbarOptions.show = false
190
- this.toolbarOptions.node = null
191
- },
192
- //监听滚动隐藏工具条
193
- handleScroll() {
194
- const setScroll = el => {
195
- DapEvent.on(el, `scroll.editify_${this.uid}`, () => {
196
- if (this.toolbarConfig.use && this.toolbarOptions.show) {
197
- this.hideToolbar()
198
- }
199
- })
200
- if (el.parentNode) {
201
- setScroll(el.parentNode)
202
- }
203
- }
204
- setScroll(this.$refs.content)
205
- },
206
- //移除上述滚动事件的监听
207
- removeScrollHandle() {
208
- const removeScroll = el => {
209
- DapEvent.off(el, `scroll.editify_${this.uid}`)
210
- if (el.parentNode) {
211
- removeScroll(el.parentNode)
212
- }
213
- }
214
- removeScroll(this.$refs.content)
215
- },
216
- //工具条显示判断
217
- handleToolbar() {
218
- if (this.disabled || this.isSourceView) {
219
- return
220
- }
221
- this.hideToolbar()
222
- this.$nextTick(() => {
223
- const table = getCurrentParsedomElement(this, 'table')
224
- const pre = getCurrentParsedomElement(this, 'pre')
225
- const link = getCurrentParsedomElement(this, 'a')
226
- const image = getCurrentParsedomElement(this, 'img')
227
- const video = getCurrentParsedomElement(this, 'video')
228
- if (link) {
229
- this.toolbarOptions.type = 'link'
230
- this.toolbarOptions.node = `[data-editify-uid="${this.uid}"] [data-editify-element="${link.key}"]`
231
- if (this.toolbarOptions.show) {
232
- this.$refs.toolbar.$refs.layer.setPosition()
233
- } else {
234
- this.toolbarOptions.show = true
235
- }
236
- } else if (image) {
237
- this.toolbarOptions.type = 'image'
238
- this.toolbarOptions.node = `[data-editify-uid="${this.uid}"] [data-editify-element="${image.key}"]`
239
- if (this.toolbarOptions.show) {
240
- this.$refs.toolbar.$refs.layer.setPosition()
241
- } else {
242
- this.toolbarOptions.show = true
243
- }
244
- } else if (video) {
245
- this.toolbarOptions.type = 'video'
246
- this.toolbarOptions.node = `[data-editify-uid="${this.uid}"] [data-editify-element="${video.key}"]`
247
- if (this.toolbarOptions.show) {
248
- this.$refs.toolbar.$refs.layer.setPosition()
249
- } else {
250
- this.toolbarOptions.show = true
251
- }
252
- } else if (table) {
253
- this.toolbarOptions.type = 'table'
254
- this.toolbarOptions.node = `[data-editify-uid="${this.uid}"] [data-editify-element="${table.key}"]`
255
- if (this.toolbarOptions.show) {
256
- this.$refs.toolbar.$refs.layer.setPosition()
257
- } else {
258
- this.toolbarOptions.show = true
259
- }
260
- } else if (pre) {
261
- this.toolbarOptions.type = 'codeBlock'
262
- this.toolbarOptions.node = `[data-editify-uid="${this.uid}"] [data-editify-element="${pre.key}"]`
263
- if (this.toolbarOptions.show) {
264
- this.$refs.toolbar.$refs.layer.setPosition()
265
- } else {
266
- this.toolbarOptions.show = true
267
- }
268
- } else {
269
- const result = this.dataRangeCaches.flatList.filter(item => {
270
- return item.element.isText()
271
- })
272
- if (result.length && !hasTableInRange(this) && !hasPreInRange(this) && !hasLinkInRange(this) && !hasImageInRange(this) && !hasVideoInRange(this)) {
273
- this.toolbarOptions.type = 'text'
274
- if (this.toolbarOptions.show) {
275
- this.$refs.toolbar.$refs.layer.setPosition()
276
- } else {
277
- this.toolbarOptions.show = true
278
- }
279
- }
280
- }
281
- })
282
- },
283
- //设定编辑器内的视频高度
284
- setVideoHeight() {
285
- this.$refs.content.querySelectorAll('video').forEach(video => {
286
- video.style.height = video.offsetWidth / this.videoRatio + 'px'
287
- })
288
- },
289
- //初始创建编辑器
290
- createEditor() {
291
- //创建编辑器
292
- this.editor = new AlexEditor(this.$refs.content, {
293
- value: this.value,
294
- disabled: this.disabled,
295
- renderRules: [
296
- el => {
297
- parseList(this.editor, el)
298
- },
299
- el => {
300
- orderdListHandle(this.editor, el)
301
- },
302
- el => {
303
- mediaHandle(this.editor, el)
304
- },
305
- el => {
306
- tableHandle(this.editor, el)
307
- },
308
- el => {
309
- preHandle(this.editor, el, this.toolbarConfig?.use && this.toolbarConfig?.codeBlock?.languages?.show, this.toolbarConfig?.codeBlock?.languages.options)
310
- },
311
- el => {
312
- specialInblockHandle(this.editor, el)
313
- },
314
- ...this.renderRules
315
- ],
316
- allowCopy: this.allowCopy,
317
- allowPaste: this.allowPaste,
318
- allowCut: this.allowCut,
319
- allowPasteHtml: this.allowPasteHtml,
320
- allowPasteHtml: this.allowPasteHtml,
321
- customImagePaste: this.handleCustomImagePaste,
322
- customVideoPaste: this.handleCustomVideoPaste,
323
- customMerge: this.handleCustomMerge,
324
- customParseNode: this.handleCustomParseNode
325
- })
326
- //编辑器渲染后会有一个渲染过程,会改变内容,因此重新获取内容的值来设置value
327
- this.internalModify(this.editor.value)
328
- //设置监听事件
329
- this.editor.on('change', this.handleEditorChange)
330
- this.editor.on('focus', this.handleEditorFocus)
331
- this.editor.on('blur', this.handleEditorBlur)
332
- this.editor.on('insertParagraph', this.handleInsertParagraph)
333
- this.editor.on('rangeUpdate', this.handleRangeUpdate)
334
- this.editor.on('pasteHtml', this.handlePasteHtml)
335
- this.editor.on('deleteInStart', this.handleDeleteInStart)
336
- this.editor.on('deleteComplete', this.handleDeleteComplete)
337
- this.editor.on('afterRender', this.handleAfterRender)
338
- //格式化和dom渲染
339
- this.editor.formatElementStack()
340
- this.editor.domRender()
341
- //自动获取焦点
342
- if (this.autofocus && !this.isSourceView && !this.disabled) {
343
- this.collapseToEnd()
344
- }
345
- },
346
- //鼠标在页面按下:处理表格拖拽改变列宽和菜单栏是否使用判断
347
- documentMouseDown(e) {
348
- if (this.disabled) {
349
- return
350
- }
351
- //鼠标在编辑器内按下
352
- if (DapElement.isContains(this.$refs.content, e.target)) {
353
- const elm = e.target
354
- const key = DapData.get(elm, 'data-alex-editor-key')
355
- if (key) {
356
- const element = this.editor.getElementByKey(key)
357
- if (element && element.parsedom == 'td') {
358
- const length = element.parent.children.length
359
- //最后一个td不设置
360
- if (element.parent.children[length - 1].isEqual(element)) {
361
- return
362
- }
363
- const rect = DapElement.getElementBounding(elm)
364
- //在可拖拽范围内
365
- if (e.pageX >= Math.abs(rect.left + elm.offsetWidth - 5) && e.pageX <= Math.abs(rect.left + elm.offsetWidth + 5)) {
366
- this.tableColumnResizeParams.element = element
367
- this.tableColumnResizeParams.start = e.pageX
368
- }
369
- }
370
- }
371
- }
372
- //如果点击了除编辑器外的地方,菜单栏不可使用
373
- if (!DapElement.isContains(this.$el, e.target) && !this.isSourceView) {
374
- this.canUseMenu = false
375
- }
376
- },
377
- //鼠标在页面移动:处理表格拖拽改变列宽
378
- documentMouseMove(e) {
379
- if (this.disabled) {
380
- return
381
- }
382
- if (!this.tableColumnResizeParams.element) {
383
- return
384
- }
385
- const table = getCurrentParsedomElement(this, 'table')
386
- if (!table) {
387
- return
388
- }
389
- const colgroup = table.children.find(item => {
390
- return item.parsedom == 'colgroup'
391
- })
392
- const index = this.tableColumnResizeParams.element.parent.children.findIndex(el => {
393
- return el.isEqual(this.tableColumnResizeParams.element)
394
- })
395
- const width = `${this.tableColumnResizeParams.element.elm.offsetWidth + e.pageX - this.tableColumnResizeParams.start}`
396
- colgroup.children[index].marks['width'] = width
397
- colgroup.children[index].elm.setAttribute('width', width)
398
- this.tableColumnResizeParams.start = e.pageX
399
- },
400
- //鼠标在页面松开:处理表格拖拽改变列宽
401
- documentMouseUp() {
402
- if (this.disabled) {
403
- return
404
- }
405
- if (!this.tableColumnResizeParams.element) {
406
- return
407
- }
408
- const table = getCurrentParsedomElement(this, 'table')
409
- if (!table) {
410
- return
411
- }
412
- const colgroup = table.children.find(item => {
413
- return item.parsedom == 'colgroup'
414
- })
415
- const index = this.tableColumnResizeParams.element.parent.children.findIndex(el => {
416
- return el.isEqual(this.tableColumnResizeParams.element)
417
- })
418
- const width = Number(colgroup.children[index].marks['width'])
419
- if (!isNaN(width)) {
420
- colgroup.children[index].marks['width'] = `${Number(((width / this.tableColumnResizeParams.element.parent.elm.offsetWidth) * 100).toFixed(2))}%`
421
- this.editor.formatElementStack()
422
- this.editor.domRender()
423
- this.editor.rangeRender()
424
- }
425
- this.tableColumnResizeParams.element = null
426
- this.tableColumnResizeParams.start = 0
427
- },
428
- //鼠标点击页面:处理任务列表复选框勾选
429
- documentClick(e) {
430
- if (this.disabled) {
431
- return
432
- }
433
- //鼠标在编辑器内点击
434
- if (DapElement.isContains(this.$refs.content, e.target)) {
435
- const elm = e.target
436
- const key = DapData.get(elm, 'data-alex-editor-key')
437
- if (key) {
438
- const element = this.editor.getElementByKey(key)
439
- //如果是任务列表元素
440
- if (isTask(element)) {
441
- const rect = DapElement.getElementBounding(elm)
442
- //在复选框范围内
443
- if (e.pageX >= Math.abs(rect.left) && e.pageX <= Math.abs(rect.left + 16) && e.pageY >= Math.abs(rect.top + 2) && e.pageY <= Math.abs(rect.top + 18)) {
444
- //取消勾选
445
- if (element.marks['data-editify-task'] == 'checked') {
446
- element.marks['data-editify-task'] = 'uncheck'
447
- }
448
- //勾选
449
- else {
450
- element.marks['data-editify-task'] = 'checked'
451
- }
452
- if (!this.editor.range) {
453
- this.editor.initRange()
454
- }
455
- this.editor.range.anchor.moveToEnd(element)
456
- this.editor.range.focus.moveToEnd(element)
457
- this.editor.formatElementStack()
458
- this.editor.domRender()
459
- this.editor.rangeRender()
460
- }
461
- }
462
- }
463
- }
464
- },
465
- //自定义图片粘贴
466
- async handleCustomImagePaste(url) {
467
- const newUrl = await this.customImagePaste.apply(this, [url])
468
- if (newUrl) {
469
- insertImage(this, newUrl)
470
- }
471
- },
472
- //自定义视频粘贴
473
- async handleCustomVideoPaste(url) {
474
- const newUrl = await this.customVideoPaste.apply(this, [url])
475
- if (newUrl) {
476
- insertVideo(this, newUrl)
477
- }
478
- },
479
- //重新定义编辑器合并元素的逻辑
480
- handleCustomMerge(ele, preEle) {
481
- const uneditable = preEle.getUneditableElement()
482
- if (uneditable) {
483
- uneditable.toEmpty()
484
- } else {
485
- preEle.children.push(...ele.children)
486
- preEle.children.forEach(item => {
487
- item.parent = preEle
488
- })
489
- ele.children = null
490
- }
491
- },
492
- //针对node转为元素进行额外的处理
493
- handleCustomParseNode(ele) {
494
- if (ele.parsedom == 'code') {
495
- ele.parsedom = 'span'
496
- const marks = {
497
- 'data-editify-code': true
498
- }
499
- if (ele.hasMarks()) {
500
- Object.assign(ele.marks, marks)
501
- } else {
502
- ele.marks = marks
503
- }
504
- }
505
- if (typeof this.customParseNode == 'function') {
506
- ele = this.customParseNode.apply(this, [ele])
507
- }
508
- return ele
509
- },
510
- //编辑区域键盘按下:设置缩进快捷键
511
- handleEditorKeydown(e) {
512
- if (this.disabled) {
513
- return
514
- }
515
- //单独按下tab键
516
- if (e.keyCode == 9 && !e.metaKey && !e.shiftKey && !e.ctrlKey && !e.altKey && this.tab) {
517
- e.preventDefault()
518
- this.editor.insertText(' ')
519
- this.editor.formatElementStack()
520
- this.editor.domRender()
521
- this.editor.rangeRender()
522
- }
523
- //自定义键盘按下操作
524
- this.$emit('keydown', e)
525
- },
526
- //点击编辑器:处理图片和视频的光标聚集
527
- handleEditorClick(e) {
528
- if (this.disabled || this.isSourceView) {
529
- return
530
- }
531
- const node = e.target
532
- //点击的是图片或者视频
533
- if (node.nodeName.toLocaleLowerCase() == 'img' || node.nodeName.toLocaleLowerCase() == 'video') {
534
- const key = Number(node.getAttribute('data-editify-element'))
535
- if (DapNumber.isNumber(key)) {
536
- const element = this.editor.getElementByKey(key)
537
- if (!this.editor.range) {
538
- this.editor.initRange()
539
- }
540
- this.editor.range.anchor.moveToStart(element)
541
- this.editor.range.focus.moveToEnd(element)
542
- this.editor.rangeRender()
543
- }
544
- }
545
- },
546
- //编辑器的值更新
547
- handleEditorChange(newVal, oldVal) {
548
- if (this.disabled) {
549
- return
550
- }
551
- //内部修改
552
- this.internalModify(newVal)
553
- //触发change事件
554
- this.$emit('change', newVal, oldVal)
555
- },
556
- //编辑器失去焦点
557
- handleEditorBlur(val) {
558
- if (this.disabled) {
559
- return
560
- }
561
- if (this.border && this.color && !this.isFullScreen) {
562
- //恢复编辑区域边框颜色
563
- this.$refs.body.style.borderColor = ''
564
- //恢复编辑区域阴影颜色
565
- this.$refs.body.style.boxShadow = ''
566
- //使用菜单栏的情况下恢复菜单栏的样式
567
- if (this.menuConfig.use) {
568
- this.$refs.menu.$el.style.borderColor = ''
569
- this.$refs.menu.$el.style.boxShadow = ''
570
- }
571
- }
572
- this.$emit('blur', val)
573
- },
574
- //编辑器获取焦点
575
- handleEditorFocus(val) {
576
- if (this.disabled) {
577
- return
578
- }
579
- if (this.border && this.color && !this.isFullScreen) {
580
- //编辑区域边框颜色
581
- this.$refs.body.style.borderColor = this.color
582
- //转换颜色值
583
- const rgb = DapColor.hex2rgb(this.color)
584
- //菜单栏模式为inner
585
- if (this.menuConfig.use && this.menuConfig.mode == 'inner') {
586
- //编辑区域除顶部边框的阴影
587
- this.$refs.body.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)`
588
- //菜单栏的边框颜色
589
- this.$refs.menu.$el.style.borderColor = this.color
590
- //菜单栏除底部边框的阴影
591
- this.$refs.menu.$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)`
592
- }
593
- //其他菜单栏模式
594
- else if (this.menuConfig.use) {
595
- //编辑区域四边阴影
596
- this.$refs.body.style.boxShadow = `0 0 8px rgba(${rgb[0]},${rgb[1]},${rgb[2]},0.5)`
597
- }
598
- //不使用菜单栏
599
- else {
600
- //编辑区域四边阴影
601
- this.$refs.body.style.boxShadow = `0 0 8px rgba(${rgb[0]},${rgb[1]},${rgb[2]},0.5)`
602
- }
603
- }
604
- //获取焦点时可以使用菜单栏
605
- setTimeout(() => {
606
- this.canUseMenu = true
607
- this.$emit('focus', val)
608
- }, 0)
609
- },
610
- //编辑器换行
611
- handleInsertParagraph(element, previousElement) {
612
- //前一个块元素如果是只包含换行符的元素,并且当前块元素也是包含换行符的元素,则当前块元素转为段落
613
- if (previousElement.isOnlyHasBreak() && element.isOnlyHasBreak()) {
614
- if (previousElement.parsedom != AlexElement.BLOCK_NODE) {
615
- elementToParagraph(previousElement)
616
- this.editor.range.anchor.moveToStart(previousElement)
617
- this.editor.range.focus.moveToStart(previousElement)
618
- element.toEmpty()
619
- }
620
- }
621
- this.$emit('insertparagraph', this.value)
622
- },
623
- //编辑器焦点更新
624
- handleRangeUpdate() {
625
- if (this.disabled) {
626
- return
627
- }
628
-
629
- //如果没有range禁用菜单栏
630
- this.canUseMenu = !!this.editor.range
631
-
632
- //没有range直接返回
633
- if (!this.editor.range) {
634
- return
635
- }
636
-
637
- //获取光标选取范围内的元素数据,并且进行缓存
638
- this.dataRangeCaches = this.editor.getElementsByRange()
639
-
640
- //节流写法
641
- if (this.rangeUpdateTimer) {
642
- clearTimeout(this.rangeUpdateTimer)
643
- this.rangeUpdateTimer = null
644
- }
645
- //延时200ms进行判断
646
- this.rangeUpdateTimer = setTimeout(() => {
647
- //如果使用工具条或者菜单栏
648
- if (this.toolbarConfig.use || this.menuConfig.use) {
649
- //如果使用工具条
650
- if (this.toolbarConfig.use) {
651
- this.handleToolbar()
652
- }
653
- //如果使用菜单栏
654
- if (this.menuConfig.use) {
655
- this.$refs.menu.handleRangeUpdate()
656
- }
657
- }
658
- }, 200)
659
- this.$emit('rangeupdate')
660
- },
661
- //编辑器粘贴html
662
- handlePasteHtml(elements) {
663
- const keepStyles = Object.assign(pasteKeepData.styles, this.pasteKeepStyles || {})
664
- const keepMarks = Object.assign(pasteKeepData.marks, this.pasteKeepMarks || {})
665
- //粘贴html时过滤元素的样式和属性
666
- AlexElement.flatElements(elements).forEach(el => {
667
- let marks = {}
668
- let styles = {}
669
- if (el.hasMarks()) {
670
- for (let key in keepMarks) {
671
- if (el.marks.hasOwnProperty(key) && ((Array.isArray(keepMarks[key]) && keepMarks[key].includes(el.parsedom)) || keepMarks[key] == '*')) {
672
- marks[key] = el.marks[key]
673
- }
674
- }
675
- el.marks = marks
676
- }
677
- if (el.hasStyles() && !el.isText()) {
678
- for (let key in keepStyles) {
679
- if (el.styles.hasOwnProperty(key) && ((Array.isArray(keepStyles[key]) && keepStyles[key].includes(el.parsedom)) || keepStyles[key] == '*')) {
680
- styles[key] = el.styles[key]
681
- }
682
- }
683
- el.styles = styles
684
- }
685
- })
686
- },
687
- //编辑器部分删除情景(在编辑器起始处)
688
- handleDeleteInStart(element) {
689
- if (element.isBlock()) {
690
- elementToParagraph(element)
691
- }
692
- },
693
- //编辑器删除完成后事件
694
- handleDeleteComplete() {
695
- const uneditable = this.editor.range.anchor.element.getUneditableElement()
696
- if (uneditable) {
697
- uneditable.toEmpty()
698
- }
699
- },
700
- //编辑器dom渲染
701
- handleAfterRender() {
702
- //设定视频高度
703
- this.setVideoHeight()
704
-
705
- this.$emit('updateview')
706
- },
707
-
708
- //api:光标设置到文档底部
709
- collapseToEnd() {
710
- if (this.disabled) {
711
- return
712
- }
713
- this.editor.collapseToEnd()
714
- this.editor.rangeRender()
715
- DapElement.setScrollTop({
716
- el: this.$refs.content,
717
- number: 1000000,
718
- time: 0
719
- })
720
- },
721
- //api:光标设置到文档头部
722
- collapseToStart() {
723
- if (this.disabled) {
724
- return
725
- }
726
- this.editor.collapseToStart()
727
- this.editor.rangeRender()
728
- this.$nextTick(() => {
729
- DapElement.setScrollTop({
730
- el: this.$refs.content,
731
- number: 0,
732
- time: 0
733
- })
734
- })
735
- },
736
- //api:撤销
737
- undo() {
738
- if (this.disabled) {
739
- return
740
- }
741
- const historyRecord = this.editor.history.get(-1)
742
- if (historyRecord) {
743
- this.editor.history.current = historyRecord.current
744
- this.editor.stack = historyRecord.stack
745
- this.editor.range = historyRecord.range
746
- this.editor.formatElementStack()
747
- this.editor.domRender(true)
748
- this.editor.rangeRender()
749
- }
750
- },
751
- //api:重做
752
- redo() {
753
- if (this.disabled) {
754
- return
755
- }
756
- const historyRecord = this.editor.history.get(1)
757
- if (historyRecord) {
758
- this.editor.history.current = historyRecord.current
759
- this.editor.stack = historyRecord.stack
760
- this.editor.range = historyRecord.range
761
- this.editor.formatElementStack()
762
- this.editor.domRender(true)
763
- this.editor.rangeRender()
764
- }
765
- }
766
- },
767
- beforeUnmount() {
768
- //卸载绑定在滚动元素上的事件
769
- this.removeScrollHandle()
770
- //卸载绑定在document.documentElement上的事件
771
- DapEvent.off(document.documentElement, `mousedown.editify_${this.uid} mousemove.editify_${this.uid} mouseup.editify_${this.uid} click.editify_${this.uid}`)
772
- //卸载绑定在window上的事件
773
- DapEvent.off(window, `resize.editify_${this.uid}`)
774
- //销毁编辑器
775
- this.editor.destroy()
776
- }
777
- }
778
- </script>
779
- <style lang="less" scoped>
780
- .editify {
781
- display: flex;
782
- justify-content: flex-start;
783
- flex-direction: column;
784
- width: 100%;
785
- height: 100%;
786
- position: relative;
787
- box-sizing: border-box;
788
- -webkit-tap-highlight-color: transparent;
789
- outline: none;
790
- font-family: 'PingFang SC', 'Helvetica Neue', Helvetica, Roboto, 'Segoe UI', 'Microsoft YaHei', Arial, sans-serif;
791
- line-height: 1.5;
792
-
793
- *,
794
- *::before,
795
- *::after {
796
- box-sizing: border-box;
797
- -webkit-tap-highlight-color: transparent;
798
- outline: none;
799
- }
800
-
801
- &.fullscreen {
802
- position: fixed;
803
- z-index: 1000;
804
- left: 0;
805
- top: 0;
806
- width: 100vw !important;
807
- height: 100vh !important;
808
- background: @background;
809
-
810
- .editify-body {
811
- border-radius: 0;
812
- }
813
- }
814
-
815
- &.autoheight {
816
- height: auto;
817
-
818
- .editify-body {
819
- height: auto;
820
- flex: none;
821
- }
822
- }
823
- }
824
-
825
- .editify-body {
826
- display: block;
827
- width: 100%;
828
- height: 0;
829
- flex: 1;
830
- position: relative;
831
- background-color: @background;
832
- padding: 1px;
833
- border-radius: 4px;
834
-
835
- &.border {
836
- border: 1px solid @border-color;
837
- transition: all 500ms;
838
-
839
- &.menu_inner {
840
- border-top: none;
841
- border-radius: 0 0 4px 4px;
842
- }
843
- }
844
-
845
- &.menu_inner {
846
- padding-top: 21px;
847
-
848
- .editify-source {
849
- top: 21px;
850
- height: calc(100% - 21px);
851
- }
852
- }
853
-
854
- //编辑器样式
855
- .editify-content {
856
- display: block;
857
- position: relative;
858
- overflow-x: hidden;
859
- overflow-y: auto;
860
- width: 100%;
861
- height: 100%;
862
- border-radius: inherit;
863
- padding: 6px 10px;
864
- line-height: 1.5;
865
- color: @font-color-dark;
866
- font-size: @font-size;
867
- position: relative;
868
- line-height: 1.5;
869
-
870
- //显示占位符
871
- &.placeholder::before {
872
- position: absolute;
873
- top: 0;
874
- left: 0;
875
- display: block;
876
- width: 100%;
877
- content: attr(data-editify-placeholder);
878
- font-size: inherit;
879
- font-family: inherit;
880
- color: @font-color-disabled;
881
- line-height: inherit;
882
- padding: 6px 10px;
883
- cursor: text;
884
- touch-action: none;
885
- user-select: none;
886
- }
887
-
888
- //段落样式和标题
889
- :deep(p),
890
- :deep(h1),
891
- :deep(h2),
892
- :deep(h3),
893
- :deep(h4),
894
- :deep(h5),
895
- :deep(h6) {
896
- display: block;
897
- width: 100%;
898
- margin: 0 0 15px 0;
899
- padding: 0;
900
- }
901
- :deep(h1) {
902
- font-size: 32px;
903
- }
904
- :deep(h2) {
905
- font-size: 28px;
906
- }
907
- :deep(h3) {
908
- font-size: 24px;
909
- }
910
- :deep(h4) {
911
- font-size: 20px;
912
- }
913
- :deep(h5) {
914
- font-size: 18px;
915
- }
916
- :deep(h6) {
917
- font-size: 16px;
918
- }
919
- //有序列表样式
920
- :deep(div[data-editify-list='ol']) {
921
- margin-bottom: 15px;
922
-
923
- &::before {
924
- content: attr(data-editify-value) '.';
925
- margin-right: 10px;
926
- }
927
- }
928
- //无序列表样式
929
- :deep(div[data-editify-list='ul']) {
930
- margin-bottom: 15px;
931
-
932
- &::before {
933
- content: '\2022';
934
- margin-right: 10px;
935
- }
936
- }
937
- //代码样式
938
- :deep(span[data-editify-code]) {
939
- display: inline-block;
940
- padding: 3px 6px;
941
- margin: 0 4px;
942
- border-radius: 4px;
943
- line-height: 1;
944
- font-family: Consolas, monospace, Monaco, Andale Mono, Ubuntu Mono;
945
- background-color: @pre-background;
946
- color: @font-color;
947
- border: 1px solid @border-color;
948
- text-indent: initial;
949
- font-size: @font-size;
950
- font-weight: normal;
951
- }
952
- //链接样式
953
- :deep(a) {
954
- color: @font-color-link;
955
- transition: all 200ms;
956
- text-decoration: none;
957
- cursor: text;
958
-
959
- &:hover {
960
- color: @font-color-link-dark;
961
- text-decoration: underline;
962
- }
963
- }
964
- //表格样式
965
- :deep(table) {
966
- width: 100%;
967
- border: 1px solid @border-color;
968
- margin: 0;
969
- padding: 0;
970
- border-collapse: collapse;
971
- margin-bottom: 15px;
972
- background-color: @background;
973
- color: @font-color-dark;
974
- font-size: @font-size;
975
-
976
- * {
977
- margin: 0 !important;
978
- }
979
-
980
- tbody {
981
- margin: 0;
982
- padding: 0;
983
-
984
- tr {
985
- margin: 0;
986
- padding: 0;
987
-
988
- &:first-child {
989
- background-color: @background-darker;
990
-
991
- td {
992
- font-weight: bold;
993
- position: relative;
994
- }
995
- }
996
-
997
- td {
998
- margin: 0;
999
- border: 1px solid @border-color;
1000
- padding: 6px 10px;
1001
- position: relative;
1002
- word-break: break-word;
1003
-
1004
- &:not(:last-child)::after {
1005
- position: absolute;
1006
- right: -5px;
1007
- top: 0;
1008
- width: 10px;
1009
- height: 100%;
1010
- content: '';
1011
- z-index: 1;
1012
- cursor: col-resize;
1013
- user-select: none;
1014
- }
1015
- }
1016
- }
1017
- }
1018
- }
1019
- //代码块样式
1020
- :deep(pre) {
1021
- display: block;
1022
- padding: 6px 10px;
1023
- margin: 0 0 15px;
1024
- font-family: Consolas, monospace, Monaco, Andale Mono, Ubuntu Mono;
1025
- line-height: 1.5;
1026
- font-size: @font-size;
1027
- color: @font-color-dark;
1028
- background-color: @pre-background;
1029
- border: 1px solid @border-color;
1030
- border-radius: 4px;
1031
- overflow: auto;
1032
- position: relative;
1033
- }
1034
- //图片样式
1035
- :deep(img) {
1036
- position: relative;
1037
- display: inline-block;
1038
- width: 30%;
1039
- height: auto;
1040
- border-radius: 2px;
1041
- vertical-align: text-bottom;
1042
- margin: 0 2px;
1043
- max-width: 100%;
1044
- }
1045
- //视频样式
1046
- :deep(video) {
1047
- position: relative;
1048
- display: inline-block;
1049
- width: 30%;
1050
- border-radius: 2px;
1051
- vertical-align: text-bottom;
1052
- background-color: #000;
1053
- object-fit: contain;
1054
- margin: 0 2px;
1055
- max-width: 100%;
1056
- }
1057
- //引用样式
1058
- :deep(blockquote) {
1059
- display: block;
1060
- border-left: 8px solid @background-darker;
1061
- padding: 6px 10px 6px 20px;
1062
- margin: 0 0 15px;
1063
- line-height: 1.5;
1064
- font-size: @font-size;
1065
- color: @font-color-light;
1066
- border-radius: 0;
1067
- }
1068
- //任务列表样式
1069
- :deep(div[data-editify-task]) {
1070
- margin-bottom: 15px;
1071
- position: relative;
1072
- padding-left: 26px;
1073
- font-size: @font-size;
1074
- color: @font-color-dark;
1075
- transition: all 200ms;
1076
-
1077
- &::before {
1078
- display: block;
1079
- width: 16px;
1080
- height: 16px;
1081
- border-radius: 2px;
1082
- border: 2px solid @font-color-light;
1083
- transition: all 200ms;
1084
- box-sizing: border-box;
1085
- user-select: none;
1086
- content: '';
1087
- position: absolute;
1088
- left: 0;
1089
- top: 2px;
1090
- z-index: 1;
1091
- cursor: pointer;
1092
- }
1093
-
1094
- &::after {
1095
- position: absolute;
1096
- content: '';
1097
- left: 3px;
1098
- top: 6px;
1099
- display: inline-block;
1100
- width: 10px;
1101
- height: 6px;
1102
- border: 2px solid @font-color-light;
1103
- border-top: none;
1104
- border-right: none;
1105
- transform: rotate(-45deg);
1106
- transform-origin: center;
1107
- margin-bottom: 2px;
1108
- box-sizing: border-box;
1109
- z-index: 2;
1110
- cursor: pointer;
1111
- opacity: 0;
1112
- transition: all 200ms;
1113
- }
1114
-
1115
- &[data-editify-task='checked'] {
1116
- text-decoration: line-through;
1117
- color: @font-color-light;
1118
- &::after {
1119
- opacity: 1;
1120
- }
1121
- }
1122
- }
1123
-
1124
- //禁用样式
1125
- &.disabled {
1126
- cursor: auto !important;
1127
- &.placeholder::before {
1128
- cursor: auto;
1129
- }
1130
- :deep(a) {
1131
- cursor: pointer;
1132
- }
1133
-
1134
- :deep(table) {
1135
- td:not(:last-child)::after {
1136
- cursor: auto;
1137
- }
1138
- }
1139
- }
1140
- }
1141
-
1142
- //代码视图
1143
- .editify-source {
1144
- display: block;
1145
- width: 100%;
1146
- height: 100%;
1147
- position: absolute;
1148
- left: 0;
1149
- top: 0;
1150
- background-color: @reverse-background;
1151
- margin: 0;
1152
- padding: 6px 10px;
1153
- overflow-x: hidden;
1154
- overflow-y: auto;
1155
- font-size: @font-size;
1156
- color: @reverse-color;
1157
- font-family: Consolas, Monaco, Andale Mono, Ubuntu Mono, monospace;
1158
- resize: none;
1159
- border: none;
1160
- border-radius: inherit;
1161
- z-index: 1;
1162
- }
1163
- }
1164
-
1165
- .editify-footer {
1166
- display: flex;
1167
- justify-content: end;
1168
- align-items: center;
1169
- width: 100%;
1170
- padding: 10px;
1171
- position: relative;
1172
-
1173
- .editify-footer-words {
1174
- font-size: @font-size;
1175
- color: @font-color-light;
1176
- line-height: 1;
1177
- }
1178
-
1179
- //全屏模式下并且不是代码视图下,显示一个上边框
1180
- &.fullscreen {
1181
- border-top: 1px solid @border-color;
1182
- }
1183
- }
1184
- </style>