vue-editify 0.0.50 → 0.1.0

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