@bagelink/vue 1.2.11 → 1.2.15

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.
@@ -41,13 +41,10 @@ function createFormattingCommand(state: EditorState, type: 'text' | 'block' | 'l
41
41
  if (!state.doc) return
42
42
 
43
43
  if (type === 'text') {
44
- if (command === 'bold') state.doc.execCommand('bold', false)
45
- else if (command === 'italic') state.doc.execCommand('italic', false)
46
- else if (command === 'underline') state.doc.execCommand('underline', false)
47
- else format.text(command)
44
+ format.text(command)
48
45
  }
49
46
  else if (type === 'block') {
50
- state.doc.execCommand('formatBlock', false, `<${tag || command}>`)
47
+ format.block(command, tag || command)
51
48
  }
52
49
  else if (type === 'list') {
53
50
  const selection = state.doc.getSelection()
@@ -82,11 +79,7 @@ function createFormattingCommand(state: EditorState, type: 'text' | 'block' | 'l
82
79
  selection.removeAllRanges()
83
80
  selection.addRange(range)
84
81
  } else {
85
- // Use standard command for existing content
86
- state.doc.execCommand(
87
- command === 'orderedList' ? 'insertOrderedList' : 'insertUnorderedList',
88
- false
89
- )
82
+ format.list(command)
90
83
  }
91
84
  }
92
85
  },
