@bagelink/vue 0.0.992 → 0.0.996
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/dist/components/form/inputs/CodeEditor/Index.vue.d.ts +7 -11
- package/dist/components/form/inputs/CodeEditor/Index.vue.d.ts.map +1 -1
- package/dist/components/form/inputs/RichText/components/Toolbar.vue.d.ts.map +1 -1
- package/dist/components/form/inputs/RichText/composables/useEditor.d.ts.map +1 -1
- package/dist/components/form/inputs/RichText/config.d.ts.map +1 -1
- package/dist/components/form/inputs/RichText/index.vue.d.ts.map +1 -1
- package/dist/components/form/inputs/RichText/utils/formatting.d.ts +1 -0
- package/dist/components/form/inputs/RichText/utils/formatting.d.ts.map +1 -1
- package/dist/components/form/inputs/RichText/utils/media.d.ts +3 -3
- package/dist/components/form/inputs/RichText/utils/media.d.ts.map +1 -1
- package/dist/components/form/inputs/RichText/utils/selection.d.ts.map +1 -1
- package/dist/index.cjs +285 -102
- package/dist/index.mjs +285 -102
- package/dist/style.css +16 -14
- package/package.json +2 -1
- package/src/components/form/inputs/CodeEditor/Index.vue +26 -10
- package/src/components/form/inputs/RichText/components/Toolbar.vue +1 -1
- package/src/components/form/inputs/RichText/composables/useEditor.ts +17 -4
- package/src/components/form/inputs/RichText/config.ts +5 -1
- package/src/components/form/inputs/RichText/index.vue +1 -0
- package/src/components/form/inputs/RichText/richTextTypes.d.ts +2 -0
- package/src/components/form/inputs/RichText/utils/formatting.ts +281 -34
- package/src/components/form/inputs/RichText/utils/media.ts +9 -6
- package/src/components/form/inputs/RichText/utils/selection.ts +6 -22
|
@@ -1,9 +1,113 @@
|
|
|
1
1
|
import type { EditorState } from '../richTextTypes'
|
|
2
2
|
|
|
3
3
|
export function formatting(state: EditorState) {
|
|
4
|
+
const { doc, range, selection } = state
|
|
5
|
+
function setRangeAndSelect(node: Node, start: number, end: number) {
|
|
6
|
+
if (!range) return
|
|
7
|
+
range.setStart(node, start)
|
|
8
|
+
range.setEnd(node, end)
|
|
9
|
+
}
|
|
10
|
+
const wrapWithTag = (content: string, tag: string): HTMLElement => {
|
|
11
|
+
const wrapper = doc!.createElement(tag)
|
|
12
|
+
wrapper.textContent = content
|
|
13
|
+
return wrapper
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const createBlockElement = (tag: string, content: DocumentFragment): HTMLElement => {
|
|
17
|
+
const block = doc!.createElement(tag)
|
|
18
|
+
block.appendChild(content)
|
|
19
|
+
return block
|
|
20
|
+
}
|
|
21
|
+
const clear = () => {
|
|
22
|
+
if (!doc || !range || !selection) return
|
|
23
|
+
|
|
24
|
+
const inlineTags = ['b', 'i', 'u', 'strong', 'em']
|
|
25
|
+
|
|
26
|
+
const isInlineTag = (el: HTMLElement | null) => el && inlineTags.includes(el.tagName.toLowerCase())
|
|
27
|
+
|
|
28
|
+
if (range.collapsed) {
|
|
29
|
+
// No action if the selection is collapsed
|
|
30
|
+
return
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Extract the selected content
|
|
34
|
+
const selectedContent = range.extractContents()
|
|
35
|
+
|
|
36
|
+
// Helper function to recursively clean inline tags
|
|
37
|
+
const cleanInlineTags = (node: Node): Node => {
|
|
38
|
+
if (node.nodeType === 1 && isInlineTag(node as HTMLElement)) {
|
|
39
|
+
// Inline element: replace it with its children
|
|
40
|
+
const fragment = doc.createDocumentFragment()
|
|
41
|
+
let child = node.firstChild // Start with the first child
|
|
42
|
+
while (child) {
|
|
43
|
+
const { nextSibling } = child // Store the next sibling before appending
|
|
44
|
+
fragment.appendChild(cleanInlineTags(child))
|
|
45
|
+
child = nextSibling // Move to the next sibling
|
|
46
|
+
}
|
|
47
|
+
return fragment
|
|
48
|
+
} else if (node.nodeType === 1) {
|
|
49
|
+
// Non-inline element: clean its children
|
|
50
|
+
const element = node.cloneNode(false) as HTMLElement
|
|
51
|
+
let child = node.firstChild // Start with the first child
|
|
52
|
+
while (child) {
|
|
53
|
+
const { nextSibling } = child // Store the next sibling before appending
|
|
54
|
+
element.appendChild(cleanInlineTags(child))
|
|
55
|
+
child = nextSibling // Move to the next sibling
|
|
56
|
+
}
|
|
57
|
+
return element
|
|
58
|
+
} else if (node.nodeType === 3) {
|
|
59
|
+
// Text node: return as is
|
|
60
|
+
return node
|
|
61
|
+
}
|
|
62
|
+
return doc.createTextNode('') // Ignore other node types
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Clean the extracted content
|
|
66
|
+
const fragment = doc.createDocumentFragment()
|
|
67
|
+
Array.from(selectedContent.childNodes).forEach((child) => {
|
|
68
|
+
const cleanedChild = cleanInlineTags(child)
|
|
69
|
+
if (cleanedChild.nodeType === 11) {
|
|
70
|
+
// If it's a DocumentFragment, append its children directly
|
|
71
|
+
fragment.appendChild(cleanedChild)
|
|
72
|
+
} else {
|
|
73
|
+
fragment.appendChild(cleanedChild)
|
|
74
|
+
}
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
// Replace the range content with the cleaned content
|
|
78
|
+
range.deleteContents()
|
|
79
|
+
range.insertNode(fragment)
|
|
80
|
+
|
|
81
|
+
// Normalize the DOM (merge adjacent text nodes)
|
|
82
|
+
const normalizeDom = (node: Node) => {
|
|
83
|
+
if (node.nodeType === 3 && node.nextSibling?.nodeType === 3) {
|
|
84
|
+
// Merge adjacent text nodes
|
|
85
|
+
if (node.textContent && node.nextSibling.textContent) {
|
|
86
|
+
node.textContent += node.nextSibling.textContent
|
|
87
|
+
}
|
|
88
|
+
node.parentNode?.removeChild(node.nextSibling)
|
|
89
|
+
} else if (node.nodeType === 1) {
|
|
90
|
+
// Recursively normalize child nodes
|
|
91
|
+
Array.from(node.childNodes).forEach((child) => {
|
|
92
|
+
normalizeDom(child)
|
|
93
|
+
})
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
Array.from(fragment.childNodes).forEach((child) => {
|
|
98
|
+
normalizeDom(child)
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
// Update the selection to cover the cleaned content
|
|
102
|
+
selection.removeAllRanges()
|
|
103
|
+
range.selectNodeContents(fragment)
|
|
104
|
+
selection.addRange(range)
|
|
105
|
+
}
|
|
106
|
+
|
|
4
107
|
const text = (command: string) => {
|
|
5
|
-
const { doc, range, selection } = state
|
|
6
108
|
if (!doc || !range || !selection) return
|
|
109
|
+
const { startOffset, endOffset } = range
|
|
110
|
+
|
|
7
111
|
let tag = ''
|
|
8
112
|
if (command === 'bold') tag = 'b'
|
|
9
113
|
if (command === 'italic') tag = 'i'
|
|
@@ -14,52 +118,95 @@ export function formatting(state: EditorState) {
|
|
|
14
118
|
const relatedTags: { [key: string]: string[] } = {
|
|
15
119
|
b: ['strong'],
|
|
16
120
|
i: ['em'],
|
|
17
|
-
u: [],
|
|
121
|
+
u: ['u'],
|
|
18
122
|
em: ['i'],
|
|
19
|
-
strong: ['b']
|
|
123
|
+
strong: ['b'],
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const isTagMatch = (el: HTMLElement | null) => el
|
|
127
|
+
&& (el.tagName.toLowerCase() === tag || relatedTags[tag].includes(el.tagName.toLowerCase()))
|
|
128
|
+
|
|
129
|
+
if (range.collapsed) {
|
|
130
|
+
// Handle collapsed range
|
|
131
|
+
const container = range.commonAncestorContainer
|
|
132
|
+
const parent = container.parentElement
|
|
133
|
+
if (parent && isTagMatch(parent)) {
|
|
134
|
+
const textNode = doc.createTextNode(parent.textContent || '')
|
|
135
|
+
parent.parentNode?.replaceChild(textNode, parent)
|
|
136
|
+
range.selectNodeContents(textNode)
|
|
137
|
+
selection.removeAllRanges()
|
|
138
|
+
setRangeAndSelect(textNode, startOffset, endOffset)
|
|
139
|
+
selection.addRange(range)
|
|
140
|
+
}
|
|
141
|
+
return
|
|
20
142
|
}
|
|
21
143
|
|
|
22
|
-
const selectedContent = range.extractContents()
|
|
23
|
-
const wrapper = doc.createElement(tag)
|
|
24
|
-
wrapper.appendChild(selectedContent)
|
|
25
144
|
const container = range.commonAncestorContainer
|
|
26
145
|
const parent = container.parentElement
|
|
27
146
|
|
|
28
|
-
if (parent && (parent
|
|
29
|
-
|
|
30
|
-
|
|
147
|
+
if (parent && isTagMatch(parent)) {
|
|
148
|
+
// Split text content into three parts: before, selected, and after
|
|
149
|
+
const parentText = parent.textContent || ''
|
|
150
|
+
const beforeText = parentText.substring(0, range.startOffset - parentText.indexOf(parent.textContent || ''))
|
|
151
|
+
const selectedText = parentText.substring(range.startOffset, range.endOffset)
|
|
152
|
+
const afterText = parentText.substring(range.endOffset)
|
|
153
|
+
|
|
154
|
+
// Create text nodes for before, selected, and after
|
|
155
|
+
if (beforeText) {
|
|
156
|
+
const beforeNode = wrapWithTag(beforeText, tag)
|
|
157
|
+
parent.parentNode?.insertBefore(beforeNode, parent)
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Add unwrapped selected text
|
|
161
|
+
const unwrappedTextNode = doc.createTextNode(selectedText)
|
|
162
|
+
parent.parentNode?.insertBefore(unwrappedTextNode, parent)
|
|
163
|
+
|
|
164
|
+
if (afterText) {
|
|
165
|
+
const afterNode = wrapWithTag(afterText, tag)
|
|
166
|
+
parent.parentNode?.insertBefore(afterNode, parent)
|
|
31
167
|
}
|
|
168
|
+
|
|
169
|
+
// Remove the original parent node
|
|
32
170
|
parent.parentNode?.removeChild(parent)
|
|
171
|
+
|
|
172
|
+
// Reselect the unwrapped text
|
|
173
|
+
range.selectNode(unwrappedTextNode)
|
|
33
174
|
} else {
|
|
175
|
+
// Standard case: wrap the selection
|
|
176
|
+
const selectedContent = range.extractContents()
|
|
177
|
+
const wrapper = doc.createElement(tag)
|
|
178
|
+
wrapper.appendChild(selectedContent)
|
|
34
179
|
range.insertNode(wrapper)
|
|
35
|
-
}
|
|
36
180
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
181
|
+
// Clean up nested tags
|
|
182
|
+
const nestedTags = wrapper.querySelectorAll(`${tag},${(relatedTags[tag] || []).join(',')}`)
|
|
183
|
+
nestedTags.forEach((nestedTag) => {
|
|
184
|
+
while (nestedTag.firstChild) {
|
|
185
|
+
nestedTag.parentNode?.insertBefore(nestedTag.firstChild, nestedTag)
|
|
186
|
+
}
|
|
187
|
+
nestedTag.parentNode?.removeChild(nestedTag)
|
|
188
|
+
})
|
|
189
|
+
|
|
190
|
+
// Reselect the wrapped content
|
|
191
|
+
range.selectNodeContents(wrapper)
|
|
192
|
+
}
|
|
44
193
|
|
|
45
194
|
selection.removeAllRanges()
|
|
46
195
|
selection.addRange(range)
|
|
47
196
|
}
|
|
48
197
|
const block = (command: string, value: string) => {
|
|
49
|
-
const { doc, range, selection } = state
|
|
50
198
|
if (!doc || !range || !selection) return
|
|
51
199
|
const container = range.commonAncestorContainer
|
|
52
200
|
const parent = container.nodeType === 3 ? container.parentElement : (container as Element)
|
|
53
201
|
if (!parent) return
|
|
54
202
|
|
|
55
|
-
const currentHeading = parent.closest('p,h1,h2,h3,h4,h5,h6')
|
|
203
|
+
const currentHeading = parent.closest('p,h1,h2,h3,h4,h5,h6,blockquote')
|
|
56
204
|
const { startOffset, endOffset } = range
|
|
57
205
|
const blockEl = currentHeading || parent.closest('p,h1,h2,h3,h4,h5,h6') || parent
|
|
58
206
|
range.selectNodeContents(blockEl)
|
|
59
207
|
|
|
60
208
|
const content = range.extractContents()
|
|
61
|
-
const newElement =
|
|
62
|
-
newElement.appendChild(content)
|
|
209
|
+
const newElement = createBlockElement(value, content)
|
|
63
210
|
|
|
64
211
|
if (currentHeading) {
|
|
65
212
|
currentHeading.parentNode?.replaceChild(newElement, currentHeading)
|
|
@@ -68,31 +215,131 @@ export function formatting(state: EditorState) {
|
|
|
68
215
|
}
|
|
69
216
|
|
|
70
217
|
selection.removeAllRanges()
|
|
71
|
-
|
|
72
|
-
range.setEnd(container, endOffset)
|
|
218
|
+
setRangeAndSelect(container, startOffset, endOffset)
|
|
73
219
|
selection.addRange(range)
|
|
74
220
|
}
|
|
75
221
|
const list = (command: string) => {
|
|
76
|
-
|
|
77
|
-
|
|
222
|
+
if (!doc || !range || !selection) return
|
|
223
|
+
|
|
78
224
|
const listTag = command === 'orderedList' ? 'ol' : 'ul'
|
|
79
225
|
const container = range.commonAncestorContainer
|
|
80
226
|
const parent = container.nodeType === 3 ? container.parentElement : (container as Element)
|
|
81
227
|
const currentList = parent?.closest('ul,ol')
|
|
82
228
|
|
|
229
|
+
if (!parent) return
|
|
230
|
+
|
|
231
|
+
// Expand range to include the entire list item if the caret is inside one
|
|
232
|
+
const listItem = parent.closest('li')
|
|
233
|
+
if (listItem) {
|
|
234
|
+
const listParent = listItem.parentElement // The <ol> or <ul>
|
|
235
|
+
if (!listParent) return
|
|
236
|
+
|
|
237
|
+
// Create new fragments for before and after the selected list item
|
|
238
|
+
const beforeFragment = doc.createDocumentFragment()
|
|
239
|
+
const afterFragment = doc.createDocumentFragment()
|
|
240
|
+
|
|
241
|
+
let currentNode = listParent.firstChild
|
|
242
|
+
let isBefore = true
|
|
243
|
+
|
|
244
|
+
while (currentNode) {
|
|
245
|
+
if (currentNode === listItem) {
|
|
246
|
+
isBefore = false // Mark as "after" once we reach the selected item
|
|
247
|
+
} else if (isBefore) {
|
|
248
|
+
beforeFragment.appendChild(currentNode.cloneNode(true))
|
|
249
|
+
} else {
|
|
250
|
+
afterFragment.appendChild(currentNode.cloneNode(true))
|
|
251
|
+
}
|
|
252
|
+
currentNode = currentNode.nextSibling
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Convert the selected list item to a standalone block (e.g., <p>)
|
|
256
|
+
const standaloneBlock = doc.createElement('p')
|
|
257
|
+
while (listItem.firstChild) {
|
|
258
|
+
standaloneBlock.appendChild(listItem.firstChild)
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// Replace the original list with the split fragments and the standalone block
|
|
262
|
+
const grandParent = listParent.parentNode
|
|
263
|
+
if (!grandParent) return
|
|
264
|
+
|
|
265
|
+
if (beforeFragment.childNodes.length > 0) {
|
|
266
|
+
const newListBefore = doc.createElement(listParent.tagName.toLowerCase())
|
|
267
|
+
newListBefore.appendChild(beforeFragment)
|
|
268
|
+
grandParent.insertBefore(newListBefore, listParent)
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
grandParent.insertBefore(standaloneBlock, listParent)
|
|
272
|
+
|
|
273
|
+
if (afterFragment.childNodes.length > 0) {
|
|
274
|
+
const newListAfter = doc.createElement(listParent.tagName.toLowerCase())
|
|
275
|
+
newListAfter.appendChild(afterFragment)
|
|
276
|
+
grandParent.insertBefore(newListAfter, listParent.nextSibling)
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// Remove the original list
|
|
280
|
+
grandParent.removeChild(listParent)
|
|
281
|
+
|
|
282
|
+
// Update the selection
|
|
283
|
+
selection.removeAllRanges()
|
|
284
|
+
range.selectNodeContents(standaloneBlock)
|
|
285
|
+
selection.addRange(range)
|
|
286
|
+
|
|
287
|
+
return
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// Fallback for toggling list behavior (same as previous implementation)
|
|
291
|
+
const startBlock = parent.closest('p,h1,h2,h3,h4,h5,h6,blockquote') || parent
|
|
292
|
+
const endBlock = range.endContainer.nodeType === 3
|
|
293
|
+
? (range.endContainer.parentElement?.closest('p,h1,h2,h3,h4,h5,h6,blockquote') || range.endContainer.parentElement)
|
|
294
|
+
: (range.endContainer as Element)
|
|
295
|
+
|
|
296
|
+
const blocks = []
|
|
297
|
+
let currentBlock = startBlock
|
|
298
|
+
while (currentBlock) {
|
|
299
|
+
blocks.push(currentBlock)
|
|
300
|
+
if (currentBlock === endBlock) break
|
|
301
|
+
currentBlock = currentBlock.nextElementSibling as Element
|
|
302
|
+
}
|
|
303
|
+
|
|
83
304
|
if (currentList) {
|
|
84
|
-
const
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
305
|
+
const fragment = doc.createDocumentFragment()
|
|
306
|
+
blocks.forEach((block) => {
|
|
307
|
+
const paragraph = doc.createElement('p')
|
|
308
|
+
while (block.firstChild) {
|
|
309
|
+
paragraph.appendChild(block.firstChild)
|
|
310
|
+
}
|
|
311
|
+
fragment.appendChild(paragraph)
|
|
312
|
+
})
|
|
313
|
+
|
|
314
|
+
const listParent = currentList.parentNode
|
|
315
|
+
if (listParent) {
|
|
316
|
+
listParent.insertBefore(fragment, currentList.nextSibling)
|
|
317
|
+
|
|
318
|
+
if (!currentList.querySelector('li')) {
|
|
319
|
+
listParent.removeChild(currentList)
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
selection.removeAllRanges()
|
|
324
|
+
range.selectNodeContents(fragment)
|
|
325
|
+
selection.addRange(range)
|
|
88
326
|
} else {
|
|
89
327
|
const listEl = doc.createElement(listTag)
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
328
|
+
|
|
329
|
+
blocks.forEach((block) => {
|
|
330
|
+
const li = doc.createElement('li')
|
|
331
|
+
while (block.firstChild) {
|
|
332
|
+
li.appendChild(block.firstChild)
|
|
333
|
+
}
|
|
334
|
+
listEl.appendChild(li)
|
|
335
|
+
})
|
|
336
|
+
|
|
94
337
|
range.insertNode(listEl)
|
|
338
|
+
|
|
339
|
+
selection.removeAllRanges()
|
|
340
|
+
range.selectNodeContents(listEl)
|
|
341
|
+
selection.addRange(range)
|
|
95
342
|
}
|
|
96
343
|
}
|
|
97
|
-
return { text, block, list }
|
|
344
|
+
return { text, block, list, clear }
|
|
98
345
|
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import type { Modal } from '../richTextTypes'
|
|
1
|
+
import type { EditorState, Modal } from '../richTextTypes'
|
|
2
2
|
|
|
3
|
-
export function insertImage(modal: Modal,
|
|
4
|
-
|
|
3
|
+
export function insertImage(modal: Modal, state: EditorState) {
|
|
4
|
+
const { range, doc } = state
|
|
5
|
+
if (!range || !doc) return
|
|
5
6
|
|
|
6
7
|
modal.showModalForm({
|
|
7
8
|
title: 'Upload Image',
|
|
@@ -14,15 +15,17 @@ export function insertImage(modal: Modal, doc: Document, range: Range | null) {
|
|
|
14
15
|
const img = doc.createElement('img')
|
|
15
16
|
img.src = data.src
|
|
16
17
|
img.alt = data.alt
|
|
17
|
-
range.deleteContents()
|
|
18
|
+
// range.deleteContents()
|
|
19
|
+
range.collapse(false)
|
|
18
20
|
range.insertNode(img)
|
|
19
21
|
}
|
|
20
22
|
}
|
|
21
23
|
})
|
|
22
24
|
}
|
|
23
25
|
|
|
24
|
-
export function insertLink(modal: Modal,
|
|
25
|
-
|
|
26
|
+
export function insertLink(modal: Modal, state: EditorState) {
|
|
27
|
+
const { range, doc } = state
|
|
28
|
+
if (!range || !doc) return
|
|
26
29
|
|
|
27
30
|
modal.showModalForm({
|
|
28
31
|
title: 'Insert Link',
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
|
|
2
1
|
export function getSelection(state: any) {
|
|
3
2
|
return state.selection
|
|
4
3
|
}
|
|
@@ -23,26 +22,11 @@ export function isStyleActive(style: string, doc: Document) {
|
|
|
23
22
|
return checkParent(element.parentElement, tags)
|
|
24
23
|
}
|
|
25
24
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
return checkParent(parent, ['em', 'i'])
|
|
31
|
-
case 'underline':
|
|
32
|
-
return checkParent(parent, ['u'])
|
|
33
|
-
case 'h1':
|
|
34
|
-
return checkParent(parent, ['h1'])
|
|
35
|
-
case 'h2':
|
|
36
|
-
return checkParent(parent, ['h2'])
|
|
37
|
-
case 'h3':
|
|
38
|
-
return checkParent(parent, ['h3'])
|
|
39
|
-
case 'h4':
|
|
40
|
-
return checkParent(parent, ['h4'])
|
|
41
|
-
case 'h5':
|
|
42
|
-
return checkParent(parent, ['h5'])
|
|
43
|
-
case 'h6':
|
|
44
|
-
return checkParent(parent, ['h6'])
|
|
45
|
-
default:
|
|
46
|
-
return false
|
|
25
|
+
const styleTags: { [key: string]: string[] } = {
|
|
26
|
+
bold: ['strong', 'b'],
|
|
27
|
+
italic: ['em', 'i'],
|
|
28
|
+
underline: ['u'],
|
|
47
29
|
}
|
|
30
|
+
|
|
31
|
+
return checkParent(parent, styleTags[style] ?? [style])
|
|
48
32
|
}
|