@bagelink/vue 0.0.986 → 0.0.992

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 (75) hide show
  1. package/dist/components/Btn.vue.d.ts.map +1 -1
  2. package/dist/components/Loading.vue.d.ts +16 -0
  3. package/dist/components/Loading.vue.d.ts.map +1 -0
  4. package/dist/components/form/BglField.vue.d.ts.map +1 -1
  5. package/dist/components/form/inputs/CodeEditor/CodeTypes.d.ts +197 -0
  6. package/dist/components/form/inputs/CodeEditor/CodeTypes.d.ts.map +1 -0
  7. package/dist/components/form/inputs/CodeEditor/Index.vue.d.ts +15 -0
  8. package/dist/components/form/inputs/CodeEditor/Index.vue.d.ts.map +1 -0
  9. package/dist/components/form/inputs/CodeEditor/format.d.ts +2 -0
  10. package/dist/components/form/inputs/CodeEditor/format.d.ts.map +1 -0
  11. package/dist/components/form/inputs/FileUpload.vue.d.ts.map +1 -1
  12. package/dist/components/form/inputs/RichText/Toolbar.vue.d.ts +5 -3
  13. package/dist/components/form/inputs/RichText/Toolbar.vue.d.ts.map +1 -1
  14. package/dist/components/form/inputs/RichText/components/Toolbar.vue.d.ts +14 -0
  15. package/dist/components/form/inputs/RichText/components/Toolbar.vue.d.ts.map +1 -0
  16. package/dist/components/form/inputs/RichText/components/gridBox.vue.d.ts +7 -0
  17. package/dist/components/form/inputs/RichText/components/gridBox.vue.d.ts.map +1 -0
  18. package/dist/components/form/inputs/RichText/composables/useEditor.d.ts +86 -0
  19. package/dist/components/form/inputs/RichText/composables/useEditor.d.ts.map +1 -0
  20. package/dist/components/form/inputs/RichText/composables/useEditorKeyboard.d.ts +2 -0
  21. package/dist/components/form/inputs/RichText/composables/useEditorKeyboard.d.ts.map +1 -0
  22. package/dist/components/form/inputs/RichText/config.d.ts +5 -0
  23. package/dist/components/form/inputs/RichText/config.d.ts.map +1 -0
  24. package/dist/components/form/inputs/RichText/index.vue.d.ts +1 -1
  25. package/dist/components/form/inputs/RichText/index.vue.d.ts.map +1 -1
  26. package/dist/components/form/inputs/RichText/utils/formatting.d.ts +7 -0
  27. package/dist/components/form/inputs/RichText/utils/formatting.d.ts.map +1 -0
  28. package/dist/components/form/inputs/RichText/utils/media.d.ts +4 -0
  29. package/dist/components/form/inputs/RichText/utils/media.d.ts.map +1 -0
  30. package/dist/components/form/inputs/RichText/utils/selection.d.ts +4 -0
  31. package/dist/components/form/inputs/RichText/utils/selection.d.ts.map +1 -0
  32. package/dist/components/form/inputs/RichText/utils/table.d.ts +7 -0
  33. package/dist/components/form/inputs/RichText/utils/table.d.ts.map +1 -0
  34. package/dist/components/form/inputs/index.d.ts +1 -0
  35. package/dist/components/form/inputs/index.d.ts.map +1 -1
  36. package/dist/components/index.d.ts +1 -0
  37. package/dist/components/index.d.ts.map +1 -1
  38. package/dist/components/layout/TabsNav.vue.d.ts.map +1 -1
  39. package/dist/editor-CUDRLdmS.js +4 -0
  40. package/dist/editor-Cu374vEW.cjs +4 -0
  41. package/dist/editor-a8DSbb6P.js +4 -0
  42. package/dist/editor-xBt_vIha.cjs +4 -0
  43. package/dist/index.cjs +15594 -1103
  44. package/dist/index.mjs +15595 -1104
  45. package/dist/style.css +790 -511
  46. package/package.json +6 -33
  47. package/src/components/Btn.vue +113 -136
  48. package/src/components/Loading.vue +177 -0
  49. package/src/components/form/BglField.vue +2 -0
  50. package/src/components/form/inputs/CodeEditor/CodeTypes.ts +446 -0
  51. package/src/components/form/inputs/CodeEditor/Index.vue +117 -0
  52. package/src/components/form/inputs/CodeEditor/format.ts +98 -0
  53. package/src/components/form/inputs/FileUpload.vue +2 -1
  54. package/src/components/form/inputs/RichText/components/Toolbar.vue +51 -0
  55. package/src/components/form/inputs/RichText/components/gridBox.vue +51 -0
  56. package/src/components/form/inputs/RichText/composables/useEditor.ts +215 -0
  57. package/src/components/form/inputs/RichText/composables/useEditorKeyboard.ts +21 -0
  58. package/src/components/form/inputs/RichText/config.ts +73 -0
  59. package/src/components/form/inputs/RichText/editor.css +25 -0
  60. package/src/components/form/inputs/RichText/index.vue +84 -195
  61. package/src/components/form/inputs/RichText/richTextTypes.d.ts +77 -0
  62. package/src/components/form/inputs/RichText/utils/formatting.ts +98 -0
  63. package/src/components/form/inputs/RichText/utils/media.ts +42 -0
  64. package/src/components/form/inputs/RichText/utils/selection.ts +48 -0
  65. package/src/components/form/inputs/RichText/utils/table.ts +81 -0
  66. package/src/components/form/inputs/index.ts +1 -0
  67. package/src/components/index.ts +2 -2
  68. package/src/components/layout/TabsNav.vue +1 -0
  69. package/src/styles/theme.css +256 -256
  70. package/src/components/form/inputs/RichText/Toolbar.vue +0 -87
  71. package/src/components/form/inputs/RichText/formatting.ts +0 -246
  72. package/src/components/form/inputs/RichText/richtext-types.ts +0 -29
  73. package/src/components/formkit/FileUploader.vue +0 -406
  74. package/src/components/formkit/MiscFields.vue +0 -74
  75. package/src/components/formkit/Toggle.vue +0 -149