@@ -96,15 +89,16 @@ function createFormattingCommand(state: EditorState, type: 'text' | 'block' | 'l
96
89
 
97
90
  // Helper function to check if a node is empty (contains only whitespace or <br>)
98
91
  function isNodeEmpty(node: Node): boolean {
99
- const text = node.textContent?.trim() || ''
92
+ // Check for text content after removing whitespace and nonbreaking spaces
93
+ const text = node.textContent?.replace(/\s/g, '') || ''
100
94
  if (text) return false
101
95
 
102
96
  // Check for <br> tags
103
- const brElements = (node as Element).getElementsByTagName('br')
97
+ const brElements = (node as Element).getElementsByTagName ? (node as Element).getElementsByTagName('br') : []
104
98
  if (brElements.length === 0) return true
105
99
 
106
- // If there's only one <br> and it's the only content, consider it empty
107
- return brElements.length === 1 && node.childNodes.length === 1
100
+ // If there's only one <br> and it's the only content (besides potential &nbsp;), consider it empty
101
+ return brElements.length === 1 && node.childNodes.length <= 2 // Allow for &nbsp; + <br>
108
102
  }
109
103
 
110
104
  export function createCommandRegistry(state: EditorState): CommandRegistry {
@@ -112,25 +106,226 @@ export function createCommandRegistry(state: EditorState): CommandRegistry {
112
106
 
113
107
  // History commands
114
108
  const historyCommands = {
115
- undo: createCommand('Undo', () => state.doc?.execCommand('undo', false)),
116
- redo: createCommand('Redo', () => state.doc?.execCommand('redo', false))
109
+ undo: createCommand('Undo', () => {
110
+ if (state.undoStack.length > 0 && state.doc) {
111
+ const lastContent = state.undoStack.pop()
112
+ if (lastContent !== undefined) {
113
+ state.redoStack.push(state.content)
114
+ state.content = lastContent
115
+ if (state.doc) state.doc.body.innerHTML = lastContent
116
+ }
117
+ }
118
+ }),
119
+ redo: createCommand('Redo', () => {
120
+ if (state.redoStack.length > 0 && state.doc) {
121
+ const nextContent = state.redoStack.pop()
122
+ if (nextContent !== undefined) {
123
+ state.undoStack.push(state.content)
124
+ state.content = nextContent
125
+ if (state.doc) state.doc.body.innerHTML = nextContent
126
+ }
127
+ }
128
+ })
117
129
  }
118
130
 
119
131
  // Basic text formatting commands
120
- const textCommands = ['bold', 'italic', 'underline'].reduce((acc, cmd) => ({
121
- ...acc,
122
- [cmd]: createFormattingCommand(state, 'text', cmd)
123
- }), {})
132
+ const textCommands = {
133
+ bold: createCommand('Bold', (state) => {
134
+ console.log('[Command] Bold called')
135
+ if (!state.doc || !state.range || !state.selection) return
136
+
137
+ const { range } = state
138
+ if (range.collapsed) return
139
+
140
+ try {
141
+ // Create a span with bold style
142
+ const span = state.doc.createElement('span')
143
+ span.style.fontWeight = 'bold'
144
+
145
+ // Try to surround contents, if that fails use extract and insert
146
+ try {
147
+ range.surroundContents(span)
148
+ } catch (e) {
149
+ // Handle selections that cross node boundaries
150
+ const fragment = range.extractContents()
151
+ span.appendChild(fragment)
152
+ range.insertNode(span)
153
+ }
154
+
155
+ // Select the new formatted content
156
+ range.selectNodeContents(span)
157
+ state.selection.removeAllRanges()
158
+ state.selection.addRange(range)
159
+
160
+ // Update content
161
+ state.content = state.doc.body.innerHTML
162
+ } catch (error) {
163
+ console.error('[Command] Error applying bold:', error)
164
+ }
165
+ }),
166
+
167
+ italic: createCommand('Italic', (state) => {
168
+ console.log('[Command] Italic called')
169
+ if (!state.doc || !state.range || !state.selection) return
170
+
171
+ const { range } = state
172
+ if (range.collapsed) return
173
+
174
+ try {
175
+ // Create a span with italic style
176
+ const span = state.doc.createElement('span')
177
+ span.style.fontStyle = 'italic'
178
+
179
+ // Try to surround contents, if that fails use extract and insert
180
+ try {
181
+ range.surroundContents(span)
182
+ } catch (e) {
183
+ // Handle selections that cross node boundaries
184
+ const fragment = range.extractContents()
185
+ span.appendChild(fragment)
186
+ range.insertNode(span)
187
+ }
188
+
189
+ // Select the new formatted content
190
+ range.selectNodeContents(span)
191
+ state.selection.removeAllRanges()
192
+ state.selection.addRange(range)
193
+
194
+ // Update content
195
+ state.content = state.doc.body.innerHTML
196
+ } catch (error) {
197
+ console.error('[Command] Error applying italic:', error)
198
+ }
199
+ }),
200
+
201
+ underline: createCommand('Underline', (state) => {
202
+ console.log('[Command] Underline called')
203
+ if (!state.doc || !state.range || !state.selection) return
204
+
205
+ const { range } = state
206
+ if (range.collapsed) return
207
+
208
+ try {
209
+ // Create a span with underline style
210
+ const span = state.doc.createElement('span')
211
+ span.style.textDecoration = 'underline'
212
+
213
+ // Try to surround contents, if that fails use extract and insert
214
+ try {
215
+ range.surroundContents(span)
216
+ } catch (e) {
217
+ // Handle selections that cross node boundaries
218
+ const fragment = range.extractContents()
219
+ span.appendChild(fragment)
220
+ range.insertNode(span)
221
+ }
222
+
223
+ // Select the new formatted content
224
+ range.selectNodeContents(span)
225
+ state.selection.removeAllRanges()
226
+ state.selection.addRange(range)
227
+
228
+ // Update content
229
+ state.content = state.doc.body.innerHTML
230
+ } catch (error) {
231
+ console.error('[Command] Error applying underline:', error)
232
+ }
233
+ })
234
+ }
124
235
 
125
236
  // Heading commands
126
237
  const headingCommands = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'].reduce((acc, cmd) => ({
127
238
  ...acc,
128
- [cmd]: createFormattingCommand(state, 'block', cmd)
239
+ [cmd]: createCommand(`Heading ${cmd}`, (state) => {
240
+ console.log(`[Command] ${cmd} heading called`)
241
+ if (!state.doc || !state.range || !state.selection) return
242
+
243
+ const { range, doc, selection } = state
244
+ const container = range.commonAncestorContainer
245
+ const parentBlock = container.nodeType === 3 ? container.parentElement : container as HTMLElement
246
+
247
+ // Find current block element
248
+ const currentBlock = parentBlock?.closest('p,h1,h2,h3,h4,h5,h6,blockquote,div') || parentBlock
249
+
250
+ // Check if we need to toggle off (already in a heading with the same tag)
251
+ const isToggleOff = currentBlock?.tagName.toLowerCase() === cmd.toLowerCase()
252
+
253
+ // Determine which tag to apply
254
+ const newTag = isToggleOff ? 'p' : cmd
255
+
256
+ try {
257
+ // Create a new block element of the proper type
258
+ const newBlock = doc.createElement(newTag)
259
+
260
+ if (currentBlock) {
261
+ // Copy content from current block to new block
262
+ while (currentBlock.firstChild) {
263
+ newBlock.appendChild(currentBlock.firstChild)
264
+ }
265
+
266
+ // Replace the old block with the new one
267
+ currentBlock.parentNode?.replaceChild(newBlock, currentBlock)
268
+
269
+ // Move cursor into the new block
270
+ range.selectNodeContents(newBlock)
271
+ range.collapse(false) // Move to end
272
+ selection.removeAllRanges()
273
+ selection.addRange(range)
274
+ }
275
+
276
+ // Update content
277
+ state.content = doc.body.innerHTML
278
+ } catch (error) {
279
+ console.error(`[Command] Error applying ${cmd} heading:`, error)
280
+ }
281
+ }),
129
282
  }), {})
