@bagelink/vue 0.0.984 → 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 (65) hide show
  1. package/dist/components/Btn.vue.d.ts.map +1 -1
  2. package/dist/components/DropDown.vue.d.ts +2 -2
  3. package/dist/components/DropDown.vue.d.ts.map +1 -1
  4. package/dist/components/form/inputs/CodeEditor/CodeTypes.d.ts +197 -0
  5. package/dist/components/form/inputs/CodeEditor/CodeTypes.d.ts.map +1 -0
  6. package/dist/components/form/inputs/CodeEditor/Index.vue.d.ts +60 -0
  7. package/dist/components/form/inputs/CodeEditor/Index.vue.d.ts.map +1 -0
  8. package/dist/components/form/inputs/CodeEditor/format.d.ts +2 -0
  9. package/dist/components/form/inputs/CodeEditor/format.d.ts.map +1 -0
  10. package/dist/components/form/inputs/RichText/Toolbar.vue.d.ts +5 -3
  11. package/dist/components/form/inputs/RichText/Toolbar.vue.d.ts.map +1 -1
  12. package/dist/components/form/inputs/RichText/components/Toolbar.vue.d.ts +14 -0
  13. package/dist/components/form/inputs/RichText/components/Toolbar.vue.d.ts.map +1 -0
  14. package/dist/components/form/inputs/RichText/components/gridBox.vue.d.ts +11 -0
  15. package/dist/components/form/inputs/RichText/components/gridBox.vue.d.ts.map +1 -0
  16. package/dist/components/form/inputs/RichText/composables/useEditor.d.ts +86 -0
  17. package/dist/components/form/inputs/RichText/composables/useEditor.d.ts.map +1 -0
  18. package/dist/components/form/inputs/RichText/composables/useEditorKeyboard.d.ts +2 -0
  19. package/dist/components/form/inputs/RichText/composables/useEditorKeyboard.d.ts.map +1 -0
  20. package/dist/components/form/inputs/RichText/config.d.ts +5 -0
  21. package/dist/components/form/inputs/RichText/config.d.ts.map +1 -0
  22. package/dist/components/form/inputs/RichText/index.vue.d.ts +1 -1
  23. package/dist/components/form/inputs/RichText/index.vue.d.ts.map +1 -1
  24. package/dist/components/form/inputs/RichText/utils/formatting.d.ts +7 -0
  25. package/dist/components/form/inputs/RichText/utils/formatting.d.ts.map +1 -0
  26. package/dist/components/form/inputs/RichText/utils/media.d.ts +4 -0
  27. package/dist/components/form/inputs/RichText/utils/media.d.ts.map +1 -0
  28. package/dist/components/form/inputs/RichText/utils/selection.d.ts +4 -0
  29. package/dist/components/form/inputs/RichText/utils/selection.d.ts.map +1 -0
  30. package/dist/components/form/inputs/RichText/utils/table.d.ts +6 -0
  31. package/dist/components/form/inputs/RichText/utils/table.d.ts.map +1 -0
  32. package/dist/components/form/inputs/SelectInput.vue.d.ts +2 -2
  33. package/dist/components/form/inputs/index.d.ts +1 -0
  34. package/dist/components/form/inputs/index.d.ts.map +1 -1
  35. package/dist/editor-CUDRLdmS.js +4 -0
  36. package/dist/editor-Cu374vEW.cjs +4 -0
  37. package/dist/index.cjs +53544 -766
  38. package/dist/index.mjs +53545 -767
  39. package/dist/style.css +19779 -97
  40. package/package.json +2 -1
  41. package/src/components/Btn.vue +3 -0
  42. package/src/components/Dropdown.vue +1 -1
  43. package/src/components/form/inputs/CodeEditor/CodeTypes.ts +446 -0
  44. package/src/components/form/inputs/CodeEditor/Index.vue +440 -0
  45. package/src/components/form/inputs/CodeEditor/format.ts +98 -0
  46. package/src/components/form/inputs/CodeEditor/themes/brown-papersq.png +0 -0
  47. package/src/components/form/inputs/CodeEditor/themes/pojoaque.jpg +0 -0
  48. package/src/components/form/inputs/CodeEditor/themes/themes-base16.css +12809 -0
  49. package/src/components/form/inputs/CodeEditor/themes/themes.css +6740 -0
  50. package/src/components/form/inputs/RichText/components/Toolbar.vue +51 -0
  51. package/src/components/form/inputs/RichText/components/gridBox.vue +22 -0
  52. package/src/components/form/inputs/RichText/composables/useEditor.ts +208 -0
  53. package/src/components/form/inputs/RichText/composables/useEditorKeyboard.ts +21 -0
  54. package/src/components/form/inputs/RichText/config.ts +73 -0
  55. package/src/components/form/inputs/RichText/editor.css +25 -0
  56. package/src/components/form/inputs/RichText/index.vue +81 -193
  57. package/src/components/form/inputs/RichText/richTextTypes.d.ts +77 -0
  58. package/src/components/form/inputs/RichText/utils/formatting.ts +98 -0
  59. package/src/components/form/inputs/RichText/utils/media.ts +42 -0
  60. package/src/components/form/inputs/RichText/utils/selection.ts +48 -0
  61. package/src/components/form/inputs/RichText/utils/table.ts +79 -0
  62. package/src/components/form/inputs/index.ts +1 -0
  63. package/src/components/form/inputs/RichText/Toolbar.vue +0 -87
  64. package/src/components/form/inputs/RichText/formatting.ts +0 -246
  65. package/src/components/form/inputs/RichText/richtext-types.ts +0 -29