@@ -1,253 +1,142 @@
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
- }
84
-
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
- }
32
+ editor.init(doc, modal)
33
+ useEditorKeyboard(doc, (action, value) => {
34
+ editor.handleToolbarAction(action, value)
35
+ })
103
36
 
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-075" :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" language="html"
63
+ @update:modelValue="editor.updateContent('html')"
64
+ />
179
65
  </div>
180
66
  </div>
181
67
  </template>
182
68
 
183
69
  <style scoped>
184
70
  .rich-text-editor {
185
- background: var(--input-bg);
186
- border: 1px solid var(--border-color);
187
- transition: all 0.3s ease;
71
+ background: var(--input-bg);
72
+ border: 1px solid var(--border-color);
73
+ transition: all 0.3s ease;
188
74
  }
189
75
 
190
76
  .editor-container {
191
- display: flex;
192
- gap: 1rem;
77
+ display: flex;
78
+ gap: 1rem;
193
79
  }
194
80
 
195
- .content-area, .preview-area {
196
- flex: 1;
197
- min-height: 200px;
198
- background: var(--bgl-richtext-color);
81
+ .content-area,
82
+ .preview-area {
83
+ flex: 1;
84
+ min-height: 200px;
85
+ background: var(--bgl-richtext-color);
199
86
  }
200
87
 
201
- .split-view .content-area,
202
- .split-view .preview-area {
203
- width: 50%;
88
+ .split-view {
89
+ display: grid;
90
+ grid-template-columns: 1fr 1fr;
204
91
  }
205
92
 
206
93
  .editableContent {
207
- min-height: 100%;
208
- white-space: pre-wrap;
209
- outline: none;
94
+ width: 100%;
95
+ min-height: 240px;
96
+ height: 100%;
97
+ border: none;
98
+ outline: none;
99
+ background: transparent;
210
100
  }
211
101
 
212
102
  .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;
103
+ width: 100%;
104
+ height: 100%;
105
+ min-height: 200px;
106
+ font-family: monospace;
107
+ border: none;
108
+ outline: none;
109
+ resize: none;
110
+ color: white;
111
+ background-color: var(--bgl-black);
221
112
  }
222
113
 
223
114
  .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;
115
+ font-family: monospace;
116
+ white-space: pre-wrap;
117
+ overflow-x: auto;
232
118
  }
233
119
 
