vue-editify 0.0.50 → 0.1.0

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 || {})
@@ -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
  }