@bagelink/vue 0.0.986 → 0.0.988

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 (61) hide show
  1. package/dist/components/Btn.vue.d.ts.map +1 -1
  2. package/dist/components/form/inputs/CodeEditor/CodeTypes.d.ts +197 -0
  3. package/dist/components/form/inputs/CodeEditor/CodeTypes.d.ts.map +1 -0
  4. package/dist/components/form/inputs/CodeEditor/Index.vue.d.ts +60 -0
  5. package/dist/components/form/inputs/CodeEditor/Index.vue.d.ts.map +1 -0
  6. package/dist/components/form/inputs/CodeEditor/format.d.ts +2 -0
  7. package/dist/components/form/inputs/CodeEditor/format.d.ts.map +1 -0
  8. package/dist/components/form/inputs/RichText/Toolbar.vue.d.ts +5 -3
  9. package/dist/components/form/inputs/RichText/Toolbar.vue.d.ts.map +1 -1
  10. package/dist/components/form/inputs/RichText/components/Toolbar.vue.d.ts +14 -0
  11. package/dist/components/form/inputs/RichText/components/Toolbar.vue.d.ts.map +1 -0
  12. package/dist/components/form/inputs/RichText/components/gridBox.vue.d.ts +11 -0
  13. package/dist/components/form/inputs/RichText/components/gridBox.vue.d.ts.map +1 -0
  14. package/dist/components/form/inputs/RichText/composables/useEditor.d.ts +86 -0
  15. package/dist/components/form/inputs/RichText/composables/useEditor.d.ts.map +1 -0
  16. package/dist/components/form/inputs/RichText/composables/useEditorKeyboard.d.ts +2 -0
  17. package/dist/components/form/inputs/RichText/composables/useEditorKeyboard.d.ts.map +1 -0
  18. package/dist/components/form/inputs/RichText/config.d.ts +5 -0
  19. package/dist/components/form/inputs/RichText/config.d.ts.map +1 -0
  20. package/dist/components/form/inputs/RichText/index.vue.d.ts +1 -1
  21. package/dist/components/form/inputs/RichText/index.vue.d.ts.map +1 -1
  22. package/dist/components/form/inputs/RichText/utils/formatting.d.ts +7 -0
  23. package/dist/components/form/inputs/RichText/utils/formatting.d.ts.map +1 -0
  24. package/dist/components/form/inputs/RichText/utils/media.d.ts +4 -0
  25. package/dist/components/form/inputs/RichText/utils/media.d.ts.map +1 -0
  26. package/dist/components/form/inputs/RichText/utils/selection.d.ts +4 -0
  27. package/dist/components/form/inputs/RichText/utils/selection.d.ts.map +1 -0
  28. package/dist/components/form/inputs/RichText/utils/table.d.ts +6 -0
  29. package/dist/components/form/inputs/RichText/utils/table.d.ts.map +1 -0
  30. package/dist/components/form/inputs/index.d.ts +1 -0
  31. package/dist/components/form/inputs/index.d.ts.map +1 -1
  32. package/dist/editor-CUDRLdmS.js +4 -0
  33. package/dist/editor-Cu374vEW.cjs +4 -0
  34. package/dist/index.cjs +53541 -763
  35. package/dist/index.mjs +53542 -764
  36. package/dist/style.css +19779 -97
  37. package/package.json +2 -1
  38. package/src/components/Btn.vue +3 -0
  39. package/src/components/form/inputs/CodeEditor/CodeTypes.ts +446 -0
  40. package/src/components/form/inputs/CodeEditor/Index.vue +440 -0
  41. package/src/components/form/inputs/CodeEditor/format.ts +98 -0
  42. package/src/components/form/inputs/CodeEditor/themes/brown-papersq.png +0 -0
  43. package/src/components/form/inputs/CodeEditor/themes/pojoaque.jpg +0 -0
  44. package/src/components/form/inputs/CodeEditor/themes/themes-base16.css +12809 -0
  45. package/src/components/form/inputs/CodeEditor/themes/themes.css +6740 -0
  46. package/src/components/form/inputs/RichText/components/Toolbar.vue +51 -0
  47. package/src/components/form/inputs/RichText/components/gridBox.vue +22 -0
  48. package/src/components/form/inputs/RichText/composables/useEditor.ts +208 -0
  49. package/src/components/form/inputs/RichText/composables/useEditorKeyboard.ts +21 -0
  50. package/src/components/form/inputs/RichText/config.ts +73 -0
  51. package/src/components/form/inputs/RichText/editor.css +25 -0
  52. package/src/components/form/inputs/RichText/index.vue +81 -193
  53. package/src/components/form/inputs/RichText/richTextTypes.d.ts +77 -0
  54. package/src/components/form/inputs/RichText/utils/formatting.ts +98 -0
  55. package/src/components/form/inputs/RichText/utils/media.ts +42 -0
  56. package/src/components/form/inputs/RichText/utils/selection.ts +48 -0
  57. package/src/components/form/inputs/RichText/utils/table.ts +79 -0
  58. package/src/components/form/inputs/index.ts +1 -0
  59. package/src/components/form/inputs/RichText/Toolbar.vue +0 -87
  60. package/src/components/form/inputs/RichText/formatting.ts +0 -246
  61. package/src/components/form/inputs/RichText/richtext-types.ts +0 -29
