@bagelink/vue 1.4.141 → 1.4.145
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/Btn.vue.d.ts.map +1 -1
- package/dist/components/Carousel.vue.d.ts +1 -1
- package/dist/components/Modal.vue.d.ts +3 -0
- package/dist/components/Modal.vue.d.ts.map +1 -1
- package/dist/components/Slider.vue.d.ts +1 -1
- package/dist/components/Slider.vue.d.ts.map +1 -1
- package/dist/components/analytics/BarChart.vue.d.ts +11 -3
- package/dist/components/analytics/BarChart.vue.d.ts.map +1 -1
- package/dist/components/analytics/LineChart.vue.d.ts +9 -0
- package/dist/components/analytics/LineChart.vue.d.ts.map +1 -1
- package/dist/components/analytics/PieChart.vue.d.ts +30 -2
- package/dist/components/analytics/PieChart.vue.d.ts.map +1 -1
- package/dist/components/form/inputs/RichText/components/EditorToolbar.vue.d.ts +8 -0
- package/dist/components/form/inputs/RichText/components/EditorToolbar.vue.d.ts.map +1 -1
- package/dist/components/form/inputs/RichText/components/TableGridSelector.vue.d.ts +9 -0
- package/dist/components/form/inputs/RichText/components/TableGridSelector.vue.d.ts.map +1 -0
- package/dist/components/form/inputs/RichText/composables/useCommands.d.ts.map +1 -1
- package/dist/components/form/inputs/RichText/composables/useEditor.d.ts +0 -14
- 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 +15 -15
- package/dist/components/form/inputs/RichText/index.vue.d.ts.map +1 -1
- package/dist/components/form/inputs/RichText/richTextTypes.d.ts +1 -3
- 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/media-clean.d.ts +2 -0
- package/dist/components/form/inputs/RichText/utils/media-clean.d.ts.map +1 -0
- package/dist/components/form/inputs/RichText/utils/media.d.ts +4 -4
- package/dist/components/form/inputs/RichText/utils/media.d.ts.map +1 -1
- package/dist/components/form/inputs/RichText/utils/selection.d.ts.map +1 -1
- package/dist/components/form/inputs/RichText/utils/table.d.ts +1 -1
- package/dist/components/form/inputs/RichText/utils/table.d.ts.map +1 -1
- package/dist/components/index.d.ts +1 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/layout/AppContent.vue.d.ts.map +1 -1
- package/dist/components/layout/AppLayout.vue.d.ts.map +1 -1
- package/dist/components/layout/AppSidebar.vue.d.ts.map +1 -1
- package/dist/index.cjs +123 -22
- package/dist/index.mjs +123 -22
- package/dist/style.css +1 -1
- package/package.json +1 -1
- package/src/components/Btn.vue +50 -42
- package/src/components/Modal.vue +49 -50
- package/src/components/analytics/BarChart.vue +118 -7
- package/src/components/analytics/KpiCard.vue +2 -2
- package/src/components/analytics/LineChart.vue +189 -105
- package/src/components/analytics/PieChart.vue +392 -49
- package/src/components/form/inputs/RichText/CheckList.md +23 -0
- package/src/components/form/inputs/RichText/components/EditorToolbar.vue +243 -38
- package/src/components/form/inputs/RichText/components/TableGridSelector.vue +94 -0
- package/src/components/form/inputs/RichText/composables/useCommands.ts +4 -1
- package/src/components/form/inputs/RichText/composables/useEditor.ts +6 -6
- package/src/components/form/inputs/RichText/composables/useEditorKeyboard.ts +1 -0
- package/src/components/form/inputs/RichText/config.ts +23 -11
- package/src/components/form/inputs/RichText/editor.css +300 -33
- package/src/components/form/inputs/RichText/index.vue +3014 -75
- package/src/components/form/inputs/RichText/richTextTypes.ts +2 -3
- package/src/components/form/inputs/RichText/utils/commands.ts +279 -50
- package/src/components/form/inputs/RichText/utils/media-clean.ts +0 -0
- package/src/components/form/inputs/RichText/utils/media.ts +133 -67
- package/src/components/form/inputs/RichText/utils/selection.ts +10 -2
- package/src/components/form/inputs/RichText/utils/table.ts +1 -1
- package/src/components/index.ts +1 -0
- package/src/components/layout/AppContent.vue +26 -26
- package/src/components/layout/AppLayout.vue +21 -3
- package/src/components/layout/AppSidebar.vue +5 -2
- package/src/styles/layout.css +267 -0
- package/src/styles/mobilLayout.css +266 -0
- package/src/styles/modal.css +3 -17
|
@@ -2,55 +2,240 @@
|
|
|
2
2
|
import type { ToolbarConfig, ToolbarConfigOption, ToolbarOption } from '../richTextTypes'
|
|
3
3
|
import { Btn, Dropdown } from '@bagelink/vue'
|
|
4
4
|
import { basicToolbarConfig, toolbarOptions } from '../config'
|
|
5
|
-
import
|
|
5
|
+
import TableGridSelector from './TableGridSelector.vue'
|
|
6
6
|
|
|
7
|
-
const {
|
|
7
|
+
const {
|
|
8
|
+
config = basicToolbarConfig,
|
|
9
|
+
selectedStyles,
|
|
10
|
+
hideImages = false,
|
|
11
|
+
hideVideos = false,
|
|
12
|
+
hideEmbeds = false,
|
|
13
|
+
hideTables = false,
|
|
14
|
+
hideAlignment = false,
|
|
15
|
+
hideDirections = false,
|
|
16
|
+
hideH5H6 = false,
|
|
17
|
+
hide = []
|
|
18
|
+
} = defineProps<{
|
|
8
19
|
config?: ToolbarConfig
|
|
9
20
|
selectedStyles: Set<string>
|
|
21
|
+
hideImages?: boolean
|
|
22
|
+
hideVideos?: boolean
|
|
23
|
+
hideEmbeds?: boolean
|
|
24
|
+
hideTables?: boolean
|
|
25
|
+
hideAlignment?: boolean
|
|
26
|
+
hideDirections?: boolean
|
|
27
|
+
hideH5H6?: boolean
|
|
28
|
+
hide?: string[]
|
|
10
29
|
}>()
|
|
11
30
|
const emit = defineEmits(['action'])
|
|
12
31
|
|
|
13
|
-
|
|
32
|
+
// Function to get the current alignment icon based on active styles
|
|
33
|
+
function getCurrentAlignmentIcon(): string {
|
|
34
|
+
if (selectedStyles.has('alignLeft')) return 'format_align_left'
|
|
35
|
+
if (selectedStyles.has('alignCenter')) return 'format_align_center'
|
|
36
|
+
if (selectedStyles.has('alignRight')) return 'format_align_right'
|
|
37
|
+
if (selectedStyles.has('alignJustify')) return 'format_align_justify'
|
|
38
|
+
return 'format_align_left' // default
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Function to check if any alignment is active
|
|
42
|
+
function isAlignmentActive(): boolean {
|
|
43
|
+
return selectedStyles.has('alignLeft') ||
|
|
44
|
+
selectedStyles.has('alignCenter') ||
|
|
45
|
+
selectedStyles.has('alignRight') ||
|
|
46
|
+
selectedStyles.has('alignJustify')
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Helper function to check if an action should be hidden
|
|
50
|
+
function shouldHideAction(actionName: string): boolean {
|
|
51
|
+
// Check if it's in the hide array - this covers ALL items including:
|
|
52
|
+
// bold, italic, underline, h1, h2, h3, h4, h5, h6, link, image, video, embed,
|
|
53
|
+
// ul, ol, blockquote, code, clear, direction, table, fullScreen
|
|
54
|
+
if (hide.includes(actionName)) return true
|
|
55
|
+
|
|
56
|
+
// Alternative name mappings for some actions
|
|
57
|
+
const alternativeNames: Record<string, string[]> = {
|
|
58
|
+
'image': ['insertImage', 'image'],
|
|
59
|
+
'video': ['insertVideo', 'video'],
|
|
60
|
+
'embed': ['insertEmbed', 'embed'],
|
|
61
|
+
'table': ['insertTable'],
|
|
62
|
+
'direction': ['textDirection'],
|
|
63
|
+
'fullScreen': ['fullScreen'],
|
|
64
|
+
'ul': ['unorderedList'],
|
|
65
|
+
'ol': ['orderedList'],
|
|
66
|
+
'splitView': ['splitView'],
|
|
67
|
+
'p': ['p'],
|
|
68
|
+
'align': ['alignMenu'],
|
|
69
|
+
'alignment': ['alignMenu']
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Check alternative names
|
|
73
|
+
for (const [hideKey, actionNames] of Object.entries(alternativeNames)) {
|
|
74
|
+
if (hide.includes(hideKey) && actionNames.includes(actionName)) {
|
|
75
|
+
return true
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Map action names to check against specific hide props (for backward compatibility)
|
|
80
|
+
const actionMap: Record<string, boolean> = {
|
|
81
|
+
'insertImage': hideImages,
|
|
82
|
+
'insertVideo': hideVideos,
|
|
83
|
+
'insertEmbed': hideEmbeds,
|
|
84
|
+
'insertTable': hideTables,
|
|
85
|
+
'alignMenu': hideAlignment,
|
|
86
|
+
'textDirection': hideDirections,
|
|
87
|
+
'h5': hideH5H6,
|
|
88
|
+
'h6': hideH5H6
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return actionMap[actionName] || false
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Helper function to check if a separator should be shown
|
|
95
|
+
function shouldShowSeparator(currentIndex: number): boolean {
|
|
96
|
+
const allActions = config.map(configToOption).filter(Boolean)
|
|
97
|
+
|
|
98
|
+
// Simple approach: find the last visible item before this separator
|
|
99
|
+
let lastVisibleBeforeIndex = -1
|
|
100
|
+
for (let i = currentIndex - 1; i >= 0; i--) {
|
|
101
|
+
const action = allActions[i]
|
|
102
|
+
if (action && action.name !== 'separator' && !shouldHideAction(action.name)) {
|
|
103
|
+
lastVisibleBeforeIndex = i
|
|
104
|
+
break
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Find the first visible item after this separator
|
|
109
|
+
let firstVisibleAfterIndex = -1
|
|
110
|
+
for (let i = currentIndex + 1; i < allActions.length; i++) {
|
|
111
|
+
const action = allActions[i]
|
|
112
|
+
if (action && action.name !== 'separator' && !shouldHideAction(action.name)) {
|
|
113
|
+
firstVisibleAfterIndex = i
|
|
114
|
+
break
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Don't show if we don't have visible items on both sides
|
|
119
|
+
if (lastVisibleBeforeIndex === -1 || firstVisibleAfterIndex === -1) {
|
|
120
|
+
return false
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Check if there's already a visible separator between these items
|
|
124
|
+
for (let i = lastVisibleBeforeIndex + 1; i < currentIndex; i++) {
|
|
125
|
+
const action = allActions[i]
|
|
126
|
+
if (action && action.name === 'separator') {
|
|
127
|
+
// There's already a separator closer to the visible items
|
|
128
|
+
return false
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return true
|
|
133
|
+
} const configToOption = (action: ToolbarConfigOption) => toolbarOptions.find(option => option.name === action) as ToolbarOption
|
|
14
134
|
|
|
15
135
|
function runAction(name: ToolbarConfigOption, value?: string) {
|
|
136
|
+
console.log('EditorToolbar: runAction called', { name, value })
|
|
16
137
|
emit('action', name, value)
|
|
17
138
|
}
|
|
139
|
+
|
|
140
|
+
function handleQuickTableInsert(rows: number, cols: number) {
|
|
141
|
+
// Create a simple table quickly
|
|
142
|
+
const table = document.createElement('table')
|
|
143
|
+
table.style.width = '100%'
|
|
144
|
+
table.style.borderCollapse = 'collapse'
|
|
145
|
+
table.style.marginBottom = '1rem'
|
|
146
|
+
|
|
147
|
+
// Add header
|
|
148
|
+
const thead = document.createElement('thead')
|
|
149
|
+
const headerRow = thead.insertRow()
|
|
150
|
+
for (let j = 0; j < cols; j++) {
|
|
151
|
+
const th = document.createElement('th')
|
|
152
|
+
th.innerHTML = `Header ${j + 1}`
|
|
153
|
+
th.style.padding = '8px'
|
|
154
|
+
th.style.border = '1px solid #ddd'
|
|
155
|
+
th.style.backgroundColor = '#f4f4f4'
|
|
156
|
+
headerRow.appendChild(th)
|
|
157
|
+
}
|
|
158
|
+
table.appendChild(thead)
|
|
159
|
+
|
|
160
|
+
// Add body
|
|
161
|
+
const tbody = document.createElement('tbody')
|
|
162
|
+
for (let i = 0; i < rows; i++) {
|
|
163
|
+
const row = tbody.insertRow()
|
|
164
|
+
for (let j = 0; j < cols; j++) {
|
|
165
|
+
const cell = row.insertCell()
|
|
166
|
+
cell.innerHTML = ' '
|
|
167
|
+
cell.style.padding = '8px'
|
|
168
|
+
cell.style.border = '1px solid #ddd'
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
table.appendChild(tbody)
|
|
172
|
+
|
|
173
|
+
emit('action', 'insertTable', table.outerHTML)
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function handleOpenAdvanced() {
|
|
177
|
+
// The advanced table editor is now handled by the main RichText component
|
|
178
|
+
// through the insertTable command which opens the main modal
|
|
179
|
+
emit('action', 'insertTable')
|
|
180
|
+
}
|
|
18
181
|
</script>
|
|
19
182
|
|
|
20
183
|
<template>
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
184
|
+
<div class="toolbar flex gap-025 pb-05 flex-wrap" role="toolbar" style="position: relative;">
|
|
185
|
+
<template v-for="(action, index) in config.map(configToOption).filter(Boolean)" :key="index">
|
|
186
|
+
<!-- Tables -->
|
|
187
|
+
<TableGridSelector v-if="action.name === 'insertTable' && !shouldHideAction('insertTable')" @insert="handleQuickTableInsert" @open-advanced="handleOpenAdvanced" />
|
|
188
|
+
<!-- Images -->
|
|
189
|
+
<Btn v-else-if="action.name === 'insertImage' && !shouldHideAction('insertImage')" v-tooltip="action.label" :icon="action.icon" thin flat :aria-label="action.name"
|
|
190
|
+
:class="[action.class, { active: selectedStyles.has(action.name) }]" class="" tabindex="-1" @click="runAction(action.name)" />
|
|
191
|
+
|
|
192
|
+
<!-- Videos -->
|
|
193
|
+
<Btn v-else-if="action.name === 'insertVideo' && !shouldHideAction('insertVideo')" v-tooltip="action.label" :icon="action.icon" thin flat :aria-label="action.name"
|
|
194
|
+
:class="[action.class, { active: selectedStyles.has(action.name) }]" class="" tabindex="-1" @click="runAction(action.name)" />
|
|
195
|
+
|
|
196
|
+
<!-- Embeds -->
|
|
197
|
+
<Btn v-else-if="action.name === 'insertEmbed' && !shouldHideAction('insertEmbed')" v-tooltip="action.label" :icon="action.icon" thin flat :aria-label="action.name"
|
|
198
|
+
:class="[action.class, { active: selectedStyles.has(action.name) }]" class="" tabindex="-1" @click="runAction(action.name)" />
|
|
199
|
+
|
|
200
|
+
<!-- Alignment Menu -->
|
|
201
|
+
<Dropdown v-else-if="action.name === 'alignMenu' && !shouldHideAction('alignMenu')" placement="bottom-start" thin flat :icon="getCurrentAlignmentIcon()"
|
|
202
|
+
:class="{ 'alignment-active': isAlignmentActive() }">
|
|
203
|
+
<template #default="{ hide }">
|
|
204
|
+
<div class="flex flex-column p-025">
|
|
205
|
+
<Btn thin flat icon="format_align_left" :class="{ active: selectedStyles.has('alignLeft') }" @click="runAction('alignLeft'); hide()" />
|
|
206
|
+
<Btn thin flat icon="format_align_center" :class="{ active: selectedStyles.has('alignCenter') }" @click="runAction('alignCenter'); hide()" />
|
|
207
|
+
<Btn thin flat icon="format_align_right" :class="{ active: selectedStyles.has('alignRight') }" @click="runAction('alignRight'); hide()" />
|
|
208
|
+
<Btn thin flat icon="format_align_justify" :class="{ active: selectedStyles.has('alignJustify') }" @click="runAction('alignJustify'); hide()" />
|
|
209
|
+
</div>
|
|
210
|
+
</template>
|
|
211
|
+
</Dropdown>
|
|
212
|
+
|
|
213
|
+
<!-- Text Direction -->
|
|
214
|
+
<Btn v-else-if="action.name === 'textDirection' && !shouldHideAction('textDirection')" v-tooltip="action.label"
|
|
215
|
+
:icon="selectedStyles.has('textDirection') ? 'format_textdirection_l_to_r' : 'format_textdirection_r_to_l'" thin flat :aria-label="action.name"
|
|
216
|
+
:class="[action.class, { active: selectedStyles.has('textDirection') }]" class="" tabindex="-1" @click="runAction(action.name)" />
|
|
217
|
+
|
|
218
|
+
<!-- H5 and H6 -->
|
|
219
|
+
<Btn v-else-if="(action.name === 'h5' || action.name === 'h6') && !shouldHideAction(action.name)" v-tooltip="action.label" :icon="action.icon" thin flat :aria-label="action.name"
|
|
220
|
+
:class="[action.class, { active: selectedStyles.has(action.name) }]" class="" tabindex="-1" @click="runAction(action.name)" />
|
|
221
|
+
|
|
222
|
+
<!-- All other buttons - check if they should be hidden by the hide array -->
|
|
223
|
+
<Btn v-else-if="action.name !== 'separator' &&
|
|
224
|
+
action.name !== 'insertTable' &&
|
|
225
|
+
action.name !== 'insertImage' &&
|
|
226
|
+
action.name !== 'insertVideo' &&
|
|
227
|
+
action.name !== 'insertEmbed' &&
|
|
228
|
+
action.name !== 'alignMenu' &&
|
|
229
|
+
action.name !== 'textDirection' &&
|
|
230
|
+
action.name !== 'h5' &&
|
|
231
|
+
action.name !== 'h6' &&
|
|
232
|
+
!shouldHideAction(action.name)" v-tooltip="action.label" :icon="action.icon" thin flat :aria-label="action.name" :class="[action.class, { active: selectedStyles.has(action.name) }]" class=""
|
|
233
|
+
tabindex="-1" @click="runAction(action.name)" />
|
|
234
|
+
|
|
235
|
+
<!-- Separator -->
|
|
236
|
+
<span v-else-if="action.name === 'separator' && shouldShowSeparator(index)" :key="`separator-${index}`" class="opacity-2 mb-025">|</span>
|
|
237
|
+
</template>
|
|
238
|
+
</div>
|
|
54
239
|
</template>
|
|
55
240
|
|
|
56
241
|
<style scoped>
|
|
@@ -59,8 +244,28 @@ function runAction(name: ToolbarConfigOption, value?: string) {
|
|
|
59
244
|
color: white;
|
|
60
245
|
}
|
|
61
246
|
.toolbar :deep(.active):hover {
|
|
62
|
-
|
|
247
|
+
background: var(--bgl-primary) !important;
|
|
248
|
+
color: white;
|
|
249
|
+
}
|
|
250
|
+
/* Headings menu styling */
|
|
251
|
+
.toolbar :deep(.dropdown-content) {
|
|
252
|
+
min-width: 40px;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
.toolbar :deep(.dropdown-content .btn) {
|
|
256
|
+
min-width: 32px;
|
|
257
|
+
height: 32px;
|
|
258
|
+
padding: 0;
|
|
259
|
+
}
|
|
260
|
+
/* Alignment active state */
|
|
261
|
+
.alignment-active :deep(button) {
|
|
262
|
+
background: var(--bgl-primary) !important;
|
|
263
|
+
color: white !important;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
.alignment-active :deep(button:hover) {
|
|
267
|
+
background: var(--bgl-primary) !important;
|
|
268
|
+
color: white !important;
|
|
63
269
|
}
|
|
64
270
|
</style>
|
|
65
271
|
|
|
66
|
-
<style scoped></style>
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { ref, computed } from 'vue'
|
|
3
|
+
import { Btn, Dropdown } from '@bagelink/vue'
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
const emit = defineEmits<{
|
|
7
|
+
insert: [rows: number, cols: number]
|
|
8
|
+
openAdvanced: []
|
|
9
|
+
}>()
|
|
10
|
+
|
|
11
|
+
const hoveredRow = ref(0)
|
|
12
|
+
const hoveredCol = ref(0)
|
|
13
|
+
|
|
14
|
+
const maxRows = 10
|
|
15
|
+
const maxCols = 10
|
|
16
|
+
|
|
17
|
+
const gridCells = computed(() => {
|
|
18
|
+
const cells = []
|
|
19
|
+
for (let row = 1; row <= maxRows; row++) {
|
|
20
|
+
for (let col = 1; col <= maxCols; col++) {
|
|
21
|
+
cells.push({
|
|
22
|
+
row,
|
|
23
|
+
col,
|
|
24
|
+
isActive: row <= hoveredRow.value && col <= hoveredCol.value
|
|
25
|
+
})
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return cells
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
const selectionText = computed(() => {
|
|
32
|
+
if (hoveredRow.value === 0 || hoveredCol.value === 0) {
|
|
33
|
+
return 'Select table size'
|
|
34
|
+
}
|
|
35
|
+
return `${hoveredRow.value} × ${hoveredCol.value}`
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
const handleCellHover = (row: number, col: number) => {
|
|
39
|
+
hoveredRow.value = row
|
|
40
|
+
hoveredCol.value = col
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const handleCellClick = (row: number, col: number, hide: () => void) => {
|
|
44
|
+
emit('insert', row, col)
|
|
45
|
+
hide()
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const handleAdvanced = (hide: () => void) => {
|
|
49
|
+
emit('openAdvanced')
|
|
50
|
+
hide()
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const handleMouseLeave = () => {
|
|
54
|
+
hoveredRow.value = 0
|
|
55
|
+
hoveredCol.value = 0
|
|
56
|
+
}
|
|
57
|
+
</script>
|
|
58
|
+
|
|
59
|
+
<template>
|
|
60
|
+
<Dropdown placement="bottom-start" thin flat icon="table">
|
|
61
|
+
<template #default="{ hide }">
|
|
62
|
+
<div class="table-grid-selector p-075">
|
|
63
|
+
<!-- Header -->
|
|
64
|
+
<div class="txt-center mb-075">
|
|
65
|
+
<div style="font-size: 14px; font-weight: 500; color: #333; margin-bottom: 4px;">
|
|
66
|
+
{{ selectionText }}
|
|
67
|
+
</div>
|
|
68
|
+
<div style="font-size: 12px; color: #666;">
|
|
69
|
+
Hover to select size
|
|
70
|
+
</div>
|
|
71
|
+
</div>
|
|
72
|
+
|
|
73
|
+
<!-- Grid -->
|
|
74
|
+
<div style="display: grid; grid-template-columns: repeat(10, 20px); grid-template-rows: repeat(10, 20px); gap: 2px; margin-bottom: 12px; direction: ltr;" @mouseleave="handleMouseLeave">
|
|
75
|
+
<div v-for="cell in gridCells" :key="`${cell.row}-${cell.col}`" @mouseenter="handleCellHover(cell.row, cell.col)" @click="handleCellClick(cell.row, cell.col, hide)" :style="{
|
|
76
|
+
width: '20px',
|
|
77
|
+
height: '20px',
|
|
78
|
+
border: '1px solid #ddd',
|
|
79
|
+
backgroundColor: cell.isActive ? 'var(--bgl-primary)' : '#fff',
|
|
80
|
+
cursor: 'pointer',
|
|
81
|
+
borderRadius: '2px',
|
|
82
|
+
transition: 'background-color 0.1s ease'
|
|
83
|
+
}">
|
|
84
|
+
</div>
|
|
85
|
+
</div>
|
|
86
|
+
|
|
87
|
+
<!-- Advanced button -->
|
|
88
|
+
<div class="border-top pt-075">
|
|
89
|
+
<Btn @click="handleAdvanced(hide)" value="Advanced Settings" icon="settings" class="bg-gray-30 color-black" />
|
|
90
|
+
</div>
|
|
91
|
+
</div>
|
|
92
|
+
</template>
|
|
93
|
+
</Dropdown>
|
|
94
|
+
</template>
|
|
@@ -15,6 +15,7 @@ export function useCommands(state: EditorState, debug?: { logCommand: (command:
|
|
|
15
15
|
'bold',
|
|
16
16
|
'italic',
|
|
17
17
|
'underline',
|
|
18
|
+
'link',
|
|
18
19
|
'h1',
|
|
19
20
|
'h2',
|
|
20
21
|
'h3',
|
|
@@ -29,7 +30,9 @@ export function useCommands(state: EditorState, debug?: { logCommand: (command:
|
|
|
29
30
|
'alignCenter',
|
|
30
31
|
'alignRight',
|
|
31
32
|
'alignJustify',
|
|
32
|
-
'textDirection'
|
|
33
|
+
'textDirection',
|
|
34
|
+
'ltrDirection',
|
|
35
|
+
'rtlDirection'
|
|
33
36
|
]
|
|
34
37
|
|
|
35
38
|
styleTypes.forEach((style) => {
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import type { EditorState, EditorDebugInterface, EditorDebuggerInstance } from '../richTextTypes'
|
|
2
|
-
import { useModal } from '@bagelink/vue'
|
|
3
2
|
import { reactive } from 'vue'
|
|
4
3
|
import { EditorDebugger } from '../utils/debug'
|
|
5
4
|
import { isStyleActive } from '../utils/selection'
|
|
@@ -34,7 +33,6 @@ function restoreIframes(doc: Document, content: string, iframes: HTMLIFrameEleme
|
|
|
34
33
|
}
|
|
35
34
|
|
|
36
35
|
export function useEditor() {
|
|
37
|
-
const modal = useModal()
|
|
38
36
|
let cleanupListeners: (() => void) | null = null
|
|
39
37
|
|
|
40
38
|
const state = reactive<EditorState>({
|
|
@@ -50,7 +48,6 @@ export function useEditor() {
|
|
|
50
48
|
redoStack: [],
|
|
51
49
|
rangeCount: 0,
|
|
52
50
|
range: null,
|
|
53
|
-
modal,
|
|
54
51
|
debug: undefined
|
|
55
52
|
})
|
|
56
53
|
|
|
@@ -77,7 +74,9 @@ export function useEditor() {
|
|
|
77
74
|
'alignCenter',
|
|
78
75
|
'alignRight',
|
|
79
76
|
'alignJustify',
|
|
80
|
-
'textDirection'
|
|
77
|
+
'textDirection',
|
|
78
|
+
'ltrDirection',
|
|
79
|
+
'rtlDirection'
|
|
81
80
|
]
|
|
82
81
|
styleTypes.forEach((style) => {
|
|
83
82
|
if (state.doc && isStyleActive(style, state.doc)) {
|
|
@@ -115,7 +114,8 @@ export function useEditor() {
|
|
|
115
114
|
try {
|
|
116
115
|
selection.removeAllRanges()
|
|
117
116
|
selection.addRange(range)
|
|
118
|
-
} catch
|
|
117
|
+
} catch {
|
|
118
|
+
// Range restoration failed, ignore
|
|
119
119
|
}
|
|
120
120
|
}
|
|
121
121
|
}
|
|
@@ -159,7 +159,7 @@ export function useEditor() {
|
|
|
159
159
|
// Update styles immediately for better responsiveness
|
|
160
160
|
updateState.styles()
|
|
161
161
|
}
|
|
162
|
-
} catch
|
|
162
|
+
} catch {
|
|
163
163
|
state.selection = null
|
|
164
164
|
state.range = null
|
|
165
165
|
state.rangeCount = 0
|
|
@@ -14,6 +14,7 @@ const shortcuts: KeyboardShortcut[] = [
|
|
|
14
14
|
{ key: 'b', command: 'bold' },
|
|
15
15
|
{ key: 'i', command: 'italic' },
|
|
16
16
|
{ key: 'u', command: 'underline' },
|
|
17
|
+
{ key: 'k', command: 'link' },
|
|
17
18
|
{ key: 'z', command: 'undo' },
|
|
18
19
|
{ key: 'z', modifiers: { shift: true }, command: 'redo' },
|
|
19
20
|
{ key: 'y', command: 'redo' },
|
|
@@ -33,16 +33,21 @@ export const basicToolbarConfig: ToolbarConfig = [
|
|
|
33
33
|
'italic',
|
|
34
34
|
'underline',
|
|
35
35
|
'separator',
|
|
36
|
-
'
|
|
37
|
-
'
|
|
38
|
-
'
|
|
39
|
-
'
|
|
40
|
-
'
|
|
36
|
+
'alignMenu',
|
|
37
|
+
'ltrDirection',
|
|
38
|
+
'rtlDirection',
|
|
39
|
+
'separator',
|
|
40
|
+
'insertTable',
|
|
41
41
|
'separator',
|
|
42
42
|
'link',
|
|
43
43
|
'image',
|
|
44
|
+
'video',
|
|
44
45
|
'embed',
|
|
45
46
|
'clear',
|
|
47
|
+
'separator',
|
|
48
|
+
'undo',
|
|
49
|
+
'redo',
|
|
50
|
+
'separator',
|
|
46
51
|
'splitView',
|
|
47
52
|
'fullScreen',
|
|
48
53
|
]
|
|
@@ -71,6 +76,7 @@ export const fullToolbarConfig: ToolbarConfig = [
|
|
|
71
76
|
'separator',
|
|
72
77
|
'link',
|
|
73
78
|
'image',
|
|
79
|
+
'video',
|
|
74
80
|
'embed',
|
|
75
81
|
'separator',
|
|
76
82
|
'splitView',
|
|
@@ -84,12 +90,15 @@ export const fullToolbarConfig: ToolbarConfig = [
|
|
|
84
90
|
]
|
|
85
91
|
|
|
86
92
|
export const toolbarOptions: ToolbarOption[] = [
|
|
87
|
-
{ name: '
|
|
88
|
-
{ name: '
|
|
89
|
-
{ name: '
|
|
90
|
-
{ name: '
|
|
91
|
-
{ name: '
|
|
92
|
-
{ name: '
|
|
93
|
+
{ name: 'headingsMenu', label: 'Headings', icon: 'title' },
|
|
94
|
+
{ name: 'mediaMenu', label: 'Media', icon: 'perm_media' },
|
|
95
|
+
{ name: 'alignMenu', label: 'Alignment', icon: 'format_align_left' },
|
|
96
|
+
{ name: 'h1', label: 'Heading 1', icon: 'format_h1' },
|
|
97
|
+
{ name: 'h2', label: 'Heading 2', icon: 'format_h2' },
|
|
98
|
+
{ name: 'h3', label: 'Heading 3', icon: 'format_h3' },
|
|
99
|
+
{ name: 'h4', label: 'Heading 4', icon: 'format_h4' },
|
|
100
|
+
{ name: 'h5', label: 'Heading 5', icon: 'format_h5' },
|
|
101
|
+
{ name: 'h6', label: 'Heading 6', icon: 'format_h6' },
|
|
93
102
|
{ name: 'p', label: 'Paragraph', icon: 'format_paragraph' },
|
|
94
103
|
{ name: 'blockquote', label: 'Blockquote', icon: 'format_quote' },
|
|
95
104
|
{ name: 'bold', label: 'Bold', icon: 'format_bold' },
|
|
@@ -99,6 +108,7 @@ export const toolbarOptions: ToolbarOption[] = [
|
|
|
99
108
|
{ name: 'unorderedList', label: 'Unordered List', icon: 'format_list_bulleted' },
|
|
100
109
|
{ name: 'link', label: 'Link', icon: 'add_link' },
|
|
101
110
|
{ name: 'image', label: 'Image', icon: 'add_photo_alternate' },
|
|
111
|
+
{ name: 'video', label: 'Video', icon: 'play_circle' },
|
|
102
112
|
{ name: 'embed', label: 'Embed', icon: 'media_link' },
|
|
103
113
|
{ name: 'splitView', label: 'Split View', icon: 'code' },
|
|
104
114
|
{ name: 'clear', label: 'Clear Formatting', icon: 'format_clear' },
|
|
@@ -130,4 +140,6 @@ export const toolbarOptions: ToolbarOption[] = [
|
|
|
130
140
|
{ name: 'separator' },
|
|
131
141
|
{ name: 'fullScreen', label: 'Full Screen', icon: 'fullscreen', class: 'ms-auto' },
|
|
132
142
|
{ name: 'textDirection', label: 'Text Direction (RTL/LTR)', icon: 'format_textdirection_r_to_l' },
|
|
143
|
+
{ name: 'ltrDirection', label: 'Left to Right', icon: 'format_textdirection_l_to_r' },
|
|
144
|
+
{ name: 'rtlDirection', label: 'Right to Left', icon: 'format_textdirection_r_to_l' },
|
|
133
145
|
]
|