@@ -1,253 +1,141 @@
1
- <script lang="ts" setup>
2
- import type { ToolbarConfig } from './richtext-types'
3
- import { useModal } from '@bagelink/vue'
4
- import { onMounted, watch, ref } from 'vue'
5
- import { useFormatting } from './formatting'
6
- import Toolbar from './Toolbar.vue'
1
+ <script setup lang="ts">
2
+ import type { ToolbarConfig } from './richTextTypes'
3
+ import { useModal, CodeEditor } from '@bagelink/vue'
4
+ import { watch } from 'vue'
5
+ import Toolbar from './components/Toolbar.vue'
6
+ import { useEditor } from './composables/useEditor'
7
+ import { useEditorKeyboard } from './composables/useEditorKeyboard'
7
8
 
8
9
  const props = defineProps<{ modelValue: string, toolbarConfig?: ToolbarConfig }>()
9
10
  const emit = defineEmits(['update:modelValue'])
10
11
 
12
+ const iframe = $ref<HTMLIFrameElement>()
11
13
  const modal = useModal()
12
- const { applyFormatting, clearFormatting } = useFormatting(modal)
13
-
14
- const editableContent = ref<HTMLElement>()
15
- const defaultConfig: ToolbarConfig = [
16
- 'formatBlock',
17
- 'bold',
18
- 'italic',
19
- 'underline',
20
- 'fontSize',
21
- 'fontFamily',
22
- 'textColor',
23
- 'backgroundColor',
24
- 'alignLeft',
25
- 'alignCenter',
26
- 'alignRight',
27
- 'alignJustify',
28
- 'orderedList',
29
- 'unorderedList',
30
- 'indent',
31
- 'outdent',
32
- 'link',
33
- 'image',
34
- 'table',
35
- 'splitView',
36
- 'clear',
37
- 'fullScreen'
38
- ]
39
-
40
- const config = ref<ToolbarConfig>(props.toolbarConfig || defaultConfig)
41
- const contentHtml = ref(props.modelValue)
42
- const isCodeView = ref(false)
43
- const isSplitView = ref(false)
44
- const selectedStyles = ref(new Set<string>())
45
-
46
- function updateContent(source: 'html' | 'editor' = 'editor') {
47
- if (source === 'editor' && !isCodeView.value) {
48
- contentHtml.value = editableContent.value?.innerHTML ?? ''
49
- }
50
- emit('update:modelValue', contentHtml.value)
51
- }
14
+ const editor = useEditor()
52
15
 
