@bagelink/vue 0.0.978 → 0.0.980

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.
@@ -1,7 +1,7 @@
1
1
  <script lang="ts" setup>
2
2
  import type { ToolbarConfig } from './richtext-types'
3
3
  import { useModal } from '@bagelink/vue'
4
- import { onMounted, watch } from 'vue'
4
+ import { onMounted, watch, ref } from 'vue'
5
5
  import { useFormatting } from './formatting'
6
6
  import Toolbar from './Toolbar.vue'
7
7
 
@@ -11,7 +11,7 @@ const emit = defineEmits(['update:modelValue'])
11
11
  const modal = useModal()
12
12
  const { applyFormatting, clearFormatting } = useFormatting(modal)
13
13
 
14
- const editableContent = $ref<HTMLElement | undefined>()
14
+ const editableContent = ref<HTMLElement>()
15
15
  const defaultConfig: ToolbarConfig = [
16
16
  'formatBlock',
17
17
  'bold',
@@ -32,81 +32,94 @@ const defaultConfig: ToolbarConfig = [
32
32
  'link',
33
33
  'image',
34
34
  'table',
35
- 'blockquote',
36
- 'codeBlock',
37
35
  'splitView',
38
- 'youtube',
39
- 'codeView',
40
36
  'clear',
41
37
  'fullScreen'
42
38
  ]
43
- const config = $ref<ToolbarConfig>(props.toolbarConfig || defaultConfig)
44
- let contentHtml = $ref(props.modelValue)
45
- let isCodeView = $ref(false)
46
- let isSplitView = $ref(false)
47
39
 
48
- function updateContent() {
49
- if (!isCodeView) {
50
- contentHtml = editableContent?.innerHTML ?? ''
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 ?? ''
51
49
  }
52
- emit('update:modelValue', contentHtml)
50
+ emit('update:modelValue', contentHtml.value)
53
51
  }
54
52
 
55
- function updateToolbarHighlight() {
56
- if (document.getSelection()) {
57
- const selection = document.getSelection()
58
- const range = selection?.rangeCount ? selection.getRangeAt(0) : null
59
- const container = range?.commonAncestorContainer as HTMLElement
60
- if (container) {
61
- let currentElement = container.nodeType === 3 ? container.parentElement : container
62
- while (currentElement && currentElement !== editableContent) {
63
- if (['H1', 'H2', 'H3'].includes(currentElement.tagName)) {
64
- // emit('action', { type: currentElement.tagName.toLowerCase() }); // Emit the tag to highlight button
65
- break
66
- }
67
- currentElement = currentElement.parentElement
68
- }
69
- }
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
70
58
  }
59
+ updateContent('html')
71
60
  }
72
61
 
73
- document.addEventListener('selectionchange', updateToolbarHighlight)
62
+ function updateToolbarHighlight() {
63
+ selectedStyles.value.clear()
64
+ const selection = window.getSelection()
65
+ if (!selection?.rangeCount) return
74
66
 
75
- // type ToolbarAction = 'link' | 'image' | 'table' | 'youtube' | 'fontSize' | 'fontFamily' | 'textColor' | 'backgroundColor' | 'bold' | 'italic' | 'underline' | 'alignLeft' | 'alignCenter' | 'alignRight' | 'alignJustify' | 'orderedList' | 'unorderedList' | 'indent' | 'outdent' | 'blockquote' | 'codeBlock' | 'splitView' | 'codeView' | 'clear' | 'fullScreen'
76
- export type ToolbarAction = ToolbarConfig[number]
67
+ const range = selection.getRangeAt(0)
68
+ const element = range.commonAncestorContainer as HTMLElement
69
+ const parent = element.nodeType === 3 ? element.parentElement : element
70
+
71
+ if (!parent) return
72
+
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
77
 
78
78
  function handleToolbarAction(action: string, value?: string) {
79
- if (!editableContent) return
80
- if (['alignLeft', 'alignCenter', 'alignRight', 'alignJustify'].includes(action))
79
+ if (!editableContent.value) return
80
+
81
+ if (['alignLeft', 'alignCenter', 'alignRight', 'alignJustify'].includes(action)) {
81
82
  value = action.replace('align', '').toLowerCase()
82
- if (action === 'splitView') isSplitView = !isSplitView
83
- else if (action === 'codeView') isCodeView = !isCodeView
84
- else if (action === 'fullScreen') toggleFullScreen()
85
- else if (action === 'clear') clearFormatting()
86
- else applyFormatting(action, value)
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
+ }
87
101
  updateContent()
88
102
  }
89
103
 
90
104
  function toggleFullScreen() {
91
105
  const editor = document.querySelector('.rich-text-editor')
92
- if (editor) {
93
- editor.classList.toggle('fullscreen-mode')
94
- }
106
+ editor?.classList.toggle('fullscreen-mode')
95
107
  }
96
108
 
97
109
  watch(() => props.modelValue, (newValue) => {
98
- if (newValue !== contentHtml) {
99
- contentHtml = newValue
100
- if (editableContent) {
101
- editableContent.innerHTML = newValue
110
+ if (newValue !== contentHtml.value) {
111
+ contentHtml.value = newValue
112
+ if (editableContent.value) {
113
+ editableContent.value.innerHTML = newValue
102
114
  }
103
115
  }
104
116
  })
105
117
 
106
118
  onMounted(() => {
107
- if (editableContent) {
108
- editableContent.innerHTML = contentHtml
119
+ if (editableContent.value) {
120
+ editableContent.value.innerHTML = contentHtml.value
109
121
  }
122
+ document.addEventListener('selectionchange', updateToolbarHighlight)
110
123
  })
111
124
 
112
125
  // Keyboard shortcuts
@@ -125,31 +138,26 @@ function handleKeyDown(event: KeyboardEvent) {
125
138
  event.preventDefault()
126
139
  handleToolbarAction('underline')
127
140
  break
128
- case 'k':
129
- event.preventDefault()
130
- handleToolbarAction('link')
131
- break
132
- case 'l':
133
- event.preventDefault()
134
- handleToolbarAction('orderedList')
135
- break
136
- default:
137
- break
138
141
  }
139
142
  }
140
143
  }
141
- function logInput(e: InputEvent) {
142
- const target = e.target as HTMLElement
143
- contentHtml = target.textContent || ''
144
- }
145
144
  </script>
146
145
 
147
146
  <template>
148
147
  <div class="rich-text-editor rounded pt-05 px-05 pb-1">
149
- <Toolbar :config @action="handleToolbarAction" />
150
- <div class="editor-container flex flex-stretch gap-1 m_column">
151
- <div class="content-area radius-05 p-1 w-100 grid">
152
- <textarea v-if="isCodeView" v-model="contentHtml" @input="updateContent" />
148
+ <Toolbar
149
+ :config="config"
150
+ :selected-styles="selectedStyles"
151
+ @action="handleToolbarAction"
152
+ />
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
+ />
153
161
  <div
154
162
  v-else
155
163
  ref="editableContent"
@@ -158,119 +166,36 @@ function logInput(e: InputEvent) {
158
166
  role="textbox"
159
167
  aria-multiline="true"
160
168
  tabindex="0"
161
- @input="updateContent"
169
+ @input="updateContent()"
162
170
  @keydown="handleKeyDown"
163
171
  />
164
172
  </div>
165
- <code
173
+ <div
166
174
  v-if="isSplitView"
167
- contenteditable="true"
168
- class="preview-area w-100 radius-05 p-1"
169
- @input="(e) => logInput(e as InputEvent)"
170
- v-text="contentHtml"
171
- />
175
+ class="preview-area radius-05 p-1"
176
+ >
177
+ <pre><code v-text="contentHtml" /></pre>
178
+ </div>
172
179
  </div>
173
180
  </div>
174
181
  </template>
175
182
 
176
183
  <style scoped>
177
- @font-face {
178
- font-family: 'FontWithASyntaxHighlighter';
179
- font-style: normal;
180
- font-weight: 400;
181
- font-stretch: 100%;
182
- font-display: swap;
183
- src: url(https://bagel.sfo2.cdn.digitaloceanspaces.com/Fonts/FontWithASyntaxHighlighter-Regular.woff2) format('woff2')
184
- }
185
-
186
- @font-palette-values --myCustomPalette {
187
- font-family: 'FontWithASyntaxHighlighter';
188
- override-colors:
189
- 0 #6191c2, /* keywords, {} */
190
- 1 #75975d, /* comments */
191
- 2 yellow, /* literals */
192
- 3 #a3c19a, /* numbers */
193
- 4 lightblue, /* functions, [] */
194
- 5 orange, /* js others */
195
- 6 black, /* not in use */
196
- 7 #bc8abd, /* inside quotes, css properties, few chars */
197
- 8 #818181 /* few chars */
198
- ;
199
- }
200
- .preview-area{
201
- font-family: "FontWithASyntaxHighlighter", monospace;
202
- font-palette: --myCustomPalette;
203
- background: var(--bgl-code-bg);
204
- color: var(--bgl-code-color)
205
-
206
- }
207
184
  .rich-text-editor {
208
- background: var(--input-bg);
209
- margin-bottom: 0.5rem;
210
- width: 100%;
211
- }
212
- .editableContent{
213
- min-height: 100%;
214
- white-space: pre-wrap;
215
- outline: none;
185
+ background: var(--input-bg);
186
+ border: 1px solid var(--border-color);
187
+ transition: all 0.3s ease;
216
188
  }
217
- .content-area{
218
- background: var(--bgl-richtext-color);
219
- }
220
- .content-area, .preview-area{
221
- min-height: 200px;
222
- }
223
- .fullscreen-mode {
224
- position: fixed;
225
- top: 0;
226
- left: 0;
227
- width: 100%;
228
- height: 100%;
229
- z-index: 9999;
230
- background: var(--input-bg);
231
- padding: 2rem;
232
- overflow: auto;
233
- border-radius: 0;
234
- }
235
- .fullscreen-mode .content-area{
236
- height: calc(100vh - 5rem);
237
- padding: 4rem !important;
238
- max-width: 900px;
239
- margin-inline: auto ;
240
- overflow-y: auto;
241
- }
242
- .fullscreen-mode .toolbar{
243
- max-width: 900px;
244
- margin-inline: auto ;
245
- /* border-bottom: 1px solid var(--border-color); */
246
- /* margin-bottom: 2rem; */
247
- }
248
-
249
- /* [contenteditable='true'] {
250
- white-space: pre-wrap;
251
- word-wrap: break-word;
252
- outline: none;
253
- height: 100%;
254
- } */
255
- /*
256
189
 
257
190
  .editor-container {
258
191
  display: flex;
259
- border: 1px solid #ccc;
260
- border-radius: 4px;
261
- overflow: hidden;
192
+ gap: 1rem;
262
193
  }
263
194
 
264
- .content-area,
265
- .preview-area {
195
+ .content-area, .preview-area {
266
196
  flex: 1;
267
- min-height: 300px;
268
- padding: 10px;
269
- overflow-y: auto;
270
- }
271
-
272
- .content-area:focus {
273
- outline: none;
197
+ min-height: 200px;
198
+ background: var(--bgl-richtext-color);
274
199
  }
275
200
 
276
201
  .split-view .content-area,
@@ -278,24 +203,51 @@ function logInput(e: InputEvent) {
278
203
  width: 50%;
279
204
  }
280
205
 
206
+ .editableContent {
207
+ min-height: 100%;
208
+ white-space: pre-wrap;
209
+ outline: none;
210
+ }
211
+
212
+ .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;
221
+ }
222
+
281
223
  .preview-area {
282
- background-color: #f9f9f9;
283
- border-left: 1px solid #ccc;
284
- } */
224
+ font-family: monospace;
225
+ white-space: pre-wrap;
226
+ overflow-x: auto;
227
+ }
285
228
 
286
- /* @media (max-width: 768px) {
287
- .split-view {
288
- flex-direction: column;
289
- }
229
+ .preview-area code {
230
+ display: block;
231
+ padding: 1rem;
232
+ }
290
233
 
291
- .split-view .content-area,
292
- .split-view .preview-area {
293
- width: 100%;
294
- }
234
+ .fullscreen-mode {
235
+ position: fixed;
236
+ top: 0;
237
+ left: 0;
238
+ width: 100vw;
239
+ height: 100vh;
240
+ z-index: 9999;
241
+ padding: 2rem;
242
+ }
243
+
244
+ .fullscreen-mode .editor-container {
245
+ height: calc(100vh - 8rem);
246
+ }
295
247
 
296
- .preview-area {
297
- border-left: none;
298
- border-top: 1px solid #ccc;
299
- }
300
- } */
248
+ .fullscreen-mode .content-area,
249
+ .fullscreen-mode .preview-area {
250
+ height: 100%;
251
+ overflow-y: auto;
252
+ }
301
253
  </style>
@@ -10,8 +10,8 @@ export { default as PasswordInput } from './PasswordInput.vue'
10
10
  export { default as RadioGroup } from './RadioGroup.vue'
11
11
  export { default as RadioPillsInput } from './RadioPillsInput.vue'
12
12
  export { default as RangeInput } from './RangeInput.vue'
13
- export { default as RichText } from './RichText.vue'
14
- export { default as RichText2 } from './RichText2/index.vue'
13
+ // export { default as RichText } from './RichText.vue'
14
+ export { default as RichText } from './RichText2/index.vue'
15
15
  export { default as SelectInput } from './SelectInput.vue'
16
16
  export { default as SignaturePad } from './SignaturePad.vue'
17
17
  export { default as TableField } from './TableField.vue'
@@ -28,7 +28,7 @@ export interface BagelOptions {
28
28
  availableLangs?: string[]
29
29
  defaultLang?: string
30
30
  language?: string
31
- host: string
31
+ host?: string
32
32
 
33
33
  onError?: (err: Error) => void
34
34