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/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>
|