@bagelink/vue 1.4.64 → 1.4.71
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/NavBar.vue.d.ts.map +1 -1
- package/dist/components/dataTable/DataTable.vue.d.ts.map +1 -1
- package/dist/components/dataTable/useTableSelection.d.ts +1 -1
- package/dist/components/dataTable/useTableSelection.d.ts.map +1 -1
- package/dist/components/form/inputs/RichText/composables/useCommands.d.ts.map +1 -1
- package/dist/components/form/inputs/RichText/composables/useEditor.d.ts.map +1 -1
- package/dist/components/form/inputs/RichText/composables/useEditorKeyboard.d.ts.map +1 -1
- package/dist/components/form/inputs/RichText/index.vue.d.ts +128 -1
- package/dist/components/form/inputs/RichText/index.vue.d.ts.map +1 -1
- package/dist/components/form/inputs/RichText/utils/commands.d.ts.map +1 -1
- package/dist/components/form/inputs/RichText/utils/selection.d.ts.map +1 -1
- package/dist/index.cjs +20 -20
- package/dist/index.mjs +20 -20
- package/dist/style.css +1 -1
- package/package.json +1 -1
- package/src/components/NavBar.vue +17 -5
- package/src/components/dataTable/DataTable.vue +20 -44
- package/src/components/dataTable/useTableSelection.ts +40 -1
- package/src/components/form/inputs/RichText/composables/useCommands.ts +9 -29
- package/src/components/form/inputs/RichText/composables/useEditor.ts +45 -46
- package/src/components/form/inputs/RichText/composables/useEditorKeyboard.ts +52 -3
- package/src/components/form/inputs/RichText/index.vue +148 -43
- package/src/components/form/inputs/RichText/utils/commands.ts +347 -282
- package/src/components/form/inputs/RichText/utils/selection.ts +55 -37
package/package.json
CHANGED
|
@@ -41,7 +41,11 @@ onMounted(calcIsOpen)
|
|
|
41
41
|
<div :class="{ open: isOpen, closed: !isOpen }">
|
|
42
42
|
<slot name="top" :isOpen="isOpen" />
|
|
43
43
|
<div
|
|
44
|
-
class="nav-expend"
|
|
44
|
+
class="nav-expend"
|
|
45
|
+
role="button"
|
|
46
|
+
aria-label="Toggle Navigation"
|
|
47
|
+
tabindex="0"
|
|
48
|
+
@click="toggleMenu"
|
|
45
49
|
@keypress.enter="toggleMenu"
|
|
46
50
|
>
|
|
47
51
|
<Icon icon="chevron_right" class="top-arrow" />
|
|
@@ -51,8 +55,12 @@ onMounted(calcIsOpen)
|
|
|
51
55
|
<div class="nav-scroll">
|
|
52
56
|
<div class="nav-links-wrapper">
|
|
53
57
|
<component
|
|
54
|
-
:is="link.to ? 'router-link' : 'div'"
|
|
55
|
-
|
|
58
|
+
:is="link.to ? 'router-link' : 'div'"
|
|
59
|
+
v-for="link in links"
|
|
60
|
+
:key="link.label"
|
|
61
|
+
class="nav-button"
|
|
62
|
+
:to="link.to"
|
|
63
|
+
@click="link.onClick?.()"
|
|
56
64
|
>
|
|
57
65
|
<Icon :icon="link.icon" />
|
|
58
66
|
<div class="tooltip">
|
|
@@ -64,8 +72,12 @@ onMounted(calcIsOpen)
|
|
|
64
72
|
|
|
65
73
|
<div class="bot-buttons-wrapper">
|
|
66
74
|
<component
|
|
67
|
-
:is="link.to ? 'router-link' : 'div'"
|
|
68
|
-
|
|
75
|
+
:is="link.to ? 'router-link' : 'div'"
|
|
76
|
+
v-for="link in footerLinks"
|
|
77
|
+
:key="link.label"
|
|
78
|
+
class="nav-button"
|
|
79
|
+
:to="link.to"
|
|
80
|
+
@click="link.onClick?.()"
|
|
69
81
|
>
|
|
70
82
|
<Icon :icon="link.icon" />
|
|
71
83
|
<div class="tooltip">
|
|
@@ -121,76 +121,46 @@ const showLoading = computed(() => loading.value)
|
|
|
121
121
|
</script>
|
|
122
122
|
|
|
123
123
|
<template>
|
|
124
|
-
<div
|
|
125
|
-
class="table-list-wrap h-100"
|
|
126
|
-
v-bind="containerProps"
|
|
127
|
-
:class="{ overflowHiddenLoading: showLoading }"
|
|
128
|
-
>
|
|
124
|
+
<div class="table-list-wrap h-100" v-bind="containerProps" :class="{ overflowHiddenLoading: showLoading }">
|
|
129
125
|
<Loading v-if="showLoading" class="h100p" />
|
|
130
126
|
<div v-bind="wrapperProps" :class="{ 'pointer-events-none': showLoading }">
|
|
131
|
-
<table class="infinite-wrapper">
|
|
127
|
+
<table class="infinite-wrapper" :class="{ selecting: computedSelectedItems.length }">
|
|
132
128
|
<thead class="row first-row">
|
|
133
129
|
<th v-if="isSelectable">
|
|
134
|
-
<input
|
|
135
|
-
ref="allSelectorEl"
|
|
136
|
-
type="checkbox"
|
|
137
|
-
@click.stop
|
|
138
|
-
@change="(e) => toggleSelectAll(e)"
|
|
139
|
-
>
|
|
130
|
+
<input ref="allSelectorEl" type="checkbox" @click.stop @change="(e) => toggleSelectAll(e)">
|
|
140
131
|
<!-- @change="toggleSelectAll" -->
|
|
141
132
|
</th>
|
|
142
133
|
<th
|
|
143
|
-
v-for="field in computedSchema"
|
|
144
|
-
:key="field.id"
|
|
145
|
-
class="col"
|
|
134
|
+
v-for="field in computedSchema" :key="field.id" class="col"
|
|
146
135
|
@click="toggleSort(field?.id || '')"
|
|
147
136
|
>
|
|
148
137
|
<div class="flex">
|
|
149
138
|
{{ field.label || keyToLabel(field?.id) }}
|
|
150
|
-
<div
|
|
151
|
-
class="
|
|
152
|
-
:class="{ sorted: sortField === field?.id }"
|
|
153
|
-
>
|
|
154
|
-
<Icon
|
|
155
|
-
:class="{ desc: sortDirection === 'DESC' }"
|
|
156
|
-
icon="keyboard_arrow_up"
|
|
157
|
-
/>
|
|
139
|
+
<div class="list-arrows" :class="{ sorted: sortField === field?.id }">
|
|
140
|
+
<Icon :class="{ desc: sortDirection === 'DESC' }" icon="keyboard_arrow_up" />
|
|
158
141
|
</div>
|
|
159
142
|
</div>
|
|
160
143
|
</th>
|
|
161
144
|
</thead>
|
|
162
145
|
<tbody>
|
|
163
146
|
<tr
|
|
164
|
-
v-for="{ data: row } in list"
|
|
165
|
-
:key="row?.id || `row-${Math.random()}`"
|
|
147
|
+
v-for="{ data: row } in list" :key="row?.id || `row-${Math.random()}`"
|
|
166
148
|
class="row row-item position-relative"
|
|
167
149
|
:class="{ selected: row?.id && computedSelectedItems.includes(row.id) }"
|
|
168
|
-
@click="toggleSelectItem(row)"
|
|
150
|
+
@click="(event) => toggleSelectItem(row, event)"
|
|
169
151
|
>
|
|
170
152
|
<td v-if="isSelectable">
|
|
171
153
|
<div @click.stop>
|
|
172
|
-
<input
|
|
173
|
-
v-model="selectedItems"
|
|
174
|
-
type="checkbox"
|
|
175
|
-
:value="row?.id || ''"
|
|
176
|
-
>
|
|
154
|
+
<input v-model="selectedItems" type="checkbox" :value="row?.id || ''">
|
|
177
155
|
</div>
|
|
178
156
|
</td>
|
|
179
157
|
<td
|
|
180
|
-
v-for="field in computedSchema"
|
|
181
|
-
:key="`${field.id}-${row?.id || Math.random()}`"
|
|
158
|
+
v-for="field in computedSchema" :key="`${field.id}-${row?.id || Math.random()}`"
|
|
182
159
|
class="col"
|
|
183
160
|
>
|
|
184
|
-
<slot
|
|
185
|
-
v-if="field.id && slots?.[field.id]"
|
|
186
|
-
:name="field.id"
|
|
187
|
-
:row="row"
|
|
188
|
-
:field="field"
|
|
189
|
-
/>
|
|
161
|
+
<slot v-if="field.id && slots?.[field.id]" :name="field.id" :row="row" :field="field" />
|
|
190
162
|
<div v-else>
|
|
191
|
-
<component
|
|
192
|
-
:is="renderFieldForRow(field, row)"
|
|
193
|
-
/>
|
|
163
|
+
<component :is="renderFieldForRow(field, row)" />
|
|
194
164
|
</div>
|
|
195
165
|
</td>
|
|
196
166
|
</tr>
|
|
@@ -233,13 +203,19 @@ tbody tr.selected:hover {
|
|
|
233
203
|
border-radius: 5px;
|
|
234
204
|
object-fit: cover;
|
|
235
205
|
}
|
|
206
|
+
|
|
236
207
|
.col:has(img) {
|
|
237
208
|
padding-inline-end: 0.5rem;
|
|
238
209
|
}
|
|
239
210
|
|
|
240
|
-
.
|
|
211
|
+
.selecting .col {
|
|
212
|
+
user-select: none;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
.overflowHiddenLoading {
|
|
241
216
|
overflow: hidden !important;
|
|
242
217
|
}
|
|
218
|
+
|
|
243
219
|
.list-arrows.sorted .desc {
|
|
244
220
|
transform: rotate(180deg);
|
|
245
221
|
display: inline-block;
|
|
@@ -313,7 +289,7 @@ th {
|
|
|
313
289
|
padding: 0rem 0.25rem;
|
|
314
290
|
}
|
|
315
291
|
|
|
316
|
-
.col
|
|
292
|
+
.col>div {
|
|
317
293
|
display: flex;
|
|
318
294
|
gap: 0.5rem;
|
|
319
295
|
}
|
|
@@ -9,6 +9,7 @@ export interface UseTableSelectionOptions<T> extends TableSelectionOptions<T> {
|
|
|
9
9
|
|
|
10
10
|
export function useTableSelection<T extends { [key: string]: any }>(options: UseTableSelectionOptions<T>) {
|
|
11
11
|
const allSelectorEl = ref<HTMLInputElement>()
|
|
12
|
+
const lastSelectedIndex = ref<number | null>(null)
|
|
12
13
|
const computedSelectedItems = computed(() => options.selectedItems.value)
|
|
13
14
|
const isSelectable = computed(() => options.selectable === true && Array.isArray(options.selectedItems.value))
|
|
14
15
|
const allItems = $computed(() => options.data.value)
|
|
@@ -39,7 +40,37 @@ export function useTableSelection<T extends { [key: string]: any }>(options: Use
|
|
|
39
40
|
= !allSelected && options.selectedItems.value.length > 0
|
|
40
41
|
}
|
|
41
42
|
|
|
42
|
-
function toggleSelectItem(item: T) {
|
|
43
|
+
function toggleSelectItem(item: T, event?: MouseEvent) {
|
|
44
|
+
const currentIndex = allItems.findIndex(i => i.id === item.id)
|
|
45
|
+
|
|
46
|
+
// Handle Shift+click for range selection
|
|
47
|
+
if (event?.shiftKey && lastSelectedIndex.value !== null && currentIndex !== -1) {
|
|
48
|
+
const startIndex = Math.min(lastSelectedIndex.value, currentIndex)
|
|
49
|
+
const endIndex = Math.max(lastSelectedIndex.value, currentIndex)
|
|
50
|
+
|
|
51
|
+
// Get all items in the range
|
|
52
|
+
const rangeItems = allItems.slice(startIndex, endIndex + 1)
|
|
53
|
+
const rangeItemIds = rangeItems.map(i => i.id)
|
|
54
|
+
|
|
55
|
+
// Check if all items in range are currently selected
|
|
56
|
+
const allRangeSelected = rangeItemIds.every(id => options.selectedItems.value.includes(id)
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
if (allRangeSelected) {
|
|
60
|
+
// Deselect all items in range
|
|
61
|
+
options.selectedItems.value = options.selectedItems.value.filter(id => !rangeItemIds.includes(id)
|
|
62
|
+
)
|
|
63
|
+
} else {
|
|
64
|
+
// Select all items in range (add missing ones)
|
|
65
|
+
const newSelections = rangeItemIds.filter(id => !options.selectedItems.value.includes(id)
|
|
66
|
+
)
|
|
67
|
+
options.selectedItems.value.push(...newSelections)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Regular single item selection logic
|
|
43
74
|
if (computedSelectedItems.value.length === 0) {
|
|
44
75
|
// Clean the item if a cleanData function is provided, otherwise use the default cleaning
|
|
45
76
|
const cleanItem = options.cleanData ? options.cleanData(item) : { ...item }
|
|
@@ -54,14 +85,19 @@ export function useTableSelection<T extends { [key: string]: any }>(options: Use
|
|
|
54
85
|
}
|
|
55
86
|
|
|
56
87
|
options.onSelect(cleanItem)
|
|
88
|
+
lastSelectedIndex.value = currentIndex
|
|
57
89
|
return
|
|
58
90
|
}
|
|
91
|
+
|
|
59
92
|
const index = computedSelectedItems.value.indexOf(item.id)
|
|
60
93
|
if (index > -1) {
|
|
61
94
|
options.selectedItems.value.splice(index, 1)
|
|
62
95
|
} else {
|
|
63
96
|
options.selectedItems.value.push(item.id)
|
|
64
97
|
}
|
|
98
|
+
|
|
99
|
+
// Update last selected index for future range selections
|
|
100
|
+
lastSelectedIndex.value = currentIndex
|
|
65
101
|
}
|
|
66
102
|
|
|
67
103
|
// Update toggleSelectAll to pass allItems to updateAllSelectorState
|
|
@@ -73,6 +109,9 @@ export function useTableSelection<T extends { [key: string]: any }>(options: Use
|
|
|
73
109
|
} else {
|
|
74
110
|
options.selectedItems.value = []
|
|
75
111
|
}
|
|
112
|
+
|
|
113
|
+
// Reset last selected index when selecting/deselecting all
|
|
114
|
+
lastSelectedIndex.value = null
|
|
76
115
|
}
|
|
77
116
|
|
|
78
117
|
return {
|
|
@@ -7,8 +7,6 @@ export function useCommands(state: EditorState, debug?: { logCommand: (command:
|
|
|
7
7
|
|
|
8
8
|
return {
|
|
9
9
|
execute: (command: string, value?: string) => {
|
|
10
|
-
console.log(`[useCommands] Executing command: ${command}`, value ? `with value: ${value}` : '')
|
|
11
|
-
|
|
12
10
|
if (!state.doc) {
|
|
13
11
|
console.log('[useCommands] No document available, skipping command')
|
|
14
12
|
return
|
|
@@ -17,9 +15,8 @@ export function useCommands(state: EditorState, debug?: { logCommand: (command:
|
|
|
17
15
|
// Log command if debug is enabled
|
|
18
16
|
debug?.logCommand(command, value)
|
|
19
17
|
|
|
20
|
-
// Handle view state commands directly
|
|
18
|
+
// Handle view state commands directly (these don't need DOM manipulation)
|
|
21
19
|
if (['splitView', 'codeView', 'fullScreen'].includes(command)) {
|
|
22
|
-
console.log(`[useCommands] Handling view state command: ${command}`)
|
|
23
20
|
switch (command) {
|
|
24
21
|
case 'splitView':
|
|
25
22
|
state.isSplitView = !state.isSplitView
|
|
@@ -36,41 +33,24 @@ export function useCommands(state: EditorState, debug?: { logCommand: (command:
|
|
|
36
33
|
// Focus the editor before executing command
|
|
37
34
|
try {
|
|
38
35
|
state.doc.body.focus()
|
|
39
|
-
console.log('[useCommands] Editor focused')
|
|
40
36
|
} catch (e) {
|
|
41
37
|
console.error('[useCommands] Error focusing editor:', e)
|
|
42
38
|
}
|
|
43
39
|
|
|
40
|
+
// Update current selection state before command execution
|
|
41
|
+
const selection = state.doc.getSelection()
|
|
42
|
+
if (selection?.rangeCount) {
|
|
43
|
+
state.selection = selection
|
|
44
|
+
state.range = selection.getRangeAt(0).cloneRange()
|
|
45
|
+
state.rangeCount = selection.rangeCount
|
|
46
|
+
}
|
|
47
|
+
|
|
44
48
|
// Execute the command
|
|
45
49
|
try {
|
|
46
|
-
console.log('[useCommands] Executing command via executor')
|
|
47
50
|
executor.execute(command, value)
|
|
48
|
-
console.log('[useCommands] Command execution completed')
|
|
49
51
|
} catch (e) {
|
|
50
52
|
console.error('[useCommands] Error during command execution:', e)
|
|
51
53
|
}
|
|
52
|
-
|
|
53
|
-
// Update content state only if it has changed
|
|
54
|
-
const newContent = state.doc.body.innerHTML
|
|
55
|
-
if (newContent !== state.content) {
|
|
56
|
-
console.log('[useCommands] Content changed, updating state')
|
|
57
|
-
state.content = newContent
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
// Update selection state if needed
|
|
61
|
-
const selection = state.doc.getSelection()
|
|
62
|
-
if (selection?.rangeCount) {
|
|
63
|
-
const hasSelectionChanged = !state.selection
|
|
64
|
-
|| state.selection !== selection
|
|
65
|
-
|| state.rangeCount !== selection.rangeCount
|
|
66
|
-
|
|
67
|
-
if (hasSelectionChanged) {
|
|
68
|
-
console.log('[useCommands] Selection changed, updating state')
|
|
69
|
-
state.selection = selection
|
|
70
|
-
state.range = selection.getRangeAt(0).cloneRange()
|
|
71
|
-
state.rangeCount = selection.rangeCount
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
54
|
},
|
|
75
55
|
isActive: executor.isActive,
|
|
76
56
|
getValue: executor.getValue
|
|
@@ -70,10 +70,9 @@ export function useEditor() {
|
|
|
70
70
|
'h5',
|
|
71
71
|
'h6',
|
|
72
72
|
'blockquote',
|
|
73
|
-
'table',
|
|
74
73
|
'p',
|
|
75
|
-
'
|
|
76
|
-
'
|
|
74
|
+
'orderedList',
|
|
75
|
+
'unorderedList'
|
|
77
76
|
]
|
|
78
77
|
styleTypes.forEach((style) => {
|
|
79
78
|
if (state.doc && isStyleActive(style, state.doc)) {
|
|
@@ -209,44 +208,28 @@ export function useEditor() {
|
|
|
209
208
|
const isDirectChildOfBody = node.parentElement === doc.body
|
|
210
209
|
|
|
211
210
|
if (isEmpty || hasOnlyNbsp || (hasOnlyBr && !isDirectChildOfBody)) {
|
|
212
|
-
|
|
213
|
-
const p = doc.createElement('p')
|
|
214
|
-
p.innerHTML = '<br>'
|
|
215
|
-
node.parentNode?.replaceChild(p, node)
|
|
216
|
-
} else {
|
|
217
|
-
nodesToRemove.push(node)
|
|
218
|
-
}
|
|
211
|
+
nodesToRemove.push(node)
|
|
219
212
|
}
|
|
220
213
|
node = walker.nextNode() as Element
|
|
221
214
|
}
|
|
222
215
|
nodesToRemove.forEach((node) => { node.remove() })
|
|
223
216
|
},
|
|
224
217
|
normalizeContent: (doc: Document) => {
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
const textNodes: Text[] = []
|
|
233
|
-
let node: Node | null
|
|
234
|
-
while ((node = walker.nextNode())) {
|
|
235
|
-
if (node.parentElement === doc.body) {
|
|
236
|
-
textNodes.push(node as Text)
|
|
237
|
-
}
|
|
218
|
+
// Only normalize direct text nodes to paragraphs, don't force any structure
|
|
219
|
+
const walker = doc.createTreeWalker(doc.body, NodeFilter.SHOW_TEXT)
|
|
220
|
+
const textNodes: Text[] = []
|
|
221
|
+
let node: Node | null
|
|
222
|
+
while ((node = walker.nextNode())) {
|
|
223
|
+
if (node.parentElement === doc.body && node.textContent?.trim()) {
|
|
224
|
+
textNodes.push(node as Text)
|
|
238
225
|
}
|
|
239
|
-
textNodes.forEach((textNode) => {
|
|
240
|
-
if (textNode.textContent?.trim()) {
|
|
241
|
-
const p = doc.createElement('p')
|
|
242
|
-
p.dir = doc.body.dir
|
|
243
|
-
p.appendChild(textNode.cloneNode())
|
|
244
|
-
doc.body.replaceChild(p, textNode)
|
|
245
|
-
} else {
|
|
246
|
-
doc.body.removeChild(textNode)
|
|
247
|
-
}
|
|
248
|
-
})
|
|
249
226
|
}
|
|
227
|
+
textNodes.forEach((textNode) => {
|
|
228
|
+
const p = doc.createElement('p')
|
|
229
|
+
p.dir = doc.body.dir
|
|
230
|
+
p.appendChild(textNode.cloneNode())
|
|
231
|
+
doc.body.replaceChild(p, textNode)
|
|
232
|
+
})
|
|
250
233
|
}
|
|
251
234
|
}
|
|
252
235
|
|
|
@@ -320,8 +303,9 @@ export function useEditor() {
|
|
|
320
303
|
// Store state reference in document for table operations
|
|
321
304
|
(doc as any).editorState = state
|
|
322
305
|
|
|
323
|
-
// Initial setup without triggering updates
|
|
324
|
-
if (state.content)
|
|
306
|
+
// Initial setup without triggering updates - only set content if it exists and is not just empty paragraphs
|
|
307
|
+
if (state.content && state.content.trim()
|
|
308
|
+
&& !state.content.match(/^<p(?:\s[^>]*)?>(?:<br\s*\/?>)?\s*<\/p>$/i)) {
|
|
325
309
|
const preserved = preserveIframes(state.content)
|
|
326
310
|
doc.body.innerHTML = preserved.html
|
|
327
311
|
setTimeout(() => {
|
|
@@ -333,17 +317,32 @@ export function useEditor() {
|
|
|
333
317
|
|
|
334
318
|
cleanup.normalizeContent(doc)
|
|
335
319
|
|
|
336
|
-
// Set initial selection
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
320
|
+
// Set initial selection only if there's content
|
|
321
|
+
if (doc.body.firstElementChild) {
|
|
322
|
+
const range = doc.createRange()
|
|
323
|
+
const selection = doc.getSelection()
|
|
324
|
+
if (selection) {
|
|
325
|
+
range.selectNodeContents(doc.body)
|
|
326
|
+
range.collapse(false)
|
|
327
|
+
selection.removeAllRanges()
|
|
328
|
+
selection.addRange(range)
|
|
329
|
+
state.range = range.cloneRange()
|
|
330
|
+
state.selection = selection
|
|
331
|
+
state.rangeCount = selection.rangeCount
|
|
332
|
+
}
|
|
333
|
+
} else {
|
|
334
|
+
// For empty editor, set cursor at the beginning of body
|
|
335
|
+
const selection = doc.getSelection()
|
|
336
|
+
if (selection) {
|
|
337
|
+
const range = doc.createRange()
|
|
338
|
+
range.setStart(doc.body, 0)
|
|
339
|
+
range.setEnd(doc.body, 0)
|
|
340
|
+
selection.removeAllRanges()
|
|
341
|
+
selection.addRange(range)
|
|
342
|
+
state.range = range.cloneRange()
|
|
343
|
+
state.selection = selection
|
|
344
|
+
state.rangeCount = selection.rangeCount
|
|
345
|
+
}
|
|
347
346
|
}
|
|
348
347
|
|
|
349
348
|
// Setup event listeners immediately
|
|
@@ -31,7 +31,7 @@ const shortcuts: KeyboardShortcut[] = [
|
|
|
31
31
|
export function useEditorKeyboard(doc: Document, executor: CommandExecutor): void {
|
|
32
32
|
// Handle keyboard shortcuts
|
|
33
33
|
doc.addEventListener('keydown', (e) => {
|
|
34
|
-
// Handle Enter key in lists
|
|
34
|
+
// Handle Enter key in lists and empty editor
|
|
35
35
|
if (e.key === 'Enter' && !e.shiftKey) {
|
|
36
36
|
const selection = doc.getSelection()
|
|
37
37
|
if (!selection || !selection.rangeCount) return
|
|
@@ -56,7 +56,7 @@ export function useEditorKeyboard(doc: Document, executor: CommandExecutor): voi
|
|
|
56
56
|
// If the list is now empty, remove it
|
|
57
57
|
if (!list.querySelector('li')) {
|
|
58
58
|
const p = doc.createElement('p')
|
|
59
|
-
p.innerHTML = '
|
|
59
|
+
p.innerHTML = ''
|
|
60
60
|
list.parentNode?.replaceChild(p, list)
|
|
61
61
|
|
|
62
62
|
// Set cursor in the new paragraph
|
|
@@ -69,7 +69,7 @@ export function useEditorKeyboard(doc: Document, executor: CommandExecutor): voi
|
|
|
69
69
|
// Create a new list item
|
|
70
70
|
e.preventDefault()
|
|
71
71
|
const newLi = doc.createElement('li')
|
|
72
|
-
newLi.innerHTML = '
|
|
72
|
+
newLi.innerHTML = '' // Start with empty list item
|
|
73
73
|
listItem.insertAdjacentElement('afterend', newLi)
|
|
74
74
|
|
|
75
75
|
// Move cursor to new list item
|
|
@@ -79,6 +79,55 @@ export function useEditorKeyboard(doc: Document, executor: CommandExecutor): voi
|
|
|
79
79
|
selection.addRange(range)
|
|
80
80
|
}
|
|
81
81
|
}
|
|
82
|
+
} else {
|
|
83
|
+
// Handle Enter in regular content - create new paragraph
|
|
84
|
+
const blockElement = (container.nodeType === 3 ? container.parentElement : container as Element)?.closest('p,h1,h2,h3,h4,h5,h6,blockquote,div')
|
|
85
|
+
|
|
86
|
+
if (blockElement && range.collapsed) {
|
|
87
|
+
// If we're at the end of a block element, create a new paragraph
|
|
88
|
+
if (isAtEndOfNode(blockElement, range)) {
|
|
89
|
+
e.preventDefault()
|
|
90
|
+
const newP = doc.createElement('p')
|
|
91
|
+
newP.innerHTML = ''
|
|
92
|
+
blockElement.insertAdjacentElement('afterend', newP)
|
|
93
|
+
|
|
94
|
+
// Move cursor to new paragraph
|
|
95
|
+
range.selectNodeContents(newP)
|
|
96
|
+
range.collapse(true)
|
|
97
|
+
selection.removeAllRanges()
|
|
98
|
+
selection.addRange(range)
|
|
99
|
+
}
|
|
100
|
+
} else if (!blockElement && doc.body.textContent?.trim()) {
|
|
101
|
+
// If we're typing directly in the body and press Enter, wrap in paragraphs
|
|
102
|
+
e.preventDefault()
|
|
103
|
+
|
|
104
|
+
// Split content at cursor position
|
|
105
|
+
const textContent = doc.body.textContent || ''
|
|
106
|
+
const cursorPos = range.startOffset
|
|
107
|
+
const beforeText = textContent.substring(0, cursorPos).trim()
|
|
108
|
+
const afterText = textContent.substring(cursorPos).trim()
|
|
109
|
+
|
|
110
|
+
// Clear body
|
|
111
|
+
doc.body.innerHTML = ''
|
|
112
|
+
|
|
113
|
+
// Create first paragraph with before text
|
|
114
|
+
if (beforeText) {
|
|
115
|
+
const p1 = doc.createElement('p')
|
|
116
|
+
p1.textContent = beforeText
|
|
117
|
+
doc.body.appendChild(p1)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Create second paragraph for after text
|
|
121
|
+
const p2 = doc.createElement('p')
|
|
122
|
+
p2.textContent = afterText
|
|
123
|
+
doc.body.appendChild(p2)
|
|
124
|
+
|
|
125
|
+
// Set cursor at beginning of second paragraph
|
|
126
|
+
range.selectNodeContents(p2)
|
|
127
|
+
range.collapse(true)
|
|
128
|
+
selection.removeAllRanges()
|
|
129
|
+
selection.addRange(range)
|
|
130
|
+
}
|
|
82
131
|
}
|
|
83
132
|
}
|
|
84
133
|
|