vue-editify 0.1.47 → 0.1.48

Sign up to get free protection for your applications and to get access to all the features.
@@ -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 ? hasMathformulaInRange(editifyInstance.exposed!.editor.value, editifyInstance.exposed!.dataRangeCaches.value) : false,
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
+ }