vue-editify 0.0.51 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/Editify.vue CHANGED
@@ -1,11 +1,11 @@
1
1
  <template>
2
- <div class="editify" :class="{ fullheight: height === true && !isFullScreen, fullscreen: isFullScreen }">
2
+ <div class="editify" :class="{ fullscreen: isFullScreen }">
3
3
  <!-- 菜单区域 -->
4
- <Menu v-if="menuConfig.use" :config="menuConfig" :disabled="disabled || !canUseMenu" :color="color" ref="menu"></Menu>
4
+ <Menu v-if="menuConfig.use" :config="menuConfig" :color="color" ref="menu"></Menu>
5
5
  <!-- 编辑层,与编辑区域宽高相同必须适配 -->
6
6
  <div ref="body" class="editify-body" :class="{ border: showBorder, menu_inner: menuConfig.use && menuConfig.mode == 'inner' }" :data-editify-uid="uid">
7
7
  <!-- 编辑器 -->
8
- <div ref="content" class="editify-content" :class="{ placeholder: showPlaceholder, disabled: disabled }" :style="contentStyle" @keydown="handleEditorKeydown" @click="handleEditorClick" @compositionstart="isInputChinese = true" @compositionend="isInputChinese = false" :data-editify-placeholder="placeholder"></div>
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
9
  <!-- 代码视图 -->
10
10
  <textarea v-if="isSourceView" :value="value" readonly class="editify-source" />
11
11
  <!-- 工具条 -->
@@ -21,16 +21,18 @@
21
21
  <script>
22
22
  import { getCurrentInstance } from 'vue'
23
23
  import { AlexEditor, AlexElement } from 'alex-editor'
24
- import Dap from 'dap-util'
25
- import { pasteKeepData, editorProps, parseList, mediaHandle, tableHandle, preHandle, blockToParagraph, blockToList, blockIsList, getButtonOptionsConfig, getToolbarConfig, getMenuConfig, mergeObject, blockIsTask, blockToTask } from './core'
26
- import Toolbar from './components/bussiness/Toolbar'
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'
27
28
  import Tooltip from './components/base/Tooltip'
28
- import Menu from './components/bussiness/Menu'
29
+ import Toolbar from './components/Toolbar'
30
+ import Menu from './components/Menu'
29
31
 