53
- function handleHtmlInput(e: Event) {
54
- const target = e.target as HTMLTextAreaElement
55
- contentHtml.value = target.value
56
- if (editableContent.value) {
57
- editableContent.value.innerHTML = target.value
16
+ async function initEditor() {
17
+ if (!iframe) {
18
+ setTimeout(initEditor, 100)
19
+ return
58
20
  }
59
- updateContent('html')
60
- }
61
-
62
- function updateToolbarHighlight() {
63
- selectedStyles.value.clear()
64
- const selection = window.getSelection()
65
- if (!selection?.rangeCount) return
66
-
67
- const range = selection.getRangeAt(0)
68
- const element = range.commonAncestorContainer as HTMLElement
69
- const parent = element.nodeType === 3 ? element.parentElement : element
21
+ editor.state.content = props.modelValue || ''
22
+ const doc = iframe.contentDocument || iframe.contentWindow?.document
23
+ if (!doc) return
70
24
 
71
- if (!parent) return
25
+ doc.designMode = 'on'
26
+ doc.body.contentEditable = 'true'
72
27
 
73
- if (parent.style.fontWeight === 'bold') selectedStyles.value.add('bold')
74
- if (parent.style.fontStyle === 'italic') selectedStyles.value.add('italic')
75
- if (parent.style.textDecoration === 'underline') selectedStyles.value.add('underline')
76
- }
77
-
78
- function handleToolbarAction(action: string, value?: string) {
79
- if (!editableContent.value) return
28
+ const style = doc.createElement('style')
29
+ style.textContent = (await import('./editor.css?inline')).default
30
+ doc.head.appendChild(style)
80
31
 
81
- if (['alignLeft', 'alignCenter', 'alignRight', 'alignJustify'].includes(action)) {
82
- value = action.replace('align', '').toLowerCase()
83
- }
32
+ editor.init(doc, modal)
33
+ useEditorKeyboard(doc, (action, value) => {
34
+ editor.handleToolbarAction(action, value)
35
+ })
84
36
 
85
- switch (action) {
86
- case 'splitView':
87
- isSplitView.value = !isSplitView.value
88
- break
89
- case 'codeView':
90
- isCodeView.value = !isCodeView.value
91
- break
92
- case 'fullScreen':
93
- toggleFullScreen()
94
- break
95
- case 'clear':
96
- clearFormatting()
97
- break
98
- default:
99
- applyFormatting(action, value)
100
- }
101
- updateContent()
102
- }
103
-
104
- function toggleFullScreen() {
105
- const editor = document.querySelector('.rich-text-editor')
106
- editor?.classList.toggle('fullscreen-mode')
37
+ doc.body.focus()
107
38
  }
108
39
 
109
40
  watch(() => props.modelValue, (newValue) => {
110
- if (newValue !== contentHtml.value) {
111
- contentHtml.value = newValue
112
- if (editableContent.value) {
113
- editableContent.value.innerHTML = newValue
114
- }
41
+ if (newValue !== editor.state.content) {
42
+ editor.state.content = newValue
115
43
  }
116
44
  })
117
45
 
118
- onMounted(() => {
119
- if (editableContent.value) {
120
- editableContent.value.innerHTML = contentHtml.value
121
- }
122
- document.addEventListener('selectionchange', updateToolbarHighlight)
46
+ watch(() => editor.state.content, (newValue) => {
47
+ emit('update:modelValue', newValue)
123
48
  })
124
-
125
- // Keyboard shortcuts
126
- function handleKeyDown(event: KeyboardEvent) {
127
- if (event.ctrlKey || event.metaKey) {
128
- switch (event.key.toLowerCase()) {
129
- case 'b':
130
- event.preventDefault()
131
- handleToolbarAction('bold')
132
- break
133
- case 'i':
134
- event.preventDefault()
135
- handleToolbarAction('italic')
136
- break
137
- case 'u':
138
- event.preventDefault()
139
- handleToolbarAction('underline')
140
- break
141
- }
142
- }
143
- }
144
49
  </script>
145
50
 
146
51
  <template>
147
- <div class="rich-text-editor rounded pt-05 px-05 pb-1">
52
+ <div class="rich-text-editor rounded pt-05 px-05 pb-1" :class="{ 'fullscreen-mode': editor.state.isFullscreen }">
148
53
  <Toolbar
149
- :config="config"
150
- :selected-styles="selectedStyles"
151
- @action="handleToolbarAction"
54
+ v-if="editor.state.hasInit" :config="toolbarConfig" :selectedStyles="editor.state.selectedStyles"
55
+ @action="editor.handleToolbarAction"
152
56
  />
153
- <div class="editor-container" :class="{ 'split-view': isSplitView }">
154
- <div class="content-area radius-05 p-1">
155
- <textarea
156
- v-if="isCodeView"
157
- v-model="contentHtml"
158
- class="html-editor"
159
- @input="handleHtmlInput"
160
- />
161
- <div
162
- v-else
163
- ref="editableContent"
164
- contenteditable="true"
165
- class="editableContent"
166
- role="textbox"
167
- aria-multiline="true"
168
- tabindex="0"
169
- @input="updateContent()"
170
- @keydown="handleKeyDown"
171
- />
172
- </div>
173
- <div
174
- v-if="isSplitView"
175
- class="preview-area radius-05 p-1"
176
- >
177
- <pre><code v-text="contentHtml" /></pre>
57
+ <div class="editor-container" :class="{ 'split-view': editor?.state.isSplitView }">
58
+ <div class="content-area radius-05">
59
+ <iframe id="rich-text-iframe" ref="iframe" class="editableContent" title="Editor" @load="initEditor" />
178
60
  </div>
61
+ <CodeEditor
62
+ v-if="editor?.state.isSplitView" v-model="editor.state.content" :languages="['html']"
63
+ @update:modelValue="editor.updateContent('html')"
64
+ />
179
65
  </div>
180
66
  </div>
67
+ <pre wrap>
68
+ {{ editor.state }}
69
+ </pre>
181
70
  </template>
182
71
 
183
72
  <style scoped>
184
73
  .rich-text-editor {
185
- background: var(--input-bg);
186
- border: 1px solid var(--border-color);
187
- transition: all 0.3s ease;
74
+ background: var(--input-bg);
75
+ border: 1px solid var(--border-color);
76
+ transition: all 0.3s ease;
188
77
  }
189
78
 
190
79
  .editor-container {
191
- display: flex;
192
- gap: 1rem;
80
+ display: flex;
81
+ gap: 1rem;
193
82
  }
194
83
 
195
- .content-area, .preview-area {
196
- flex: 1;
197
- min-height: 200px;
198
- background: var(--bgl-richtext-color);
84
+ .content-area,
85
+ .preview-area {
86
+ flex: 1;
87
+ min-height: 200px;
88
+ background: var(--bgl-richtext-color);
199
89
  }
200
90
 
201
91
  .split-view .content-area,
202
92
  .split-view .preview-area {
203
- width: 50%;
93
+ width: 50%;
204
94
  }
205
95
 
206
96
  .editableContent {
207
- min-height: 100%;
208
- white-space: pre-wrap;
209
- outline: none;
97
+ width: 100%;
98
+ min-height: 200px;
99
+ border: none;
100
+ outline: none;
101
+ background: transparent;
210
102
  }
211
103
 
212
104
  .html-editor {
213
- width: 100%;
214
- height: 100%;
215
- min-height: 200px;
216
- font-family: monospace;
217
- background: transparent;
218
- border: none;
219
- outline: none;
220
- resize: none;
105
+ width: 100%;
106
+ height: 100%;
107
+ min-height: 200px;
108
+ font-family: monospace;
109
+ border: none;
110
+ outline: none;
111
+ resize: none;
112
+ color: white;
113
+ background-color: var(--bgl-black);
221
114
  }
222
115
 
223
116
  .preview-area {
224
- font-family: monospace;
225
- white-space: pre-wrap;
226
- overflow-x: auto;
227
- }
228
-
229
- .preview-area code {
230
- display: block;
231
- padding: 1rem;
117
+ font-family: monospace;
118
+ white-space: pre-wrap;
119
+ overflow-x: auto;
232
120
  }
233
121
 
234
122
  .fullscreen-mode {
235
- position: fixed;
236
- top: 0;
237
- left: 0;
238
- width: 100vw;
239
- height: 100vh;
240
- z-index: 9999;
241
- padding: 2rem;
123
+ position: fixed;
124
+ top: 0;
125
+ left: 0;
126
+ width: 100vw;
127
+ height: 100vh;
128
+ z-index: 9999;
129
+ padding: 2rem;
242
130
  }
243
131
 
244
132
  .fullscreen-mode .editor-container {
245
- height: calc(100vh - 8rem);
133
+ height: calc(100vh - 8rem);
246
134
  }
247
135
 
248
136
  .fullscreen-mode .content-area,
249
137
  .fullscreen-mode .preview-area {
250
- height: 100%;
251
- overflow-y: auto;
138
+ height: 100%;
139
+ overflow-y: auto;
252
140
  }
253
141
  </style>
@@ -0,0 +1,77 @@
1
+ import type { MaterialIcons } from '@bagelink/vue'
2
+
3
+ export interface Modal {
4
+ showModalForm: (options: {
5
+ title: string
6
+ schema: any[]
7
+ onSubmit: (data: any) => void
8
+ }) => void
9
+ }
10
+
11
+ export interface EditorState {
12
+ isFullscreen: boolean
13
+ hasInit: boolean
14
+ isCodeView: boolean
15
+ isSplitView: boolean
16
+ selectedStyles: Set<string>
17
+ content: string
18
+ rangeCount: number
19
+ selection?: Selection | null
20
+ range?: Range | null
21
+ doc?: Document
22
+ modal?: Modal
23
+ }
24
+
25
+ export type FormattingCommand =
26
+ | 'bold'
27
+ | 'italic'
28
+ | 'underline'
29
+ | 'formatBlock'
30
+ | 'orderedList'
31
+ | 'unorderedList'
32
+ | 'link'
33
+ | 'image'
34
+ | 'youtube'
35
+ | 'table'
36
+ | 'splitView'
37
+ | 'codeView'
38
+
39
+ export type HeadingLevel = 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6'
40
+
41
+ export type ToolbarConfigOption =
42
+ | FormattingCommand
43
+ | HeadingLevel
44
+ | 'separator'
45
+ | 'fullScreen'
46
+ | 'clear'
47
+ | 'mergeCells'
48
+ | 'splitCells'
49
+ | 'addRowBefore'
50
+ | 'addRowAfter'
51
+ | 'deleteRow'
52
+ | 'alignLeft'
53
+ | 'alignCenter'
54
+ | 'alignRight'
55
+ | 'alignJustify'
56
+ | 'indent'
57
+ | 'outdent'
58
+ | 'fontColor'
59
+ | 'bgColor'
60
+ | 'insertTable'
61
+ | 'deleteTable'
62
+ | 'insertRowAbove'
63
+ | 'insertRowBelow'
64
+ | 'insertColumnLeft'
65
+ | 'insertColumnRight'
66
+ | 'deleteColumn'
67
+ | 'undo'
68
+ | 'redo'
69
+
70
+ export type ToolbarConfig = ToolbarConfigOption[]
71
+
72
+ export interface ToolbarOption {
73
+ name: ToolbarConfigOption
74
+ label?: string
75
+ icon?: MaterialIcons
76
+ class?: string
77
+ }
@@ -0,0 +1,98 @@
1
+ import type { EditorState } from '../richTextTypes'
2
+
3
+ export function formatting(state: EditorState) {
4
+ const text = (command: string) => {
5
+ const { doc, range, selection } = state
6
+ if (!doc || !range || !selection) return
7
+ let tag = ''
8
+ if (command === 'bold') tag = 'b'
9
+ if (command === 'italic') tag = 'i'
10
+ if (command === 'underline') tag = 'u'
11
+ if (command === 'emphasis') tag = 'em'
12
+ if (command === 'strong') tag = 'strong'
13
+
14
+ const relatedTags: { [key: string]: string[] } = {
15
+ b: ['strong'],
16
+ i: ['em'],
17
+ u: [],
18
+ em: ['i'],
19
+ strong: ['b']
20
+ }
21
+
22
+ const selectedContent = range.extractContents()
23
+ const wrapper = doc.createElement(tag)
24
+ wrapper.appendChild(selectedContent)
25
+ const container = range.commonAncestorContainer
26
+ const parent = container.parentElement
27
+
28
+ if (parent && (parent.tagName.toLowerCase() === tag || relatedTags[tag].includes(parent.tagName.toLowerCase()))) {
29
+ while (wrapper.firstChild) {
30
+ parent.parentNode?.insertBefore(wrapper.firstChild, parent)
31
+ }
32
+ parent.parentNode?.removeChild(parent)
33
+ } else {
34
+ range.insertNode(wrapper)
35
+ }
36
+
37
+ const nestedTags = wrapper.querySelectorAll(`${tag},${(relatedTags[tag] || []).join(',')}`)
38
+ nestedTags.forEach((nestedTag) => {
39
+ while (nestedTag.firstChild) {
40
+ nestedTag.parentNode?.insertBefore(nestedTag.firstChild, nestedTag)
41
+ }
42
+ nestedTag.parentNode?.removeChild(nestedTag)
43
+ })
44
+
45
+ selection.removeAllRanges()
46
+ selection.addRange(range)
47
+ }
48
+ const block = (command: string, value: string) => {
49
+ const { doc, range, selection } = state
50
+ if (!doc || !range || !selection) return
51
+ const container = range.commonAncestorContainer
52
+ const parent = container.nodeType === 3 ? container.parentElement : (container as Element)
53
+ if (!parent) return
54
+
55
+ const currentHeading = parent.closest('p,h1,h2,h3,h4,h5,h6')
56
+ const { startOffset, endOffset } = range
57
+ const blockEl = currentHeading || parent.closest('p,h1,h2,h3,h4,h5,h6') || parent
58
+ range.selectNodeContents(blockEl)
59
+
60
+ const content = range.extractContents()
61
+ const newElement = doc.createElement(currentHeading && currentHeading.tagName.toLowerCase() === value ? 'p' : value)
62
+ newElement.appendChild(content)
63
+
64
+ if (currentHeading) {
65
+ currentHeading.parentNode?.replaceChild(newElement, currentHeading)
66
+ } else {
67
+ range.insertNode(newElement)
68
+ }
69
+
70
+ selection.removeAllRanges()
71
+ range.setStart(container, startOffset)
72
+ range.setEnd(container, endOffset)
73
+ selection.addRange(range)
74
+ }
75
+ const list = (command: string) => {
76
+ const { doc, range } = state
77
+ if (!doc || !range) return
78
+ const listTag = command === 'orderedList' ? 'ol' : 'ul'
79
+ const container = range.commonAncestorContainer
80
+ const parent = container.nodeType === 3 ? container.parentElement : (container as Element)
81
+ const currentList = parent?.closest('ul,ol')
82
+
83
+ if (currentList) {
84
+ const content = range.extractContents()
85
+ const p = doc.createElement('p')
86
+ p.appendChild(content)
87
+ currentList.parentNode?.replaceChild(p, currentList)
88
+ } else {
89
+ const listEl = doc.createElement(listTag)
90
+ const li = doc.createElement('li')
91
+ const content = range.extractContents()
92
+ li.appendChild(content)
93
+ listEl.appendChild(li)
94
+ range.insertNode(listEl)
95
+ }
96
+ }
97
+ return { text, block, list }
98
+ }
@@ -0,0 +1,42 @@
1
+ import type { Modal } from '../richTextTypes'
2
+
3
+ export function insertImage(modal: Modal, doc: Document, range: Range | null) {
4
+ if (!range) return
5
+
6
+ modal.showModalForm({
7
+ title: 'Upload Image',
8
+ schema: [
9
+ { id: 'src', $el: 'file', attrs: { bindkey: 'url' } },
10
+ { id: 'alt', $el: 'text', label: 'Alt Text' },
11
+ ],
12
+ onSubmit: (data: { src: string, alt: string }) => {
13
+ if (data.src) {
14
+ const img = doc.createElement('img')
15
+ img.src = data.src
16
+ img.alt = data.alt
17
+ range.deleteContents()
18
+ range.insertNode(img)
19
+ }
20
+ }
21
+ })
22
+ }
23
+
24
+ export function insertLink(modal: Modal, doc: Document, range: Range | null) {
25
+ if (!range) return
26
+
27
+ modal.showModalForm({
28
+ title: 'Insert Link',
29
+ schema: [
30
+ { id: 'url', $el: 'text', label: 'URL' },
31
+ { id: 'openInNewTab', $el: 'check', label: 'Open in new tab' },
32
+ ],
33
+ onSubmit: (data: { url: string, openInNewTab: boolean }) => {
34
+ if (data.url) {
35
+ const anchor = doc.createElement('a')
36
+ anchor.href = data.url
37
+ if (data.openInNewTab) anchor.target = '_blank'
38
+ range.surroundContents(anchor)
39
+ }
40
+ }
41
+ })
42
+ }
@@ -0,0 +1,48 @@
1
+
2
+ export function getSelection(state: any) {
3
+ return state.selection
4
+ }
5
+
6
+ export function getSelectionRange(state: any) {
7
+ return state.range
8
+ }
9
+
10
+ export function isStyleActive(style: string, doc: Document) {
11
+ const selection = doc.getSelection()
12
+ if (!selection || !selection.rangeCount) return false
13
+
14
+ const range = selection.getRangeAt(0)
15
+ const container = range.commonAncestorContainer
16
+ const parent = container.nodeType === 3 ? container.parentElement : container as Element
17
+
18
+ if (!parent) return false
19
+
20
+ const checkParent = (element: Element | null, tags: string[]): boolean => {
21
+ if (!element) return false
22
+ if (tags.includes(element.tagName.toLowerCase())) return true
23
+ return checkParent(element.parentElement, tags)
24
+ }
25
+
26
+ switch (style) {
27
+ case 'bold':
28
+ return checkParent(parent, ['strong', 'b'])
29
+ case 'italic':
30
+ return checkParent(parent, ['em', 'i'])
31
+ case 'underline':
32
+ return checkParent(parent, ['u'])
33
+ case 'h1':
34
+ return checkParent(parent, ['h1'])
35
+ case 'h2':
36
+ return checkParent(parent, ['h2'])
37
+ case 'h3':
38
+ return checkParent(parent, ['h3'])
39
+ case 'h4':
40
+ return checkParent(parent, ['h4'])
41
+ case 'h5':
42
+ return checkParent(parent, ['h5'])
43
+ case 'h6':
44
+ return checkParent(parent, ['h6'])
45
+ default:
46
+ return false
47
+ }
48
+ }
@@ -0,0 +1,79 @@
1
+
2
+ export function createTable(rows: number, cols: number, doc: Document) {
3
+ const table = doc.createElement('table')
4
+ table.style.width = '100%'
5
+ table.style.borderCollapse = 'collapse'
6
+
7
+ for (let i = 0; i < rows; i++) {
8
+ const row = table.insertRow()
9
+ for (let j = 0; j < cols; j++) {
10
+ const cell = row.insertCell()
11
+ cell.style.border = '1px solid var(--border-color)'
12
+ cell.style.padding = '8px'
13
+ cell.innerHTML = '&nbsp;'
14
+ }
15
+ }
16
+
17
+ return table
18
+ }
19
+
20
+ export function mergeCells(range: Range, doc: Document) {
21
+ const cells = Array.from(range.cloneContents().querySelectorAll('td'))
22
+ if (cells.length < 2) return
23
+
24
+ const firstCell = range.startContainer.parentElement?.closest('td')
25
+ if (!firstCell) return
26
+
27
+ firstCell.colSpan = cells.length
28
+ firstCell.innerHTML = cells.map(cell => cell.innerHTML).join(' ')
29
+
30
+ cells.slice(1).forEach((cell) => {
31
+ const actualCell = doc.getElementById(cell.id)
32
+ actualCell?.remove()
33
+ })
34
+ }
35
+
36
+ export function splitCell(range: Range, doc: Document) {
37
+ const cell = range.startContainer.parentElement?.closest('td')
38
+ if (!cell || !cell.colSpan || cell.colSpan === 1) return
39
+
40
+ const newCells = new Array(cell.colSpan - 1).fill(0).map(() => {
41
+ const newCell = doc.createElement('td')
42
+ newCell.style.border = '1px solid var(--border-color)'
43
+ newCell.style.padding = '8px'
44
+ newCell.innerHTML = '&nbsp;'
45
+ return newCell
46
+ })
47
+
48
+ cell.colSpan = 1
49
+ cell.insertAdjacentElement('afterend', newCells[0])
50
+ newCells.slice(1).forEach((newCell) => {
51
+ newCells[0].insertAdjacentElement('afterend', newCell)
52
+ })
53
+ }
54
+
55
+ export function addRow(position: 'before' | 'after', range: Range, doc: Document) {
56
+ const cell = range.startContainer.parentElement?.closest('td')
57
+ if (!cell) return
58
+
59
+ const row = cell.parentElement
60
+ const table = row?.parentElement
61
+ if (!row || !table) return
62
+
63
+ const newRow = row.cloneNode(true) as HTMLTableRowElement
64
+ Array.from(newRow.cells).forEach((cell) => {
65
+ cell.innerHTML = '&nbsp;'
66
+ })
67
+
68
+ row.insertAdjacentElement(position === 'before' ? 'beforebegin' : 'afterend', newRow)
69
+ }
70
+
71
+ export function deleteRow(range: Range) {
72
+ const cell = range.startContainer.parentElement?.closest('td')
73
+ if (!cell) return
74
+
75
+ const row = cell.parentElement
76
+ if (!row) return
77
+
78
+ row.remove()
79
+ }
@@ -1,5 +1,6 @@
1
1
  export { default as Checkbox } from './Checkbox.vue'
2
2
  export { default as CheckInput } from './CheckInput.vue'
3
+ export { default as CodeEditor } from './CodeEditor/Index.vue'
3
4
  export { default as ColorPicker } from './ColorPicker.vue'
4
5
  export { default as DateInput } from './DateInput.vue'
5
6
  export { default as DatePicker } from './DatePicker.vue'