vue-editify 0.1.39 → 0.1.41
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/examples/App.vue +11 -6
- package/lib/core/function.d.ts +18 -6
- package/lib/core/tool.d.ts +0 -6
- package/lib/editify.es.js +325 -210
- package/lib/editify.umd.js +1 -1
- package/lib/index.d.ts +4 -3
- package/lib/plugins/mathformula/index.d.ts +18 -0
- package/lib/plugins/mathformula/insertMathformula/insertMathformula.vue.d.ts +9 -0
- package/lib/plugins/mathformula/insertMathformula/props.d.ts +4 -0
- package/lib/style.css +1 -1
- package/package.json +1 -1
- package/src/components/button/button.less +1 -1
- package/src/components/icon/icon.less +1 -1
- package/src/components/menu/menu.vue +2 -2
- package/src/components/toolbar/toolbar.vue +91 -85
- package/src/core/function.ts +89 -58
- package/src/core/rule.ts +60 -4
- package/src/core/tool.ts +2 -23
- package/src/editify/editify.less +35 -3
- package/src/editify/editify.vue +37 -32
- package/src/icon/iconfont.ttf +0 -0
- package/src/icon/iconfont.woff +0 -0
- package/src/index.ts +4 -3
- package/src/locale/en_US.ts +1 -0
- package/src/locale/zh_CN.ts +1 -0
- package/src/plugins/mathformula/index.ts +114 -27
- package/src/plugins/mathformula/insertMathformula/insertMathformula.vue +12 -2
- package/src/plugins/mathformula/insertMathformula/props.ts +5 -0
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
|
-
}
|
217
|
+
}
|
218
|
+
//如果没有width标记
|
219
|
+
else if (!col.marks!['width']) {
|
208
220
|
col.marks!['width'] = 'auto'
|
209
221
|
}
|
210
222
|
})
|
211
|
-
|
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
|
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
|
767
|
+
//添加额外的按钮禁用判定,回调参数为name
|
789
768
|
extraDisabled: null,
|
790
769
|
//菜单栏的样式自定义
|
791
770
|
style: null,
|
package/src/editify/editify.less
CHANGED
@@ -357,7 +357,7 @@
|
|
357
357
|
color: @font-color-link;
|
358
358
|
transition: all 200ms;
|
359
359
|
position: relative;
|
360
|
-
|
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
|
|
package/src/editify/editify.vue
CHANGED
@@ -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,
|
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
|
184
|
-
const
|
185
|
-
const
|
186
|
-
const
|
187
|
-
const
|
188
|
-
|
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="${
|
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
|
-
}
|
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="${
|
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
|
-
}
|
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="${
|
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
|
-
}
|
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="${
|
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
|
-
}
|
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="${
|
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
|
-
}
|
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
|
-
;(
|
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
|
370
|
-
if (
|
380
|
+
const tables = getMatchElementsByRange(editor.value!, dataRangeCaches.value, { parsedom: 'table' })
|
381
|
+
if (tables.length != 1) {
|
371
382
|
return
|
372
383
|
}
|
373
|
-
const colgroup =
|
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
|
393
|
-
if (
|
403
|
+
const tables = getMatchElementsByRange(editor.value!, dataRangeCaches.value, { parsedom: 'table' })
|
404
|
+
if (tables.length != 1) {
|
394
405
|
return
|
395
406
|
}
|
396
|
-
const colgroup =
|
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
|
-
|
547
|
-
marks = mergeObject(marks, keepMarks)!
|
553
|
+
marks = mergeObject(marks, props.pasteKeepMarks(el))!
|
548
554
|
}
|
549
555
|
if (typeof props.pasteKeepStyles == 'function') {
|
550
|
-
|
551
|
-
styles = mergeObject(styles, keepStyles)!
|
556
|
+
styles = mergeObject(styles, props.pasteKeepStyles(el))!
|
552
557
|
}
|
553
558
|
//将处理后的样式和标记给元素
|
554
559
|
el.marks = marks
|
package/src/icon/iconfont.ttf
CHANGED
Binary file
|
package/src/icon/iconfont.woff
CHANGED
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 {
|
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.
|
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 }
|
package/src/locale/en_US.ts
CHANGED
package/src/locale/zh_CN.ts
CHANGED
@@ -1,7 +1,6 @@
|
|
1
|
-
import { common as DapCommon
|
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
|
-
|
86
|
-
|
136
|
+
//是否禁用该插件按钮
|
137
|
+
let isDisabled: boolean = false
|
138
|
+
//如果光标范围内有链接、代码块则禁用
|
87
139
|
if (editifyInstance.exposed!.editor.value) {
|
88
|
-
isDisabled =
|
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
|
-
|
119
|
-
const
|
120
|
-
|
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:
|
201
|
+
throwOnError: true
|
123
202
|
})
|
124
|
-
)
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
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>
|