vue-editify 0.1.39 → 0.1.41

Sign up to get free protection for your applications and to get access to all the features.
package/src/core/rule.ts CHANGED
@@ -1,6 +1,5 @@
1
1
  import { AlexEditor, AlexElement } from 'alex-editor'
2
2
  import { LanguagesItemType, getHljsHtml } from '../hljs'
3
- import { getColNumbers } from './tool'
4
3
  import { isList, isTask } from './function'
5
4
  import { common as DapCommon } from 'dap-util'
6
5
 
@@ -192,25 +191,55 @@ export const tableHandle = function (editor: AlexEditor, element: AlexElement) {
192
191
  element.styles = styles
193
192
  }
194
193
  const elements = AlexElement.flatElements(element.children!)
194
+ //所有的行元素
195
195
  const rows = elements.filter(el => {
196
196
  return el.parsedom == 'tr'
197
197
  })
198
+ //colgroup元素
198
199
  let colgroup = elements.find(el => {
199
200
  return el.parsedom == 'colgroup'
200
201
  })
202
+ //理论上的col的数量:取最多列数
203
+ const colNumber = Math.max(
204
+ ...rows.map(row => {
205
+ return row.children!.length
206
+ })
207
+ )
208
+ //如果colgroup元素存在
201
209
  if (colgroup) {
210
+ //遍历每个col元素设置默认的width:'auto
202
211
  colgroup.children!.forEach(col => {
212
+ //如果没有任何标记
203
213
  if (!col.hasMarks()) {
204
214
  col.marks = {
205
215
  width: 'auto'
206
216
  }
207
- } else if (!col.marks!['width']) {
217
+ }
218
+ //如果没有width标记
219
+ else if (!col.marks!['width']) {
208
220
  col.marks!['width'] = 'auto'
209
221
  }
210
222
  })
211
- } else {
223
+ //对缺少的col元素进行补全
224
+ const length = colgroup.children!.length
225
+ if (length < colNumber) {
226
+ for (let i = 0; i < colNumber - length; i++) {
227
+ const col = new AlexElement(
228
+ 'closed',
229
+ 'col',
230
+ {
231
+ width: 'auto'
232
+ },
233
+ null,
234
+ null
235
+ )
236
+ editor.addElementTo(col, colgroup, colgroup.children!.length)
237
+ }
238
+ }
239
+ }
240
+ //如果colgroup元素不存在则新建
241
+ else {
212
242
  colgroup = new AlexElement('inblock', 'colgroup', null, null, null)
213
- const colNumber = getColNumbers(rows[0])
214
243
  for (let i = colNumber - 1; i >= 0; i--) {
215
244
  const col = new AlexElement(
216
245
  'closed',
@@ -227,6 +256,16 @@ export const tableHandle = function (editor: AlexEditor, element: AlexElement) {
227
256
  element.children = []
228
257
  const tbody = new AlexElement('inblock', 'tbody', null, null, null)
229
258
  rows.reverse().forEach(row => {
259
+ //对缺少的列进行补全
260
+ const length = row.children!.length
261
+ if (length < colNumber) {
262
+ for (let i = 0; i < colNumber - length; i++) {
263
+ const column = new AlexElement('inblock', 'td', null, null, null)
264
+ const breakElement = new AlexElement('closed', 'br', null, null, null)
265
+ editor.addElementTo(breakElement, column)
266
+ editor.addElementTo(column, row, row.children!.length)
267
+ }
268
+ }
230
269
  editor.addElementTo(row, tbody)
231
270
  })
232
271
  editor.addElementTo(tbody, element)
@@ -235,6 +274,23 @@ export const tableHandle = function (editor: AlexEditor, element: AlexElement) {
235
274
  if (element.parsedom == 'th') {
236
275
  element.parsedom = 'td'
237
276
  }
277
+ if (element.parsedom == 'td') {
278
+ //移除列的rowspan和colspan属性
279
+ if (element.hasMarks()) {
280
+ if (element.marks!['rowspan']) {
281
+ delete element.marks!['rowspan']
282
+ }
283
+ if (element.marks!['colspan']) {
284
+ delete element.marks!['colspan']
285
+ }
286
+ }
287
+ //移除列的display样式
288
+ if (element.hasStyles()) {
289
+ if (element.styles!['display']) {
290
+ delete element.styles!['display']
291
+ }
292
+ }
293
+ }
238
294
  }
239
295
 
240
296
  /**
package/src/core/tool.ts CHANGED
@@ -300,27 +300,6 @@ export const cloneData = function (data: any) {
300
300
  return data
301
301
  }
302
302
 
303
- /**
304
- * 根据行元素获取colgroup的col数量
305
- * @param row
306
- * @returns
307
- */
308
- export const getColNumbers = function (row: AlexElement) {
309
- const children = row.children || []
310
- let num = 0
311
- children.forEach(td => {
312
- if (td.hasMarks() && td.marks!.hasOwnProperty('colspan')) {
313
- const span = Number(td.marks!.colspan)
314
- if (!isNaN(span)) {
315
- num += span
316
- }
317
- } else {
318
- num += 1
319
- }
320
- })
321
- return num
322
- }
323
-
324
303
  /**
325
304
  * 获取菜单按钮列表数据配置
326
305
  * @param editTrans
@@ -766,7 +745,7 @@ export const getToolbarConfig = function (editTrans: (key: string) => any, editL
766
745
  rightBorder: false
767
746
  }
768
747
  },
769
- //(只对文本工具条中的按钮生效)添加额外的按钮禁用判定,回调参数为name,this指向组件实例
748
+ //(只对文本工具条中的按钮生效)添加额外的按钮禁用判定,回调参数为name
770
749
  extraDisabled: null
771
750
  }
772
751
  }
@@ -785,7 +764,7 @@ export const getMenuConfig = function (editTrans: (key: string) => any, editLoca
785
764
  tooltip: true,
786
765
  //菜单栏显示模式,支持default/inner/fixed
787
766
  mode: 'default',
788
- //添加额外的按钮禁用判定,回调参数为name,this指向组件实例
767
+ //添加额外的按钮禁用判定,回调参数为name
789
768
  extraDisabled: null,
790
769
  //菜单栏的样式自定义
791
770
  style: null,
@@ -357,7 +357,7 @@
357
357
  color: @font-color-link;
358
358
  transition: all 200ms;
359
359
  position: relative;
360
- padding: 0 10px;
360
+ margin: 0 10px;
361
361
  font-size: 14px;
362
362
  vertical-align: baseline;
363
363
 
@@ -380,6 +380,28 @@
380
380
  }
381
381
  }
382
382
 
383
+ //数学公式样式
384
+ :deep(span[data-editify-mathformula]) {
385
+ display: inline-block;
386
+ border: 1px dashed @border-color;
387
+ padding: 6px 10px;
388
+ border-radius: 4px;
389
+ margin: 0 4px;
390
+ transition: all 200ms;
391
+ max-width: 100%;
392
+
393
+ .katex,
394
+ math {
395
+ width: 100%;
396
+ overflow: hidden;
397
+ }
398
+
399
+ &:hover {
400
+ cursor: pointer;
401
+ background: @background-dark;
402
+ }
403
+ }
404
+
383
405
  //禁用样式
384
406
  &.editify-disabled {
385
407
  cursor: auto !important;
@@ -387,16 +409,26 @@
387
409
  &.editify-placeholder::before {
388
410
  cursor: auto;
389
411
  }
390
-
391
412
  :deep(a) {
392
413
  cursor: pointer;
393
414
  }
394
-
395
415
  :deep(table) {
396
416
  td:not(:last-child)::after {
397
417
  cursor: auto;
398
418
  }
399
419
  }
420
+ :deep(span[data-editify-mathformula]) {
421
+ display: inline-block;
422
+ border: none;
423
+ padding: 6px 10px;
424
+ border-radius: none;
425
+ margin: 0 4px;
426
+
427
+ &:hover {
428
+ cursor: auto;
429
+ background: none;
430
+ }
431
+ }
400
432
  }
401
433
  }
402
434
 
@@ -24,7 +24,7 @@ import { AlexEditor, AlexElement, AlexElementRangeType, AlexElementsRangeType }
24
24
  import { element as DapElement, event as DapEvent, data as DapData, number as DapNumber, color as DapColor } from 'dap-util'
25
25
  import { mergeObject, getToolbarConfig, getMenuConfig, MenuConfigType, ObjectType, ToolbarConfigType, PluginResultType } from '../core/tool'
26
26
  import { parseList, orderdListHandle, commonElementHandle, tableHandle, preHandle, specialInblockHandle } from '../core/rule'
27
- import { isTask, elementToParagraph, getCurrentParsedomElement, hasTableInRange, hasLinkInRange, hasPreInRange, hasImageInRange, hasVideoInRange } from '../core/function'
27
+ import { isTask, elementToParagraph, getMatchElementsByRange, hasTableInRange, hasLinkInRange, hasPreInRange, hasImageInRange, hasVideoInRange } from '../core/function'
28
28
  import Toolbar from '../components/toolbar/toolbar.vue'
29
29
  import Menu from '../components/menu/menu.vue'
30
30
  import Layer from '../components/layer/layer.vue'
@@ -180,59 +180,70 @@ const handleToolbar = () => {
180
180
  }
181
181
  hideToolbar()
182
182
  nextTick(() => {
183
- const table = getCurrentParsedomElement(editor.value!, dataRangeCaches.value, 'table')
184
- const pre = getCurrentParsedomElement(editor.value!, dataRangeCaches.value, 'pre')
185
- const link = getCurrentParsedomElement(editor.value!, dataRangeCaches.value, 'a')
186
- const image = getCurrentParsedomElement(editor.value!, dataRangeCaches.value, 'img')
187
- const video = getCurrentParsedomElement(editor.value!, dataRangeCaches.value, 'video')
188
- if (link) {
183
+ const tables = getMatchElementsByRange(editor.value!, dataRangeCaches.value, { parsedom: 'table' })
184
+ const pres = getMatchElementsByRange(editor.value!, dataRangeCaches.value, { parsedom: 'pre' })
185
+ const links = getMatchElementsByRange(editor.value!, dataRangeCaches.value, { parsedom: 'a' })
186
+ const images = getMatchElementsByRange(editor.value!, dataRangeCaches.value, { parsedom: 'img' })
187
+ const videos = getMatchElementsByRange(editor.value!, dataRangeCaches.value, { parsedom: 'video' })
188
+ //显示链接工具条
189
+ if (links.length == 1) {
189
190
  toolbarOptions.value.type = 'link'
190
- toolbarOptions.value.node = `[data-editify-uid="${instance.uid}"] [data-editify-element="${link.key}"]`
191
+ toolbarOptions.value.node = `[data-editify-uid="${instance.uid}"] [data-editify-element="${links[0].key}"]`
191
192
  if (toolbarOptions.value.show) {
192
193
  ;(<InstanceType<typeof Layer>>toolbarRef.value!.$refs.layerRef).setPosition()
193
194
  } else {
194
195
  toolbarOptions.value.show = true
195
196
  }
196
- } else if (image) {
197
+ }
198
+ //显示图片工具条
199
+ else if (images.length == 1) {
197
200
  toolbarOptions.value.type = 'image'
198
- toolbarOptions.value.node = `[data-editify-uid="${instance.uid}"] [data-editify-element="${image.key}"]`
201
+ toolbarOptions.value.node = `[data-editify-uid="${instance.uid}"] [data-editify-element="${images[0].key}"]`
199
202
  if (toolbarOptions.value.show) {
200
203
  ;(<InstanceType<typeof Layer>>toolbarRef.value!.$refs.layerRef).setPosition()
201
204
  } else {
202
205
  toolbarOptions.value.show = true
203
206
  }
204
- } else if (video) {
207
+ }
208
+ //显示视频工具条
209
+ else if (videos.length == 1) {
205
210
  toolbarOptions.value.type = 'video'
206
- toolbarOptions.value.node = `[data-editify-uid="${instance.uid}"] [data-editify-element="${video.key}"]`
211
+ toolbarOptions.value.node = `[data-editify-uid="${instance.uid}"] [data-editify-element="${videos[0].key}"]`
207
212
  if (toolbarOptions.value.show) {
208
213
  ;(<InstanceType<typeof Layer>>toolbarRef.value!.$refs.layerRef).setPosition()
209
214
  } else {
210
215
  toolbarOptions.value.show = true
211
216
  }
212
- } else if (table) {
217
+ }
218
+ //显示表格工具条
219
+ else if (tables.length == 1) {
213
220
  toolbarOptions.value.type = 'table'
214
- toolbarOptions.value.node = `[data-editify-uid="${instance.uid}"] [data-editify-element="${table.key}"]`
221
+ toolbarOptions.value.node = `[data-editify-uid="${instance.uid}"] [data-editify-element="${tables[0].key}"]`
215
222
  if (toolbarOptions.value.show) {
216
223
  ;(<InstanceType<typeof Layer>>toolbarRef.value!.$refs.layerRef).setPosition()
217
224
  } else {
218
225
  toolbarOptions.value.show = true
219
226
  }
220
- } else if (pre) {
227
+ }
228
+ //显示代码块工具条
229
+ else if (pres.length == 1) {
221
230
  toolbarOptions.value.type = 'codeBlock'
222
- toolbarOptions.value.node = `[data-editify-uid="${instance.uid}"] [data-editify-element="${pre.key}"]`
231
+ toolbarOptions.value.node = `[data-editify-uid="${instance.uid}"] [data-editify-element="${pres[0].key}"]`
223
232
  if (toolbarOptions.value.show) {
224
233
  ;(<InstanceType<typeof Layer>>toolbarRef.value!.$refs.layerRef).setPosition()
225
234
  } else {
226
235
  toolbarOptions.value.show = true
227
236
  }
228
- } else {
237
+ }
238
+ //显示文本工具条
239
+ else {
229
240
  const result = dataRangeCaches.value.flatList.filter((item: AlexElementRangeType) => {
230
241
  return item.element.isText()
231
242
  })
232
243
  if (result.length && !hasTableInRange(editor.value!, dataRangeCaches.value) && !hasPreInRange(editor.value!, dataRangeCaches.value) && !hasLinkInRange(editor.value!, dataRangeCaches.value) && !hasImageInRange(editor.value!, dataRangeCaches.value) && !hasVideoInRange(editor.value!, dataRangeCaches.value)) {
233
244
  toolbarOptions.value.type = 'text'
234
245
  if (toolbarOptions.value.show) {
235
- ;(<InstanceType<typeof Layer>>toolbarRef.value!.$refs.layerRef).setPosition()
246
+ ;(toolbarRef.value!.$refs.layerRef as InstanceType<typeof Layer>).setPosition()
236
247
  } else {
237
248
  toolbarOptions.value.show = true
238
249
  }
@@ -366,11 +377,11 @@ const documentMouseMove = (e: Event) => {
366
377
  if (!tableColumnResizeParams.value.element) {
367
378
  return
368
379
  }
369
- const table = getCurrentParsedomElement(editor.value!, dataRangeCaches.value, 'table')
370
- if (!table) {
380
+ const tables = getMatchElementsByRange(editor.value!, dataRangeCaches.value, { parsedom: 'table' })
381
+ if (tables.length != 1) {
371
382
  return
372
383
  }
373
- const colgroup = table.children!.find(item => {
384
+ const colgroup = tables[0].children!.find(item => {
374
385
  return item.parsedom == 'colgroup'
375
386
  })!
376
387
  const index = tableColumnResizeParams.value.element.parent!.children!.findIndex(el => {
@@ -389,11 +400,11 @@ const documentMouseUp = () => {
389
400
  if (!tableColumnResizeParams.value.element) {
390
401
  return
391
402
  }
392
- const table = getCurrentParsedomElement(editor.value!, dataRangeCaches.value, 'table')
393
- if (!table) {
403
+ const tables = getMatchElementsByRange(editor.value!, dataRangeCaches.value, { parsedom: 'table' })
404
+ if (tables.length != 1) {
394
405
  return
395
406
  }
396
- const colgroup = table.children!.find(item => {
407
+ const colgroup = tables[0].children!.find(item => {
397
408
  return item.parsedom == 'colgroup'
398
409
  })!
399
410
  const index = tableColumnResizeParams.value.element.parent!.children!.findIndex(el => {
@@ -466,10 +477,6 @@ const handleCustomHtmlPaste = async (elements: AlexElement[]) => {
466
477
  if (el.marks!['disabled']) {
467
478
  marks['disabled'] = el.marks!['disabled']
468
479
  }
469
- //td的colspan属性保留
470
- if (el.parsedom == 'td' && el.marks!['colspan']) {
471
- marks['colspan'] = el.marks!['colspan']
472
- }
473
480
  //图片和视频的src属性保留
474
481
  if (['img', 'video'].includes(el.parsedom!) && el.marks!['src']) {
475
482
  marks['src'] = el.marks!['src']
@@ -543,12 +550,10 @@ const handleCustomHtmlPaste = async (elements: AlexElement[]) => {
543
550
  })
544
551
  //对外的自定义属性和样式保留
545
552
  if (typeof props.pasteKeepMarks == 'function') {
546
- const keepMarks = props.pasteKeepMarks(el)
547
- marks = mergeObject(marks, keepMarks)!
553
+ marks = mergeObject(marks, props.pasteKeepMarks(el))!
548
554
  }
549
555
  if (typeof props.pasteKeepStyles == 'function') {
550
- const keepStyles = props.pasteKeepStyles(el)
551
- styles = mergeObject(styles, keepStyles)!
556
+ styles = mergeObject(styles, props.pasteKeepStyles(el))!
552
557
  }
553
558
  //将处理后的样式和标记给元素
554
559
  el.marks = marks
Binary file
Binary file
package/src/index.ts CHANGED
@@ -9,16 +9,17 @@ export type { ButtonTypeType, ButtonOptionsItemType, ButtonSelectConfigType, But
9
9
  export type { InsertImageUploadErrorType } from './components/insertImage/props'
10
10
  export type { InsertVideoUploadErrorType } from './components/insertVideo/props'
11
11
  export type { MenuButtonType, MenuSelectButtonType, MenuDisplayButtonType, MenuImageButtonType, MenuVideoButtonType, MenuTableButtonType, MenuCustomButtonType, CodeBlockToolbarType, TextToolbarType, ToolbarConfigType, MenuSequenceType, MenuModeType, MenuExtendType, MenuConfigType, PluginType, PluginResultType } from './core/tool'
12
+ export type { ElementMatchConfig } from './core/function'
12
13
 
13
14
  //导出编辑器操作方法
14
- export { getParsedomElementByElement, getCurrentParsedomElement, elementIsInList, elementIsInTask, isList, isTask, hasPreInRange, isRangeInPre, hasQuoteInRange, isRangeInQuote, hasListInRange, isRangeInList, hasTaskInRange, isRangeInTask, hasLinkInRange, hasTableInRange, hasImageInRange, hasVideoInRange, queryTextStyle, queryTextMark, getRangeText, setIndentIncrease, setIndentDecrease, setQuote, setAlign, setList, setTask, setTextStyle, setTextMark, removeTextStyle, removeTextMark, setLineHeight, insertLink, insertImage, insertVideo, insertTable, insertCodeBlock } from './core/function'
15
+ export { elementIsMatch, getMatchElementByElement, getMatchElementsByRange, elementIsInList, elementIsInTask, isList, isTask, hasPreInRange, isRangeInPre, hasQuoteInRange, isRangeInQuote, hasListInRange, isRangeInList, hasTaskInRange, isRangeInTask, hasLinkInRange, hasTableInRange, hasImageInRange, hasVideoInRange, queryTextStyle, queryTextMark, getRangeText, setIndentIncrease, setIndentDecrease, setQuote, setAlign, setList, setTask, setTextStyle, setTextMark, removeTextStyle, removeTextMark, setLineHeight, insertLink, insertImage, insertVideo, insertTable, insertCodeBlock } from './core/function'
15
16
 
16
17
  //安装函数
17
18
  const install: FunctionPlugin = (app: App) => {
18
19
  app.component(Editify.name!, Editify)
19
20
  }
20
21
  //版本号
21
- const version = '0.1.39'
22
+ const version = '0.1.41'
22
23
 
23
24
  //导出AlexElement元素
24
25
  export { AlexElement } from 'alex-editor'
@@ -29,7 +30,7 @@ export type { InsertAttachmentUploadErrorType } from './plugins/attachment/inser
29
30
  export { attachment, isAttachment, hasAttachmentInRange } from './plugins/attachment'
30
31
  //导出mathformula插件相关的方法和类型
31
32
  export type { MathformulaOptionsType } from './plugins/mathformula'
32
- export { mathformula, isMathformula, isUnderMathformula, getMathformulaElement } from './plugins/mathformula'
33
+ export { mathformula, isMathformula, isUnderMathformula, getMathformulaElement, hasMathformulaInRange, getMathformulaElementByRange } from './plugins/mathformula'
33
34
 
34
35
  //导出组件和安装函数
35
36
  export { install as default, install, Editify, version }
@@ -99,5 +99,6 @@ export const en_US: ObjectType = {
99
99
 
100
100
  //数学公式插件语言配置
101
101
  insertMathformula: 'Insert mathematical formula',
102
+ editMathformula: 'Edit mathematical formula',
102
103
  mathformulaPlaceholder: 'Please enter LaTex syntax'
103
104
  }
@@ -99,5 +99,6 @@ export const zh_CN: ObjectType = {
99
99
 
100
100
  //数学公式插件语言配置
101
101
  insertMathformula: '插入数学公式',
102
+ editMathformula: '编辑数学公式',
102
103
  mathformulaPlaceholder: '请输入LaTex语法'
103
104
  }
@@ -1,7 +1,6 @@
1
- import { common as DapCommon, element as DapElement } from 'dap-util'
1
+ import { common as DapCommon } from 'dap-util'
2
2
  import { ObjectType, PluginType, cloneData } from '../../core/tool'
3
3
  import { ComponentInternalInstance, h } from 'vue'
4
-
5
4
  import { AlexEditor, AlexElement, AlexElementsRangeType } from 'alex-editor'
6
5
  import 'katex/dist/katex.css'
7
6
  import KaTex from 'katex'
@@ -21,6 +20,8 @@ export type MathformulaOptionsType = {
21
20
  rightBorder?: boolean
22
21
  //按钮是否禁用
23
22
  disabled?: boolean
23
+ //公式异常处理
24
+ handleError?: (error: Error) => void
24
25
  }
25
26
 
26
27
  /**
@@ -32,7 +33,11 @@ export const isMathformula = (el: AlexElement) => {
32
33
  return el.parsedom == 'span' && el.hasMarks() && el.marks!['data-editify-mathformula']
33
34
  }
34
35
 
35
- //是否在公式元素下
36
+ /**
37
+ * 判断某个元素是否在公式元素内
38
+ * @param el
39
+ * @returns
40
+ */
36
41
  export const isUnderMathformula = (el: AlexElement): boolean => {
37
42
  if (isMathformula(el)) {
38
43
  return true
@@ -43,7 +48,11 @@ export const isUnderMathformula = (el: AlexElement): boolean => {
43
48
  return false
44
49
  }
45
50
 
46
- //获取公式元素
51
+ /**
52
+ * 根据某个元素获取所在的公式元素,如果不在公式元素内则返回null
53
+ * @param el
54
+ * @returns
55
+ */
47
56
  export const getMathformulaElement = (el: AlexElement): AlexElement | null => {
48
57
  if (isMathformula(el)) {
49
58
  return el
@@ -72,6 +81,48 @@ export const hasMathformulaInRange = (editor: AlexEditor, dataRangeCaches: AlexE
72
81
  })
73
82
  }
74
83
 
84
+ /**
85
+ * 选区是否在某个公式元素下,如果是返回该公式元素否则返回null
86
+ * @param editor
87
+ * @param dataRangeCaches
88
+ * @returns
89
+ */
90
+ export const getMathformulaElementByRange = (editor: AlexEditor, dataRangeCaches: AlexElementsRangeType) => {
91
+ if (!editor.range) {
92
+ return null
93
+ }
94
+ if (editor.range.anchor.element.isEqual(editor.range.focus.element)) {
95
+ return getMathformulaElement(editor.range.anchor.element)
96
+ }
97
+ const arr = dataRangeCaches.list.map(item => {
98
+ return getMathformulaElement(item.element)
99
+ })
100
+ let hasNull = arr.some(el => {
101
+ return el == null
102
+ })
103
+ //如果存在null,则表示有的选区元素不在公式元素下,返回null
104
+ if (hasNull) {
105
+ return null
106
+ }
107
+ //如果只有一个元素,则返回该元素
108
+ if (arr.length == 1) {
109
+ return arr[0]!
110
+ }
111
+ //默认数组中的元素都相等
112
+ let flag = true
113
+ for (let i = 1; i < arr.length; i++) {
114
+ if (!arr[i]!.isEqual(arr[0]!)) {
115
+ flag = false
116
+ break
117
+ }
118
+ }
119
+ //如果相等,则返回该元素
120
+ if (flag) {
121
+ return arr[0]
122
+ }
123
+ return null
124
+ }
125
+
75
126
  /**
76
127
  * 数学公式插件
77
128
  * @param options
@@ -82,18 +133,23 @@ export const mathformula = (options?: MathformulaOptionsType) => {
82
133
  options = {}
83
134
  }
84
135
  const plugin: PluginType = (editifyInstance: ComponentInternalInstance, editTrans: (key: string) => any) => {
85
- let isDisabled = false
86
- //如果光标范围内有数学公式、链接、代码块则禁用
136
+ //是否禁用该插件按钮
137
+ let isDisabled: boolean = false
138
+ //如果光标范围内有链接、代码块则禁用
87
139
  if (editifyInstance.exposed!.editor.value) {
88
- isDisabled = hasMathformulaInRange(editifyInstance.exposed!.editor.value, editifyInstance.exposed!.dataRangeCaches.value) || hasPreInRange(editifyInstance.exposed!.editor.value, editifyInstance.exposed!.dataRangeCaches.value) || hasLinkInRange(editifyInstance.exposed!.editor.value, editifyInstance.exposed!.dataRangeCaches.value)
140
+ isDisabled = hasPreInRange(editifyInstance.exposed!.editor.value, editifyInstance.exposed!.dataRangeCaches.value) || hasLinkInRange(editifyInstance.exposed!.editor.value, editifyInstance.exposed!.dataRangeCaches.value)
89
141
  }
142
+ //数学公式文本框内置LaTex文本内容
143
+ let defaultLaTexContent: string = ''
144
+
90
145
  return {
146
+ //插件名称
91
147
  name: 'mathformula',
92
148
  //菜单项配置
93
149
  menu: {
94
150
  sequence: options!.sequence || 101,
95
151
  extraDisabled: (name: string) => {
96
- //如果光标选区内有数学公式则禁用链接菜单、代码块菜单
152
+ //如果光标选区内有数学公式则禁用链接、图片、视频、表格和代码块菜单
97
153
  if (name == 'link' || name == 'image' || name == 'video' || name == 'table' || name == 'codeBlock') {
98
154
  return hasMathformulaInRange(editifyInstance.exposed!.editor.value, editifyInstance.exposed!.dataRangeCaches.value)
99
155
  }
@@ -105,36 +161,67 @@ export const mathformula = (options?: MathformulaOptionsType) => {
105
161
  leftBorder: options!.leftBorder,
106
162
  rightBorder: options!.rightBorder,
107
163
  hideScroll: true,
108
- active: false,
164
+ active: editifyInstance.exposed!.editor.value ? hasMathformulaInRange(editifyInstance.exposed!.editor.value, editifyInstance.exposed!.dataRangeCaches.value) : false,
109
165
  disabled: isDisabled || options!.disabled,
166
+ //浮层展开时触发的事件
167
+ onLayerShow() {
168
+ //获取选区所在的数学公式元素
169
+ const mathformulaElement = getMathformulaElementByRange(editifyInstance.exposed!.editor.value, editifyInstance.exposed!.dataRangeCaches.value)
170
+ //如果该元素存在,则设置默认的LaTex文本内容
171
+ if (mathformulaElement) {
172
+ defaultLaTexContent = mathformulaElement.marks!['data-editify-mathformula'] || ''
173
+ }
174
+ },
110
175
  default: () => h(Icon, { value: 'mathformula' }),
111
176
  layer: (_name: string, btnInstance: InstanceType<typeof Button>) => {
112
177
  return h(InsertMathformula, {
113
178
  color: <string | null>editifyInstance.props.color,
179
+ defaultLaTexContent: defaultLaTexContent,
114
180
  onInsert: (content: string) => {
181
+ //如果公式文本框有内容则进行下一步处理
115
182
  if (content) {
116
183
  //获取编辑器对象
117
184
  const editor = <AlexEditor>editifyInstance.exposed!.editor.value
118
- //渲染LaTex为mathml并转为dom
119
- const dom = DapElement.string2dom(
120
- KaTex.renderToString(content, {
185
+ //获取选区所在的数学公式元素
186
+ const mathformulaElement = getMathformulaElementByRange(editifyInstance.exposed!.editor.value, editifyInstance.exposed!.dataRangeCaches.value)
187
+ //如果在数学公式下
188
+ if (mathformulaElement) {
189
+ //清除该数学公式
190
+ mathformulaElement.toEmpty()
191
+ //移动光标到后一个元素上
192
+ editor.range!.anchor.moveToStart(editor.getNextElement(mathformulaElement)!)
193
+ editor.range!.focus.moveToStart(editor.getNextElement(mathformulaElement)!)
194
+ }
195
+ //定义转换后的mathml内容
196
+ let mathml: string = ''
197
+ try {
198
+ //获取转换后的mathml
199
+ mathml = KaTex.renderToString(content, {
121
200
  output: 'mathml',
122
- throwOnError: false
201
+ throwOnError: true
123
202
  })
124
- ) as HTMLElement
125
- //设置最终的html内容
126
- const html = `<span data-editify-mathformula="true" class="katex" >${dom.innerHTML}</span>`
127
- //html内容转为元素数组
128
- const elements = editor.parseHtml(html)
129
- //插入编辑器
130
- editor.insertElement(elements[0])
131
- //移动光标到新插入的元素
132
- editor.range!.anchor.moveToEnd(elements[0])
133
- editor.range!.focus.moveToEnd(elements[0])
134
- //渲染
135
- editor.formatElementStack()
136
- editor.domRender()
137
- editor.rangeRender()
203
+ } catch (error) {
204
+ mathml = ''
205
+ if (typeof options!.handleError == 'function') {
206
+ options!.handleError(error as Error)
207
+ }
208
+ }
209
+ //如果mathml存在则表示数学公式渲染成功则插入到编辑器
210
+ if (mathml) {
211
+ //设置最终的html内容
212
+ const html = `<span data-editify-mathformula="${content}" contenteditable="false">${mathml}</span>`
213
+ //html内容转为元素数组
214
+ const elements = editor.parseHtml(html)
215
+ //插入编辑器
216
+ editor.insertElement(elements[0])
217
+ //移动光标到新插入的元素
218
+ editor.range!.anchor.moveToEnd(elements[0])
219
+ editor.range!.focus.moveToEnd(elements[0])
220
+ //渲染
221
+ editor.formatElementStack()
222
+ editor.domRender()
223
+ editor.rangeRender()
224
+ }
138
225
  }
139
226
  //关闭浮层
140
227
  btnInstance.show = false
@@ -1,6 +1,6 @@
1
1
  <template>
2
2
  <div class="editify-mathformula">
3
- <div class="editify-mathformula-label">{{ $editTrans('insertMathformula') }}</div>
3
+ <div class="editify-mathformula-label">{{ props.defaultLaTexContent ? $editTrans('editMathformula') : $editTrans('insertMathformula') }}</div>
4
4
  <textarea class="editify-mathformula-textarea" v-model.trim="latexContent" :placeholder="$editTrans('mathformulaPlaceholder')" @focus="handleInputFocus" @blur="handleInputBlur"></textarea>
5
5
  <div class="editify-mathformula-footer">
6
6
  <span :style="{ color: color || '' }" @click="insertMathformula">{{ $editTrans('confirm') }}</span>
@@ -8,7 +8,7 @@
8
8
  </div>
9
9
  </template>
10
10
  <script setup lang="ts">
11
- import { inject, ref } from 'vue'
11
+ import { inject, ref, watch } from 'vue'
12
12
  import { InsertMathformulaProps } from './props'
13
13
 
14
14
  defineOptions({
@@ -36,5 +36,15 @@ const handleInputBlur = (e: Event) => {
36
36
  const insertMathformula = () => {
37
37
  emits('insert', latexContent.value)
38
38
  }
39
+
40
+ watch(
41
+ () => props.defaultLaTexContent,
42
+ newVal => {
43
+ latexContent.value = newVal
44
+ },
45
+ {
46
+ immediate: true
47
+ }
48
+ )
39
49
  </script>
40
50
  <style scoped src="./insertMathformula.less"></style>
@@ -5,6 +5,11 @@ export const InsertMathformulaProps = {
5
5
  color: {
6
6
  type: String as PropType<string | null>,
7
7
  default: ''
8
+ },
9
+ //预置的LaTex文本内容
10
+ defaultLaTexContent: {
11
+ type: String,
12
+ default: ''
8
13
  }
9
14
  }
10
15