130
283
 
131
284
  // Block commands
132
285
  const blockCommands = {
133
- p: createFormattingCommand(state, 'block', 'p'),
286
+ p: createCommand('Paragraph', (state) => {
287
+ console.log('[Command] Paragraph called')
288
+ if (!state.doc || !state.range || !state.selection) return
289
+
290
+ const { range, doc, selection } = state
291
+ const container = range.commonAncestorContainer
292
+ const parentBlock = container.nodeType === 3 ? container.parentElement : container as HTMLElement
293
+
294
+ // Find current block element
295
+ const currentBlock = parentBlock?.closest('p,h1,h2,h3,h4,h5,h6,blockquote,div') || parentBlock
296
+
297
+ // Check if already a paragraph - if so, nothing to do
298
+ if (currentBlock?.tagName.toLowerCase() === 'p') {
299
+ console.log('[Command] Already a paragraph')
300
+ return
301
+ }
302
+
303
+ try {
304
+ // Create a new paragraph
305
+ const newParagraph = doc.createElement('p')
306
+
307
+ if (currentBlock) {
308
+ // Copy content from current block to paragraph
309
+ while (currentBlock.firstChild) {
310
+ newParagraph.appendChild(currentBlock.firstChild)
311
+ }
312
+
313
+ // Replace the old block with the paragraph
314
+ currentBlock.parentNode?.replaceChild(newParagraph, currentBlock)
315
+
316
+ // Move cursor into the new paragraph
317
+ range.selectNodeContents(newParagraph)
318
+ range.collapse(false) // Move to end
319
+ selection.removeAllRanges()
320
+ selection.addRange(range)
321
+ }
322
+
323
+ // Update content
324
+ state.content = doc.body.innerHTML
325
+ } catch (error) {
326
+ console.error('[Command] Error applying paragraph:', error)
327
+ }
328
+ }),
134
329
  blockquote: createFormattingCommand(state, 'block', 'blockquote')
135
330
  }
136
331
 
