@bagelink/vue 1.4.139 → 1.4.141
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/RichText/components/EditorToolbar.vue.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/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/richTextTypes.d.ts +1 -1
- package/dist/components/form/inputs/RichText/richTextTypes.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/formatting.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 +19 -19
- package/dist/style.css +1 -1
- package/package.json +1 -1
- package/src/components/dataTable/DataTable.vue +1 -1
- package/src/components/form/inputs/RichText/components/EditorToolbar.vue +11 -0
- package/src/components/form/inputs/RichText/composables/useCommands.ts +42 -0
- package/src/components/form/inputs/RichText/composables/useEditor.ts +8 -5
- package/src/components/form/inputs/RichText/composables/useEditorKeyboard.ts +2 -128
- package/src/components/form/inputs/RichText/config.ts +15 -4
- package/src/components/form/inputs/RichText/index.vue +275 -73
- package/src/components/form/inputs/RichText/richTextTypes.ts +5 -0
- package/src/components/form/inputs/RichText/utils/commands.ts +614 -82
- package/src/components/form/inputs/RichText/utils/formatting.ts +17 -15
- package/src/components/form/inputs/RichText/utils/selection.ts +32 -11
package/package.json
CHANGED
|
@@ -127,7 +127,7 @@ const showLoading = computed(() => loading.value)
|
|
|
127
127
|
@click="toggleSort(field?.id || '')"
|
|
128
128
|
>
|
|
129
129
|
<div class="flex">
|
|
130
|
-
{{ field.label || keyToLabel(field?.id) }}
|
|
130
|
+
{{ field.label || field.attrs?.label || keyToLabel(field?.id) }}
|
|
131
131
|
<div class="list-arrows" :class="{ sorted: sortField === field?.id }">
|
|
132
132
|
<Icon :class="{ desc: sortDirection === 'DESC' }" icon="keyboard_arrow_up" />
|
|
133
133
|
</div>
|
|
@@ -30,6 +30,17 @@ function runAction(name: ToolbarConfigOption, value?: string) {
|
|
|
30
30
|
/>
|
|
31
31
|
</template>
|
|
32
32
|
</Dropdown>
|
|
33
|
+
<Btn
|
|
34
|
+
v-else-if="action.name === 'textDirection'"
|
|
35
|
+
v-tooltip="action.label"
|
|
36
|
+
:icon="selectedStyles.has('textDirection') ? 'format_textdirection_l_to_r' : 'format_textdirection_r_to_l'"
|
|
37
|
+
thin flat
|
|
38
|
+
:aria-label="action.name"
|
|
39
|
+
:class="[action.class, { active: selectedStyles.has('textDirection') }]"
|
|
40
|
+
class=""
|
|
41
|
+
tabindex="-1"
|
|
42
|
+
@click="runAction(action.name)"
|
|
43
|
+
/>
|
|
33
44
|
<Btn
|
|
34
45
|
v-else-if="action.name !== 'separator'" v-tooltip="action.label" :icon="action.icon" thin flat
|
|
35
46
|
:aria-label="action.name" :class="[action.class, { active: selectedStyles.has(action.name) }]"
|
|
@@ -1,10 +1,46 @@
|
|
|
1
1
|
import type { EditorState } from '../richTextTypes'
|
|
2
2
|
import { createCommandExecutor, createCommandRegistry } from '../utils/commands'
|
|
3
|
+
import { isStyleActive } from '../utils/selection'
|
|
3
4
|
|
|
4
5
|
export function useCommands(state: EditorState, debug?: { logCommand: (command: string, value?: string) => void }) {
|
|
5
6
|
const commands = createCommandRegistry(state)
|
|
6
7
|
const executor = createCommandExecutor(state, commands)
|
|
7
8
|
|
|
9
|
+
// Function to immediately update styles
|
|
10
|
+
const updateStylesImmediately = () => {
|
|
11
|
+
if (!state.doc) return
|
|
12
|
+
|
|
13
|
+
const styles = new Set<string>()
|
|
14
|
+
const styleTypes = [
|
|
15
|
+
'bold',
|
|
16
|
+
'italic',
|
|
17
|
+
'underline',
|
|
18
|
+
'h1',
|
|
19
|
+
'h2',
|
|
20
|
+
'h3',
|
|
21
|
+
'h4',
|
|
22
|
+
'h5',
|
|
23
|
+
'h6',
|
|
24
|
+
'blockquote',
|
|
25
|
+
'p',
|
|
26
|
+
'orderedList',
|
|
27
|
+
'unorderedList',
|
|
28
|
+
'alignLeft',
|
|
29
|
+
'alignCenter',
|
|
30
|
+
'alignRight',
|
|
31
|
+
'alignJustify',
|
|
32
|
+
'textDirection'
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
styleTypes.forEach((style) => {
|
|
36
|
+
if (isStyleActive(style, state.doc!)) {
|
|
37
|
+
styles.add(style)
|
|
38
|
+
}
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
state.selectedStyles = styles
|
|
42
|
+
}
|
|
43
|
+
|
|
8
44
|
return {
|
|
9
45
|
execute: (command: string, value?: string) => {
|
|
10
46
|
if (!state.doc) {
|
|
@@ -48,6 +84,12 @@ export function useCommands(state: EditorState, debug?: { logCommand: (command:
|
|
|
48
84
|
// Execute the command
|
|
49
85
|
try {
|
|
50
86
|
executor.execute(command, value)
|
|
87
|
+
|
|
88
|
+
// Update styles immediately after command execution for all commands except view state
|
|
89
|
+
const viewCommands = ['splitView', 'codeView', 'fullScreen']
|
|
90
|
+
if (!viewCommands.includes(command)) {
|
|
91
|
+
updateStylesImmediately()
|
|
92
|
+
}
|
|
51
93
|
} catch (e) {
|
|
52
94
|
console.error('[useCommands] Error during command execution:', e)
|
|
53
95
|
}
|
|
@@ -72,7 +72,12 @@ export function useEditor() {
|
|
|
72
72
|
'blockquote',
|
|
73
73
|
'p',
|
|
74
74
|
'orderedList',
|
|
75
|
-
'unorderedList'
|
|
75
|
+
'unorderedList',
|
|
76
|
+
'alignLeft',
|
|
77
|
+
'alignCenter',
|
|
78
|
+
'alignRight',
|
|
79
|
+
'alignJustify',
|
|
80
|
+
'textDirection'
|
|
76
81
|
]
|
|
77
82
|
styleTypes.forEach((style) => {
|
|
78
83
|
if (state.doc && isStyleActive(style, state.doc)) {
|
|
@@ -151,10 +156,8 @@ export function useEditor() {
|
|
|
151
156
|
state.range = newSelection.getRangeAt(0).cloneRange()
|
|
152
157
|
}
|
|
153
158
|
|
|
154
|
-
// Update styles
|
|
155
|
-
|
|
156
|
-
updateState.styles()
|
|
157
|
-
})
|
|
159
|
+
// Update styles immediately for better responsiveness
|
|
160
|
+
updateState.styles()
|
|
158
161
|
}
|
|
159
162
|
} catch (e) {
|
|
160
163
|
state.selection = null
|
|
@@ -31,105 +31,8 @@ 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
|
-
//
|
|
35
|
-
|
|
36
|
-
const selection = doc.getSelection()
|
|
37
|
-
if (!selection || !selection.rangeCount) return
|
|
38
|
-
|
|
39
|
-
const range = selection.getRangeAt(0)
|
|
40
|
-
const container = range.commonAncestorContainer
|
|
41
|
-
const listItem = (container.nodeType === 3 ? container.parentElement : container as Element)?.closest('li')
|
|
42
|
-
|
|
43
|
-
if (listItem) {
|
|
44
|
-
// If we're at the end of a list item
|
|
45
|
-
if (range.collapsed && isAtEndOfNode(listItem, range)) {
|
|
46
|
-
// If the list item is empty, break out of the list
|
|
47
|
-
if (isNodeEmpty(listItem)) {
|
|
48
|
-
e.preventDefault()
|
|
49
|
-
// Create a new paragraph after the list
|
|
50
|
-
const list = listItem.parentElement
|
|
51
|
-
if (!list) return
|
|
52
|
-
|
|
53
|
-
// Remove the empty list item
|
|
54
|
-
listItem.remove()
|
|
55
|
-
|
|
56
|
-
// If the list is now empty, remove it
|
|
57
|
-
if (!list.querySelector('li')) {
|
|
58
|
-
const p = doc.createElement('p')
|
|
59
|
-
p.innerHTML = ''
|
|
60
|
-
list.parentNode?.replaceChild(p, list)
|
|
61
|
-
|
|
62
|
-
// Set cursor in the new paragraph
|
|
63
|
-
range.selectNodeContents(p)
|
|
64
|
-
range.collapse(true)
|
|
65
|
-
selection.removeAllRanges()
|
|
66
|
-
selection.addRange(range)
|
|
67
|
-
}
|
|
68
|
-
} else {
|
|
69
|
-
// Create a new list item
|
|
70
|
-
e.preventDefault()
|
|
71
|
-
const newLi = doc.createElement('li')
|
|
72
|
-
newLi.innerHTML = '' // Start with empty list item
|
|
73
|
-
listItem.insertAdjacentElement('afterend', newLi)
|
|
74
|
-
|
|
75
|
-
// Move cursor to new list item
|
|
76
|
-
range.selectNodeContents(newLi)
|
|
77
|
-
range.collapse(true)
|
|
78
|
-
selection.removeAllRanges()
|
|
79
|
-
selection.addRange(range)
|
|
80
|
-
}
|
|
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
|
-
}
|
|
131
|
-
}
|
|
132
|
-
}
|
|
34
|
+
// Remove Enter key handling from here - it's handled in setupAutoWrapping in index.vue
|
|
35
|
+
// This was causing conflicts with the main Enter key handler
|
|
133
36
|
|
|
134
37
|
// Handle other keyboard shortcuts
|
|
135
38
|
if (!e.ctrlKey && !e.metaKey) return
|
|
@@ -150,32 +53,3 @@ export function useEditorKeyboard(doc: Document, executor: CommandExecutor): voi
|
|
|
150
53
|
}
|
|
151
54
|
})
|
|
152
55
|
}
|
|
153
|
-
|
|
154
|
-
// Helper function to check if we're at the end of a node
|
|
155
|
-
function isAtEndOfNode(node: Node, range: Range): boolean {
|
|
156
|
-
if (node.nodeType === 3) { // Text node
|
|
157
|
-
return range.startOffset === (node as Text).length
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
const { lastChild } = node
|
|
161
|
-
if (!lastChild) return true
|
|
162
|
-
|
|
163
|
-
if (lastChild.nodeType === 3) { // Text node
|
|
164
|
-
return range.startContainer === lastChild && range.startOffset === lastChild.textContent?.length
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
return range.startContainer === node && range.startOffset === node.childNodes.length
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
// Helper function to check if a node is empty (contains only whitespace or <br> or )
|
|
171
|
-
function isNodeEmpty(node: Node): boolean {
|
|
172
|
-
const text = node.textContent?.replace(/\s/g, '') || '' // Remove non-breaking spaces and whitespace
|
|
173
|
-
if (text) return false
|
|
174
|
-
|
|
175
|
-
// Check for <br> tags
|
|
176
|
-
const brElements = (node as Element).getElementsByTagName('br')
|
|
177
|
-
if (brElements.length === 0) return true
|
|
178
|
-
|
|
179
|
-
// If there's only one <br> and it's the only content (besides potential ), consider it empty
|
|
180
|
-
return brElements.length === 1 && node.childNodes.length <= 2 // Allow for + <br>
|
|
181
|
-
}
|
|
@@ -9,10 +9,10 @@ export const tableTools: ToolbarConfig = [
|
|
|
9
9
|
'insertColumnLeft',
|
|
10
10
|
'insertColumnRight',
|
|
11
11
|
'deleteColumn',
|
|
12
|
-
'
|
|
13
|
-
'
|
|
14
|
-
'
|
|
15
|
-
'
|
|
12
|
+
'tableAlignLeft',
|
|
13
|
+
'tableAlignCenter',
|
|
14
|
+
'tableAlignRight',
|
|
15
|
+
'tableAlignJustify',
|
|
16
16
|
'deleteTable'
|
|
17
17
|
]
|
|
18
18
|
|
|
@@ -33,6 +33,12 @@ export const basicToolbarConfig: ToolbarConfig = [
|
|
|
33
33
|
'italic',
|
|
34
34
|
'underline',
|
|
35
35
|
'separator',
|
|
36
|
+
'alignLeft',
|
|
37
|
+
'alignCenter',
|
|
38
|
+
'alignRight',
|
|
39
|
+
'alignJustify',
|
|
40
|
+
'textDirection',
|
|
41
|
+
'separator',
|
|
36
42
|
'link',
|
|
37
43
|
'image',
|
|
38
44
|
'embed',
|
|
@@ -100,6 +106,10 @@ export const toolbarOptions: ToolbarOption[] = [
|
|
|
100
106
|
{ name: 'alignCenter', label: 'Align Center', icon: 'format_align_center' },
|
|
101
107
|
{ name: 'alignRight', label: 'Align Right', icon: 'format_align_right' },
|
|
102
108
|
{ name: 'alignJustify', label: 'Align Justify', icon: 'format_align_justify' },
|
|
109
|
+
{ name: 'tableAlignLeft', label: 'Table Align Left', icon: 'format_align_left' },
|
|
110
|
+
{ name: 'tableAlignCenter', label: 'Table Align Center', icon: 'format_align_center' },
|
|
111
|
+
{ name: 'tableAlignRight', label: 'Table Align Right', icon: 'format_align_right' },
|
|
112
|
+
{ name: 'tableAlignJustify', label: 'Table Align Justify', icon: 'format_align_justify' },
|
|
103
113
|
{ name: 'indent', label: 'Indent', icon: 'format_indent_increase' },
|
|
104
114
|
{ name: 'outdent', label: 'Outdent', icon: 'format_indent_decrease' },
|
|
105
115
|
{ name: 'fontColor', label: 'Font Color', icon: 'format_color_text' },
|
|
@@ -119,4 +129,5 @@ export const toolbarOptions: ToolbarOption[] = [
|
|
|
119
129
|
{ name: 'redo', label: 'Redo', icon: 'redo' },
|
|
120
130
|
{ name: 'separator' },
|
|
121
131
|
{ name: 'fullScreen', label: 'Full Screen', icon: 'fullscreen', class: 'ms-auto' },
|
|
132
|
+
{ name: 'textDirection', label: 'Text Direction (RTL/LTR)', icon: 'format_textdirection_r_to_l' },
|
|
122
133
|
]
|