@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
|
@@ -44,6 +44,107 @@ onUnmounted(() => {
|
|
|
44
44
|
editor.cleanup()
|
|
45
45
|
})
|
|
46
46
|
|
|
47
|
+
function setupAutoWrapping(doc: Document) {
|
|
48
|
+
// Handle typing in empty editor
|
|
49
|
+
doc.addEventListener('input', () => {
|
|
50
|
+
// If the editor is empty or only has whitespace, wrap content in a paragraph
|
|
51
|
+
if (!doc.body.firstElementChild
|
|
52
|
+
|| (!doc.body.textContent?.trim() && !doc.body.querySelector('p,h1,h2,h3,h4,h5,h6,blockquote,ul,ol,table'))) {
|
|
53
|
+
// If there's any text content, wrap it in a paragraph
|
|
54
|
+
if (doc.body.textContent?.trim()) {
|
|
55
|
+
const p = doc.createElement('p')
|
|
56
|
+
p.dir = doc.body.dir
|
|
57
|
+
|
|
58
|
+
// Move all content to the paragraph
|
|
59
|
+
while (doc.body.firstChild) {
|
|
60
|
+
p.appendChild(doc.body.firstChild)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
doc.body.appendChild(p)
|
|
64
|
+
|
|
65
|
+
// Set cursor at the end of the paragraph
|
|
66
|
+
const selection = doc.getSelection()
|
|
67
|
+
if (selection) {
|
|
68
|
+
const range = doc.createRange()
|
|
69
|
+
range.selectNodeContents(p)
|
|
70
|
+
range.collapse(false)
|
|
71
|
+
selection.removeAllRanges()
|
|
72
|
+
selection.addRange(range)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Update editor state
|
|
76
|
+
editor.state.content = doc.body.innerHTML
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
// Handle backspace to prevent completely empty editor
|
|
82
|
+
doc.addEventListener('keydown', (e) => {
|
|
83
|
+
if (e.key === 'Backspace') {
|
|
84
|
+
// Check if this would make the editor completely empty
|
|
85
|
+
const selection = doc.getSelection()
|
|
86
|
+
if (!selection || !selection.rangeCount) return
|
|
87
|
+
|
|
88
|
+
const range = selection.getRangeAt(0)
|
|
89
|
+
|
|
90
|
+
// If we're about to delete the last block element
|
|
91
|
+
const blockElements = doc.body.querySelectorAll('p,h1,h2,h3,h4,h5,h6,blockquote,li,div')
|
|
92
|
+
if (blockElements.length <= 1) {
|
|
93
|
+
const lastBlock = blockElements[0]
|
|
94
|
+
|
|
95
|
+
// If we're in the last block and it's about to be empty
|
|
96
|
+
if (lastBlock && (
|
|
97
|
+
(lastBlock.textContent?.trim() === '' && range.collapsed)
|
|
98
|
+
|| (range.startContainer === lastBlock && range.endContainer === lastBlock
|
|
99
|
+
&& range.startOffset === 0 && range.endOffset === lastBlock.childNodes.length)
|
|
100
|
+
)) {
|
|
101
|
+
e.preventDefault()
|
|
102
|
+
|
|
103
|
+
// Clear the content but keep the block structure
|
|
104
|
+
lastBlock.innerHTML = ''
|
|
105
|
+
|
|
106
|
+
// Set cursor in the empty block
|
|
107
|
+
const newRange = doc.createRange()
|
|
108
|
+
newRange.selectNodeContents(lastBlock)
|
|
109
|
+
newRange.collapse(true)
|
|
110
|
+
selection.removeAllRanges()
|
|
111
|
+
selection.addRange(newRange)
|
|
112
|
+
|
|
113
|
+
// Update editor state
|
|
114
|
+
editor.state.content = doc.body.innerHTML
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
// Handle paste events to ensure content is properly wrapped
|
|
121
|
+
doc.addEventListener('paste', (e) => {
|
|
122
|
+
setTimeout(() => {
|
|
123
|
+
// After paste, ensure any unwrapped text gets wrapped in paragraphs
|
|
124
|
+
const walker = doc.createTreeWalker(doc.body, NodeFilter.SHOW_TEXT)
|
|
125
|
+
const textNodes: Text[] = []
|
|
126
|
+
let node: Node | null
|
|
127
|
+
while ((node = walker.nextNode())) {
|
|
128
|
+
if (node.parentElement === doc.body && node.textContent?.trim()) {
|
|
129
|
+
textNodes.push(node as Text)
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
textNodes.forEach((textNode) => {
|
|
134
|
+
const p = doc.createElement('p')
|
|
135
|
+
p.dir = doc.body.dir
|
|
136
|
+
p.appendChild(textNode.cloneNode())
|
|
137
|
+
doc.body.replaceChild(p, textNode)
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
// Update editor state if changes were made
|
|
141
|
+
if (textNodes.length > 0) {
|
|
142
|
+
editor.state.content = doc.body.innerHTML
|
|
143
|
+
}
|
|
144
|
+
}, 0)
|
|
145
|
+
})
|
|
146
|
+
}
|
|
147
|
+
|
|
47
148
|
async function initEditor() {
|
|
48
149
|
if (isInitializing.value || !iframe.value || hasInitialized.value) {
|
|
49
150
|
return
|
|
@@ -103,37 +204,31 @@ async function initEditor() {
|
|
|
103
204
|
editor.init(doc)
|
|
104
205
|
useEditorKeyboard(doc, commands)
|
|
105
206
|
|
|
106
|
-
//
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
doc.body
|
|
112
|
-
|
|
113
|
-
editor.state.content = doc.body.innerHTML
|
|
114
|
-
} else {
|
|
115
|
-
// Convert any direct text nodes to paragraphs
|
|
116
|
-
const walker = doc.createTreeWalker(doc.body, NodeFilter.SHOW_TEXT)
|
|
117
|
-
const textNodes: Text[] = []
|
|
118
|
-
let node: Node | null
|
|
119
|
-
while ((node = walker.nextNode())) {
|
|
120
|
-
if (node.parentElement === doc.body) {
|
|
121
|
-
textNodes.push(node as Text)
|
|
122
|
-
}
|
|
207
|
+
// Clean up any existing content and convert direct text nodes to paragraphs
|
|
208
|
+
const walker = doc.createTreeWalker(doc.body, NodeFilter.SHOW_TEXT)
|
|
209
|
+
const textNodes: Text[] = []
|
|
210
|
+
let node: Node | null
|
|
211
|
+
while ((node = walker.nextNode())) {
|
|
212
|
+
if (node.parentElement === doc.body) {
|
|
213
|
+
textNodes.push(node as Text)
|
|
123
214
|
}
|
|
124
|
-
textNodes.forEach((textNode) => {
|
|
125
|
-
if (textNode.textContent?.trim()) {
|
|
126
|
-
const p = doc.createElement('p')
|
|
127
|
-
p.dir = doc.body.dir
|
|
128
|
-
p.appendChild(textNode.cloneNode())
|
|
129
|
-
doc.body.replaceChild(p, textNode)
|
|
130
|
-
} else {
|
|
131
|
-
doc.body.removeChild(textNode)
|
|
132
|
-
}
|
|
133
|
-
})
|
|
134
|
-
// Update state.content after cleanup
|
|
135
|
-
editor.state.content = doc.body.innerHTML
|
|
136
215
|
}
|
|
216
|
+
textNodes.forEach((textNode) => {
|
|
217
|
+
if (textNode.textContent?.trim()) {
|
|
218
|
+
const p = doc.createElement('p')
|
|
219
|
+
p.dir = doc.body.dir
|
|
220
|
+
p.appendChild(textNode.cloneNode())
|
|
221
|
+
doc.body.replaceChild(p, textNode)
|
|
222
|
+
} else {
|
|
223
|
+
doc.body.removeChild(textNode)
|
|
224
|
+
}
|
|
225
|
+
})
|
|
226
|
+
|
|
227
|
+
// Setup auto-wrapping for typed content
|
|
228
|
+
setupAutoWrapping(doc)
|
|
229
|
+
|
|
230
|
+
// Update state.content after cleanup
|
|
231
|
+
editor.state.content = doc.body.innerHTML
|
|
137
232
|
|
|
138
233
|
doc.body.focus()
|
|
139
234
|
hasInitialized.value = true
|
|
@@ -163,32 +258,39 @@ watch(() => editor.state.content, (newValue) => {
|
|
|
163
258
|
}
|
|
164
259
|
emit('update:modelValue', newValue)
|
|
165
260
|
})
|
|
261
|
+
|
|
262
|
+
// Expose for testing
|
|
263
|
+
defineExpose({
|
|
264
|
+
editor,
|
|
265
|
+
commands
|
|
266
|
+
})
|
|
166
267
|
</script>
|
|
167
268
|
|
|
168
269
|
<template>
|
|
169
270
|
<div class="bagel-input">
|
|
170
271
|
<label>{{ label }}</label>
|
|
171
|
-
|
|
272
|
+
|
|
273
|
+
<div
|
|
274
|
+
class="rich-text-editor rounded pt-05 px-05 pb-075"
|
|
275
|
+
:class="{ 'fullscreen-mode': editor.state.isFullscreen }"
|
|
276
|
+
>
|
|
172
277
|
<EditorToolbar
|
|
173
|
-
v-if="editor.state.hasInit" :config="toolbarConfig"
|
|
174
|
-
@action="commands.execute"
|
|
278
|
+
v-if="editor.state.hasInit" :config="toolbarConfig"
|
|
279
|
+
:selectedStyles="editor.state.selectedStyles" @action="commands.execute"
|
|
175
280
|
/>
|
|
176
281
|
<div class="editor-container" :class="{ 'split-view': editor.state.isSplitView }">
|
|
177
|
-
<div
|
|
282
|
+
<div
|
|
283
|
+
class="content-area radius-05"
|
|
284
|
+
:style="{ height: editor.state.isFullscreen ? 'calc(100vh - 4rem)' : editorHeight }"
|
|
285
|
+
>
|
|
178
286
|
<iframe
|
|
179
|
-
id="rich-text-iframe"
|
|
180
|
-
ref="iframe"
|
|
181
|
-
class="editableContent"
|
|
182
|
-
title="Editor"
|
|
183
|
-
srcdoc=""
|
|
287
|
+
id="rich-text-iframe" ref="iframe" class="editableContent" title="Editor" srcdoc=""
|
|
184
288
|
sandbox="allow-same-origin allow-forms allow-modals allow-orientation-lock allow-pointer-lock allow-popups allow-popups-to-escape-sandbox allow-presentation allow-scripts allow-top-navigation allow-top-navigation-by-user-activation"
|
|
185
289
|
@load="initEditor"
|
|
186
290
|
/>
|
|
187
291
|
</div>
|
|
188
292
|
<CodeEditor
|
|
189
|
-
v-if="editor.state.isSplitView"
|
|
190
|
-
v-model="editor.state.content"
|
|
191
|
-
language="html"
|
|
293
|
+
v-if="editor.state.isSplitView" v-model="editor.state.content" language="html"
|
|
192
294
|
:height="editor.state.isFullscreen ? 'calc(100vh - 4rem)' : editorHeight"
|
|
193
295
|
@update:modelValue="editor.updateState.content('html')"
|
|
194
296
|
/>
|
|
@@ -203,7 +305,10 @@ watch(() => editor.state.content, (newValue) => {
|
|
|
203
305
|
<Btn thin color="gray" icon="download" @click="debugMethods?.downloadSession">
|
|
204
306
|
Download Log
|
|
205
307
|
</Btn>
|
|
206
|
-
<Btn
|
|
308
|
+
<Btn
|
|
309
|
+
thin color="gray" icon="content_copy"
|
|
310
|
+
@click="copyText(debugMethods?.exportDebugWithPrompt() || '')"
|
|
311
|
+
>
|
|
207
312
|
Copy Log
|
|
208
313
|
</Btn>
|
|
209
314
|
</div>
|
|
@@ -214,7 +319,7 @@ watch(() => editor.state.content, (newValue) => {
|
|
|
214
319
|
<style>
|
|
215
320
|
.content-area p,
|
|
216
321
|
.content-area span,
|
|
217
|
-
.content-area li{
|
|
322
|
+
.content-area li {
|
|
218
323
|
line-height: 1.65;
|
|
219
324
|
}
|
|
220
325
|
</style>
|