@kaitify/core 0.0.1-beta.1
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/LICENSE +21 -0
- package/README.md +3 -0
- package/examples/App.vue +342 -0
- package/examples/content.js +1 -0
- package/examples/main.ts +4 -0
- package/examples/test.html +23 -0
- package/lib/extensions/Extension.d.ts +172 -0
- package/lib/extensions/align/index.d.ts +10 -0
- package/lib/extensions/attachment/index.d.ts +29 -0
- package/lib/extensions/back-color/index.d.ts +9 -0
- package/lib/extensions/blockquote/index.d.ts +12 -0
- package/lib/extensions/bold/index.d.ts +9 -0
- package/lib/extensions/code/index.d.ts +12 -0
- package/lib/extensions/code-block/hljs.d.ts +12 -0
- package/lib/extensions/code-block/index.d.ts +15 -0
- package/lib/extensions/color/index.d.ts +9 -0
- package/lib/extensions/font-family/index.d.ts +9 -0
- package/lib/extensions/font-size/index.d.ts +9 -0
- package/lib/extensions/heading/index.d.ts +13 -0
- package/lib/extensions/history/index.d.ts +10 -0
- package/lib/extensions/horizontal/index.d.ts +7 -0
- package/lib/extensions/image/index.d.ts +26 -0
- package/lib/extensions/indent/index.d.ts +8 -0
- package/lib/extensions/index.d.ts +29 -0
- package/lib/extensions/italic/index.d.ts +9 -0
- package/lib/extensions/line-height/index.d.ts +9 -0
- package/lib/extensions/link/index.d.ts +27 -0
- package/lib/extensions/list/index.d.ts +18 -0
- package/lib/extensions/math/index.d.ts +11 -0
- package/lib/extensions/strikethrough/index.d.ts +9 -0
- package/lib/extensions/subscript/index.d.ts +9 -0
- package/lib/extensions/superscript/index.d.ts +9 -0
- package/lib/extensions/table/index.d.ts +21 -0
- package/lib/extensions/task/index.d.ts +12 -0
- package/lib/extensions/text/index.d.ts +14 -0
- package/lib/extensions/underline/index.d.ts +9 -0
- package/lib/extensions/video/index.d.ts +27 -0
- package/lib/index.d.ts +3 -0
- package/lib/kaitify-core.es.js +38337 -0
- package/lib/kaitify-core.umd.js +2 -0
- package/lib/model/Editor.d.ts +504 -0
- package/lib/model/History.d.ts +42 -0
- package/lib/model/KNode.d.ts +258 -0
- package/lib/model/Selection.d.ts +29 -0
- package/lib/model/config/dom-observe.d.ts +10 -0
- package/lib/model/config/event-handler.d.ts +33 -0
- package/lib/model/config/format-patch.d.ts +25 -0
- package/lib/model/config/format-rules.d.ts +37 -0
- package/lib/model/config/function.d.ts +84 -0
- package/lib/model/index.d.ts +6 -0
- package/lib/tools/index.d.ts +49 -0
- package/lib/view/index.d.ts +21 -0
- package/lib/view/js-render/dom-patch.d.ts +65 -0
- package/lib/view/js-render/index.d.ts +5 -0
- package/package.json +52 -0
- package/src/css/style.less +56 -0
- package/src/css/var.less +45 -0
- package/src/extensions/Extension.ts +200 -0
- package/src/extensions/align/index.ts +115 -0
- package/src/extensions/attachment/icon.svg +1 -0
- package/src/extensions/attachment/index.ts +293 -0
- package/src/extensions/attachment/style.less +25 -0
- package/src/extensions/back-color/index.ts +56 -0
- package/src/extensions/blockquote/index.ts +144 -0
- package/src/extensions/blockquote/style.less +16 -0
- package/src/extensions/bold/index.ts +77 -0
- package/src/extensions/code/index.ts +295 -0
- package/src/extensions/code/style.less +14 -0
- package/src/extensions/code-block/hljs.less +183 -0
- package/src/extensions/code-block/hljs.ts +95 -0
- package/src/extensions/code-block/index.ts +308 -0
- package/src/extensions/code-block/style.less +20 -0
- package/src/extensions/color/index.ts +56 -0
- package/src/extensions/font-family/index.ts +80 -0
- package/src/extensions/font-size/index.ts +56 -0
- package/src/extensions/heading/index.ts +164 -0
- package/src/extensions/heading/style.less +42 -0
- package/src/extensions/history/index.ts +96 -0
- package/src/extensions/horizontal/index.ts +45 -0
- package/src/extensions/horizontal/style.less +13 -0
- package/src/extensions/image/index.ts +242 -0
- package/src/extensions/image/style.less +8 -0
- package/src/extensions/indent/index.ts +98 -0
- package/src/extensions/index.ts +29 -0
- package/src/extensions/italic/index.ts +77 -0
- package/src/extensions/line-height/index.ts +113 -0
- package/src/extensions/link/index.ts +184 -0
- package/src/extensions/link/style.less +19 -0
- package/src/extensions/list/index.ts +410 -0
- package/src/extensions/list/style.less +19 -0
- package/src/extensions/math/index.ts +233 -0
- package/src/extensions/math/style.less +21 -0
- package/src/extensions/strikethrough/index.ts +78 -0
- package/src/extensions/subscript/index.ts +77 -0
- package/src/extensions/superscript/index.ts +77 -0
- package/src/extensions/table/index.ts +1148 -0
- package/src/extensions/table/style.less +71 -0
- package/src/extensions/task/index.ts +243 -0
- package/src/extensions/task/style.less +59 -0
- package/src/extensions/text/index.ts +359 -0
- package/src/extensions/underline/index.ts +78 -0
- package/src/extensions/video/index.ts +273 -0
- package/src/extensions/video/style.less +8 -0
- package/src/index.ts +9 -0
- package/src/model/Editor.ts +1963 -0
- package/src/model/History.ts +115 -0
- package/src/model/KNode.ts +677 -0
- package/src/model/Selection.ts +39 -0
- package/src/model/config/dom-observe.ts +184 -0
- package/src/model/config/event-handler.ts +237 -0
- package/src/model/config/format-patch.ts +215 -0
- package/src/model/config/format-rules.ts +218 -0
- package/src/model/config/function.ts +1018 -0
- package/src/model/index.ts +6 -0
- package/src/tools/index.ts +156 -0
- package/src/view/index.ts +46 -0
- package/src/view/js-render/dom-patch.ts +324 -0
- package/src/view/js-render/index.ts +210 -0
- package/vite-env.d.ts +2 -0
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
import { Editor, KNode, KNodeMarksType } from '@/model'
|
|
2
|
+
import { getSelectionBlockNodes } from '@/model/config/function'
|
|
3
|
+
import { Extension } from '../Extension'
|
|
4
|
+
import { getHljsHtml, HljsLanguages, HljsLanguageType } from './hljs'
|
|
5
|
+
import './style.less'
|
|
6
|
+
|
|
7
|
+
declare module '../../model' {
|
|
8
|
+
interface EditorCommandsType {
|
|
9
|
+
getCodeBlock?: () => KNode | null
|
|
10
|
+
hasCodeBlock?: () => boolean
|
|
11
|
+
allCodeBlock?: () => boolean
|
|
12
|
+
setCodeBlock?: () => Promise<void>
|
|
13
|
+
unsetCodeBlock?: () => Promise<void>
|
|
14
|
+
updateCodeBlockLanguage?: (language: HljsLanguageType) => Promise<void>
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* 块节点转为代码块
|
|
20
|
+
*/
|
|
21
|
+
const toCodeBlock = (editor: Editor, node: KNode) => {
|
|
22
|
+
if (!node.isBlock()) {
|
|
23
|
+
return
|
|
24
|
+
}
|
|
25
|
+
//是固定的块节点或者内嵌套的块节点
|
|
26
|
+
if (node.fixed || node.nested) {
|
|
27
|
+
//创建代码块节点
|
|
28
|
+
const codeBlockNode = KNode.create({
|
|
29
|
+
type: 'block',
|
|
30
|
+
tag: 'pre',
|
|
31
|
+
children: []
|
|
32
|
+
})
|
|
33
|
+
//将块节点的子节点给代码块节点
|
|
34
|
+
node.children!.forEach((item, index) => {
|
|
35
|
+
editor.addNode(item, codeBlockNode, index)
|
|
36
|
+
})
|
|
37
|
+
//将代码块节点添加到块节点下
|
|
38
|
+
codeBlockNode.parent = node
|
|
39
|
+
node.children = [codeBlockNode]
|
|
40
|
+
}
|
|
41
|
+
//非固定块节点
|
|
42
|
+
else {
|
|
43
|
+
editor.toParagraph(node)
|
|
44
|
+
node.tag = 'pre'
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* 更新代码块内的光标位置
|
|
50
|
+
*/
|
|
51
|
+
const updateSelection = (editor: Editor, node: KNode, textNodes: KNode[], newNodes: KNode[]) => {
|
|
52
|
+
if (!editor.selection.focused()) {
|
|
53
|
+
return
|
|
54
|
+
}
|
|
55
|
+
//如果光标的起点在代码块内对光标的起点进行重新定位
|
|
56
|
+
if (editor.isSelectionInNode(node, 'start')) {
|
|
57
|
+
//获取起点所在文本节点的在所有文本节点中的序列
|
|
58
|
+
const startIndex = textNodes.findIndex(n => editor.selection.start!.node.isEqual(n))
|
|
59
|
+
//起点在整个代码内容中的位置
|
|
60
|
+
const offset = textNodes.filter((_n, i) => i < startIndex).reduce((total, item) => total + item.textContent!.length, 0) + editor.selection.start!.offset
|
|
61
|
+
//获取代码块下新的子孙节点中全部的文本节点
|
|
62
|
+
const newTextNodes = KNode.flat(newNodes).filter(n => n.isText() && !n.isEmpty())
|
|
63
|
+
let i = 0
|
|
64
|
+
let index = 0
|
|
65
|
+
//遍历
|
|
66
|
+
while (i < newTextNodes.length) {
|
|
67
|
+
let newIndex = index + newTextNodes[i].textContent!.length
|
|
68
|
+
if (offset >= index && offset <= newIndex) {
|
|
69
|
+
editor.selection.start!.node = newTextNodes[i]
|
|
70
|
+
editor.selection.start!.offset = offset - index
|
|
71
|
+
break
|
|
72
|
+
}
|
|
73
|
+
i++
|
|
74
|
+
index = newIndex
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
//如果光标的终点在代码块内对光标的终点进行重新定位
|
|
78
|
+
if (editor.isSelectionInNode(node, 'end')) {
|
|
79
|
+
//获取终点所在文本节点的在所有文本节点中的序列
|
|
80
|
+
const endIndex = textNodes.findIndex(n => editor.selection.end!.node.isEqual(n))
|
|
81
|
+
//终点在整个代码内容中的位置
|
|
82
|
+
const offset = textNodes.filter((_n, i) => i < endIndex).reduce((total, item) => total + item.textContent!.length, 0) + editor.selection.end!.offset
|
|
83
|
+
//获取全部的新文本节点
|
|
84
|
+
const newTextNodes = KNode.flat(newNodes).filter(n => n.isText() && !n.isEmpty())
|
|
85
|
+
let i = 0
|
|
86
|
+
let index = 0
|
|
87
|
+
//遍历
|
|
88
|
+
while (i < newTextNodes.length) {
|
|
89
|
+
let newIndex = index + newTextNodes[i].textContent!.length
|
|
90
|
+
if (offset >= index && offset <= newIndex) {
|
|
91
|
+
editor.selection.end!.node = newTextNodes[i]
|
|
92
|
+
editor.selection.end!.offset = offset - index
|
|
93
|
+
break
|
|
94
|
+
}
|
|
95
|
+
i++
|
|
96
|
+
index = newIndex
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* 判断代码块是否需要更新
|
|
103
|
+
*/
|
|
104
|
+
const isNeedUpdate = (editor: Editor, node: KNode, language: string, textContent: string) => {
|
|
105
|
+
try {
|
|
106
|
+
const domPre = editor.findDom(node)
|
|
107
|
+
if (domPre && domPre.nodeName.toLocaleLowerCase() == 'pre') {
|
|
108
|
+
//语言不一致
|
|
109
|
+
const oldLanguage = domPre.getAttribute('kaitify-hljs') || ''
|
|
110
|
+
if (oldLanguage != language) {
|
|
111
|
+
return true
|
|
112
|
+
}
|
|
113
|
+
//文本内容不一致
|
|
114
|
+
const oldTextContent = domPre.innerText
|
|
115
|
+
if (oldTextContent != textContent) {
|
|
116
|
+
return true
|
|
117
|
+
}
|
|
118
|
+
//子孙节点数量不一致(防止在代码块里插入非文本节点,比如图片等)
|
|
119
|
+
if (KNode.flat(node.children!).length != domPre.querySelectorAll('*').length) {
|
|
120
|
+
return true
|
|
121
|
+
}
|
|
122
|
+
return false
|
|
123
|
+
}
|
|
124
|
+
return true
|
|
125
|
+
} catch (error) {
|
|
126
|
+
return true
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export const CodeBlockExtension = () =>
|
|
131
|
+
Extension.create({
|
|
132
|
+
name: 'codeBlock',
|
|
133
|
+
extraKeepTags: ['pre'],
|
|
134
|
+
domParseNodeCallback(node) {
|
|
135
|
+
if (node.isMatch({ tag: 'pre' })) {
|
|
136
|
+
node.type = 'block'
|
|
137
|
+
}
|
|
138
|
+
return node
|
|
139
|
+
},
|
|
140
|
+
pasteKeepMarks(node) {
|
|
141
|
+
const marks: KNodeMarksType = {}
|
|
142
|
+
if (node.isMatch({ tag: 'pre' }) && node.hasMarks()) {
|
|
143
|
+
if (node.marks!.hasOwnProperty('kaitify-hljs')) marks['kaitify-hljs'] = node.marks!['kaitify-hljs']
|
|
144
|
+
}
|
|
145
|
+
return marks
|
|
146
|
+
},
|
|
147
|
+
beforePatchNodeToFormat(node) {
|
|
148
|
+
const codeBlockNode = node.getMatchNode({ tag: 'pre' })
|
|
149
|
+
if (codeBlockNode) {
|
|
150
|
+
return codeBlockNode
|
|
151
|
+
}
|
|
152
|
+
return node
|
|
153
|
+
},
|
|
154
|
+
formatRules: [
|
|
155
|
+
//代码块高亮处理
|
|
156
|
+
({ editor, node }) => {
|
|
157
|
+
if (node.isMatch({ tag: 'pre' }) && node.hasChildren()) {
|
|
158
|
+
//代码块必须是块节点
|
|
159
|
+
if (!node.isBlock()) node.type = 'block'
|
|
160
|
+
//获取语言类型
|
|
161
|
+
let language = (node.marks?.['kaitify-hljs'] || '') as string
|
|
162
|
+
//语言存在但不是列表内的
|
|
163
|
+
if (language && !HljsLanguages.some(item => item == language)) {
|
|
164
|
+
language = ''
|
|
165
|
+
}
|
|
166
|
+
//获取代码块内的所有文本节点
|
|
167
|
+
const textNodes = KNode.flat(node.children!).filter(item => item.isText() && !item.isEmpty())
|
|
168
|
+
//获取代码块内的代码文本值
|
|
169
|
+
const textContent = textNodes.reduce((val, item) => {
|
|
170
|
+
return val + item.textContent
|
|
171
|
+
}, '')
|
|
172
|
+
//只有代码块语言改变和内容改变才需要重新进行高亮处理
|
|
173
|
+
if (isNeedUpdate(editor, node, language, textContent)) {
|
|
174
|
+
//将文本节点的内容转为经过hljs处理的内容
|
|
175
|
+
const html = getHljsHtml(textContent, language)
|
|
176
|
+
if (html) {
|
|
177
|
+
//将经过hljs处理的内容转为节点数组
|
|
178
|
+
const nodes = editor.htmlParseNode(html)
|
|
179
|
+
//将新的文本节点全部加入到代码块的子节点数组中
|
|
180
|
+
node.children = nodes.map(item => {
|
|
181
|
+
item.parent = node
|
|
182
|
+
return item
|
|
183
|
+
})
|
|
184
|
+
//更新光标位置
|
|
185
|
+
updateSelection(editor, node, textNodes, nodes)
|
|
186
|
+
} else {
|
|
187
|
+
const selectionStartInNode = editor.isSelectionInNode(node, 'start')
|
|
188
|
+
const selectionEndInNode = editor.isSelectionInNode(node, 'end')
|
|
189
|
+
const placeholderNode = KNode.createPlaceholder()
|
|
190
|
+
node.children = [placeholderNode]
|
|
191
|
+
placeholderNode.parent = node
|
|
192
|
+
if (selectionStartInNode) {
|
|
193
|
+
editor.setSelectionBefore(placeholderNode, 'start')
|
|
194
|
+
}
|
|
195
|
+
if (selectionEndInNode) {
|
|
196
|
+
editor.setSelectionBefore(placeholderNode, 'end')
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
],
|
|
203
|
+
addCommands() {
|
|
204
|
+
/**
|
|
205
|
+
* 获取光标所在的代码块节点,如果光标不在一个代码块节点内,返回null
|
|
206
|
+
*/
|
|
207
|
+
const getCodeBlock = () => {
|
|
208
|
+
return this.getMatchNodeBySelection({
|
|
209
|
+
tag: 'pre'
|
|
210
|
+
})
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* 判断光标范围内是否有代码块节点
|
|
215
|
+
*/
|
|
216
|
+
const hasCodeBlock = () => {
|
|
217
|
+
return this.isSelectionNodesSomeMatch({
|
|
218
|
+
tag: 'pre'
|
|
219
|
+
})
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* 光标范围内是否都是代码块节点
|
|
224
|
+
*/
|
|
225
|
+
const allCodeBlock = () => {
|
|
226
|
+
return this.isSelectionNodesAllMatch({
|
|
227
|
+
tag: 'pre'
|
|
228
|
+
})
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* 设置代码块
|
|
233
|
+
*/
|
|
234
|
+
const setCodeBlock = async () => {
|
|
235
|
+
if (allCodeBlock()) {
|
|
236
|
+
return
|
|
237
|
+
}
|
|
238
|
+
//起点和终点在一起
|
|
239
|
+
if (this.selection.collapsed()) {
|
|
240
|
+
const blockNode = this.selection.start!.node.getBlock()
|
|
241
|
+
toCodeBlock(this, blockNode)
|
|
242
|
+
}
|
|
243
|
+
//起点和终点不在一起
|
|
244
|
+
else {
|
|
245
|
+
const blockNodes = getSelectionBlockNodes.apply(this)
|
|
246
|
+
blockNodes.forEach(item => {
|
|
247
|
+
toCodeBlock(this, item)
|
|
248
|
+
})
|
|
249
|
+
}
|
|
250
|
+
await this.updateView()
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* 取消代码块
|
|
255
|
+
*/
|
|
256
|
+
const unsetCodeBlock = async () => {
|
|
257
|
+
if (!allCodeBlock()) {
|
|
258
|
+
return
|
|
259
|
+
}
|
|
260
|
+
//起点和终点在一起
|
|
261
|
+
if (this.selection.collapsed()) {
|
|
262
|
+
const matchNode = this.selection.start!.node.getMatchNode({ tag: 'pre' })
|
|
263
|
+
if (matchNode) this.toParagraph(matchNode)
|
|
264
|
+
}
|
|
265
|
+
//起点和终点不在一起
|
|
266
|
+
else {
|
|
267
|
+
const blockNodes = getSelectionBlockNodes.apply(this)
|
|
268
|
+
blockNodes.forEach(item => {
|
|
269
|
+
const matchNode = item.getMatchNode({ tag: 'pre' })
|
|
270
|
+
if (matchNode) this.toParagraph(matchNode)
|
|
271
|
+
})
|
|
272
|
+
}
|
|
273
|
+
await this.updateView()
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* 更新光标所在代码块的语言类型
|
|
278
|
+
*/
|
|
279
|
+
const updateCodeBlockLanguage = async (language?: HljsLanguageType) => {
|
|
280
|
+
if (!this.selection.focused()) {
|
|
281
|
+
return
|
|
282
|
+
}
|
|
283
|
+
const codeBlockNode = getCodeBlock()
|
|
284
|
+
if (!codeBlockNode) {
|
|
285
|
+
return
|
|
286
|
+
}
|
|
287
|
+
if (codeBlockNode.hasMarks()) {
|
|
288
|
+
codeBlockNode.marks!['kaitify-hljs'] = language || ''
|
|
289
|
+
} else {
|
|
290
|
+
codeBlockNode.marks = {
|
|
291
|
+
'kaitify-hljs': language || ''
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
await this.updateView()
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
return {
|
|
298
|
+
getCodeBlock,
|
|
299
|
+
hasCodeBlock,
|
|
300
|
+
allCodeBlock,
|
|
301
|
+
setCodeBlock,
|
|
302
|
+
unsetCodeBlock,
|
|
303
|
+
updateCodeBlockLanguage
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
})
|
|
307
|
+
|
|
308
|
+
export * from './hljs'
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
.Kaitify pre {
|
|
2
|
+
background: #f6f9ff;
|
|
3
|
+
color: var(--kaitify-font-color);
|
|
4
|
+
border: 1px solid #f6f9ff;
|
|
5
|
+
border-radius: var(--kaitify-border-radius);
|
|
6
|
+
padding: var(--kaitify-padding);
|
|
7
|
+
line-height: var(--kaitify-line-height);
|
|
8
|
+
font-family: Consolas, monospace, Monaco, Andale Mono, Ubuntu Mono;
|
|
9
|
+
margin: 0 0 var(--kaitify-large-margin) 0 !important;
|
|
10
|
+
overflow-x: auto;
|
|
11
|
+
|
|
12
|
+
&:last-child {
|
|
13
|
+
margin-bottom: 0 !important;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
:root[kaitify-dark] .Kaitify pre {
|
|
18
|
+
background: #29292d;
|
|
19
|
+
border-color: #29292d;
|
|
20
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { KNodeStylesType } from '@/model'
|
|
2
|
+
import { Extension } from '../Extension'
|
|
3
|
+
|
|
4
|
+
declare module '../../model' {
|
|
5
|
+
interface EditorCommandsType {
|
|
6
|
+
isColor?: (value: string) => boolean
|
|
7
|
+
setColor?: (value: string) => Promise<void>
|
|
8
|
+
unsetColor?: (value: string) => Promise<void>
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const ColorExtension = () =>
|
|
13
|
+
Extension.create({
|
|
14
|
+
name: 'color',
|
|
15
|
+
pasteKeepStyles(node) {
|
|
16
|
+
const styles: KNodeStylesType = {}
|
|
17
|
+
if (node.isText() && node.hasStyles()) {
|
|
18
|
+
if (node.styles!.hasOwnProperty('color')) styles.color = node.styles!.color
|
|
19
|
+
}
|
|
20
|
+
return styles
|
|
21
|
+
},
|
|
22
|
+
addCommands() {
|
|
23
|
+
/**
|
|
24
|
+
* 光标所在文本的颜色是否与入参一致
|
|
25
|
+
*/
|
|
26
|
+
const isColor = (value: string) => {
|
|
27
|
+
return this.commands.isTextStyle!('color', value)
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* 设置颜色
|
|
31
|
+
*/
|
|
32
|
+
const setColor = async (value: string) => {
|
|
33
|
+
if (isColor(value)) {
|
|
34
|
+
return
|
|
35
|
+
}
|
|
36
|
+
await this.commands.setTextStyle!({
|
|
37
|
+
color: value
|
|
38
|
+
})
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* 取消颜色
|
|
42
|
+
*/
|
|
43
|
+
const unsetColor = async (value: string) => {
|
|
44
|
+
if (!isColor(value)) {
|
|
45
|
+
return
|
|
46
|
+
}
|
|
47
|
+
await this.commands.removeTextStyle!(['color'])
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return {
|
|
51
|
+
isColor,
|
|
52
|
+
setColor,
|
|
53
|
+
unsetColor
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
})
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { KNodeMarksType, KNodeStylesType } from '@/model'
|
|
2
|
+
import { splitNodeToNodes } from '@/model/config/function'
|
|
3
|
+
import { deleteProperty } from '@/tools'
|
|
4
|
+
import { Extension } from '../Extension'
|
|
5
|
+
|
|
6
|
+
declare module '../../model' {
|
|
7
|
+
interface EditorCommandsType {
|
|
8
|
+
isFontFamily?: (value: string) => boolean
|
|
9
|
+
setFontFamily?: (value: string) => Promise<void>
|
|
10
|
+
unsetFontFamily?: (value: string) => Promise<void>
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const FontFamilyExtension = () =>
|
|
15
|
+
Extension.create({
|
|
16
|
+
name: 'fontFamily',
|
|
17
|
+
pasteKeepStyles(node) {
|
|
18
|
+
const styles: KNodeStylesType = {}
|
|
19
|
+
if (node.isText() && node.hasStyles()) {
|
|
20
|
+
if (node.styles!.hasOwnProperty('fontFamily')) styles.fontFamily = node.styles!.fontFamily
|
|
21
|
+
}
|
|
22
|
+
return styles
|
|
23
|
+
},
|
|
24
|
+
extraKeepTags: ['font'],
|
|
25
|
+
domParseNodeCallback(node) {
|
|
26
|
+
if (node.isMatch({ tag: 'font' })) {
|
|
27
|
+
node.type = 'inline'
|
|
28
|
+
}
|
|
29
|
+
return node
|
|
30
|
+
},
|
|
31
|
+
formatRules: [
|
|
32
|
+
({ editor, node }) => {
|
|
33
|
+
if (!node.isEmpty() && node.isMatch({ tag: 'font' })) {
|
|
34
|
+
const marks: KNodeMarksType = node.marks || {}
|
|
35
|
+
const styles: KNodeStylesType = node.styles || {}
|
|
36
|
+
node.styles = {
|
|
37
|
+
...styles,
|
|
38
|
+
fontFamily: (marks.face as string) || ''
|
|
39
|
+
}
|
|
40
|
+
node.marks = deleteProperty(marks, 'face')
|
|
41
|
+
node.tag = editor.textRenderTag
|
|
42
|
+
splitNodeToNodes.apply(editor, [node])
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
],
|
|
46
|
+
addCommands() {
|
|
47
|
+
/**
|
|
48
|
+
* 光标所在文本的字体是否与入参一致
|
|
49
|
+
*/
|
|
50
|
+
const isFontFamily = (value: string) => {
|
|
51
|
+
return this.commands.isTextStyle!('fontFamily', value)
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* 设置字体
|
|
55
|
+
*/
|
|
56
|
+
const setFontFamily = async (value: string) => {
|
|
57
|
+
if (isFontFamily(value)) {
|
|
58
|
+
return
|
|
59
|
+
}
|
|
60
|
+
await this.commands.setTextStyle!({
|
|
61
|
+
fontFamily: value
|
|
62
|
+
})
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* 取消字体
|
|
66
|
+
*/
|
|
67
|
+
const unsetFontFamily = async (value: string) => {
|
|
68
|
+
if (!isFontFamily(value)) {
|
|
69
|
+
return
|
|
70
|
+
}
|
|
71
|
+
await this.commands.removeTextStyle!(['fontFamily'])
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return {
|
|
75
|
+
isFontFamily,
|
|
76
|
+
setFontFamily,
|
|
77
|
+
unsetFontFamily
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
})
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { KNodeStylesType } from '@/model'
|
|
2
|
+
import { Extension } from '../Extension'
|
|
3
|
+
|
|
4
|
+
declare module '../../model' {
|
|
5
|
+
interface EditorCommandsType {
|
|
6
|
+
isFontSize?: (value: string) => boolean
|
|
7
|
+
setFontSize?: (value: string) => Promise<void>
|
|
8
|
+
unsetFontSize?: (value: string) => Promise<void>
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const FontSizeExtension = () =>
|
|
13
|
+
Extension.create({
|
|
14
|
+
name: 'fontSize',
|
|
15
|
+
pasteKeepStyles(node) {
|
|
16
|
+
const styles: KNodeStylesType = {}
|
|
17
|
+
if (node.isText() && node.hasStyles()) {
|
|
18
|
+
if (node.styles!.hasOwnProperty('fontSize')) styles.fontSize = node.styles!.fontSize
|
|
19
|
+
}
|
|
20
|
+
return styles
|
|
21
|
+
},
|
|
22
|
+
addCommands() {
|
|
23
|
+
/**
|
|
24
|
+
* 光标所在文本的字号大小是否与入参一致
|
|
25
|
+
*/
|
|
26
|
+
const isFontSize = (value: string) => {
|
|
27
|
+
return this.commands.isTextStyle!('fontSize', value)
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* 设置字号
|
|
31
|
+
*/
|
|
32
|
+
const setFontSize = async (value: string) => {
|
|
33
|
+
if (isFontSize(value)) {
|
|
34
|
+
return
|
|
35
|
+
}
|
|
36
|
+
await this.commands.setTextStyle!({
|
|
37
|
+
fontSize: value
|
|
38
|
+
})
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* 取消字号
|
|
42
|
+
*/
|
|
43
|
+
const unsetFontSize = async (value: string) => {
|
|
44
|
+
if (!isFontSize(value)) {
|
|
45
|
+
return
|
|
46
|
+
}
|
|
47
|
+
await this.commands.removeTextStyle!(['fontSize'])
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return {
|
|
51
|
+
isFontSize,
|
|
52
|
+
setFontSize,
|
|
53
|
+
unsetFontSize
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
})
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { Editor, KNode } from '@/model'
|
|
2
|
+
import { getSelectionBlockNodes } from '@/model/config/function'
|
|
3
|
+
import { Extension } from '../Extension'
|
|
4
|
+
import './style.less'
|
|
5
|
+
|
|
6
|
+
export type HeadingLevelType = 0 | 1 | 2 | 3 | 4 | 5 | 6
|
|
7
|
+
|
|
8
|
+
declare module '../../model' {
|
|
9
|
+
interface EditorCommandsType {
|
|
10
|
+
getHeading?: (level: HeadingLevelType) => KNode | null
|
|
11
|
+
hasHeading?: (level: HeadingLevelType) => boolean
|
|
12
|
+
allHeading?: (level: HeadingLevelType) => boolean
|
|
13
|
+
setHeading?: (level: HeadingLevelType) => Promise<void>
|
|
14
|
+
unsetHeading?: (level: HeadingLevelType) => Promise<void>
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* 块节点转为标题
|
|
20
|
+
*/
|
|
21
|
+
const toHeading = (editor: Editor, node: KNode, level: HeadingLevelType) => {
|
|
22
|
+
if (!node.isBlock()) {
|
|
23
|
+
return
|
|
24
|
+
}
|
|
25
|
+
const headingLevelMap = {
|
|
26
|
+
0: editor.blockRenderTag,
|
|
27
|
+
1: 'h1',
|
|
28
|
+
2: 'h2',
|
|
29
|
+
3: 'h3',
|
|
30
|
+
4: 'h4',
|
|
31
|
+
5: 'h5',
|
|
32
|
+
6: 'h6'
|
|
33
|
+
}
|
|
34
|
+
//是固定的块节点或者内嵌套的块节点
|
|
35
|
+
if (node.fixed || node.nested) {
|
|
36
|
+
//创建标题节点
|
|
37
|
+
const headingNode = KNode.create({
|
|
38
|
+
type: 'block',
|
|
39
|
+
tag: headingLevelMap[level],
|
|
40
|
+
children: []
|
|
41
|
+
})
|
|
42
|
+
//将块节点的子节点给标题节点
|
|
43
|
+
node.children!.forEach((item, index) => {
|
|
44
|
+
editor.addNode(item, headingNode, index)
|
|
45
|
+
})
|
|
46
|
+
//将标题节点添加到块节点下
|
|
47
|
+
headingNode.parent = node
|
|
48
|
+
node.children = [headingNode]
|
|
49
|
+
}
|
|
50
|
+
//非固定块节点
|
|
51
|
+
else {
|
|
52
|
+
editor.toParagraph(node)
|
|
53
|
+
node.tag = headingLevelMap[level]
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export const HeadingExtension = () =>
|
|
58
|
+
Extension.create({
|
|
59
|
+
name: 'heading',
|
|
60
|
+
extraKeepTags: ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'],
|
|
61
|
+
domParseNodeCallback(node) {
|
|
62
|
+
if (node.isMatch({ tag: 'h1' }) || node.isMatch({ tag: 'h2' }) || node.isMatch({ tag: 'h3' }) || node.isMatch({ tag: 'h4' }) || node.isMatch({ tag: 'h5' }) || node.isMatch({ tag: 'h6' })) {
|
|
63
|
+
node.type = 'block'
|
|
64
|
+
}
|
|
65
|
+
return node
|
|
66
|
+
},
|
|
67
|
+
formatRules: [
|
|
68
|
+
({ node }) => {
|
|
69
|
+
if (node.isMatch({ tag: 'h1' }) || node.isMatch({ tag: 'h2' }) || node.isMatch({ tag: 'h3' }) || node.isMatch({ tag: 'h4' }) || node.isMatch({ tag: 'h5' }) || node.isMatch({ tag: 'h6' })) {
|
|
70
|
+
node.type = 'block'
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
],
|
|
74
|
+
addCommands() {
|
|
75
|
+
const headingLevelMap = {
|
|
76
|
+
0: this.blockRenderTag,
|
|
77
|
+
1: 'h1',
|
|
78
|
+
2: 'h2',
|
|
79
|
+
3: 'h3',
|
|
80
|
+
4: 'h4',
|
|
81
|
+
5: 'h5',
|
|
82
|
+
6: 'h6'
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* 获取光标所在的标题,如果光标不在一个标题内,返回null
|
|
86
|
+
*/
|
|
87
|
+
const getHeading = (level: HeadingLevelType) => {
|
|
88
|
+
return this.getMatchNodeBySelection({
|
|
89
|
+
tag: headingLevelMap[level]
|
|
90
|
+
})
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* 判断光标范围内是否有标题
|
|
95
|
+
*/
|
|
96
|
+
const hasHeading = (level: HeadingLevelType) => {
|
|
97
|
+
return this.isSelectionNodesSomeMatch({
|
|
98
|
+
tag: headingLevelMap[level]
|
|
99
|
+
})
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* 光标范围内是否都是标题
|
|
104
|
+
*/
|
|
105
|
+
const allHeading = (level: HeadingLevelType) => {
|
|
106
|
+
return this.isSelectionNodesAllMatch({
|
|
107
|
+
tag: headingLevelMap[level]
|
|
108
|
+
})
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* 设置标题
|
|
113
|
+
*/
|
|
114
|
+
const setHeading = async (level: HeadingLevelType) => {
|
|
115
|
+
if (allHeading(level)) {
|
|
116
|
+
return
|
|
117
|
+
}
|
|
118
|
+
//起点和终点在一起
|
|
119
|
+
if (this.selection.collapsed()) {
|
|
120
|
+
const blockNode = this.selection.start!.node.getBlock()
|
|
121
|
+
toHeading(this, blockNode, level)
|
|
122
|
+
}
|
|
123
|
+
//起点和终点不在一起
|
|
124
|
+
else {
|
|
125
|
+
const blockNodes = getSelectionBlockNodes.apply(this)
|
|
126
|
+
blockNodes.forEach(item => {
|
|
127
|
+
toHeading(this, item, level)
|
|
128
|
+
})
|
|
129
|
+
}
|
|
130
|
+
await this.updateView()
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* 取消标题
|
|
135
|
+
*/
|
|
136
|
+
const unsetHeading = async (level: HeadingLevelType) => {
|
|
137
|
+
if (!allHeading(level)) {
|
|
138
|
+
return
|
|
139
|
+
}
|
|
140
|
+
//起点和终点在一起
|
|
141
|
+
if (this.selection.collapsed()) {
|
|
142
|
+
const matchNode = this.selection.start!.node.getMatchNode({ tag: headingLevelMap[level] })
|
|
143
|
+
if (matchNode) this.toParagraph(matchNode)
|
|
144
|
+
}
|
|
145
|
+
//起点和终点不在一起
|
|
146
|
+
else {
|
|
147
|
+
const blockNodes = getSelectionBlockNodes.apply(this)
|
|
148
|
+
blockNodes.forEach(item => {
|
|
149
|
+
const matchNode = item.getMatchNode({ tag: headingLevelMap[level] })
|
|
150
|
+
if (matchNode) this.toParagraph(matchNode)
|
|
151
|
+
})
|
|
152
|
+
}
|
|
153
|
+
await this.updateView()
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return {
|
|
157
|
+
getHeading,
|
|
158
|
+
hasHeading,
|
|
159
|
+
allHeading,
|
|
160
|
+
setHeading,
|
|
161
|
+
unsetHeading
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
})
|