@bagelink/vue 1.4.141 → 1.4.147

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.
Files changed (70) hide show
  1. package/dist/components/Btn.vue.d.ts.map +1 -1
  2. package/dist/components/Carousel.vue.d.ts +1 -1
  3. package/dist/components/Modal.vue.d.ts +3 -0
  4. package/dist/components/Modal.vue.d.ts.map +1 -1
  5. package/dist/components/Slider.vue.d.ts +1 -1
  6. package/dist/components/Slider.vue.d.ts.map +1 -1
  7. package/dist/components/analytics/BarChart.vue.d.ts +11 -3
  8. package/dist/components/analytics/BarChart.vue.d.ts.map +1 -1
  9. package/dist/components/analytics/LineChart.vue.d.ts +9 -0
  10. package/dist/components/analytics/LineChart.vue.d.ts.map +1 -1
  11. package/dist/components/analytics/PieChart.vue.d.ts +30 -2
  12. package/dist/components/analytics/PieChart.vue.d.ts.map +1 -1
  13. package/dist/components/form/inputs/RichText/components/EditorToolbar.vue.d.ts +8 -0
  14. package/dist/components/form/inputs/RichText/components/EditorToolbar.vue.d.ts.map +1 -1
  15. package/dist/components/form/inputs/RichText/components/TableGridSelector.vue.d.ts +9 -0
  16. package/dist/components/form/inputs/RichText/components/TableGridSelector.vue.d.ts.map +1 -0
  17. package/dist/components/form/inputs/RichText/composables/useCommands.d.ts.map +1 -1
  18. package/dist/components/form/inputs/RichText/composables/useEditor.d.ts +0 -14
  19. package/dist/components/form/inputs/RichText/composables/useEditor.d.ts.map +1 -1
  20. package/dist/components/form/inputs/RichText/composables/useEditorKeyboard.d.ts.map +1 -1
  21. package/dist/components/form/inputs/RichText/config.d.ts.map +1 -1
  22. package/dist/components/form/inputs/RichText/index.vue.d.ts +15 -15
  23. package/dist/components/form/inputs/RichText/index.vue.d.ts.map +1 -1
  24. package/dist/components/form/inputs/RichText/richTextTypes.d.ts +1 -3
  25. package/dist/components/form/inputs/RichText/richTextTypes.d.ts.map +1 -1
  26. package/dist/components/form/inputs/RichText/utils/commands.d.ts.map +1 -1
  27. package/dist/components/form/inputs/RichText/utils/media-clean.d.ts +2 -0
  28. package/dist/components/form/inputs/RichText/utils/media-clean.d.ts.map +1 -0
  29. package/dist/components/form/inputs/RichText/utils/media.d.ts +4 -4
  30. package/dist/components/form/inputs/RichText/utils/media.d.ts.map +1 -1
  31. package/dist/components/form/inputs/RichText/utils/selection.d.ts.map +1 -1
  32. package/dist/components/form/inputs/RichText/utils/table.d.ts +1 -1
  33. package/dist/components/form/inputs/RichText/utils/table.d.ts.map +1 -1
  34. package/dist/components/index.d.ts +1 -0
  35. package/dist/components/index.d.ts.map +1 -1
  36. package/dist/components/layout/AppContent.vue.d.ts.map +1 -1
  37. package/dist/components/layout/AppLayout.vue.d.ts.map +1 -1
  38. package/dist/components/layout/AppSidebar.vue.d.ts.map +1 -1
  39. package/dist/index.cjs +123 -22
  40. package/dist/index.mjs +123 -22
  41. package/dist/style.css +1 -1
  42. package/package.json +1 -1
  43. package/src/components/Btn.vue +50 -42
  44. package/src/components/Modal.vue +49 -50
  45. package/src/components/analytics/BarChart.vue +118 -7
  46. package/src/components/analytics/KpiCard.vue +2 -2
  47. package/src/components/analytics/LineChart.vue +189 -105
  48. package/src/components/analytics/PieChart.vue +392 -49
  49. package/src/components/form/inputs/RichText/CheckList.md +23 -0
  50. package/src/components/form/inputs/RichText/components/EditorToolbar.vue +243 -38
  51. package/src/components/form/inputs/RichText/components/TableGridSelector.vue +94 -0
  52. package/src/components/form/inputs/RichText/composables/useCommands.ts +4 -1
  53. package/src/components/form/inputs/RichText/composables/useEditor.ts +6 -6
  54. package/src/components/form/inputs/RichText/composables/useEditorKeyboard.ts +1 -0
  55. package/src/components/form/inputs/RichText/config.ts +23 -11
  56. package/src/components/form/inputs/RichText/editor.css +300 -33
  57. package/src/components/form/inputs/RichText/index.vue +3014 -75
  58. package/src/components/form/inputs/RichText/richTextTypes.ts +2 -3
  59. package/src/components/form/inputs/RichText/utils/commands.ts +279 -50
  60. package/src/components/form/inputs/RichText/utils/media-clean.ts +0 -0
  61. package/src/components/form/inputs/RichText/utils/media.ts +133 -67
  62. package/src/components/form/inputs/RichText/utils/selection.ts +10 -2
  63. package/src/components/form/inputs/RichText/utils/table.ts +1 -1
  64. package/src/components/index.ts +1 -0
  65. package/src/components/layout/AppContent.vue +26 -26
  66. package/src/components/layout/AppLayout.vue +21 -3
  67. package/src/components/layout/AppSidebar.vue +5 -2
  68. package/src/styles/layout.css +267 -0
  69. package/src/styles/mobilLayout.css +266 -0
  70. 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 GridBox from './gridBox.vue'
5
+ import TableGridSelector from './TableGridSelector.vue'
6
6
 
7
- const { config = basicToolbarConfig, selectedStyles } = defineProps<{
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
- const configToOption = (action: ToolbarConfigOption) => toolbarOptions.find(option => option.name === action) as ToolbarOption
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 = '&nbsp;'
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
- <div class="toolbar flex gap-025 pb-05 flex-wrap" role="toolbar">
22
- <template v-for="(action, index) in config.map(configToOption).filter(Boolean)" :key="index">
23
- <Dropdown v-if="action.name === 'insertTable'" placement="bottom-start" thin flat icon="table">
24
- <template #default="{ hide }">
25
- <GridBox
26
- :gridSize="5" @select="(value) => {
27
- runAction('insertTable', value);
28
- hide();
29
- }"
30
- />
31
- </template>
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
- />
44
- <Btn
45
- v-else-if="action.name !== 'separator'" v-tooltip="action.label" :icon="action.icon" thin flat
46
- :aria-label="action.name" :class="[action.class, { active: selectedStyles.has(action.name) }]"
47
- class=""
48
- tabindex="-1"
49
- @click="runAction(action.name)"
50
- />
51
- <span v-else-if="action.name === 'separator'" :key="`separator-${index}`" class="opacity-2 mb-025">|</span>
52
- </template>
53
- </div>
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
- color: var(--bgl-black);
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 (e) {
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 (e) {
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
- 'alignLeft',
37
- 'alignCenter',
38
- 'alignRight',
39
- 'alignJustify',
40
- 'textDirection',
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: 'h1', label: 'h1', icon: 'format_h1' },
88
- { name: 'h2', label: 'h2', icon: 'format_h2' },
89
- { name: 'h3', label: 'h3', icon: 'format_h3' },
90
- { name: 'h4', label: 'h4', icon: 'format_h4' },
91
- { name: 'h5', label: 'h5', icon: 'format_h5' },
92
- { name: 'h6', label: 'h6', icon: 'format_h6' },
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
  ]