@@ -179,7 +374,96 @@ export function createCommandRegistry(state: EditorState): CommandRegistry {
179
374
 
180
375
  // Other formatting commands
181
376
  const otherCommands = {
182
- clear: createCommand('Clear Formatting', () => { format.clear() }),
377
+ clear: createCommand('Clear Formatting', (state) => {
378
+ console.log('[Command] Clear formatting called')
379
+ console.log('[Command] Current selection:', state.selection?.toString())
380
+ console.log('[Command] Range exists:', !!state.range)
381
+ console.log('[Command] Document exists:', !!state.doc)
382
+
383
+ try {
384
+ if (!state.doc || !state.range || !state.selection) {
385
+ console.log('[Command] Missing required state for clear formatting')
386
+ return
387
+ }
388
+
389
+ const { selection, range, doc } = state
390
+
391
+ if (range.collapsed) {
392
+ console.log('[Command] Selection is collapsed, nothing to clear')
393
+ return
394
+ }
395
+
396
+ // Clone the contents to work with
397
+ const fragment = range.cloneContents()
398
+ const tempDiv = doc.createElement('div')
399
+ tempDiv.appendChild(fragment)
400
+
401
+ console.log('[Command] Original HTML:', tempDiv.innerHTML)
402
+
403
+ // Process all elements to remove styling but keep structure
404
+ const structuralTags = ['p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'ul', 'ol', 'li', 'blockquote', 'div', 'table', 'tr', 'td', 'th']
405
+ const inlineTags = ['b', 'i', 'u', 'strong', 'em', 'span', 'font', 'strike', 'sub', 'sup']
406
+
407
+ // Function to clean an element of styling
408
+ const cleanElement = (element: Element) => {
409
+ // Remove style and class attributes
410
+ element.removeAttribute('style')
411
+ element.removeAttribute('class')
412
+
413
+ // Process child elements
414
+ Array.from(element.children).forEach((child) => {
415
+ if (inlineTags.includes(child.tagName.toLowerCase())) {
416
+ // For inline formatting elements, replace with their text content
417
+ const textContent = child.textContent || ''
418
+ const textNode = doc.createTextNode(textContent)
419
+ child.replaceWith(textNode)
420
+ } else {
421
+ // For structural elements, keep them but clean styling
422
+ cleanElement(child)
423
+ }
424
+ })
425
+ }
426
+
427
+ // Clean all elements
428
+ Array.from(tempDiv.querySelectorAll('*')).forEach((element) => {
429
+ const tagName = element.tagName.toLowerCase()
430
+
431
+ // For inline formatting tags, we'll remove and keep only their content
432
+ // which happens in cleanElement when processing children
433
+
434
+ // For structural tags, keep them but remove styling
435
+ if (structuralTags.includes(tagName)) {
436
+ cleanElement(element)
437
+ }
438
+ })
439
+
440
+ console.log('[Command] Cleaned HTML:', tempDiv.innerHTML)
441
+
442
+ // Replace the selection with the cleaned content
443
+ range.deleteContents()
444
+
445
+ // Create a new document fragment to hold the cleaned content
446
+ const cleanedFragment = doc.createDocumentFragment()
447
+
448
+ // Append all nodes from the tempDiv
449
+ while (tempDiv.firstChild) {
450
+ cleanedFragment.appendChild(tempDiv.firstChild)
451
+ }
452
+
453
+ range.insertNode(cleanedFragment)
454
+
455
+ // Try to restore a reasonable selection
456
+ range.collapse(false)
457
+ selection.removeAllRanges()
458
+ selection.addRange(range)
459
+
460
+ // Update content
461
+ state.content = doc.body.innerHTML
462
+ console.log('[Command] Clear formatting completed')
463
+ } catch (error) {
464
+ console.error('[Command] Error in clear formatting:', error)
465
+ }
466
+ }),
183
467
  indent: createCommand('Indent', () => { format.text('indent') }),
184
468
  outdent: createCommand('Outdent', () => { format.text('outdent') })
185
469
  }
@@ -28,7 +28,22 @@ export function formatting(state: EditorState) {
28
28
  range.surroundContents(span)
29
29
  }
30
30
  } else {
31
- doc.execCommand(command, false)
31
+ if (range.collapsed) return // No selection, nothing to format
32
+
33
+ const span = doc.createElement('span')
34
+ if (command === 'bold') span.style.fontWeight = 'bold'
35
+ else if (command === 'italic') span.style.fontStyle = 'italic'
36
+ else if (command === 'underline') span.style.textDecoration = 'underline'
37
+
38
+ try {
39
+ range.surroundContents(span)
40
+ } catch (e) {
41
+ // If surroundContents fails (e.g., for selections across multiple nodes)
42
+ // Extract the fragment, wrap it, and insert it back
43
+ const fragment = range.extractContents()
44
+ span.appendChild(fragment)
45
+ range.insertNode(span)
46
+ }
32
47
  }
33
48
  }
34
49
 
@@ -53,12 +68,27 @@ export function formatting(state: EditorState) {
53
68
  }
54
69
  }
55
70
 
56
- doc.execCommand('formatBlock', false, tag)
71
+ // Create a new block element of the desired type
72
+ const newBlock = doc.createElement(tag)
73
+ if (isRTL) newBlock.dir = 'rtl'
74
+
75
+ // Find the current block to replace
76
+ const currentBlock = parentBlock?.closest('p,h1,h2,h3,h4,h5,h6,blockquote,div') || parentBlock
77
+
78
+ if (currentBlock) {
79
+ // Copy content to the new block
80
+ while (currentBlock.firstChild) {
81
+ newBlock.appendChild(currentBlock.firstChild)
82
+ }
83
+
84
+ // Replace the current block with the new one
85
+ currentBlock.parentNode?.replaceChild(newBlock, currentBlock)
57
86
 
58
- // Preserve RTL direction after block formatting
59
- if (isRTL) {
60
- const newBlock = selection.anchorNode?.parentElement?.closest(tag.toLowerCase()) as HTMLElement
61
- if (newBlock) newBlock.dir = 'rtl'
87
+ // Move cursor into the new block
88
+ range.selectNodeContents(newBlock)
89
+ range.collapse(false)
90
+ selection.removeAllRanges()
91
+ selection.addRange(range)
62
92
  }
63
93
  }