30
32
  export default {
31
33
  name: 'editify',
32
34
  props: { ...editorProps },
33
- emits: ['update:modelValue', 'focus', 'blur', 'change', 'keydown', 'insertparagraph', 'rangeupdate', 'copy', 'cut', 'paste-text', 'paste-html', 'paste-image', 'paste-video', 'before-render', 'after-render'],
35
+ emits: ['update:modelValue', 'focus', 'blur', 'change', 'keydown', 'insertparagraph', 'rangeupdate', 'updateview'],
34
36
  setup() {
35
37
  const instance = getCurrentInstance()
36
38
  return {
@@ -39,14 +41,8 @@ export default {
39
41
  },
40
42
  data() {
41
43
  return {
42
- //编辑器对象
43
- editor: null,
44
44
  //是否编辑器内部修改值
45
45
  isModelChange: false,
46
- //是否代码视图
47
- isSourceView: false,
48
- //是否全屏
49
- isFullScreen: false,
50
46
  //是否正在输入中文
51
47
  isInputChinese: false,
52
48
  //表格列宽拖拽记录数据
@@ -63,12 +59,22 @@ export default {
63
59
  //类型
64
60
  type: 'text'
65
61
  },
66
- //rangeUpdate更新延时器
67
- updateTimer: null,
62
+
63
+ /** 以下是可对外使用的属性 */
64
+
65
+ //编辑器对象
66
+ editor: null,
67
+ //是否代码视图
68
+ isSourceView: false,
69
+ //是否全屏
70
+ isFullScreen: false,
68
71
  //菜单栏是否可以使用标识
69
72
  canUseMenu: false,
70
- //手动设定的编辑器编辑区域高度
71
- contentHeight: 0
73
+ //光标选取范围内的元素数组
74
+ dataRangeCaches: {
75
+ flatList: [],
76
+ list: []
77
+ }
72
78
  }
73
79
  },
74
80
  computed: {
@@ -83,7 +89,7 @@ export default {
83
89
  },
84
90
  //编辑器的纯文本值
85
91
  textValue() {
86
- return Dap.element.string2dom(`<div>${this.value}</div>`).innerText
92
+ return DapElement.string2dom(`<div>${this.value}</div>`).innerText
87
93
  },
88
94
  //是否显示占位符
89
95
  showPlaceholder() {
@@ -103,15 +109,6 @@ export default {
103
109
  }
104
110
  return this.border
105
111
  },
106
- //编辑器样式设置
107
- contentStyle() {
108
- if (this.height === true || this.isFullScreen) {
109
- return {
110
- height: this.contentHeight + 'px'
111
- }
112
- }
113
- return this.autoheight ? { minHeight: this.height } : { height: this.height }
114
- },
115
112
  //最终生效的工具栏配置
116
113
  toolbarConfig() {
117
114
  return mergeObject(getToolbarConfig(this.$editTrans, this.$editLocale), this.toolbar || {})
@@ -152,19 +149,12 @@ export default {
152
149
  }
153
150
  }
154
151
  },
155
- //全屏切换
156
- isFullScreen() {
157
- this.$nextTick(() => {
158
- this.setContentHeight()
159
- })
160
- },
161
- //监听height为true
162
- height: {
163
- immediate: true,
164
- handler: function () {
165
- this.$nextTick(() => {
166
- this.setContentHeight()
167
- })
152
+ //监听disabled
153
+ disabled(newValue) {
154
+ if (newValue) {
155
+ this.editor.setDisabled()
156
+ } else {
157
+ this.editor.setEnabled()
168
158
  }
169
159
  }
170
160
  },
@@ -174,71 +164,17 @@ export default {
174
164
  //监听滚动隐藏工具条
175
165
  this.handleScroll()
176
166
  //鼠标按下监听
177
- Dap.event.on(document.documentElement, `mousedown.editify_${this.uid}`, this.documentMouseDown)
167
+ DapEvent.on(document.documentElement, `mousedown.editify_${this.uid}`, this.documentMouseDown)
178
168
  //鼠标移动监听
179
- Dap.event.on(document.documentElement, `mousemove.editify_${this.uid}`, this.documentMouseMove)
169
+ DapEvent.on(document.documentElement, `mousemove.editify_${this.uid}`, this.documentMouseMove)
180
170
  //鼠标松开监听
181
- Dap.event.on(document.documentElement, `mouseup.editify_${this.uid}`, this.documentMouseUp)
171
+ DapEvent.on(document.documentElement, `mouseup.editify_${this.uid}`, this.documentMouseUp)
182
172
  //鼠标点击箭头
183
- Dap.event.on(document.documentElement, `click.editify_${this.uid}`, this.documentClick)
173
+ DapEvent.on(document.documentElement, `click.editify_${this.uid}`, this.documentClick)
184
174
  //监听窗口改变
185
- Dap.event.on(window, `resize.editify_${this.uid}`, () => {
186
- this.setVideoHeight()
187
- this.setContentHeight()
188
- })
175
+ DapEvent.on(window, `resize.editify_${this.uid}`, this.setVideoHeight)
189
176
  },
190
177
  methods: {
191
- //初始创建编辑器
192
- createEditor() {
193
- //创建编辑器
194
- this.editor = new AlexEditor(this.$refs.content, {
195
- value: this.value,
196
- disabled: this.disabled,
197
- renderRules: [
198
- parseList,
199
- mediaHandle,
200
- tableHandle,
201
- el => {
202
- preHandle.apply(this.editor, [el, this.toolbarConfig?.use && this.toolbarConfig?.codeBlock?.languages?.show, this.toolbarConfig?.codeBlock?.languages.options])
203
- },
204
- ...this.renderRules
205
- ],
206
- allowCopy: this.allowCopy,
207
- allowPaste: this.allowPaste,
208
- allowCut: this.allowCut,
209
- allowPasteHtml: this.allowPasteHtml,
210
- allowPasteHtml: this.allowPasteHtml,
211
- customImagePaste: this.customImagePaste,
212
- customVideoPaste: this.customVideoPaste,
213
- customMerge: this.handleCustomMerge,
214
- customParseNode: this.handleCustomParseNode
215
- })
216
- //编辑器渲染后会有一个渲染过程,会改变内容,因此重新获取内容的值来设置value
217
- this.internalModify(this.editor.value)
218
- //设置监听事件
219
- this.editor.on('change', this.handleEditorChange)
220
- this.editor.on('focus', this.handleEditorFocus)
221
- this.editor.on('blur', this.handleEditorBlur)
222
- this.editor.on('insertParagraph', this.handleInsertParagraph)
223
- this.editor.on('rangeUpdate', this.handleRangeUpdate)
224
- this.editor.on('copy', this.handleCopy)
225
- this.editor.on('cut', this.handleCut)
226
- this.editor.on('pasteText', this.handlePasteText)
227
- this.editor.on('pasteHtml', this.handlePasteHtml)
228
- this.editor.on('pasteImage', this.handlePasteImage)
229
- this.editor.on('pasteVideo', this.handlePasteVideo)
230
- this.editor.on('deleteInStart', this.handleDeleteInStart)
231
- this.editor.on('deleteComplete', this.handleDeleteComplete)
232
- this.editor.on('beforeRender', this.handleBeforeRender)
233
- this.editor.on('afterRender', this.handleAfterRender)
234
- //格式化和dom渲染
235
- this.editor.formatElementStack()
236
- this.editor.domRender()
237
- //自动获取焦点
238
- if (this.autofocus && !this.isSourceView && !this.disabled) {
239
- this.collapseToEnd()
240
- }
241
- },
242
178
  //编辑器内部修改值的方法
243
179
  internalModify(val) {
244
180
  this.isModelChange = true
@@ -247,10 +183,15 @@ export default {
247
183
  this.isModelChange = false
248
184
  })
249
185
  },
186
+ //隐藏工具条
187
+ hideToolbar() {
188
+ this.toolbarOptions.show = false
189
+ this.toolbarOptions.node = null
190
+ },
250
191
  //监听滚动隐藏工具条
251
192
  handleScroll() {
252
193
  const setScroll = el => {
253
- Dap.event.on(el, `scroll.editify_${this.uid}`, () => {
194
+ DapEvent.on(el, `scroll.editify_${this.uid}`, () => {
254
195
  if (this.toolbarConfig.use && this.toolbarOptions.show) {
255
196
  this.hideToolbar()
256
197
  }
@@ -264,7 +205,7 @@ export default {
264
205
  //移除上述滚动事件的监听
265
206
  removeScrollHandle() {
266
207
  const removeScroll = el => {
267
- Dap.event.off(el, `scroll.editify_${this.uid}`)
208
+ DapEvent.off(el, `scroll.editify_${this.uid}`)
268
209
  if (el.parentNode) {
269
210
  removeScroll(el.parentNode)
270
211
  }
@@ -272,17 +213,17 @@ export default {
272
213
  removeScroll(this.$refs.content)
273
214
  },
274
215
  //工具条显示判断
275
- handleToolbar(useCache = false) {
216
+ handleToolbar() {
276
217
  if (this.disabled || this.isSourceView) {
277
218
  return
278
219
  }
279
220
  this.hideToolbar()
280
221
  this.$nextTick(() => {
281
- const table = this.getCurrentParsedomElement('table', useCache)
282
- const pre = this.getCurrentParsedomElement('pre', true)
283
- const link = this.getCurrentParsedomElement('a', true)
284
- const image = this.getCurrentParsedomElement('img', true)
285
- const video = this.getCurrentParsedomElement('video', true)
222
+ const table = getCurrentParsedomElement(this, 'table')
223
+ const pre = getCurrentParsedomElement(this, 'pre')
224
+ const link = getCurrentParsedomElement(this, 'a')
225
+ const image = getCurrentParsedomElement(this, 'img')
226
+ const video = getCurrentParsedomElement(this, 'video')
286
227
  if (link) {
287
228
  this.toolbarOptions.type = 'link'
288
229
  this.toolbarOptions.node = `[data-editify-uid="${this.uid}"] [data-editify-element="${link.key}"]`
@@ -324,10 +265,10 @@ export default {
324
265
  this.toolbarOptions.show = true
325
266
  }
326
267
  } else {
327
- const result = this.editor.getElementsByRange(true).flatIncludes.filter(item => {
268
+ const result = this.dataRangeCaches.flatList.filter(item => {
328
269
  return item.element.isText()
329
270
  })
330
- if (result.length && !this.hasTable(true) && !this.hasPreStyle(true) && !this.hasLink(true) && !this.hasImage(true) && !this.hasVideo(true)) {
271
+ if (result.length && !hasTableInRange(this) && !hasPreInRange(this) && !hasLinkInRange(this) && !hasImageInRange(this) && !hasVideoInRange(this)) {
331
272
  this.toolbarOptions.type = 'text'
332
273
  if (this.toolbarOptions.show) {
333
274
  this.$refs.toolbar.$refs.layer.setPosition()
@@ -338,41 +279,68 @@ export default {
338
279
  }
339
280
  })
340
281
  },
341
- //重新定义编辑器合并元素的逻辑
342
- handleCustomMerge(ele, preEle) {
343
- const uneditable = preEle.getUneditableElement()
344
- if (uneditable) {
345
- uneditable.toEmpty()
346
- } else {
347
- preEle.children.push(...ele.children)
348
- preEle.children.forEach(item => {
349
- item.parent = preEle
350
- })
351
- ele.children = null
352
- }
282
+ //设定编辑器内的视频高度
283
+ setVideoHeight() {
284
+ this.$refs.content.querySelectorAll('video').forEach(video => {
285
+ video.style.height = video.offsetWidth / this.videoRatio + 'px'
286
+ })
353
287
  },
354
- //针对node转为元素进行额外的处理
355
- handleCustomParseNode(ele) {
356
- if (ele.parsedom == 'code') {
357
- ele.parsedom = 'span'
358
- const marks = {
359
- 'data-editify-code': true
360
- }
361
- if (ele.hasMarks()) {
362
- Object.assign(ele.marks, marks)
363
- } else {
364
- ele.marks = marks
365
- }
366
- }
367
- if (typeof this.customParseNode == 'function') {
368
- ele = this.customParseNode.apply(this, [ele])
288
+ //初始创建编辑器
289
+ createEditor() {
290
+ //创建编辑器
291
+ this.editor = new AlexEditor(this.$refs.content, {
292
+ value: this.value,
293
+ disabled: this.disabled,
294
+ renderRules: [
295
+ el => {
296
+ parseList(this.editor, el)
297
+ },
298
+ el => {
299
+ orderdListHandle(this.editor, el)
300
+ },
301
+ el => {
302
+ mediaHandle(this.editor, el)
303
+ },
304
+ el => {
305
+ tableHandle(this.editor, el)
306
+ },
307
+ el => {
308
+ preHandle(this.editor, el, this.toolbarConfig?.use && this.toolbarConfig?.codeBlock?.languages?.show, this.toolbarConfig?.codeBlock?.languages.options)
309
+ },
310
+ el => {
311
+ specialInblockHandle(this.editor, el)
312
+ },
313
+ ...this.renderRules
314
+ ],
315
+ allowCopy: this.allowCopy,
316
+ allowPaste: this.allowPaste,
317
+ allowCut: this.allowCut,
318
+ allowPasteHtml: this.allowPasteHtml,
319
+ allowPasteHtml: this.allowPasteHtml,
320
+ customImagePaste: this.handleCustomImagePaste,
321
+ customVideoPaste: this.handleCustomVideoPaste,
322
+ customMerge: this.handleCustomMerge,
323
+ customParseNode: this.handleCustomParseNode
324
+ })
325
+ //编辑器渲染后会有一个渲染过程,会改变内容,因此重新获取内容的值来设置value
326
+ this.internalModify(this.editor.value)
327
+ //设置监听事件
328
+ this.editor.on('change', this.handleEditorChange)
329
+ this.editor.on('focus', this.handleEditorFocus)
330
+ this.editor.on('blur', this.handleEditorBlur)
331
+ this.editor.on('insertParagraph', this.handleInsertParagraph)
332
+ this.editor.on('rangeUpdate', this.handleRangeUpdate)
333
+ this.editor.on('pasteHtml', this.handlePasteHtml)
334
+ this.editor.on('deleteInStart', this.handleDeleteInStart)
335
+ this.editor.on('deleteComplete', this.handleDeleteComplete)
336
+ this.editor.on('afterRender', this.handleAfterRender)
337
+ //格式化和dom渲染
338
+ this.editor.formatElementStack()
339
+ this.editor.domRender()
340
+ //自动获取焦点
341
+ if (this.autofocus && !this.isSourceView && !this.disabled) {
342
+ this.collapseToEnd()
369
343
  }
370
- return ele
371
- },
372
- //隐藏工具条
373
- hideToolbar() {
374
- this.toolbarOptions.show = false
375
- this.toolbarOptions.node = null
376
344
  },
377
345
  //鼠标在页面按下:处理表格拖拽改变列宽和菜单栏是否使用判断
378
346
  documentMouseDown(e) {
@@ -380,9 +348,9 @@ export default {
380
348
  return
381
349
  }
382
350
  //鼠标在编辑器内按下
383
- if (Dap.element.isContains(this.$refs.content, e.target)) {
351
+ if (DapElement.isContains(this.$refs.content, e.target)) {
384
352
  const elm = e.target
385
- const key = Dap.data.get(elm, 'data-alex-editor-key')
353
+ const key = DapData.get(elm, 'data-alex-editor-key')
386
354
  if (key) {
387
355
  const element = this.editor.getElementByKey(key)
388
356
  if (element && element.parsedom == 'td') {
@@ -391,7 +359,7 @@ export default {
391
359
  if (element.parent.children[length - 1].isEqual(element)) {
392
360
  return
393
361
  }
394
- const rect = Dap.element.getElementBounding(elm)
362
+ const rect = DapElement.getElementBounding(elm)
395
363
  //在可拖拽范围内
396
364
  if (e.pageX >= Math.abs(rect.left + elm.offsetWidth - 5) && e.pageX <= Math.abs(rect.left + elm.offsetWidth + 5)) {
397
365
  this.tableColumnResizeParams.element = element
@@ -401,7 +369,7 @@ export default {
401
369
  }
402
370
  }
403
371
  //如果点击了除编辑器外的地方,菜单栏不可使用
404
- if (!Dap.element.isContains(this.$el, e.target) && !this.isSourceView) {
372
+ if (!DapElement.isContains(this.$el, e.target) && !this.isSourceView) {
405
373
  this.canUseMenu = false
406
374
  }
407
375
  },
@@ -413,7 +381,7 @@ export default {
413
381
  if (!this.tableColumnResizeParams.element) {
414
382
  return
415
383
  }
416
- const table = this.getCurrentParsedomElement('table')
384
+ const table = getCurrentParsedomElement(this, 'table')
417
385
  if (!table) {
418
386
  return
419
387
  }
@@ -429,14 +397,14 @@ export default {
429
397
  this.tableColumnResizeParams.start = e.pageX
430
398
  },
431
399
  //鼠标在页面松开:处理表格拖拽改变列宽
432
- documentMouseUp(e) {
400
+ documentMouseUp() {
433
401
  if (this.disabled) {
434
402
  return
435
403
  }
436
404
  if (!this.tableColumnResizeParams.element) {
437
405
  return
438
406
  }
439
- const table = this.getCurrentParsedomElement('table')
407
+ const table = getCurrentParsedomElement(this, 'table')
440
408
  if (!table) {
441
409
  return
442
410
  }
@@ -461,17 +429,17 @@ export default {
461
429
  if (this.disabled) {
462
430
  return
463
431
  }
464
- //鼠标在编辑器内按下
465
- if (Dap.element.isContains(this.$refs.content, e.target)) {
432
+ //鼠标在编辑器内点击
433
+ if (DapElement.isContains(this.$refs.content, e.target)) {
466
434
  const elm = e.target
467
- const key = Dap.data.get(elm, 'data-alex-editor-key')
435
+ const key = DapData.get(elm, 'data-alex-editor-key')
468
436
  if (key) {
469
437
  const element = this.editor.getElementByKey(key)
470
438
  //如果是任务列表元素
471
- if (blockIsTask(element)) {
472
- const rect = Dap.element.getElementBounding(elm)
439
+ if (isTask(element)) {
440
+ const rect = DapElement.getElementBounding(elm)
473
441
  //在复选框范围内
474
- if (e.pageX >= Math.abs(rect.left) && e.pageX <= Math.abs(rect.left + 16) && e.pageY >= Math.abs(rect.top + elm.offsetHeight / 2 - 8) && e.pageY <= Math.abs(rect.top + elm.offsetHeight / 2 + 8)) {
442
+ 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)) {
475
443
  //取消勾选
476
444
  if (element.marks['data-editify-task'] == 'checked') {
477
445
  element.marks['data-editify-task'] = 'uncheck'
@@ -493,6 +461,99 @@ export default {
493
461
  }
494
462
  }
495
463
  },
464
+ //自定义图片粘贴
465
+ async handleCustomImagePaste(url) {
466
+ const newUrl = await this.customImagePaste.apply(this, [url])
467
+ if (newUrl) {
468
+ insertImage(this, newUrl)
469
+ }
470
+ },
471
+ //自定义视频粘贴
472
+ async handleCustomVideoPaste(url) {
473
+ const newUrl = await this.customVideoPaste.apply(this, [url])
474
+ if (newUrl) {
475
+ insertVideo(this, newUrl)
476
+ }
477
+ },
478
+ //重新定义编辑器合并元素的逻辑
479
+ handleCustomMerge(ele, preEle) {
480
+ const uneditable = preEle.getUneditableElement()
481
+ if (uneditable) {
482
+ uneditable.toEmpty()
483
+ } else {
484
+ preEle.children.push(...ele.children)
485
+ preEle.children.forEach(item => {
486
+ item.parent = preEle
487
+ })
488
+ ele.children = null
489
+ }
490
+ },
491
+ //针对node转为元素进行额外的处理
492
+ handleCustomParseNode(ele) {
493
+ if (ele.parsedom == 'code') {
494
+ ele.parsedom = 'span'
495
+ const marks = {
496
+ 'data-editify-code': true
497
+ }
498
+ if (ele.hasMarks()) {
499
+ Object.assign(ele.marks, marks)
500
+ } else {
501
+ ele.marks = marks
502
+ }
503
+ }
504
+ if (typeof this.customParseNoe == 'function') {
505
+ ele = this.customParseNoe.apply(this, [ele])
506
+ }
507
+ return ele
508
+ },
509
+ //编辑区域键盘按下:设置缩进快捷键
510
+ handleEditorKeydown(e) {
511
+ if (this.disabled) {
512
+ return
513
+ }
514
+ //增加缩进
515
+ if (e.keyCode == 9 && !e.metaKey && !e.shiftKey && !e.ctrlKey && !e.altKey) {
516
+ e.preventDefault()
517
+ if (!hasTableInRange(this)) {
518
+ setIndentIncrease(this)
519
+ this.editor.formatElementStack()
520
+ this.editor.domRender()
521
+ this.editor.rangeRender()
522
+ }
523
+ }
524
+ //减少缩进
525
+ else if (e.keyCode == 9 && !e.metaKey && e.shiftKey && !e.ctrlKey && !e.altKey) {
526
+ e.preventDefault()
527
+ if (!hasTableInRange(this)) {
528
+ setIndentDecrease(this)
529
+ this.editor.formatElementStack()
530
+ this.editor.domRender()
531
+ this.editor.rangeRender()
532
+ }
533
+ }
534
+ //自定义键盘按下操作
535
+ this.$emit('keydown', e)
536
+ },
537
+ //点击编辑器:处理图片和视频的光标聚集
538
+ handleEditorClick(e) {
539
+ if (this.disabled || this.isSourceView) {
540
+ return
541
+ }
542
+ const node = e.target
543
+ //点击的是图片或者视频
544
+ if (node.nodeName.toLocaleLowerCase() == 'img' || node.nodeName.toLocaleLowerCase() == 'video') {
545
+ const key = Number(node.getAttribute('data-editify-element'))
546
+ if (DapNumber.isNumber(key)) {
547
+ const element = this.editor.getElementByKey(key)
548
+ if (!this.editor.range) {
549
+ this.editor.initRange()
550
+ }
551
+ this.editor.range.anchor.moveToStart(element)
552
+ this.editor.range.focus.moveToEnd(element)
553
+ this.editor.rangeRender()
554
+ }
555
+ }
556
+ },
496
557
  //编辑器的值更新
497
558
  handleEditorChange(newVal, oldVal) {
498
559
  if (this.disabled) {
@@ -530,7 +591,7 @@ export default {
530
591
  //编辑区域边框颜色
531
592
  this.$refs.body.style.borderColor = this.color
532
593
  //转换颜色值
533
- const rgb = Dap.color.hex2rgb(this.color)
594
+ const rgb = DapColor.hex2rgb(this.color)
534
595
  //菜单栏模式为inner
535
596
  if (this.menuConfig.use && this.menuConfig.mode == 'inner') {
536
597
  //编辑区域除顶部边框的阴影
@@ -554,103 +615,52 @@ export default {
554
615
  //获取焦点时可以使用菜单栏
555
616
  setTimeout(() => {
556
617
  this.canUseMenu = true
618
+ this.$emit('focus', val)
557
619
  }, 0)
558
- this.$emit('focus', val)
559
620
  },
560
- //编辑区域键盘按下
561
- handleEditorKeydown(e) {
621
+ //编辑器换行
622
+ handleInsertParagraph(element, previousElement) {
623
+ //前一个块元素如果是只包含换行符的元素,并且当前块元素也是包含换行符的元素,则当前块元素转为段落
624
+ if (previousElement.isOnlyHasBreak() && element.isOnlyHasBreak()) {
625
+ if (previousElement.parsedom != AlexElement.BLOCK_NODE) {
626
+ elementToParagraph(previousElement)
627
+ this.editor.range.anchor.moveToStart(previousElement)
628
+ this.editor.range.focus.moveToStart(previousElement)
629
+ element.toEmpty()
630
+ }
631
+ }
632
+ this.$emit('insertparagraph', this.value)
633
+ },
634
+ //编辑器焦点更新
635
+ handleRangeUpdate() {
562
636
  if (this.disabled) {
563
637
  return
564
638
  }
565
- //增加缩进
566
- if (e.keyCode == 9 && !e.metaKey && !e.shiftKey && !e.ctrlKey && !e.altKey) {
567
- e.preventDefault()
568
- this.setIndentIncrease()
569
- }
570
- //减少缩进
571
- else if (e.keyCode == 9 && !e.metaKey && e.shiftKey && !e.ctrlKey && !e.altKey) {
572
- e.preventDefault()
573
- this.setIndentDecrease()
574
- }
575
- //自定义键盘按下操作
576
- this.$emit('keydown', e)
577
- },
578
- //点击编辑器
579
- handleEditorClick(e) {
580
- if (this.disabled || this.isSourceView) {
639
+
640
+ //如果没有range禁用菜单栏
641
+ this.canUseMenu = !!this.editor.range
642
+
643
+ //没有range直接返回
644
+ if (!this.editor.range) {
581
645
  return
582
646
  }
583
- const node = e.target
584
- //点击的是图片或者视频
585
- if (node.nodeName.toLocaleLowerCase() == 'img' || node.nodeName.toLocaleLowerCase() == 'video') {
586
- const key = node.getAttribute('data-editify-element')
587
- if (key) {
588
- const element = this.editor.getElementByKey(key)
589
- if (!this.editor.range) {
590
- this.editor.initRange()
591
- }
592
- this.editor.range.anchor.moveToStart(element)
593
- this.editor.range.focus.moveToEnd(element)
594
- this.editor.rangeRender()
595
- }
596
- }
597
- },
598
- //编辑器换行
599
- handleInsertParagraph(element, previousElement) {
600
- //前一个块元素如果是只包含换行符的元素,并且当前块元素也是包含换行符的元素,则当前块元素转为段落
601
- if (previousElement.isOnlyHasBreak() && element.isOnlyHasBreak()) {
602
- if (!previousElement.isBlock()) {
603
- previousElement.convertToBlock()
647
+
648
+ //获取光标选取范围内的元素数据,并且进行缓存
649
+ this.dataRangeCaches = this.editor.getElementsByRange()
650
+
651
+ //如果使用工具条或者菜单栏
652
+ if (this.toolbarConfig.use || this.menuConfig.use) {
653
+ //如果使用工具条
654
+ if (this.toolbarConfig.use) {
655
+ this.handleToolbar()
604
656
  }
605
- if (previousElement.parsedom != AlexElement.BLOCK_NODE) {
606
- blockToParagraph(previousElement)
607
- this.editor.range.anchor.moveToStart(previousElement)
608
- this.editor.range.focus.moveToStart(previousElement)
609
- element.toEmpty()
657
+ //如果使用菜单栏
658
+ if (this.menuConfig.use) {
659
+ this.$refs.menu.handleRangeUpdate()
610
660
  }
611
661
  }
612
- this.$emit('insertparagraph', this.value)
613
- },
614
- //编辑器焦点更新
615
- handleRangeUpdate() {
616
- if (this.disabled) {
617
- return
618
- }
619
- this.canUseMenu = !!this.editor.range
620
- if (!this.editor.range) {
621
- return
622
- }
623
- if (this.updateTimer) {
624
- clearTimeout(this.updateTimer)
625
- }
626
- this.updateTimer = setTimeout(() => {
627
- //如果使用工具条或者菜单栏
628
- if (this.toolbarConfig.use || this.menuConfig.use) {
629
- //先获取选区内的元素设置内部缓存
630
- this.editor.getElementsByRange()
631
- //如果使用工具条
632
- if (this.toolbarConfig.use) {
633
- this.handleToolbar(true)
634
- }
635
- //如果使用菜单栏
636
- if (this.menuConfig.use) {
637
- this.$refs.menu.handleRangeUpdate(true)
638
- }
639
- }
640
- }, 200)
641
- this.$emit('rangeupdate', this.value)
642
- },
643
- //编辑器复制
644
- handleCopy(text, html) {
645
- this.$emit('copy', text, html)
646
- },
647
- //编辑器剪切
648
- handleCut(text, html) {
649
- this.$emit('cut', text, html)
650
- },
651
- //编辑器粘贴纯文本
652
- handlePasteText(data) {
653
- this.$emit('paste-text', data)
662
+
663
+ this.$emit('rangeupdate')
654
664
  },
655
665
  //编辑器粘贴html
656
666
  handlePasteHtml(elements) {
@@ -677,20 +687,11 @@ export default {
677
687
  el.styles = styles
678
688
  }
679
689
  })
680
- this.$emit('paste-html', elements)
681
- },
682
- //编辑器粘贴图片
683
- handlePasteImage(url) {
684
- this.$emit('paste-image', url)
685
690
  },
686
- //编辑器粘贴视频
687
- handlePasteVideo(url) {
688
- this.$emit('paste-video', url)
689
- },
690
- //编辑器部分删除情景
691
+ //编辑器部分删除情景(在编辑器起始处)
691
692
  handleDeleteInStart(element) {
692
693
  if (element.isBlock()) {
693
- blockToParagraph(element)
694
+ elementToParagraph(element)
694
695
  }
695
696
  },
696
697
  //编辑器删除完成后事件
@@ -700,39 +701,12 @@ export default {
700
701
  uneditable.toEmpty()
701
702
  }
702
703
  },
703
- //编辑器dom渲染之前
704
- handleBeforeRender() {
705
- this.$emit('before-render')
706
- },
707
704
  //编辑器dom渲染
708
705
  handleAfterRender() {
709
706
  //设定视频高度
710
707
  this.setVideoHeight()
711
- //触发事件
712
- this.$emit('after-render')
713
- },
714
- //设定视频高度
715
- setVideoHeight() {
716
- this.$refs.content.querySelectorAll('video').forEach(video => {
717
- video.style.height = video.offsetWidth / this.videoRatio + 'px'
718
- })
719
- },
720
- //设置编辑器主体高度
721
- setContentHeight() {
722
- if (this.height === true || this.isFullScreen) {
723
- let height = this.$el.offsetHeight
724
- if (this.menuConfig.use) {
725
- height -= this.$refs.menu.$el.offsetHeight
726
- }
727
- if (this.showWordLength) {
728
- height -= this.$refs.footer.offsetHeight
729
- }
730
- if (this.$refs.menu.menuMode == 'default') {
731
- height -= 10
732
- }
733
- //这里减去2px是因为body设了padding:1px
734
- this.contentHeight = height - 2
735
- }
708
+
709
+ this.$emit('updateview')
736
710
  },
737
711
 
738
712
  //api:光标设置到文档底部
@@ -742,7 +716,7 @@ export default {
742
716
  }
743
717
  this.editor.collapseToEnd()
744
718
  this.editor.rangeRender()
745
- Dap.element.setScrollTop({
719
+ DapElement.setScrollTop({
746
720
  el: this.$refs.content,
747
721
  number: 1000000,
748
722
  time: 0
@@ -756,364 +730,13 @@ export default {
756
730
  this.editor.collapseToStart()
757
731
  this.editor.rangeRender()
758
732
  this.$nextTick(() => {
759
- Dap.element.setScrollTop({
733
+ DapElement.setScrollTop({
760
734
  el: this.$refs.content,
761
735
  number: 0,
762
736
  time: 0
763
737
  })
764
738
  })
765
739
  },
766
- //api:获取某个元素是否在某个标签元素下,如果是返回该标签元素,否则返回null
767
- getParsedomElementByElement(element, parsedom) {
768
- if (element.isBlock()) {
769
- return element.parsedom == parsedom ? element : null
770
- }
771
- if (!element.isText() && element.parsedom == parsedom) {
772
- return element
773
- }
774
- return this.getParsedomElementByElement(element.parent, parsedom)
775
- },
776
- //api:获取光标是否在指定标签元素下,如果是返回该标签元素,否则返回null
777
- getCurrentParsedomElement(parsedom, useCache = false) {
778
- if (this.disabled) {
779
- return null
780
- }
781
- if (!this.editor.range) {
782
- return null
783
- }
784
- if (this.editor.range.anchor.element.isEqual(this.editor.range.focus.element)) {
785
- return this.getParsedomElementByElement(this.editor.range.anchor.element, parsedom)
786
- }
787
- const arr = this.editor.getElementsByRange(useCache).includes.map(item => {
788
- return this.getParsedomElementByElement(item.element, parsedom)
789
- })
790
- let hasNull = arr.some(el => {
791
- return el == null
792
- })
793
- //如果存在null,则表示有的选区元素不在指定标签下,返回null
794
- if (hasNull) {
795
- return null
796
- }
797
- //如果只有一个元素,则返回该元素
798
- if (arr.length == 1) {
799
- return arr[0]
800
- }
801
- //默认数组中的元素都相等
802
- let flag = true
803
- for (let i = 1; i < arr.length; i++) {
804
- if (!arr[i].isEqual(arr[0])) {
805
- flag = false
806
- break
807
- }
808
- }
809
- //如果相等,则返回该元素
810
- if (flag) {
811
- return arr[0]
812
- }
813
- return null
814
- },
815
- //api:删除光标所在的指定标签元素
816
- deleteByParsedom(parsedom, isRender = true, useCache = false) {
817
- if (this.disabled) {
818
- return
819
- }
820
- if (!this.editor.range) {
821
- return
822
- }
823
- const element = this.getCurrentParsedomElement(parsedom, useCache)
824
- if (element) {
825
- element.toEmpty()
826
- if (isRender) {
827
- this.editor.formatElementStack()
828
- this.editor.domRender()
829
- this.editor.rangeRender()
830
- }
831
- }
832
- },
833
- //api:当光标在链接上时可以移除链接
834
- removeLink(isRender = true, useCache = false) {
835
- if (this.disabled) {
836
- return
837
- }
838
- if (!this.editor.range) {
839
- return
840
- }
841
- const link = this.getCurrentParsedomElement('a', useCache)
842
- if (link) {
843
- link.parsedom = AlexElement.TEXT_NODE
844
- delete link.marks.target
845
- delete link.marks.href
846
- if (isRender) {
847
- this.editor.formatElementStack()
848
- this.editor.domRender()
849
- this.editor.rangeRender()
850
- }
851
- }
852
- },
853
- //api:设置标题
854
- setHeading(parsedom, isRender = true, useCache = false) {
855
- if (this.disabled) {
856
- return
857
- }
858
- if (!this.editor.range) {
859
- return
860
- }
861
- const values = getButtonOptionsConfig(this.$editTrans, this.$editLocale).heading.map(item => {
862
- return item.value
863
- })
864
- if (!values.includes(parsedom)) {
865
- throw new Error('The parameter supports only h1-h6 and p')
866
- }
867
- if (this.editor.range.anchor.isEqual(this.editor.range.focus)) {
868
- const block = this.editor.range.anchor.element.getBlock()
869
- //先转为段落
870
- blockToParagraph(block)
871
- //设置标题
872
- block.parsedom = parsedom
873
- } else {
874
- const result = this.editor.getElementsByRange(useCache).includes
875
- result.forEach(el => {
876
- if (el.element.isBlock()) {
877
- blockToParagraph(el.element)
878
- el.element.parsedom = parsedom
879
- } else {
880
- const block = el.element.getBlock()
881
- blockToParagraph(block)
882
- block.parsedom = parsedom
883
- }
884
- })
885
- }
886
- if (isRender) {
887
- this.editor.formatElementStack()
888
- this.editor.domRender()
889
- this.editor.rangeRender()
890
- }
891
- },
892
- //api:插入有序列表 ordered为true表示有序列表
893
- setList(ordered, isRender = true, useCache = false) {
894
- if (this.disabled) {
895
- return
896
- }
897
- if (!this.editor.range) {
898
- return
899
- }
900
- //起点和终点在一起
901
- if (this.editor.range.anchor.isEqual(this.editor.range.focus)) {
902
- const block = this.editor.range.anchor.element.getBlock()
903
- const isList = blockIsList(block, ordered)
904
- if (isList) {
905
- blockToParagraph(block)
906
- } else {
907
- blockToList(block, ordered)
908
- }
909
- }
910
- //起点和终点不在一起
911
- else {
912
- let blocks = []
913
- const result = this.editor.getElementsByRange(useCache).includes
914
- result.forEach(item => {
915
- const block = item.element.getBlock()
916
- const exist = blocks.some(el => block.isEqual(el))
917
- if (!exist) {
918
- blocks.push(block)
919
- }
920
- })
921
- blocks.forEach(block => {
922
- const isList = blockIsList(block, ordered)
923
- if (isList) {
924
- blockToParagraph(block)
925
- } else {
926
- blockToList(block, ordered)
927
- }
928
- })
929
- }
930
- if (isRender) {
931
- this.editor.formatElementStack()
932
- this.editor.domRender()
933
- this.editor.rangeRender()
934
- }
935
- },
936
- //api:插入任务列表
937
- setTask(isRender = true, useCache = false) {
938
- if (this.disabled) {
939
- return
940
- }
941
- if (!this.editor.range) {
942
- return
943
- }
944
- //起点和终点在一起
945
- if (this.editor.range.anchor.isEqual(this.editor.range.focus)) {
946
- const block = this.editor.range.anchor.element.getBlock()
947
- const isTask = blockIsTask(block)
948
- if (isTask) {
949
- blockToParagraph(block)
950
- } else {
951
- blockToTask(block)
952
- }
953
- }
954
- //起点和终点不在一起
955
- else {
956
- let blocks = []
957
- const result = this.editor.getElementsByRange(useCache).includes
958
- result.forEach(item => {
959
- const block = item.element.getBlock()
960
- const exist = blocks.some(el => block.isEqual(el))
961
- if (!exist) {
962
- blocks.push(block)
963
- }
964
- })
965
- blocks.forEach(block => {
966
- const isTask = blockIsTask(block)
967
- if (isTask) {
968
- blockToParagraph(block)
969
- } else {
970
- blockToTask(block)
971
- }
972
- })
973
- }
974
- if (isRender) {
975
- this.editor.formatElementStack()
976
- this.editor.domRender()
977
- this.editor.rangeRender()
978
- }
979
- },
980
- //api:设置样式
981
- setTextStyle(name, value, isRender = true, useCache = false) {
982
- if (this.disabled) {
983
- return
984
- }
985
- if (!this.editor.range) {
986
- return
987
- }
988
- const active = this.queryTextStyle(name, value, useCache)
989
- if (active) {
990
- this.editor.removeTextStyle([name], true)
991
- } else {
992
- let styles = {}
993
- styles[name] = value
994
- this.editor.setTextStyle(styles, true)
995
- }
996
- if (isRender) {
997
- this.editor.formatElementStack()
998
- this.editor.domRender()
999
- this.editor.rangeRender()
1000
- }
1001
- },
1002
- //api:查询是否具有某个样式
1003
- queryTextStyle(name, value, useCache = false) {
1004
- return this.editor.queryTextStyle(name, value, useCache)
1005
- },
1006
- //api:设置标记
1007
- setTextMark(name, value, isRender = true, useCache = false) {
1008
- if (this.disabled) {
1009
- return
1010
- }
1011
- if (!this.editor.range) {
1012
- return
1013
- }
1014
- const active = this.queryTextMark(name, value, useCache)
1015
- if (active) {
1016
- this.editor.removeTextMark([name], true)
1017
- } else {
1018
- let marks = {}
1019
- marks[name] = value
1020
- this.editor.setTextMark(marks, true)
1021
- }
1022
- if (isRender) {
1023
- this.editor.formatElementStack()
1024
- this.editor.domRender()
1025
- this.editor.rangeRender()
1026
- }
1027
- },
1028
- //api:查询是否具有某个标记
1029
- queryTextMark(name, value, useCache = false) {
1030
- return this.editor.queryTextMark(name, value, useCache)
1031
- },
1032
- //api:清除文本样式和标记
1033
- formatText(isRender = true, useCache = false) {
1034
- if (this.disabled) {
1035
- return
1036
- }
1037
- if (!this.editor.range) {
1038
- return
1039
- }
1040
- this.editor.removeTextStyle(null, useCache)
1041
- //这里不能使用缓存,因为removeTextStyle会更新光标位置
1042
- this.editor.removeTextMark(null)
1043
- if (isRender) {
1044
- this.editor.formatElementStack()
1045
- this.editor.domRender()
1046
- this.editor.rangeRender()
1047
- }
1048
- },
1049
- //api:设置对齐方式,参数取值justify/left/right/center
1050
- setAlign(value, isRender = true, useCache = false) {
1051
- if (this.disabled) {
1052
- return
1053
- }
1054
- if (!this.editor.range) {
1055
- return
1056
- }
1057
- if (this.editor.range.anchor.isEqual(this.editor.range.focus)) {
1058
- const block = this.editor.range.anchor.element.getBlock()
1059
- const inblock = this.editor.range.anchor.element.getInblock()
1060
- if (inblock) {
1061
- if (inblock.hasStyles()) {
1062
- inblock.styles['text-align'] = value
1063
- } else {
1064
- inblock.styles = {
1065
- 'text-align': value
1066
- }
1067
- }
1068
- } else {
1069
- if (block.hasStyles()) {
1070
- block.styles['text-align'] = value
1071
- } else {
1072
- block.styles = {
1073
- 'text-align': value
1074
- }
1075
- }
1076
- }
1077
- } else {
1078
- const result = this.editor.getElementsByRange(useCache).includes
1079
- result.forEach(el => {
1080
- if (el.element.isBlock() || el.element.isInblock()) {
1081
- if (el.element.hasStyles()) {
1082
- el.element.styles['text-align'] = value
1083
- } else {
1084
- el.element.styles = {
1085
- 'text-align': value
1086
- }
1087
- }
1088
- } else {
1089
- const block = el.element.getBlock()
1090
- const inblock = el.element.getInblock()
1091
- if (inblock) {
1092
- if (inblock.hasStyles()) {
1093
- inblock.styles['text-align'] = value
1094
- } else {
1095
- inblock.styles = {
1096
- 'text-align': value
1097
- }
1098
- }
1099
- } else {
1100
- if (block.hasStyles()) {
1101
- block.styles['text-align'] = value
1102
- } else {
1103
- block.styles = {
1104
- 'text-align': value
1105
- }
1106
- }
1107
- }
1108
- }
1109
- })
1110
- }
1111
- if (isRender) {
1112
- this.editor.formatElementStack()
1113
- this.editor.domRender()
1114
- this.editor.rangeRender()
1115
- }
1116
- },
1117
740
  //api:撤销
1118
741
  undo() {
1119
742
  if (this.disabled) {
@@ -1143,621 +766,15 @@ export default {
1143
766
  this.editor.domRender(true)
1144
767
  this.editor.rangeRender()
1145
768
  }
1146
- },
1147
- //api:插入引用
1148
- setQuote(isRender = true, useCache = false) {
1149
- if (this.disabled) {
1150
- return
1151
- }
1152
- if (!this.editor.range) {
1153
- return
1154
- }
1155
- //起点和终点在一起
1156
- if (this.editor.range.anchor.isEqual(this.editor.range.focus)) {
1157
- const block = this.editor.range.anchor.element.getBlock()
1158
- const oldParsedom = block.parsedom
1159
- blockToParagraph(block)
1160
- if (oldParsedom != 'blockquote') {
1161
- block.parsedom = 'blockquote'
1162
- }
1163
- }
1164
- //起点和终点不在一起
1165
- else {
1166
- let blocks = []
1167
- const result = this.editor.getElementsByRange(useCache).includes
1168
- result.forEach(item => {
1169
- const block = item.element.getBlock()
1170
- const exist = blocks.some(el => block.isEqual(el))
1171
- if (!exist) {
1172
- blocks.push(block)
1173
- }
1174
- })
1175
- blocks.forEach(block => {
1176
- const oldParsedom = block.parsedom
1177
- blockToParagraph(block)
1178
- if (oldParsedom != 'blockquote') {
1179
- block.parsedom = 'blockquote'
1180
- }
1181
- })
1182
- }
1183
- if (isRender) {
1184
- this.editor.formatElementStack()
1185
- this.editor.domRender()
1186
- this.editor.rangeRender()
1187
- }
1188
- },
1189
- //api:设置行高
1190
- setLineHeight(value, isRender = true, useCache = false) {
1191
- if (this.disabled) {
1192
- return
1193
- }
1194
- if (!this.editor.range) {
1195
- return
1196
- }
1197
- if (this.editor.range.anchor.isEqual(this.editor.range.focus)) {
1198
- const block = this.editor.range.anchor.element.getBlock()
1199
- const inblock = this.editor.range.anchor.element.getInblock()
1200
- if (inblock) {
1201
- if (inblock.hasStyles()) {
1202
- inblock.styles['line-height'] = value
1203
- } else {
1204
- inblock.styles = {
1205
- 'line-height': value
1206
- }
1207
- }
1208
- } else {
1209
- if (block.hasStyles()) {
1210
- block.styles['line-height'] = value
1211
- } else {
1212
- block.styles = {
1213
- 'line-height': value
1214
- }
1215
- }
1216
- }
1217
- } else {
1218
- const result = this.editor.getElementsByRange(useCache).includes
1219
- result.forEach(el => {
1220
- if (el.element.isBlock() || el.element.isInblock()) {
1221
- if (el.element.hasStyles()) {
1222
- el.element.styles['line-height'] = value
1223
- } else {
1224
- el.element.styles = {
1225
- 'line-height': value
1226
- }
1227
- }
1228
- } else {
1229
- const block = el.element.getBlock()
1230
- const inblock = el.element.getInblock()
1231
- if (inblock) {
1232
- if (inblock.hasStyles()) {
1233
- inblock.styles['line-height'] = value
1234
- } else {
1235
- inblock.styles = {
1236
- 'line-height': value
1237
- }
1238
- }
1239
- } else {
1240
- if (block.hasStyles()) {
1241
- block.styles['line-height'] = value
1242
- } else {
1243
- block.styles = {
1244
- 'line-height': value
1245
- }
1246
- }
1247
- }
1248
- }
1249
- })
1250
- }
1251
- if (isRender) {
1252
- this.editor.formatElementStack()
1253
- this.editor.domRender()
1254
- this.editor.rangeRender()
1255
- }
1256
- },
1257
- //api:增加缩进
1258
- setIndentIncrease(isRender = true, useCache = false) {
1259
- if (this.disabled) {
1260
- return
1261
- }
1262
- if (!this.editor.range) {
1263
- return
1264
- }
1265
- const fn = element => {
1266
- if (element.hasStyles()) {
1267
- if (element.styles.hasOwnProperty('text-indent')) {
1268
- let val = element.styles['text-indent']
1269
- if (val.endsWith('em')) {
1270
- val = parseFloat(val)
1271
- } else {
1272
- val = 0
1273
- }
1274
- element.styles['text-indent'] = `${val + 2}em`
1275
- } else {
1276
- element.styles['text-indent'] = '2em'
1277
- }
1278
- } else {
1279
- element.styles = {
1280
- 'text-indent': '2em'
1281
- }
1282
- }
1283
- }
1284
- if (this.editor.range.anchor.isEqual(this.editor.range.focus)) {
1285
- const block = this.editor.range.anchor.element.getBlock()
1286
- const inblock = this.editor.range.anchor.element.getInblock()
1287
- if (inblock && inblock.behavior == 'block' && !inblock.isPreStyle()) {
1288
- fn(inblock)
1289
- } else if (!block.isPreStyle()) {
1290
- fn(block)
1291
- }
1292
- } else {
1293
- const result = this.editor.getElementsByRange(useCache).includes
1294
- result.forEach(item => {
1295
- const block = item.element.getBlock()
1296
- const inblock = item.element.getInblock()
1297
- if (inblock && inblock.behavior == 'block' && !inblock.isPreStyle()) {
1298
- fn(inblock)
1299
- } else if (!block.isPreStyle()) {
1300
- fn(block)
1301
- }
1302
- })
1303
- }
1304
- if (isRender) {
1305
- this.editor.formatElementStack()
1306
- this.editor.domRender()
1307
- this.editor.rangeRender()
1308
- }
1309
- },
1310
- //api:减少缩进
1311
- setIndentDecrease(isRender = true, useCache = false) {
1312
- if (this.disabled) {
1313
- return
1314
- }
1315
- if (!this.editor.range) {
1316
- return
1317
- }
1318
- const fn = element => {
1319
- if (element.hasStyles() && element.styles.hasOwnProperty('text-indent')) {
1320
- let val = element.styles['text-indent']
1321
- if (val.endsWith('em')) {
1322
- val = parseFloat(val)
1323
- } else {
1324
- val = 0
1325
- }
1326
- element.styles['text-indent'] = `${val - 2 >= 0 ? val - 2 : 0}em`
1327
- }
1328
- }
1329
- if (this.editor.range.anchor.isEqual(this.editor.range.focus)) {
1330
- const block = this.editor.range.anchor.element.getBlock()
1331
- const inblock = this.editor.range.anchor.element.getInblock()
1332
- if (inblock && inblock.behavior == 'block' && !inblock.isPreStyle()) {
1333
- fn(inblock)
1334
- } else if (!block.isPreStyle()) {
1335
- fn(block)
1336
- }
1337
- } else {
1338
- const result = this.editor.getElementsByRange(useCache).includes
1339
- result.forEach(item => {
1340
- const block = item.element.getBlock()
1341
- const inblock = item.element.getInblock()
1342
- if (inblock && inblock.behavior == 'block' && !inblock.isPreStyle()) {
1343
- fn(inblock)
1344
- } else if (!block.isPreStyle()) {
1345
- fn(block)
1346
- }
1347
- })
1348
- }
1349
- if (isRender) {
1350
- this.editor.formatElementStack()
1351
- this.editor.domRender()
1352
- this.editor.rangeRender()
1353
- }
1354
- },
1355
- //api:插入图片
1356
- insertImage(url, isRender = true, useCache = false) {
1357
- if (this.disabled) {
1358
- return
1359
- }
1360
- if (!this.editor.range) {
1361
- return
1362
- }
1363
- if (!url || typeof url != 'string') {
1364
- throw new Error('An image address must be given')
1365
- }
1366
- const image = new AlexElement(
1367
- 'closed',
1368
- 'img',
1369
- {
1370
- src: url
1371
- },
1372
- null,
1373
- null
1374
- )
1375
- this.editor.insertElement(image, true, useCache)
1376
- if (isRender) {
1377
- this.editor.formatElementStack()
1378
- this.editor.domRender()
1379
- this.editor.rangeRender()
1380
- }
1381
- },
1382
- //api:插入视频
1383
- insertVideo(url, isRender = true, useCache = false) {
1384
- if (this.disabled) {
1385
- return
1386
- }
1387
- if (!this.editor.range) {
1388
- return
1389
- }
1390
- if (!url || typeof url != 'string') {
1391
- throw new Error('A video address must be given')
1392
- }
1393
- const video = new AlexElement(
1394
- 'closed',
1395
- 'video',
1396
- {
1397
- src: url
1398
- },
1399
- null,
1400
- null
1401
- )
1402
- this.editor.insertElement(video, true, useCache)
1403
- const leftSpace = AlexElement.getSpaceElement()
1404
- const rightSpace = AlexElement.getSpaceElement()
1405
- this.editor.addElementAfter(rightSpace, video)
1406
- this.editor.addElementBefore(leftSpace, video)
1407
- this.editor.range.anchor.moveToEnd(rightSpace)
1408
- this.editor.range.focus.moveToEnd(rightSpace)
1409
- if (isRender) {
1410
- this.editor.formatElementStack()
1411
- this.editor.domRender()
1412
- this.editor.rangeRender()
1413
- }
1414
- },
1415
- //api:选区是否含有代码块样式
1416
- hasPreStyle(useCache = false) {
1417
- if (!this.editor.range) {
1418
- return false
1419
- }
1420
- if (this.editor.range.anchor.isEqual(this.editor.range.focus)) {
1421
- return this.editor.range.anchor.element.isPreStyle()
1422
- }
1423
- const result = this.editor.getElementsByRange(useCache).includes
1424
- return result.some(item => {
1425
- return item.element.isPreStyle()
1426
- })
1427
- },
1428
- //api:选区是否含有引用
1429
- hasQuote(useCache = false) {
1430
- if (!this.editor.range) {
1431
- return false
1432
- }
1433
- if (this.editor.range.anchor.isEqual(this.editor.range.focus)) {
1434
- const block = this.editor.range.anchor.element.getBlock()
1435
- return block.parsedom == 'blockquote'
1436
- }
1437
- const result = this.editor.getElementsByRange(useCache).includes
1438
- return result.some(item => {
1439
- if (item.element.isBlock()) {
1440
- return item.element.parsedom == 'blockquote'
1441
- } else {
1442
- const block = item.element.getBlock()
1443
- return block.parsedom == 'blockquote'
1444
- }
1445
- })
1446
- },
1447
- //api:选区是否含有列表
1448
- hasList(ordered = false, useCache = false) {
1449
- if (!this.editor.range) {
1450
- return false
1451
- }
1452
- if (this.editor.range.anchor.isEqual(this.editor.range.focus)) {
1453
- const block = this.editor.range.anchor.element.getBlock()
1454
- return blockIsList(block, ordered)
1455
- }
1456
- const result = this.editor.getElementsByRange(useCache).includes
1457
- return result.some(item => {
1458
- if (item.element.isBlock()) {
1459
- return blockIsList(item.element, ordered)
1460
- } else {
1461
- const block = item.element.getBlock()
1462
- return blockIsList(block, ordered)
1463
- }
1464
- })
1465
- },
1466
- //api:选区是否含有链接
1467
- hasLink(useCache = false) {
1468
- if (!this.editor.range) {
1469
- return false
1470
- }
1471
- if (this.editor.range.anchor.isEqual(this.editor.range.focus)) {
1472
- return !!this.getParsedomElementByElement(this.editor.range.anchor.element, 'a')
1473
- }
1474
- const result = this.editor.getElementsByRange(useCache).flatIncludes.filter(item => {
1475
- return item.element.isText()
1476
- })
1477
- return result.some(item => {
1478
- return !!this.getParsedomElementByElement(item.element, 'a')
1479
- })
1480
- },
1481
- //api:选区是否含有表格
1482
- hasTable(useCache = false) {
1483
- if (!this.editor.range) {
1484
- return false
1485
- }
1486
- if (this.editor.range.anchor.isEqual(this.editor.range.focus)) {
1487
- const block = this.editor.range.anchor.element.getBlock()
1488
- return block.parsedom == 'table'
1489
- }
1490
- const result = this.editor.getElementsByRange(useCache).includes
1491
- return result.some(item => {
1492
- if (item.element.isBlock()) {
1493
- return item.element.parsedom == 'table'
1494
- } else {
1495
- const block = item.element.getBlock()
1496
- return block.parsedom == 'table'
1497
- }
1498
- })
1499
- },
1500
- //api:选区是否含有任务列表
1501
- hasTask(useCache = false) {
1502
- if (!this.editor.range) {
1503
- return false
1504
- }
1505
- if (this.editor.range.anchor.isEqual(this.editor.range.focus)) {
1506
- const block = this.editor.range.anchor.element.getBlock()
1507
- return blockIsTask(block)
1508
- }
1509
- const result = this.editor.getElementsByRange(useCache).includes
1510
- return result.some(item => {
1511
- if (item.element.isBlock()) {
1512
- return blockIsTask(item.element)
1513
- } else {
1514
- const block = item.element.getBlock()
1515
- return blockIsTask(block)
1516
- }
1517
- })
1518
- },
1519
- //api:选区是否含有图片
1520
- hasImage(useCache = false) {
1521
- if (!this.editor.range) {
1522
- return false
1523
- }
1524
- if (this.editor.range.anchor.isEqual(this.editor.range.focus)) {
1525
- return this.editor.range.anchor.element.isClosed() && this.editor.range.anchor.element.parsedom == 'img'
1526
- }
1527
- const result = this.editor.getElementsByRange(useCache).flatIncludes
1528
- return result.some(item => {
1529
- return item.element.isClosed() && item.element.parsedom == 'img'
1530
- })
1531
- },
1532
- //api:选区是否含有视频
1533
- hasVideo(useCache = false) {
1534
- if (!this.editor.range) {
1535
- return false
1536
- }
1537
- if (this.editor.range.anchor.isEqual(this.editor.range.focus)) {
1538
- return this.editor.range.anchor.element.isClosed() && this.editor.range.anchor.element.parsedom == 'video'
1539
- }
1540
- const result = this.editor.getElementsByRange(useCache).flatIncludes
1541
- return result.some(item => {
1542
- return item.element.isClosed() && item.element.parsedom == 'video'
1543
- })
1544
- },
1545
- //api:选区是否全部在引用内
1546
- inQuote(useCache = false) {
1547
- if (!this.editor.range) {
1548
- return false
1549
- }
1550
- if (this.editor.range.anchor.isEqual(this.editor.range.focus)) {
1551
- const block = this.editor.range.anchor.element.getBlock()
1552
- return block.parsedom == 'blockquote'
1553
- }
1554
- const result = this.editor.getElementsByRange(useCache).includes
1555
- return result.every(item => {
1556
- if (item.element.isBlock()) {
1557
- return item.element.parsedom == 'blockquote'
1558
- } else {
1559
- const block = item.element.getBlock()
1560
- return block.parsedom == 'blockquote'
1561
- }
1562
- })
1563
- },
1564
- //api:选区是否全部在列表内
1565
- inList(ordered = false, useCache = false) {
1566
- if (!this.editor.range) {
1567
- return false
1568
- }
1569
- if (this.editor.range.anchor.isEqual(this.editor.range.focus)) {
1570
- const block = this.editor.range.anchor.element.getBlock()
1571
- return blockIsList(block, ordered)
1572
- }
1573
- const result = this.editor.getElementsByRange(useCache).includes
1574
- return result.every(item => {
1575
- if (item.element.isBlock()) {
1576
- return blockIsList(item.element, ordered)
1577
- } else {
1578
- const block = item.element.getBlock()
1579
- return blockIsList(block, ordered)
1580
- }
1581
- })
1582
- },
1583
- //api:选区是否全部在任务列表里
1584
- inTask(useCache = false) {
1585
- if (!this.editor.range) {
1586
- return false
1587
- }
1588
- if (this.editor.range.anchor.isEqual(this.editor.range.focus)) {
1589
- const block = this.editor.range.anchor.element.getBlock()
1590
- return blockIsTask(block)
1591
- }
1592
- const result = this.editor.getElementsByRange(useCache).includes
1593
- return result.every(item => {
1594
- if (item.element.isBlock()) {
1595
- return blockIsTask(item.element)
1596
- } else {
1597
- const block = item.element.getBlock()
1598
- return blockIsTask(block)
1599
- }
1600
- })
1601
- },
1602
- //api:创建一个空的表格
1603
- insertTable(rowLength, colLength, isRender = true, useCache = false) {
1604
- if (this.disabled) {
1605
- return
1606
- }
1607
- if (!this.editor.range) {
1608
- return
1609
- }
1610
- const table = new AlexElement('block', 'table', null, null, null)
1611
- const tbody = new AlexElement('inblock', 'tbody', null, null, null)
1612
- this.editor.addElementTo(tbody, table)
1613
- for (let i = 0; i < rowLength; i++) {
1614
- const row = new AlexElement('inblock', 'tr', null, null, null)
1615
- for (let j = 0; j < colLength; j++) {
1616
- const column = new AlexElement('inblock', 'td', null, null, null)
1617
- const breakEl = new AlexElement('closed', 'br', null, null, null)
1618
- this.editor.addElementTo(breakEl, column)
1619
- this.editor.addElementTo(column, row)
1620
- }
1621
- this.editor.addElementTo(row, tbody)
1622
- }
1623
- this.editor.insertElement(table, true, useCache)
1624
- //在表格后创建一个段落
1625
- const paragraph = new AlexElement('block', AlexElement.BLOCK_NODE, null, null, null)
1626
- const breakEl = new AlexElement('closed', 'br', null, null, null)
1627
- this.editor.addElementTo(breakEl, paragraph)
1628
- this.editor.addElementAfter(paragraph, table)
1629
- this.editor.range.anchor.moveToStart(tbody)
1630
- this.editor.range.focus.moveToStart(tbody)
1631
- if (isRender) {
1632
- this.editor.formatElementStack()
1633
- this.editor.domRender()
1634
- this.editor.rangeRender()
1635
- }
1636
- },
1637
- //api:插入代码块
1638
- insertCodeBlock(isRender = true, useCache = false) {
1639
- if (this.disabled) {
1640
- return
1641
- }
1642
- if (!this.editor.range) {
1643
- return
1644
- }
1645
- const pre = this.getCurrentParsedomElement('pre', useCache)
1646
- if (pre) {
1647
- let content = ''
1648
- AlexElement.flatElements(pre.children)
1649
- .filter(item => {
1650
- return item.isText()
1651
- })
1652
- .forEach(item => {
1653
- content += item.textContent
1654
- })
1655
- const splits = content.split('\n')
1656
- splits.forEach(item => {
1657
- const paragraph = new AlexElement('block', AlexElement.BLOCK_NODE, null, null, null)
1658
- const text = new AlexElement('text', null, null, null, item)
1659
- this.editor.addElementTo(text, paragraph)
1660
- this.editor.addElementBefore(paragraph, pre)
1661
- })
1662
- pre.toEmpty()
1663
- } else {
1664
- //起点和终点在一起
1665
- if (this.editor.range.anchor.isEqual(this.editor.range.focus)) {
1666
- const block = this.editor.range.anchor.element.getBlock()
1667
- blockToParagraph(block)
1668
- block.parsedom = 'pre'
1669
- const paragraph = new AlexElement('block', AlexElement.BLOCK_NODE, null, null, null)
1670
- const breakEl = new AlexElement('closed', 'br', null, null, null)
1671
- this.editor.addElementTo(breakEl, paragraph)
1672
- this.editor.addElementAfter(paragraph, block)
1673
- }
1674
- //起点和终点不在一起
1675
- else {
1676
- let result = this.editor.getElementsByRange(true).includes
1677
- this.editor.range.anchor.moveToStart(result[0].element.getBlock())
1678
- this.editor.range.focus.moveToEnd(result[result.length - 1].element.getBlock())
1679
- const res = this.editor.getElementsByRange(true).flatIncludes.filter(el => el.element.isText())
1680
- const obj = {}
1681
- res.forEach(el => {
1682
- if (obj[el.element.getBlock().key]) {
1683
- obj[el.element.getBlock().key].push(el.element.clone())
1684
- } else {
1685
- obj[el.element.getBlock().key] = [el.element.clone()]
1686
- }
1687
- })
1688
- const pre = new AlexElement('block', 'pre', null, null, null)
1689
- Object.keys(obj).forEach((key, index) => {
1690
- if (index > 0) {
1691
- const text = new AlexElement('text', null, null, null, '\n')
1692
- if (pre.hasChildren()) {
1693
- this.editor.addElementTo(text, pre, pre.children.length)
1694
- } else {
1695
- this.editor.addElementTo(text, pre)
1696
- }
1697
- }
1698
- obj[key].forEach(el => {
1699
- if (pre.hasChildren()) {
1700
- this.editor.addElementTo(el, pre, pre.children.length)
1701
- } else {
1702
- this.editor.addElementTo(el, pre)
1703
- }
1704
- })
1705
- })
1706
- this.editor.delete()
1707
- this.editor.insertElement(pre)
1708
- const paragraph = new AlexElement('block', AlexElement.BLOCK_NODE, null, null, null)
1709
- const breakEl = new AlexElement('closed', 'br', null, null, null)
1710
- this.editor.addElementTo(breakEl, paragraph)
1711
- this.editor.addElementAfter(paragraph, pre)
1712
- }
1713
- }
1714
- if (isRender) {
1715
- this.editor.formatElementStack()
1716
- this.editor.domRender()
1717
- this.editor.rangeRender()
1718
- }
1719
- },
1720
- //api:插入文本
1721
- insertText(text, isRender = true, useCache = false) {
1722
- if (this.disabled) {
1723
- return
1724
- }
1725
- if (!this.editor.range) {
1726
- return
1727
- }
1728
- this.editor.insertText(text, useCache)
1729
- if (isRender) {
1730
- this.editor.formatElementStack()
1731
- this.editor.domRender()
1732
- this.editor.rangeRender()
1733
- }
1734
- },
1735
- //api:插入html
1736
- insertHtml(html, isRender = true, useCache = false) {
1737
- if (this.disabled) {
1738
- return
1739
- }
1740
- if (!this.editor.range) {
1741
- return
1742
- }
1743
- const elements = this.editor.parseHtml(html)
1744
- for (let i = 0; i < elements.length; i++) {
1745
- this.editor.insertElement(elements[i], false, i == 0 ? useCache : false)
1746
- }
1747
- if (isRender) {
1748
- this.editor.formatElementStack()
1749
- this.editor.domRender()
1750
- this.editor.rangeRender()
1751
- }
1752
769
  }
1753
770
  },
1754
771
  beforeUnmount() {
1755
772
  //卸载绑定在滚动元素上的事件
1756
773
  this.removeScrollHandle()
1757
774
  //卸载绑定在document.documentElement上的事件
1758
- Dap.event.off(document.documentElement, `mousedown.editify_${this.uid} mousemove.editify_${this.uid} mouseup.editify_${this.uid} click.editify_${this.uid}`)
775
+ DapEvent.off(document.documentElement, `mousedown.editify_${this.uid} mousemove.editify_${this.uid} mouseup.editify_${this.uid} click.editify_${this.uid}`)
1759
776
  //卸载绑定在window上的事件
1760
- Dap.event.off(window, `resize.editify_${this.uid}`)
777
+ DapEvent.off(window, `resize.editify_${this.uid}`)
1761
778
  //销毁编辑器
1762
779
  this.editor.destroy()
1763
780
  }
@@ -1765,8 +782,11 @@ export default {
1765
782
  </script>
1766
783
  <style lang="less" scoped>
1767
784
  .editify {
1768
- display: block;
785
+ display: flex;
786
+ justify-content: flex-start;
787
+ flex-direction: column;
1769
788
  width: 100%;
789
+ height: 100%;
1770
790
  position: relative;
1771
791
  box-sizing: border-box;
1772
792
  -webkit-tap-highlight-color: transparent;
@@ -1781,18 +801,14 @@ export default {
1781
801
  -webkit-tap-highlight-color: transparent;
1782
802
  outline: none;
1783
803
  }
1784
-
1785
- &.fullheight {
1786
- height: 100%;
1787
- }
1788
-
1789
804
  &.fullscreen {
1790
805
  position: fixed;
1791
806
  z-index: 1000;
1792
807
  left: 0;
1793
808
  top: 0;
1794
- width: 100vw;
1795
- height: 100vh;
809
+ width: 100vw !important;
810
+ height: 100vh !important;
811
+ background: @background;
1796
812
 
1797
813
  .editify-body {
1798
814
  border-radius: 0;
@@ -1803,6 +819,8 @@ export default {
1803
819
  .editify-body {
1804
820
  display: block;
1805
821
  width: 100%;
822
+ height: 0;
823
+ flex: 1;
1806
824
  position: relative;
1807
825
  background-color: @background;
1808
826
  padding: 1px;
@@ -1834,6 +852,7 @@ export default {
1834
852
  overflow-x: hidden;
1835
853
  overflow-y: auto;
1836
854
  width: 100%;
855
+ height: 100%;
1837
856
  border-radius: inherit;
1838
857
  padding: 6px 10px;
1839
858
  line-height: 1.5;
@@ -2061,8 +1080,7 @@ export default {
2061
1080
  content: '';
2062
1081
  position: absolute;
2063
1082
  left: 0;
2064
- top: 50%;
2065
- transform: translateY(-50%);
1083
+ top: 2px;
2066
1084
  z-index: 1;
2067
1085
  cursor: pointer;
2068
1086
  }