@@ -0,0 +1,51 @@
1
+ <script lang="ts" setup>
2
+ import type { ToolbarConfig, ToolbarConfigOption, ToolbarOption } from '../richTextTypes'
3
+ import { Btn, Dropdown } from '@bagelink/vue'
4
+ import { defaultToolbarConfig, toolbarOptions } from '../config'
5
+ import GridBox from './gridBox.vue'
6
+
7
+ const { config = defaultToolbarConfig, selectedStyles } = defineProps<{
8
+ config?: ToolbarConfig
9
+ selectedStyles: Set<string>
10
+ }>()
11
+ const emit = defineEmits(['action'])
12
+
13
+ const configToOption = (action: ToolbarConfigOption) => toolbarOptions.find(option => option.name === action) as ToolbarOption
14
+
15
+ function runAction(name: ToolbarConfigOption, value?: string) {
16
+ emit('action', name, value)
17
+ }
18
+ </script>
19
+
20
+ <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="$event => {
27
+ runAction('insertTable', $event);
28
+ ($event.target as any)?.blur();
29
+ hide()
30
+ }"
31
+ />
32
+ </template>
33
+ </Dropdown>
34
+ <Btn
35
+ v-if="action.name !== 'separator'" v-tooltip="action.label" :icon="action.icon" thin flat
36
+ :aria-label="action.name" :class="[action.class, { active: selectedStyles.has(action.name) }]"
37
+ @click="runAction(action.name)"
38
+ />
39
+ <span v-else-if="action.name === 'separator'" :key="`separator-${index}`" class="opacity-2 mb-025">|</span>
40
+ </template>
41
+ </div>
42
+ </template>
43
+
44
+ <style scoped>
45
+ .toolbar :deep(.active) {
46
+ background: var(--bgl-primary);
47
+ color: white;
48
+ }
49
+ </style>
50
+
51
+ <style scoped></style>
@@ -0,0 +1,22 @@
1
+ <script setup lang="ts">
2
+ defineProps<{ gridSize: number }>()
3
+ const emit = defineEmits(['select'])
4
+ const hoveredRow = $ref(-1)
5
+ const hoveredCol = $ref(-1)
6
+ </script>
7
+
8
+ <template>
9
+ <div class="grid grid-wrap p-05">
10
+ <div
11
+ v-for="row in gridSize" :key="`row-${row}`" class="flex" @mouseout="hoveredRow = -1; hoveredCol = -1"
12
+ @focusout="hoveredRow = -1; hoveredCol = -1"
13
+ >
14
+ <div
15
+ v-for="col in 6" :key="`col-${col}`" role="button" tabindex="0" aria-label="Insert Table"
16
+ :style="{ width: '20px', height: '20px' }" class="border flex pointer"
17
+ :class="{ 'bg-gray-80': hoveredRow >= row && hoveredCol >= col }"
18
+ @mousemove="hoveredRow = row; hoveredCol = col" @click="emit('select', `${row}x${col}`)"
19
+ />
20
+ </div>
21
+ </div>
22
+ </template>
@@ -0,0 +1,208 @@
1
+ import type { EditorState, Modal } from '../richTextTypes'
2
+ import { reactive } from 'vue'
3
+ import { formatting } from '../utils/formatting'
4
+ import { insertImage, insertLink } from '../utils/media'
5
+ import { isStyleActive } from '../utils/selection'
6
+ import { addRow, deleteRow, mergeCells, splitCell } from '../utils/table'
7
+
8
+ export function useEditor() {
9
+ const state = reactive<
10
+ EditorState
11
+ >({
12
+ isFullscreen: false,
13
+ hasInit: false,
14
+ isCodeView: false,
15
+ isSplitView: false,
16
+ selectedStyles: new Set<string>(),
17
+ rangeCount: 0,
18
+ content: '',
19
+ })
20
+
21
+ const updateListStyles = (styles: Set<string>) => {
22
+ if (state.selection?.rangeCount) {
23
+ const node = state.selection.getRangeAt(0).commonAncestorContainer
24
+ const parentElement = node.nodeType === 3 ? node.parentElement : node
25
+ if (parentElement) {
26
+ const list = (parentElement as Element).closest('ul, ol')
27
+ if (list) {
28
+ styles.add(
29
+ list.tagName.toLowerCase() === 'ul'
30
+ ? 'unorderedList'
31
+ : 'orderedList'
32
+ )
33
+ }
34
+ }
35
+ }
36
+ }
37
+
38
+ const updateActiveStyles = () => {
39
+ if (!state.doc) return
40
+ const styles = new Set<string>()
41
+ const styleTypes = [
42
+ 'bold',
43
+ 'italic',
44
+ 'underline',
45
+ 'h1',
46
+ 'h2',
47
+ 'h3',
48
+ 'h4',
49
+ 'h5',
50
+ 'h6',
51
+ ]
52
+
53
+ styleTypes.forEach((style) => {
54
+ if (state.doc && isStyleActive(style, state.doc)) {
55
+ styles.add(style)
56
+ }
57
+ })
58
+
59
+ updateListStyles(styles)
60
+ state.selectedStyles = styles
61
+ }
62
+
63
+ const updateSelection = () => {
64
+ if (!state.doc) return
65
+ state.selection = state.doc.getSelection()
66
+ if (!state.selection) return
67
+
68
+ try {
69
+ if (!state.doc.body.contains(state.selection.anchorNode)) {
70
+ state.doc.body.focus()
71
+ return
72
+ }
73
+
74
+ state.rangeCount = state.selection.rangeCount
75
+ if (!state.rangeCount) {
76
+ const range = state.doc.createRange()
77
+ range.selectNodeContents(state.doc.body)
78
+ range.collapse(false)
79
+ state.selection.removeAllRanges()
80
+ state.selection.addRange(range)
81
+ }
82
+ state.range = state.selection.getRangeAt(0).cloneRange()
83
+ updateActiveStyles()
84
+ } catch (e) {
85
+ console.warn('Selection error:', e)
86
+ state.selection = null
87
+ state.range = null
88
+ state.rangeCount = 0
89
+ state.selectedStyles = new Set()
90
+ }
91
+ }
92
+
93
+ const format = () => formatting(state)
94
+
95
+ const updateContent = (type?: string) => {
96
+ if (!state.doc) return
97
+ if (type === 'html') {
98
+ state.doc.body.innerHTML = state.content
99
+ } else {
100
+ state.content = state.doc.body.innerHTML
101
+ }
102
+ updateActiveStyles()
103
+ }
104
+
105
+ const handleToolbarAction = (action: string, value?: string) => {
106
+ if (!state.doc) return
107
+ if (action === 'fullScreen') {
108
+ state.isFullscreen = !state.isFullscreen
109
+ return
110
+ }
111
+ if (action === 'splitView') {
112
+ state.isSplitView = !state.isSplitView
113
+ return
114
+ }
115
+ if (action === 'codeView') {
116
+ state.isCodeView = !state.isCodeView
117
+ return
118
+ }
119
+
120
+ const applyFormatting = (command: string, value?: string) => {
121
+ const { selection, range } = state
122
+ if (!state.doc || !state.modal || !selection || !range) return
123
+
124
+ // Check if there is a selection or just a caret position
125
+ const isCaret = selection.isCollapsed
126
+
127
+ if (!state.doc.body.contains(selection.anchorNode)) {
128
+ state.doc.body.focus()
129
+ }
130
+
131
+ switch (command) {
132
+ case 'mergeCells':
133
+ if (isCaret) return
134
+ mergeCells(range, state.doc)
135
+ break
136
+ case 'splitCells':
137
+ if (isCaret) return
138
+ splitCell(range, state.doc)
139
+ break
140
+ case 'addRowBefore':
141
+ case 'addRowAfter':
142
+ if (isCaret) return
143
+ addRow(
144
+ command === 'addRowBefore' ? 'before' : 'after',
145
+ range,
146
+ state.doc
147
+ )
148
+ break
149
+ case 'deleteRow':
150
+ if (isCaret) return
151
+ deleteRow(range)
152
+ break
153
+ case 'bold':
154
+ case 'italic':
155
+ case 'underline':
156
+ format().text(command)
157
+ break
158
+ case 'orderedList':
159
+ case 'unorderedList':
160
+ format().list(command)
161
+ break
162
+ case 'image':
163
+ case 'youtube':
164
+ if (isCaret) return
165
+ insertImage(state.modal, state.doc, range)
166
+ break
167
+ case 'link':
168
+ if (isCaret) return
169
+ insertLink(state.modal, state.doc, range)
170
+ break
171
+ default:
172
+ if (/^h[1-6]$/.test(command)) {
173
+ format().block(command, value || command)
174
+ }
175
+ }
176
+ }
177
+ state.doc.body.focus()
178
+ applyFormatting(action, value)
179
+ updateContent()
180
+ updateActiveStyles()
181
+ }
182
+ const setupEventListeners = () => {
183
+ if (!state.doc) return
184
+ state.doc.addEventListener('selectionchange', () => { updateSelection() })
185
+ state.doc.addEventListener('input', () => { updateContent() })
186
+ state.doc.addEventListener('mouseup', () => { updateSelection() })
187
+ state.doc.addEventListener('keyup', (e) => {
188
+ if (['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight'].includes(e.key)) {
189
+ updateSelection()
190
+ }
191
+ })
192
+ }
193
+
194
+ const init = (doc: Document, modal: Modal) => {
195
+ state.doc = doc
196
+ state.modal = modal
197
+ setupEventListeners()
198
+ updateContent('html')
199
+ state.hasInit = true
200
+ }
201
+
202
+ return {
203
+ state,
204
+ init,
205
+ handleToolbarAction,
206
+ updateContent,
207
+ }
208
+ }
@@ -0,0 +1,21 @@
1
+
2
+ export function useEditorKeyboard(doc: Document, handleToolbarAction: (action: string, value?: string) => void): void {
3
+ doc.addEventListener('keydown', (e) => {
4
+ if (e.ctrlKey || e.metaKey) {
5
+ switch (e.key) {
6
+ case 'b':
7
+ e.preventDefault()
8
+ handleToolbarAction('bold')
9
+ break
10
+ case 'i':
11
+ e.preventDefault()
12
+ handleToolbarAction('italic')
13
+ break
14
+ case 'u':
15
+ e.preventDefault()
16
+ handleToolbarAction('underline')
17
+ break
18
+ }
19
+ }
20
+ })
21
+ }
@@ -0,0 +1,73 @@
1
+ import type { ToolbarConfig, ToolbarOption } from './richTextTypes'
2
+
3
+ export const tableTools: ToolbarConfig = [
4
+ 'mergeCells',
5
+ 'splitCells',
6
+ 'addRowBefore',
7
+ 'addRowAfter',
8
+ 'deleteRow'
9
+ ]
10
+
11
+ export const defaultToolbarConfig: ToolbarConfig = [
12
+ 'h2',
13
+ 'h3',
14
+ 'h4',
15
+ 'h5',
16
+ 'h6',
17
+ 'separator',
18
+ 'bold',
19
+ 'italic',
20
+ 'underline',
21
+ 'separator',
22
+ 'orderedList',
23
+ 'unorderedList',
24
+ 'separator',
25
+ 'link',
26
+ 'image',
27
+ 'youtube',
28
+ 'separator',
29
+ 'splitView',
30
+ 'clear',
31
+ 'insertTable',
32
+ ...tableTools, // Adding table tools to the default toolbar
33
+ 'fullScreen',
34
+ ]
35
+
36
+ export const toolbarOptions: ToolbarOption[] = [
37
+ { name: 'h2', label: 'h2', icon: 'format_h2' },
38
+ { name: 'h3', label: 'h3', icon: 'format_h3' },
39
+ { name: 'h4', label: 'h4', icon: 'format_h4' },
40
+ { name: 'h5', label: 'h5', icon: 'format_h5' },
41
+ { name: 'h6', label: 'h6', icon: 'format_h6' },
42
+ { name: 'bold', label: 'Bold', icon: 'format_bold' },
43
+ { name: 'italic', label: 'Italic', icon: 'format_italic' },
44
+ { name: 'underline', label: 'Underline', icon: 'format_underlined' },
45
+ { name: 'orderedList', label: 'Ordered List', icon: 'format_list_numbered' },
46
+ { name: 'unorderedList', label: 'Unordered List', icon: 'format_list_bulleted' },
47
+ { name: 'link', label: 'Link', icon: 'add_link' },
48
+ { name: 'image', label: 'Image', icon: 'add_photo_alternate' },
49
+ { name: 'youtube', label: 'YouTube', icon: 'youtube_activity' },
50
+ { name: 'splitView', label: 'Split View', icon: 'code' },
51
+ { name: 'clear', label: 'Clear Formatting', icon: 'format_clear' },
52
+ { name: 'alignLeft', label: 'Align Left', icon: 'format_align_left' },
53
+ { name: 'alignCenter', label: 'Align Center', icon: 'format_align_center' },
54
+ { name: 'alignRight', label: 'Align Right', icon: 'format_align_right' },
55
+ { name: 'alignJustify', label: 'Align Justify', icon: 'format_align_justify' },
56
+ { name: 'indent', label: 'Indent', icon: 'format_indent_increase' },
57
+ { name: 'outdent', label: 'Outdent', icon: 'format_indent_decrease' },
58
+ { name: 'fontColor', label: 'Font Color', icon: 'format_color_text' },
59
+ { name: 'bgColor', label: 'Background Color', icon: 'format_color_fill' },
60
+ { name: 'insertTable', label: 'Insert Table', icon: 'table' },
61
+ { name: 'deleteTable', label: 'Delete Table', icon: 'table_rows' },
62
+ { name: 'insertRowAbove', label: 'Insert Row Above', icon: 'table_rows' },
63
+ { name: 'insertRowBelow', label: 'Insert Row Below', icon: 'table_rows' },
64
+ { name: 'deleteRow', label: 'Delete Row', icon: 'table_rows' },
65
+ { name: 'insertColumnLeft', label: 'Insert Column Left', icon: 'add_column_left' },
66
+ { name: 'insertColumnRight', label: 'Insert Column Right', icon: 'add_column_right' },
67
+ { name: 'deleteColumn', label: 'Delete Column', icon: 'view_column' },
68
+ { name: 'separator' },
69
+ { name: 'undo', label: 'Undo', icon: 'undo' },
70
+ { name: 'redo', label: 'Redo', icon: 'redo' },
71
+ { name: 'separator' },
72
+ { name: 'fullScreen', label: 'Full Screen', icon: 'fullscreen', class: 'ms-auto' },
73
+ ]
@@ -0,0 +1,25 @@
1
+ body {
2
+ margin: 0;
3
+ padding: 8px;
4
+ min-height: 200px;
5
+ font-family: sans-serif;
6
+ color: inherit;
7
+ background: transparent;
8
+ }
9
+
10
+ table {
11
+ border-collapse: collapse;
12
+ margin-bottom: 1rem;
13
+ }
14
+
15
+ th,
16
+ td {
17
+ padding: 1rem;
18
+ text-align: left;
19
+ border: 1px solid #ddd;
20
+ line-height: 1.5;
21
+ }
22
+
23
+ th {
24
+ background-color: #f4f4f4;
25
+ }