234
120
  .fullscreen-mode {
235
- position: fixed;
236
- top: 0;
237
- left: 0;
238
- width: 100vw;
239
- height: 100vh;
240
- z-index: 9999;
241
- padding: 2rem;
121
+ position: fixed;
122
+ top: 0;
123
+ left: 0;
124
+ width: 100vw;
125
+ height: 100vh;
126
+ z-index: 9999;
127
+ padding: 2rem;
242
128
  }
243
129
 
244
130
  .fullscreen-mode .editor-container {
245
- height: calc(100vh - 8rem);
131
+ height: calc(100vh - 4rem);
246
132
  }
247
133
 
248
134
  .fullscreen-mode .content-area,
249
135
  .fullscreen-mode .preview-area {
250
- height: 100%;
251
- overflow-y: auto;
136
+ height: 100%;
137
+ overflow-y: auto;
138
+ }
139
+ .fullscreen-mode .code-editor{
140
+ height: 100% !important;
252
141
  }
253
142
  </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,81 @@
1
+ import type { EditorState } from '../richTextTypes'
2
+
3
+ export function insertTable(rows: number, cols: number, state: EditorState) {
4
+ if (!state.doc) return
5
+ const table = state.doc.createElement('table')
6
+ table.style.width = '100%'
7
+ table.style.borderCollapse = 'collapse'
8
+
9
+ for (let i = 0; i < rows; i++) {
10
+ const row = table.insertRow()
11
+ for (let j = 0; j < cols; j++) {
12
+ const cell = row.insertCell()
13
+ cell.innerHTML = '&nbsp;'
14
+ }
15
+ }
16
+ const { range } = state
17
+ if (range) {
18
+ range.insertNode(table)
19
+ } else {
20
+ state.doc.body.appendChild(table)
21
+ }
22
+ }
23
+
24
+ export function mergeCells(range: Range, doc: Document) {
25
+ const cells = Array.from(range.cloneContents().querySelectorAll('td'))
26
+ if (cells.length < 2) return
27
+
28
+ const firstCell = range.startContainer.parentElement?.closest('td')
29
+ if (!firstCell) return
30
+
31
+ firstCell.colSpan = cells.length
32
+ firstCell.innerHTML = cells.map(cell => cell.innerHTML).join(' ')
33
+
34
+ cells.slice(1).forEach((cell) => {
35
+ const actualCell = doc.getElementById(cell.id)
36
+ actualCell?.remove()
37
+ })
38
+ }
39
+
40
+ export function splitCell(range: Range, doc: Document) {
41
+ const cell = range.startContainer.parentElement?.closest('td')
42
+ if (!cell || !cell.colSpan || cell.colSpan === 1) return
43
+
44
+ const newCells = new Array(cell.colSpan - 1).fill(0).map(() => {
45
+ const newCell = doc.createElement('td')
46
+ newCell.style.border = '1px solid var(--border-color)'
47
+ newCell.style.padding = '8px'
48
+ newCell.innerHTML = '&nbsp;'
49
+ return newCell
50
+ })
51
+
52
+ cell.colSpan = 1
53
+ cell.insertAdjacentElement('afterend', newCells[0])
54
+ newCells.slice(1).forEach((newCell) => {
55
+ newCells[0].insertAdjacentElement('afterend', newCell)
56
+ })
57
+ }
58
+
59
+ export function addRow(position: 'before' | 'after', range: Range, doc: Document) {
60
+ const cell = range.startContainer.parentElement?.closest('td')
61
+ if (!cell) return
62
+
63
+ const row = cell.parentElement
64
+ const table = row?.parentElement
65
+ if (!row || !table) return
66
+
67
+ const newRow = row.cloneNode(true) as HTMLTableRowElement
68
+ Array.from(newRow.cells).forEach(cell => cell.innerHTML = '&nbsp;')
69
+
70
+ row.insertAdjacentElement(position === 'before' ? 'beforebegin' : 'afterend', newRow)
71
+ }
72
+
73
+ export function deleteRow(range: Range) {
74
+ const cell = range.startContainer.parentElement?.closest('td')
75
+ if (!cell) return
76
+
77
+ const row = cell.parentElement
78
+ if (!row) return
79
+
80
+ row.remove()
81
+ }
@@ -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'