vue-editify 0.1.47 → 0.1.48
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 +30 -51
- package/lib/core/function.d.ts +50 -63
- package/lib/editify.es.js +18653 -18044
- package/lib/editify.umd.js +1 -1
- package/lib/index.d.ts +6 -2
- package/lib/plugins/infoBlock/index.d.ts +55 -0
- package/lib/plugins/panel/index.d.ts +48 -0
- package/lib/style.css +1 -1
- package/package.json +2 -2
- package/src/components/menu/menu.vue +14 -13
- package/src/components/toolbar/toolbar.vue +146 -111
- package/src/core/function.ts +249 -183
- package/src/core/rule.ts +78 -48
- package/src/editify/editify.less +52 -1
- package/src/editify/editify.vue +29 -29
- package/src/icon/iconfont.css +8 -0
- package/src/icon/iconfont.ttf +0 -0
- package/src/icon/iconfont.woff +0 -0
- package/src/index.ts +8 -2
- package/src/locale/en_US.ts +9 -1
- package/src/locale/zh_CN.ts +9 -1
- package/src/plugins/attachment/index.ts +10 -6
- package/src/plugins/infoBlock/index.ts +238 -0
- package/src/plugins/mathformula/index.ts +1 -3
- package/src/plugins/panel/index.ts +228 -0
@@ -0,0 +1,238 @@
|
|
1
|
+
import { common as DapCommon, color as DapColor } from 'dap-util'
|
2
|
+
import { PluginType } from '../../core/tool'
|
3
|
+
import { ComponentInternalInstance, h } from 'vue'
|
4
|
+
import { AlexEditor, AlexElement, AlexElementsRangeType } from 'alex-editor'
|
5
|
+
import Icon from '../../components/icon/icon.vue'
|
6
|
+
import { elementToParagraph, hasPreInRange, hasTableInRange } from '../../core/function'
|
7
|
+
import { hasPanelInRange } from '../panel'
|
8
|
+
|
9
|
+
export type InfoBlockOptionsType = {
|
10
|
+
//排序
|
11
|
+
sequence?: number
|
12
|
+
//工具提示内容
|
13
|
+
title?: string
|
14
|
+
//按钮是否显示左侧边框
|
15
|
+
leftBorder?: boolean
|
16
|
+
//按钮是否显示右侧边框
|
17
|
+
rightBorder?: boolean
|
18
|
+
//按钮是否禁用
|
19
|
+
disabled?: boolean
|
20
|
+
}
|
21
|
+
|
22
|
+
/**
|
23
|
+
* 是否信息元素
|
24
|
+
* @param el
|
25
|
+
* @returns
|
26
|
+
*/
|
27
|
+
export const isInfoBlock = (el: AlexElement) => {
|
28
|
+
return el.parsedom == 'div' && el.hasMarks() && el.marks!['data-editify-info']
|
29
|
+
}
|
30
|
+
|
31
|
+
/**
|
32
|
+
* 判断某个元素是否在信息元素内
|
33
|
+
* @param el
|
34
|
+
* @returns
|
35
|
+
*/
|
36
|
+
export const isUnderInfoBlock = (el: AlexElement): boolean => {
|
37
|
+
if (isInfoBlock(el)) {
|
38
|
+
return true
|
39
|
+
}
|
40
|
+
if (el.parent) {
|
41
|
+
return isUnderInfoBlock(el.parent)
|
42
|
+
}
|
43
|
+
return false
|
44
|
+
}
|
45
|
+
|
46
|
+
/**
|
47
|
+
* 根据某个元素获取所在的信息元素,如果不在信息元素内则返回null
|
48
|
+
* @param el
|
49
|
+
* @returns
|
50
|
+
*/
|
51
|
+
export const getInfoBlockElement = (el: AlexElement): AlexElement | null => {
|
52
|
+
if (isInfoBlock(el)) {
|
53
|
+
return el
|
54
|
+
}
|
55
|
+
if (el.parent) {
|
56
|
+
return getInfoBlockElement(el.parent)
|
57
|
+
}
|
58
|
+
return null
|
59
|
+
}
|
60
|
+
|
61
|
+
/**
|
62
|
+
* 选区是否含有信息元素
|
63
|
+
* @param editor
|
64
|
+
* @param dataRangeCaches
|
65
|
+
* @returns
|
66
|
+
*/
|
67
|
+
export const hasInfoBlockInRange = (editor: AlexEditor, dataRangeCaches: AlexElementsRangeType) => {
|
68
|
+
if (!editor.range) {
|
69
|
+
return false
|
70
|
+
}
|
71
|
+
if (editor.range.anchor.isEqual(editor.range.focus)) {
|
72
|
+
return isUnderInfoBlock(editor.range.anchor.element)
|
73
|
+
}
|
74
|
+
return dataRangeCaches.flatList.some(item => {
|
75
|
+
return isUnderInfoBlock(item.element)
|
76
|
+
})
|
77
|
+
}
|
78
|
+
|
79
|
+
/**
|
80
|
+
* 选区是否都在信息块内
|
81
|
+
* @param editor
|
82
|
+
* @param dataRangeCaches
|
83
|
+
* @returns
|
84
|
+
*/
|
85
|
+
export const isRangeInInfoBlock = (editor: AlexEditor, dataRangeCaches: AlexElementsRangeType) => {
|
86
|
+
if (!editor.range) {
|
87
|
+
return false
|
88
|
+
}
|
89
|
+
if (editor.range.anchor.isEqual(editor.range.focus)) {
|
90
|
+
return isUnderInfoBlock(editor.range.anchor.element)
|
91
|
+
}
|
92
|
+
return dataRangeCaches.flatList.every(item => {
|
93
|
+
return isUnderInfoBlock(item.element)
|
94
|
+
})
|
95
|
+
}
|
96
|
+
|
97
|
+
/**
|
98
|
+
* 选区是否在某个信息元素下,如果是返回该信息元素否则返回null
|
99
|
+
* @param editor
|
100
|
+
* @param dataRangeCaches
|
101
|
+
* @returns
|
102
|
+
*/
|
103
|
+
export const getInfoBlockElementByRange = (editor: AlexEditor, dataRangeCaches: AlexElementsRangeType) => {
|
104
|
+
if (!editor.range) {
|
105
|
+
return null
|
106
|
+
}
|
107
|
+
if (editor.range.anchor.element.isEqual(editor.range.focus.element)) {
|
108
|
+
return getInfoBlockElement(editor.range.anchor.element)
|
109
|
+
}
|
110
|
+
const arr = dataRangeCaches.list.map(item => {
|
111
|
+
return getInfoBlockElement(item.element)
|
112
|
+
})
|
113
|
+
let hasNull = arr.some(el => {
|
114
|
+
return el == null
|
115
|
+
})
|
116
|
+
//如果存在null,则表示有的选区元素不在公式元素下,返回null
|
117
|
+
if (hasNull) {
|
118
|
+
return null
|
119
|
+
}
|
120
|
+
//如果只有一个元素,则返回该元素
|
121
|
+
if (arr.length == 1) {
|
122
|
+
return arr[0]!
|
123
|
+
}
|
124
|
+
//默认数组中的元素都相等
|
125
|
+
let flag = true
|
126
|
+
for (let i = 1; i < arr.length; i++) {
|
127
|
+
if (!arr[i]!.isEqual(arr[0]!)) {
|
128
|
+
flag = false
|
129
|
+
break
|
130
|
+
}
|
131
|
+
}
|
132
|
+
//如果相等,则返回该元素
|
133
|
+
if (flag) {
|
134
|
+
return arr[0]
|
135
|
+
}
|
136
|
+
return null
|
137
|
+
}
|
138
|
+
|
139
|
+
/**
|
140
|
+
* 信息插件
|
141
|
+
* @param options
|
142
|
+
* @returns
|
143
|
+
*/
|
144
|
+
export const infoBlock = (options?: InfoBlockOptionsType) => {
|
145
|
+
if (!DapCommon.isObject(options)) {
|
146
|
+
options = {}
|
147
|
+
}
|
148
|
+
const plugin: PluginType = (editifyInstance: ComponentInternalInstance, editTrans: (key: string) => any) => {
|
149
|
+
let isDisabled: boolean = false
|
150
|
+
//光标在表格、代码块和面板中则禁用
|
151
|
+
if (editifyInstance.exposed!.editor.value) {
|
152
|
+
isDisabled = hasTableInRange(editifyInstance.exposed!.editor.value, editifyInstance.exposed!.dataRangeCaches.value) || hasPreInRange(editifyInstance.exposed!.editor.value, editifyInstance.exposed!.dataRangeCaches.value) || hasPanelInRange(editifyInstance.exposed!.editor.value, editifyInstance.exposed!.dataRangeCaches.value)
|
153
|
+
}
|
154
|
+
return {
|
155
|
+
//插件名称
|
156
|
+
name: 'infoBlock',
|
157
|
+
//菜单项配置
|
158
|
+
menu: {
|
159
|
+
sequence: options!.sequence || 103,
|
160
|
+
extend: {
|
161
|
+
type: 'default',
|
162
|
+
title: options!.title || editTrans('insertInfoBlock'),
|
163
|
+
leftBorder: options!.leftBorder,
|
164
|
+
rightBorder: options!.rightBorder,
|
165
|
+
hideScroll: true,
|
166
|
+
active: editifyInstance.exposed!.editor.value ? isRangeInInfoBlock(editifyInstance.exposed!.editor.value, editifyInstance.exposed!.dataRangeCaches.value) : false,
|
167
|
+
disabled: isDisabled || options!.disabled,
|
168
|
+
default: () => h(Icon, { value: 'info' }),
|
169
|
+
onOperate: () => {
|
170
|
+
const editor = <AlexEditor>editifyInstance.exposed!.editor.value
|
171
|
+
const dataRangeCaches = <AlexElementsRangeType>editifyInstance.exposed!.dataRangeCaches.value
|
172
|
+
//是否都在引用里
|
173
|
+
const flag = isRangeInInfoBlock(editor, dataRangeCaches)
|
174
|
+
//起点和终点在一起
|
175
|
+
if (editor.range!.anchor.isEqual(editor.range!.focus)) {
|
176
|
+
const block = editor.range!.anchor.element.getBlock()
|
177
|
+
elementToParagraph(block)
|
178
|
+
if (!flag) {
|
179
|
+
block.parsedom = 'div'
|
180
|
+
block.marks = {
|
181
|
+
'data-editify-info': 'true'
|
182
|
+
}
|
183
|
+
}
|
184
|
+
}
|
185
|
+
//起点和终点不在一起
|
186
|
+
else {
|
187
|
+
let blocks: AlexElement[] = []
|
188
|
+
dataRangeCaches.list.forEach(item => {
|
189
|
+
const block = item.element.getBlock()
|
190
|
+
const exist = blocks.some(el => block.isEqual(el))
|
191
|
+
if (!exist) {
|
192
|
+
blocks.push(block)
|
193
|
+
}
|
194
|
+
})
|
195
|
+
blocks.forEach(block => {
|
196
|
+
elementToParagraph(block)
|
197
|
+
if (!flag) {
|
198
|
+
block.parsedom = 'div'
|
199
|
+
block.marks = {
|
200
|
+
'data-editify-info': 'true'
|
201
|
+
}
|
202
|
+
}
|
203
|
+
})
|
204
|
+
}
|
205
|
+
//渲染
|
206
|
+
editor.formatElementStack()
|
207
|
+
editor.domRender()
|
208
|
+
editor.rangeRender()
|
209
|
+
}
|
210
|
+
}
|
211
|
+
},
|
212
|
+
//粘贴保留的属性
|
213
|
+
pasteKeepMarks: (element: AlexElement) => {
|
214
|
+
if (isInfoBlock(element)) {
|
215
|
+
return {
|
216
|
+
'data-editify-info': 'true'
|
217
|
+
}
|
218
|
+
}
|
219
|
+
return {}
|
220
|
+
},
|
221
|
+
renderRule: (el: AlexElement) => {
|
222
|
+
if (isInfoBlock(el)) {
|
223
|
+
const color = DapColor.hex2rgb(editifyInstance.props.color as string)
|
224
|
+
if (el.hasStyles()) {
|
225
|
+
el.styles!['background-color'] = `rgba(${color[0]},${color[1]},${color[2]},0.15)`
|
226
|
+
el.styles!['color'] = editifyInstance.props.color
|
227
|
+
} else {
|
228
|
+
el.styles = {
|
229
|
+
'background-color': `rgba(${color[0]},${color[1]},${color[2]},0.15)`,
|
230
|
+
color: editifyInstance.props.color
|
231
|
+
}
|
232
|
+
}
|
233
|
+
}
|
234
|
+
}
|
235
|
+
}
|
236
|
+
}
|
237
|
+
return plugin
|
238
|
+
}
|
@@ -133,7 +133,6 @@ export const mathformula = (options?: MathformulaOptionsType) => {
|
|
133
133
|
options = {}
|
134
134
|
}
|
135
135
|
const plugin: PluginType = (editifyInstance: ComponentInternalInstance, editTrans: (key: string) => any) => {
|
136
|
-
//是否禁用该插件按钮
|
137
136
|
let isDisabled: boolean = false
|
138
137
|
//如果光标范围内有链接、代码块则禁用
|
139
138
|
if (editifyInstance.exposed!.editor.value) {
|
@@ -141,7 +140,6 @@ export const mathformula = (options?: MathformulaOptionsType) => {
|
|
141
140
|
}
|
142
141
|
//数学公式文本框内置LaTex文本内容
|
143
142
|
let defaultLaTexContent: string = ''
|
144
|
-
|
145
143
|
return {
|
146
144
|
//插件名称
|
147
145
|
name: 'mathformula',
|
@@ -161,7 +159,7 @@ export const mathformula = (options?: MathformulaOptionsType) => {
|
|
161
159
|
leftBorder: options!.leftBorder,
|
162
160
|
rightBorder: options!.rightBorder,
|
163
161
|
hideScroll: true,
|
164
|
-
active: editifyInstance.exposed!.editor.value ?
|
162
|
+
active: editifyInstance.exposed!.editor.value ? !!getMathformulaElementByRange(editifyInstance.exposed!.editor.value, editifyInstance.exposed!.dataRangeCaches.value) : false,
|
165
163
|
disabled: isDisabled || options!.disabled,
|
166
164
|
//浮层展开时触发的事件
|
167
165
|
onLayerShow() {
|
@@ -0,0 +1,228 @@
|
|
1
|
+
import { common as DapCommon } from 'dap-util'
|
2
|
+
import { PluginType } from '../../core/tool'
|
3
|
+
import { ComponentInternalInstance, h } from 'vue'
|
4
|
+
import { AlexEditor, AlexElement, AlexElementsRangeType } from 'alex-editor'
|
5
|
+
import Icon from '../../components/icon/icon.vue'
|
6
|
+
import { hasTableInRange } from '../../core/function'
|
7
|
+
import { hasMathformulaInRange } from '../mathformula'
|
8
|
+
|
9
|
+
export type PanelOptionsType = {
|
10
|
+
//排序
|
11
|
+
sequence?: number
|
12
|
+
//工具提示内容
|
13
|
+
title?: string
|
14
|
+
//按钮是否显示左侧边框
|
15
|
+
leftBorder?: boolean
|
16
|
+
//按钮是否显示右侧边框
|
17
|
+
rightBorder?: boolean
|
18
|
+
//按钮是否禁用
|
19
|
+
disabled?: boolean
|
20
|
+
}
|
21
|
+
|
22
|
+
/**
|
23
|
+
* 是否面板元素
|
24
|
+
* @param el
|
25
|
+
* @returns
|
26
|
+
*/
|
27
|
+
export const isPanel = (el: AlexElement) => {
|
28
|
+
return el.parsedom == 'div' && el.hasMarks() && el.marks!['data-editify-panel']
|
29
|
+
}
|
30
|
+
|
31
|
+
/**
|
32
|
+
* 判断某个元素是否在面板元素内
|
33
|
+
* @param el
|
34
|
+
* @returns
|
35
|
+
*/
|
36
|
+
export const isUnderPanel = (el: AlexElement): boolean => {
|
37
|
+
if (isPanel(el)) {
|
38
|
+
return true
|
39
|
+
}
|
40
|
+
if (el.parent) {
|
41
|
+
return isUnderPanel(el.parent)
|
42
|
+
}
|
43
|
+
return false
|
44
|
+
}
|
45
|
+
|
46
|
+
/**
|
47
|
+
* 根据某个元素获取所在的面板元素,如果不在面板元素内则返回null
|
48
|
+
* @param el
|
49
|
+
* @returns
|
50
|
+
*/
|
51
|
+
export const getPanelElement = (el: AlexElement): AlexElement | null => {
|
52
|
+
if (isPanel(el)) {
|
53
|
+
return el
|
54
|
+
}
|
55
|
+
if (el.parent) {
|
56
|
+
return getPanelElement(el.parent)
|
57
|
+
}
|
58
|
+
return null
|
59
|
+
}
|
60
|
+
|
61
|
+
/**
|
62
|
+
* 选区是否含有面板元素
|
63
|
+
* @param editor
|
64
|
+
* @param dataRangeCaches
|
65
|
+
* @returns
|
66
|
+
*/
|
67
|
+
export const hasPanelInRange = (editor: AlexEditor, dataRangeCaches: AlexElementsRangeType) => {
|
68
|
+
if (!editor.range) {
|
69
|
+
return false
|
70
|
+
}
|
71
|
+
if (editor.range.anchor.isEqual(editor.range.focus)) {
|
72
|
+
return isUnderPanel(editor.range.anchor.element)
|
73
|
+
}
|
74
|
+
return dataRangeCaches.flatList.some(item => {
|
75
|
+
return isUnderPanel(item.element)
|
76
|
+
})
|
77
|
+
}
|
78
|
+
|
79
|
+
/**
|
80
|
+
* 选区是否在某个面板元素下,如果是返回该面板元素否则返回null
|
81
|
+
* @param editor
|
82
|
+
* @param dataRangeCaches
|
83
|
+
* @returns
|
84
|
+
*/
|
85
|
+
export const getPanelElementByRange = (editor: AlexEditor, dataRangeCaches: AlexElementsRangeType) => {
|
86
|
+
if (!editor.range) {
|
87
|
+
return null
|
88
|
+
}
|
89
|
+
if (editor.range.anchor.element.isEqual(editor.range.focus.element)) {
|
90
|
+
return getPanelElement(editor.range.anchor.element)
|
91
|
+
}
|
92
|
+
const arr = dataRangeCaches.list.map(item => {
|
93
|
+
return getPanelElement(item.element)
|
94
|
+
})
|
95
|
+
let hasNull = arr.some(el => {
|
96
|
+
return el == null
|
97
|
+
})
|
98
|
+
//如果存在null,则表示有的选区元素不在公式元素下,返回null
|
99
|
+
if (hasNull) {
|
100
|
+
return null
|
101
|
+
}
|
102
|
+
//如果只有一个元素,则返回该元素
|
103
|
+
if (arr.length == 1) {
|
104
|
+
return arr[0]!
|
105
|
+
}
|
106
|
+
//默认数组中的元素都相等
|
107
|
+
let flag = true
|
108
|
+
for (let i = 1; i < arr.length; i++) {
|
109
|
+
if (!arr[i]!.isEqual(arr[0]!)) {
|
110
|
+
flag = false
|
111
|
+
break
|
112
|
+
}
|
113
|
+
}
|
114
|
+
//如果相等,则返回该元素
|
115
|
+
if (flag) {
|
116
|
+
return arr[0]
|
117
|
+
}
|
118
|
+
return null
|
119
|
+
}
|
120
|
+
|
121
|
+
/**
|
122
|
+
* 面板插件
|
123
|
+
* @param options
|
124
|
+
* @returns
|
125
|
+
*/
|
126
|
+
export const panel = (options?: PanelOptionsType) => {
|
127
|
+
if (!DapCommon.isObject(options)) {
|
128
|
+
options = {}
|
129
|
+
}
|
130
|
+
const plugin: PluginType = (editifyInstance: ComponentInternalInstance, editTrans: (key: string) => any) => {
|
131
|
+
let isDisabled: boolean = false
|
132
|
+
//光标在表格、面板和数学公式下则禁用
|
133
|
+
if (editifyInstance.exposed!.editor.value) {
|
134
|
+
isDisabled = hasPanelInRange(editifyInstance.exposed!.editor.value, editifyInstance.exposed!.dataRangeCaches.value) || hasTableInRange(editifyInstance.exposed!.editor.value, editifyInstance.exposed!.dataRangeCaches.value) || hasMathformulaInRange(editifyInstance.exposed!.editor.value, editifyInstance.exposed!.dataRangeCaches.value)
|
135
|
+
}
|
136
|
+
return {
|
137
|
+
//插件名称
|
138
|
+
name: 'panel',
|
139
|
+
//菜单项配置
|
140
|
+
menu: {
|
141
|
+
sequence: options!.sequence || 102,
|
142
|
+
extraDisabled: (name: string) => {
|
143
|
+
//如果光标选区内有面板,则禁用有序列表、无需列表、任务列表、引用、代码块、表格和标题菜单
|
144
|
+
if (name == 'orderList' || name == 'unorderList' || name == 'task' || name == 'quote' || name == 'codeBlock' || name == 'table' || name == 'heading') {
|
145
|
+
return hasPanelInRange(editifyInstance.exposed!.editor.value, editifyInstance.exposed!.dataRangeCaches.value)
|
146
|
+
}
|
147
|
+
return false
|
148
|
+
},
|
149
|
+
extend: {
|
150
|
+
type: 'default',
|
151
|
+
title: options!.title || editTrans('insertPanel'),
|
152
|
+
leftBorder: options!.leftBorder,
|
153
|
+
rightBorder: options!.rightBorder,
|
154
|
+
hideScroll: true,
|
155
|
+
active: false,
|
156
|
+
disabled: isDisabled || options!.disabled,
|
157
|
+
default: () => h(Icon, { value: 'panel' }),
|
158
|
+
onOperate: () => {
|
159
|
+
const panelElement = AlexElement.create({
|
160
|
+
type: 'block',
|
161
|
+
parsedom: 'div',
|
162
|
+
marks: {
|
163
|
+
'data-editify-panel': 'true'
|
164
|
+
},
|
165
|
+
children: [
|
166
|
+
{
|
167
|
+
type: 'inblock',
|
168
|
+
parsedom: 'div',
|
169
|
+
behavior: 'block',
|
170
|
+
children: [
|
171
|
+
{
|
172
|
+
type: 'text',
|
173
|
+
textcontent: editTrans('panelTitle')
|
174
|
+
}
|
175
|
+
]
|
176
|
+
},
|
177
|
+
{
|
178
|
+
type: 'inblock',
|
179
|
+
parsedom: 'div',
|
180
|
+
behavior: 'block',
|
181
|
+
children: [
|
182
|
+
{
|
183
|
+
type: 'text',
|
184
|
+
textcontent: editTrans('panelContent')
|
185
|
+
}
|
186
|
+
]
|
187
|
+
}
|
188
|
+
]
|
189
|
+
})
|
190
|
+
//获取编辑器对象
|
191
|
+
const editor = <AlexEditor>editifyInstance.exposed!.editor.value
|
192
|
+
//插入编辑器
|
193
|
+
editor.insertElement(panelElement)
|
194
|
+
//面板后面插入段落
|
195
|
+
const paragraph = AlexElement.create({
|
196
|
+
type: 'block',
|
197
|
+
parsedom: AlexElement.BLOCK_NODE,
|
198
|
+
children: [
|
199
|
+
{
|
200
|
+
type: 'closed',
|
201
|
+
parsedom: 'br'
|
202
|
+
}
|
203
|
+
]
|
204
|
+
})
|
205
|
+
editor.addElementAfter(paragraph, panelElement)
|
206
|
+
//移动光标到新插入的元素
|
207
|
+
editor.range!.anchor.moveToEnd(panelElement.children![0])
|
208
|
+
editor.range!.focus.moveToEnd(panelElement.children![0])
|
209
|
+
//渲染
|
210
|
+
editor.formatElementStack()
|
211
|
+
editor.domRender()
|
212
|
+
editor.rangeRender()
|
213
|
+
}
|
214
|
+
}
|
215
|
+
},
|
216
|
+
//粘贴保留的属性
|
217
|
+
pasteKeepMarks: (element: AlexElement) => {
|
218
|
+
if (isPanel(element)) {
|
219
|
+
return {
|
220
|
+
'data-editify-panel': 'true'
|
221
|
+
}
|
222
|
+
}
|
223
|
+
return {}
|
224
|
+
}
|
225
|
+
}
|
226
|
+
}
|
227
|
+
return plugin
|
228
|
+
}
|