@bagelink/vue 0.0.988 → 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.
Files changed (56) hide show
  1. package/dist/components/Btn.vue.d.ts.map +1 -1
  2. package/dist/components/Loading.vue.d.ts +16 -0
  3. package/dist/components/Loading.vue.d.ts.map +1 -0
  4. package/dist/components/form/BglField.vue.d.ts.map +1 -1
  5. package/dist/components/form/inputs/CodeEditor/Index.vue.d.ts +6 -55
  6. package/dist/components/form/inputs/CodeEditor/Index.vue.d.ts.map +1 -1
  7. package/dist/components/form/inputs/FileUpload.vue.d.ts.map +1 -1
  8. package/dist/components/form/inputs/RichText/components/Toolbar.vue.d.ts.map +1 -1
  9. package/dist/components/form/inputs/RichText/components/gridBox.vue.d.ts +3 -7
  10. package/dist/components/form/inputs/RichText/components/gridBox.vue.d.ts.map +1 -1
  11. package/dist/components/form/inputs/RichText/composables/useEditor.d.ts.map +1 -1
  12. package/dist/components/form/inputs/RichText/config.d.ts.map +1 -1
  13. package/dist/components/form/inputs/RichText/index.vue.d.ts.map +1 -1
  14. package/dist/components/form/inputs/RichText/utils/formatting.d.ts +1 -0
  15. package/dist/components/form/inputs/RichText/utils/formatting.d.ts.map +1 -1
  16. package/dist/components/form/inputs/RichText/utils/media.d.ts +3 -3
  17. package/dist/components/form/inputs/RichText/utils/media.d.ts.map +1 -1
  18. package/dist/components/form/inputs/RichText/utils/selection.d.ts.map +1 -1
  19. package/dist/components/form/inputs/RichText/utils/table.d.ts +2 -1
  20. package/dist/components/form/inputs/RichText/utils/table.d.ts.map +1 -1
  21. package/dist/components/index.d.ts +1 -0
  22. package/dist/components/index.d.ts.map +1 -1
  23. package/dist/components/layout/TabsNav.vue.d.ts.map +1 -1
  24. package/dist/editor-a8DSbb6P.js +4 -0
  25. package/dist/editor-xBt_vIha.cjs +4 -0
  26. package/dist/index.cjs +10525 -48629
  27. package/dist/index.mjs +10526 -48630
  28. package/dist/style.css +818 -20219
  29. package/package.json +6 -33
  30. package/src/components/Btn.vue +110 -136
  31. package/src/components/Loading.vue +177 -0
  32. package/src/components/form/BglField.vue +2 -0
  33. package/src/components/form/inputs/CodeEditor/Index.vue +88 -395
  34. package/src/components/form/inputs/CodeEditor/format.ts +2 -2
  35. package/src/components/form/inputs/FileUpload.vue +2 -1
  36. package/src/components/form/inputs/RichText/components/Toolbar.vue +1 -1
  37. package/src/components/form/inputs/RichText/components/gridBox.vue +37 -8
  38. package/src/components/form/inputs/RichText/composables/useEditor.ts +27 -7
  39. package/src/components/form/inputs/RichText/config.ts +6 -2
  40. package/src/components/form/inputs/RichText/editor.css +14 -14
  41. package/src/components/form/inputs/RichText/index.vue +12 -10
  42. package/src/components/form/inputs/RichText/richTextTypes.d.ts +2 -0
  43. package/src/components/form/inputs/RichText/utils/formatting.ts +281 -34
  44. package/src/components/form/inputs/RichText/utils/media.ts +9 -6
  45. package/src/components/form/inputs/RichText/utils/selection.ts +6 -22
  46. package/src/components/form/inputs/RichText/utils/table.ts +60 -58
  47. package/src/components/index.ts +2 -2
  48. package/src/components/layout/TabsNav.vue +1 -0
  49. package/src/styles/theme.css +256 -256
  50. package/src/components/form/inputs/CodeEditor/themes/brown-papersq.png +0 -0
  51. package/src/components/form/inputs/CodeEditor/themes/pojoaque.jpg +0 -0
  52. package/src/components/form/inputs/CodeEditor/themes/themes-base16.css +0 -12809
  53. package/src/components/form/inputs/CodeEditor/themes/themes.css +0 -6740
  54. package/src/components/formkit/FileUploader.vue +0 -406
  55. package/src/components/formkit/MiscFields.vue +0 -74
  56. package/src/components/formkit/Toggle.vue +0 -149
@@ -3,7 +3,7 @@ import { reactive } from 'vue'
3
3
  import { formatting } from '../utils/formatting'
4
4
  import { insertImage, insertLink } from '../utils/media'
5
5
  import { isStyleActive } from '../utils/selection'
6
- import { addRow, deleteRow, mergeCells, splitCell } from '../utils/table'
6
+ import { addRow, deleteRow, mergeCells, splitCell, insertTable } from '../utils/table'
7
7
 
