vue-editify 0.0.51 → 0.1.1

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