64
94
 
@@ -145,38 +175,97 @@ export function formatting(state: EditorState) {
145
175
  }
146
176
 
147
177
  const clear = () => {
148
- if (!state.doc || !state.range || !state.selection) return
178
+ console.log('[Clear Format] Starting clear format process', state)
179
+ console.assert(state, '[Clear Format] State must exist')
180
+ console.assert(state.doc, '[Clear Format] Document must exist')
181
+ console.assert(state.range, '[Clear Format] Range must exist')
182
+ console.assert(state.selection, '[Clear Format] Selection must exist')
183
+
184
+ if (!state.doc || !state.range || !state.selection) {
185
+ console.log('[Clear Format] No document or selection')
186
+ return
187
+ }
149
188
 
150
189
  const selectionInfo = analyzeSelection(state.doc, state.range)
151
- if (!selectionInfo) return
190
+ console.log('[Clear Format] Selection info:', selectionInfo)
191
+ if (!selectionInfo) {
192
+ console.log('[Clear Format] No valid selection info')
193
+ return
194
+ }
195
+
196
+ // If selection is collapsed (just a cursor), return
197
+ if (state.range.collapsed) {
198
+ console.log('[Clear Format] Selection is collapsed, nothing to clear')
199
+ return
200
+ }
201
+
202
+ console.log('[Clear Format] Processing selection:', {
203
+ startContainer: state.range.startContainer,
204
+ endContainer: state.range.endContainer,
205
+ selectedText: state.range.toString()
206
+ })
152
207
 
153
- const inlineTags = ['b', 'i', 'u', 'strong', 'em', 'span']
208
+ // Get the selected content
154
209
  const fragment = state.range.cloneContents()
155
210
  const tempDiv = state.doc.createElement('div')
156
211
  tempDiv.appendChild(fragment)
157
212
 
158
- // Function to clean a node and its children
213
+ console.log('[Clear Format] Original HTML:', tempDiv.innerHTML)
214
+
215
+ // Function to recursively clean a node and its children
159
216
  const cleanNode = (node: Node): Node => {
160
217
  if (!state.doc) return node
161
- if (node.nodeType === 3) { // Text node
218
+
219
+ // Text nodes can be returned as-is
220
+ if (node.nodeType === 3) {
162
221
  return node.cloneNode(true)
163
222
  }
164
223
 
165
224
  if (node.nodeType === 1) { // Element node
166
225
  const el = node as HTMLElement
167
226
  const nodeName = el.nodeName.toLowerCase()
227
+ const inlineTags = ['b', 'i', 'u', 'strong', 'em', 'span', 'font', 'strike', 'sub', 'sup']
228
+
229
+ console.log('[Clear Format] Processing element:', nodeName, {
230
+ hasStyle: el.hasAttribute('style'),
231
+ style: el.getAttribute('style'),
232
+ className: el.className
233
+ })
168
234
 
169
- // If it's an inline formatting element, just return its text content
235
+ // For inline formatting elements, just extract the text content
170
236
  if (inlineTags.includes(nodeName)) {
171
- return state.doc.createTextNode(el.textContent || '')
237
+ // Create a text node with the element's content
238
+ const textContent = el.textContent || ''
239
+ console.log('[Clear Format] Extracting text from inline element:', textContent)
240
+ return state.doc.createTextNode(textContent)
172
241
  }
173
242
 
174
- // For block elements, create a new clean element
243
+ // For block elements, preserve the element type but remove formatting
175
244
  const newEl = state.doc.createElement(nodeName)
176
245
 
177
- // Remove all styles and classes
178
- newEl.removeAttribute('style')
179
- newEl.removeAttribute('class')
246
+ // Remove any style and class attributes
247
+ if (el.hasAttribute('style')) {
248
+ console.log('[Clear Format] Removing style attribute:', el.getAttribute('style'))
249
+ }
250
+ if (el.className) {
251
+ console.log('[Clear Format] Removing class:', el.className)
252
+ }
253
+
254
+ // Transfer only specific attributes we want to keep (like href for links)
255
+ if (nodeName === 'a' && el.hasAttribute('href')) {
256
+ newEl.setAttribute('href', el.getAttribute('href') || '')
257
+ if (el.hasAttribute('target')) {
258
+ newEl.setAttribute('target', el.getAttribute('target') || '')
259
+ }
260
+ console.log('[Clear Format] Preserving link attributes')
261
+ }
262
+
263
+ // For images, preserve src and alt
264
+ if (nodeName === 'img') {
265
+ if (el.hasAttribute('src')) newEl.setAttribute('src', el.getAttribute('src') || '')
266
+ if (el.hasAttribute('alt')) newEl.setAttribute('alt', el.getAttribute('alt') || '')
267
+ console.log('[Clear Format] Preserving image attributes')
268
+ }
180
269
 
181
270
  // Clean and append all child nodes
182
271
  Array.from(el.childNodes).forEach((child) => {
@@ -195,10 +284,21 @@ export function formatting(state: EditorState) {
195
284
  cleanedFragment.appendChild(cleanNode(node))
196
285
  })
197
286
 
287
+ // For debugging: check what the cleaned HTML looks like
288
+ const debugDiv = state.doc.createElement('div')
289
+ debugDiv.appendChild(cleanedFragment.cloneNode(true))
290
+ console.log('[Clear Format] Cleaned HTML:', debugDiv.innerHTML)
291
+
198
292
  // Replace the content
199
293
  state.range.deleteContents()
200
294
  state.range.insertNode(cleanedFragment)
295
+
296
+ // Restore selection
201
297
  restoreSelection(state.doc, state.range, state.selection, selectionInfo)
298
+
299
+ // Update content state
300
+ state.content = state.doc.body.innerHTML
301
+ console.log('[Clear Format] Updated document HTML:', state.content)
202
302
  }
203
303
 
204
304
  return { text, block, list, clear }
@@ -5,17 +5,52 @@ export function insertTable(rows: number, cols: number, state: EditorState) {
5
5
  const table = state.doc.createElement('table')
6
6
  table.style.width = '100%'
7
7
  table.style.borderCollapse = 'collapse'
8
+ table.style.marginBottom = '1rem'
9
+
10
+ // Add a header row
11
+ const thead = state.doc.createElement('thead')
12
+ const headerRow = thead.insertRow()
13
+ for (let j = 0; j < cols; j++) {
14
+ const th = state.doc.createElement('th')
15
+ th.innerHTML = `Header ${j + 1}`
16
+ th.style.padding = '8px'
17
+ th.style.border = '1px solid var(--border-color, #ddd)'
18
+ th.style.backgroundColor = 'var(--bgl-gray-light, #f4f4f4)'
19
+ headerRow.appendChild(th)
20
+ }
21
+ table.appendChild(thead)
8
22
 
23
+ // Add body rows
24
+ const tbody = state.doc.createElement('tbody')
9
25
  for (let i = 0; i < rows; i++) {
10
- const row = table.insertRow()
26
+ const row = tbody.insertRow()
11
27
  for (let j = 0; j < cols; j++) {
12
28
  const cell = row.insertCell()
13
29
  cell.innerHTML = '&nbsp;'
30
+ cell.style.padding = '8px'
31
+ cell.style.border = '1px solid var(--border-color, #ddd)'
14
32
  }
15
33
  }
34
+ table.appendChild(tbody)
35
+
36
+ // Insert the table at the current selection
16
37
  const { range } = state
17
38
  if (range) {
18
39
  range.insertNode(table)
40
+
41
+ // Move cursor inside first cell for convenience
42
+ if (state.doc.getSelection()) {
43
+ const firstCell = table.querySelector('td')
44
+ if (firstCell) {
45
+ range.selectNodeContents(firstCell)
46
+ range.collapse(true)
47
+ const selection = state.doc.getSelection()
48
+ if (selection) {
49
+ selection.removeAllRanges()
50
+ selection.addRange(range)
51
+ }
52
+ }
53
+ }
19
54
  } else {
20
55
  state.doc.body.appendChild(table)
21
56
  }