8
8
  export function useEditor() {
9
9
  const state = reactive<
@@ -48,6 +48,11 @@ export function useEditor() {
48
48
  'h4',
49
49
  'h5',
50
50
  'h6',
51
+ 'blockquote',
52
+ 'table',
53
+ 'p',
54
+ 'ol',
55
+ 'li'
51
56
  ]
52
57
 
53
58
  styleTypes.forEach((style) => {
@@ -70,7 +75,6 @@ export function useEditor() {
70
75
  state.doc.body.focus()
71
76
  return
72
77
  }
73
-
74
78
  state.rangeCount = state.selection.rangeCount
75
79
  if (!state.rangeCount) {
76
80
  const range = state.doc.createRange()
@@ -127,8 +131,13 @@ export function useEditor() {
127
131
  if (!state.doc.body.contains(selection.anchorNode)) {
128
132
  state.doc.body.focus()
129
133
  }
130
-
134
+ console.log('command', command, value)
131
135
  switch (command) {
136
+ case 'insertTable': {
137
+ const [rows, cols] = value?.split('x').map(Number) || [3, 3]
138
+ insertTable(rows, cols, state)
139
+ break
140
+ }
132
141
  case 'mergeCells':
133
142
  if (isCaret) return
134
143
  mergeCells(range, state.doc)
@@ -155,18 +164,29 @@ export function useEditor() {
155
164
  case 'underline':
156
165
  format().text(command)
157
166
  break
167
+ case 'clear':{
168
+ format().clear()
169
+ break
170
+ }
158
171
  case 'orderedList':
159
172
  case 'unorderedList':
160
173
  format().list(command)
161
174
  break
162
175
  case 'image':
163
- case 'youtube':
164
- if (isCaret) return
165
- insertImage(state.modal, state.doc, range)
176
+ insertImage(state.modal, state)
166
177
  break
178
+ // case 'youtube':
179
+ // if (isCaret) return
180
+ // insertImage(state.modal, state.doc, range)
181
+ // break
167
182
  case 'link':
168
183
  if (isCaret) return
169
- insertLink(state.modal, state.doc, range)
184
+ insertLink(state.modal, state)
185
+ break
186
+
187
+ case 'blockquote':
188
+ case 'p':
189
+ format().block(command, value || command)
170
190
  break
171
191
  default:
172
192
  if (/^h[1-6]$/.test(command)) {
@@ -19,17 +19,19 @@ export const defaultToolbarConfig: ToolbarConfig = [
19
19
  'italic',
20
20
  'underline',
21
21
  'separator',
22
+ 'p',
23
+ 'blockquote',
22
24
  'orderedList',
23
25
  'unorderedList',
24
26
  'separator',
25
27
  'link',
26
28
  'image',
27
- 'youtube',
29
+ // 'youtube',
28
30
  'separator',
29
31
  'splitView',
30
32
  'clear',
31
33
  'insertTable',
32
- ...tableTools, // Adding table tools to the default toolbar
34
+ // ...tableTools,
33
35
  'fullScreen',
34
36
  ]
35
37
 
@@ -39,6 +41,8 @@ export const toolbarOptions: ToolbarOption[] = [
39
41
  { name: 'h4', label: 'h4', icon: 'format_h4' },
40
42
  { name: 'h5', label: 'h5', icon: 'format_h5' },
41
43
  { name: 'h6', label: 'h6', icon: 'format_h6' },
44
+ { name: 'p', label: 'Paragraph', icon: 'format_paragraph' },
45
+ { name: 'blockquote', label: 'Blockquote', icon: 'format_quote' },
42
46
  { name: 'bold', label: 'Bold', icon: 'format_bold' },
43
47
  { name: 'italic', label: 'Italic', icon: 'format_italic' },
44
48
  { name: 'underline', label: 'Underline', icon: 'format_underlined' },
@@ -1,25 +1,25 @@
1
1
  body {
2
- margin: 0;
3
- padding: 8px;
4
- min-height: 200px;
5
- font-family: sans-serif;
6
- color: inherit;
7
- background: transparent;
2
+ margin: 0;
3
+ padding: 8px;
4
+ min-height: 200px;
5
+ font-family: sans-serif;
6
+ color: inherit;
7
+ background: transparent;
8
8
  }
9
9
 
10
10
  table {
11
- border-collapse: collapse;
12
- margin-bottom: 1rem;
11
+ border-collapse: collapse;
12
+ margin-bottom: 1rem;
13
13
  }
14
14
 
15
15
  th,
16
16
  td {
17
- padding: 1rem;
18
- text-align: left;
19
- border: 1px solid #ddd;
20
- line-height: 1.5;
17
+ padding: 1rem;
18
+ text-align: left;
19
+ border: 1px solid #2a2a2a;
20
+ line-height: 1.5;
21
21
  }
22
22
 
23
23
  th {
24
- background-color: #f4f4f4;
25
- }
24
+ background-color: #f4f4f4;
25
+ }
@@ -40,6 +40,7 @@ async function initEditor() {
40
40
  watch(() => props.modelValue, (newValue) => {
41
41
  if (newValue !== editor.state.content) {
42
42
  editor.state.content = newValue
43
+ editor.updateContent('html')
43
44
  }
44
45
  })
45
46
 
@@ -49,7 +50,7 @@ watch(() => editor.state.content, (newValue) => {
49
50
  </script>
50
51
 
51
52
  <template>
52
- <div class="rich-text-editor rounded pt-05 px-05 pb-1" :class="{ 'fullscreen-mode': editor.state.isFullscreen }">
53
+ <div class="rich-text-editor rounded pt-05 px-05 pb-075" :class="{ 'fullscreen-mode': editor.state.isFullscreen }">
53
54
  <Toolbar
54
55
  v-if="editor.state.hasInit" :config="toolbarConfig" :selectedStyles="editor.state.selectedStyles"
55
56
  @action="editor.handleToolbarAction"
@@ -59,14 +60,11 @@ watch(() => editor.state.content, (newValue) => {
59
60
  <iframe id="rich-text-iframe" ref="iframe" class="editableContent" title="Editor" @load="initEditor" />
60
61
  </div>
61
62
  <CodeEditor
62
- v-if="editor?.state.isSplitView" v-model="editor.state.content" :languages="['html']"
63
+ v-if="editor?.state.isSplitView" v-model="editor.state.content" language="html"
63
64
  @update:modelValue="editor.updateContent('html')"
64
65
  />
65
66
  </div>
66
67
  </div>
67
- <pre wrap>
68
- {{ editor.state }}
69
- </pre>
70
68
  </template>
71
69
 
72
70
  <style scoped>
@@ -88,14 +86,15 @@ watch(() => editor.state.content, (newValue) => {
88
86
  background: var(--bgl-richtext-color);
89
87
  }
90
88
 
91
- .split-view .content-area,
92
- .split-view .preview-area {
93
- width: 50%;
89
+ .split-view {
90
+ display: grid;
91
+ grid-template-columns: 1fr 1fr;
94
92
  }
95
93
 
96
94
  .editableContent {
97
95
  width: 100%;
98
- min-height: 200px;
96
+ min-height: 240px;
97
+ height: 100%;
99
98
  border: none;
100
99
  outline: none;
101
100
  background: transparent;
@@ -130,7 +129,7 @@ watch(() => editor.state.content, (newValue) => {
130
129
  }
131
130
 
132
131
  .fullscreen-mode .editor-container {
133
- height: calc(100vh - 8rem);
132
+ height: calc(100vh - 4rem);
134
133
  }
135
134
 
136
135
  .fullscreen-mode .content-area,
@@ -138,4 +137,7 @@ watch(() => editor.state.content, (newValue) => {
138
137
  height: 100%;
139
138
  overflow-y: auto;
140
139
  }
140
+ .fullscreen-mode .code-editor{
141
+ height: 100% !important;
142
+ }
141
143
  </style>
@@ -41,6 +41,8 @@ export type HeadingLevel = 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6'
41
41
  export type ToolbarConfigOption =
42
42
  | FormattingCommand
43
43
  | HeadingLevel
44
+ | 'p'
45
+ | 'blockquote'
44
46
  | 'separator'
45
47
  | 'fullScreen'
46
48
  | 'clear'
@@ -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.tagName.toLowerCase() === tag || relatedTags[tag].includes(parent.tagName.toLowerCase()))) {
29
- while (wrapper.firstChild) {
30
- parent.parentNode?.insertBefore(wrapper.firstChild, parent)
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
- const nestedTags = wrapper.querySelectorAll(`${tag},${(relatedTags[tag] || []).join(',')}`)
38
- nestedTags.forEach((nestedTag) => {
39
- while (nestedTag.firstChild) {
40
- nestedTag.parentNode?.insertBefore(nestedTag.firstChild, nestedTag)
41
- }
42
- nestedTag.parentNode?.removeChild(nestedTag)
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 = doc.createElement(currentHeading && currentHeading.tagName.toLowerCase() === value ? 'p' : value)
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
- range.setStart(container, startOffset)
72
- range.setEnd(container, endOffset)
218
+ setRangeAndSelect(container, startOffset, endOffset)
73
219
  selection.addRange(range)
74
220
  }
75
221
  const list = (command: string) => {
76
- const { doc, range } = state
77
- if (!doc || !range) return
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 content = range.extractContents()
85
- const p = doc.createElement('p')
86
- p.appendChild(content)
87
- currentList.parentNode?.replaceChild(p, currentList)
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
- const li = doc.createElement('li')
91
- const content = range.extractContents()
92
- li.appendChild(content)
93
- listEl.appendChild(li)
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, doc: Document, range: Range | null) {
4
- if (!range) return
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, doc: Document, range: Range | null) {
25
- if (!range) return
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
- switch (style) {
27
- case 'bold':
28
- return checkParent(parent, ['strong', 'b'])
29
- case 'italic':
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
  }