@bagelink/vue 1.2.79 → 1.2.81
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.
- package/bin/experimentalGenTypedRoutes.ts +13 -2
- package/dist/components/form/BagelForm.vue.d.ts +1 -0
- package/dist/components/form/BagelForm.vue.d.ts.map +1 -1
- package/dist/components/form/BglMultiStepForm.vue.d.ts +7 -4
- package/dist/components/form/BglMultiStepForm.vue.d.ts.map +1 -1
- package/dist/components/form/inputs/CodeEditor/Index.vue.d.ts +14 -6
- package/dist/components/form/inputs/CodeEditor/Index.vue.d.ts.map +1 -1
- package/dist/components/form/inputs/RichText/index.vue.d.ts.map +1 -1
- package/dist/index.cjs +161 -134
- package/dist/index.mjs +161 -134
- package/dist/style.css +111 -81
- package/dist/utils/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/components/form/BagelForm.vue +2 -1
- package/src/components/form/BglMultiStepForm.vue +47 -32
- package/src/components/form/inputs/CodeEditor/Index.vue +160 -98
- package/src/components/form/inputs/RichText/index.vue +12 -11
- package/src/utils/index.ts +38 -13
|
@@ -7,10 +7,9 @@ declare global {
|
|
|
7
7
|
}
|
|
8
8
|
}
|
|
9
9
|
import { appendStyle, appendScript } from '@bagelink/vue'
|
|
10
|
-
import {
|
|
10
|
+
import { onMounted, ref, computed, watch } from 'vue'
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
const { language, readonly = false, modelValue = '', autodetect, ignoreIllegals = true, label, height = '300px' } = defineProps<{
|
|
12
|
+
interface CodeEditorProps {
|
|
14
13
|
language?: Language
|
|
15
14
|
readonly?: boolean
|
|
16
15
|
modelValue?: string
|
|
@@ -18,39 +17,50 @@ const { language, readonly = false, modelValue = '', autodetect, ignoreIllegals
|
|
|
18
17
|
ignoreIllegals?: boolean
|
|
19
18
|
label?: string
|
|
20
19
|
height?: string
|
|
21
|
-
}
|
|
20
|
+
}
|
|
21
|
+
// Props with default values
|
|
22
|
+
const props = withDefaults(defineProps<CodeEditorProps>(), {
|
|
23
|
+
language: 'html',
|
|
24
|
+
readonly: false,
|
|
25
|
+
modelValue: '',
|
|
26
|
+
autodetect: true,
|
|
27
|
+
ignoreIllegals: true,
|
|
28
|
+
label: '',
|
|
29
|
+
height: '240px'
|
|
30
|
+
})
|
|
22
31
|
|
|
23
32
|
const emit = defineEmits(['update:modelValue'])
|
|
33
|
+
// State
|
|
34
|
+
const code = ref(props.modelValue || '')
|
|
35
|
+
const editorRef = ref<HTMLDivElement>()
|
|
36
|
+
const loaded = ref(false)
|
|
37
|
+
const hljs = ref<HilightJS | null>(null)
|
|
38
|
+
// Computed
|
|
39
|
+
const maxHeight = computed(() => {
|
|
40
|
+
const h = props.height ?? '240px'
|
|
41
|
+
return h.match(/^\d+$/) ? `${h}px` : h
|
|
42
|
+
})
|
|
24
43
|
|
|
25
|
-
|
|
44
|
+
const formattedCode = computed(() => {
|
|
45
|
+
if (!hljs.value) return escapeHtml(code.value)
|
|
26
46
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
let code = $ref('')
|
|
30
|
-
const textarea = $ref<HTMLTextAreaElement>()
|
|
31
|
-
let loaded = $ref(false)
|
|
47
|
+
try {
|
|
48
|
+
const lang = props.language || ''
|
|
32
49
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
})
|
|
50
|
+
if (lang && !props.autodetect && !hljs.value.getLanguage(lang)) {
|
|
51
|
+
console.warn(`The language "${lang}" is not available.`)
|
|
52
|
+
return escapeHtml(code.value)
|
|
53
|
+
}
|
|
38
54
|
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
})
|
|
55
|
+
const result = props.autodetect
|
|
56
|
+
? hljs.value.highlightAuto(code.value)
|
|
57
|
+
: hljs.value.highlight(code.value, { language: lang, ignoreIllegals: props.ignoreIllegals })
|
|
43
58
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
console.
|
|
47
|
-
return escapeHtml(code)
|
|
59
|
+
return result.value || escapeHtml(code.value)
|
|
60
|
+
} catch (error) {
|
|
61
|
+
console.error('Highlighting error:', error)
|
|
62
|
+
return escapeHtml(code.value)
|
|
48
63
|
}
|
|
49
|
-
const lang = language || ''
|
|
50
|
-
const result = autodetect
|
|
51
|
-
? hljs?.highlightAuto(code)
|
|
52
|
-
: hljs?.highlight(code, { language: lang, ignoreIllegals })
|
|
53
|
-
return result?.value || ''
|
|
54
64
|
})
|
|
55
65
|
|
|
56
66
|
// Methods
|
|
@@ -67,113 +77,165 @@ function escapeHtml(unsafe: string) {
|
|
|
67
77
|
})
|
|
68
78
|
}
|
|
69
79
|
|
|
80
|
+
function handleInput(e: Event) {
|
|
81
|
+
const target = e.target as HTMLTextAreaElement
|
|
82
|
+
code.value = target.value
|
|
83
|
+
emit('update:modelValue', code.value)
|
|
84
|
+
}
|
|
85
|
+
|
|
70
86
|
function handleTab(event: KeyboardEvent) {
|
|
87
|
+
if (event.key !== 'Tab') return
|
|
88
|
+
|
|
89
|
+
event.preventDefault()
|
|
71
90
|
const target = event.target as HTMLTextAreaElement
|
|
72
91
|
const start = target.selectionStart
|
|
73
|
-
const
|
|
74
|
-
code = code.slice(0, start) + tab + code.slice(start)
|
|
75
|
-
nextTick(() => {
|
|
76
|
-
target.selectionStart = target.selectionEnd = start + tab.length
|
|
77
|
-
})
|
|
78
|
-
}
|
|
92
|
+
const end = target.selectionEnd
|
|
79
93
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
94
|
+
// Add tab or indent selected text
|
|
95
|
+
const newValue = `${code.value.substring(0, start)} ${code.value.substring(end)}`
|
|
96
|
+
code.value = newValue
|
|
97
|
+
emit('update:modelValue', code.value)
|
|
98
|
+
|
|
99
|
+
// Move cursor position after the inserted tab
|
|
100
|
+
setTimeout(() => {
|
|
101
|
+
target.selectionStart = target.selectionEnd = start + 2
|
|
102
|
+
}, 0)
|
|
84
103
|
}
|
|
85
104
|
|
|
86
105
|
// Lifecycle
|
|
87
106
|
onMounted(async () => {
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
107
|
+
try {
|
|
108
|
+
// Load highlight.js
|
|
109
|
+
await appendScript('https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.0/highlight.min.js', { id: 'hljs-cdn' })
|
|
110
|
+
await appendStyle('https://cdn.jsdelivr.net/npm/highlight.js/styles/atom-one-dark.min.css')
|
|
111
|
+
|
|
112
|
+
if (window.hljs) {
|
|
113
|
+
hljs.value = window.hljs
|
|
114
|
+
loaded.value = true
|
|
115
|
+
} else {
|
|
116
|
+
console.error('Failed to load highlight.js')
|
|
117
|
+
}
|
|
118
|
+
} catch (error) {
|
|
119
|
+
console.error('Error loading highlight.js:', error)
|
|
98
120
|
}
|
|
99
121
|
})
|
|
100
122
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
if (newVal !== code) {
|
|
104
|
-
code = newVal
|
|
123
|
+
// Watch for external modelValue changes
|
|
124
|
+
watch(() => props.modelValue, (newVal) => {
|
|
125
|
+
if (newVal !== undefined && newVal !== code.value) {
|
|
126
|
+
code.value = newVal
|
|
105
127
|
}
|
|
106
128
|
}, { immediate: true })
|
|
107
129
|
</script>
|
|
108
130
|
|
|
109
131
|
<template>
|
|
110
|
-
<div class="
|
|
111
|
-
<label v-if="label" class="label
|
|
112
|
-
<div
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
132
|
+
<div class="code-editor-container ltr" :style="{ maxHeight }">
|
|
133
|
+
<label v-if="label" class="label">{{ label }}</label>
|
|
134
|
+
<div
|
|
135
|
+
v-if="loaded"
|
|
136
|
+
ref="editorRef"
|
|
137
|
+
class="code-editor-grandpa"
|
|
138
|
+
>
|
|
139
|
+
<div class="editor-content-papa relative">
|
|
140
|
+
<pre class="code-display" wrap><code v-html="formattedCode" /></pre>
|
|
117
141
|
<textarea
|
|
118
142
|
v-if="!readonly"
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
class="code-editor absolute inset-0 bg-transparent overflow-hidden h-100 p-0 m-0 codeText border-none txt-start"
|
|
143
|
+
:value="code"
|
|
144
|
+
class="code-input"
|
|
122
145
|
spellcheck="false"
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
@
|
|
127
|
-
@
|
|
146
|
+
autocomplete="off"
|
|
147
|
+
autocorrect="off"
|
|
148
|
+
autocapitalize="off"
|
|
149
|
+
@input="handleInput"
|
|
150
|
+
@keydown="handleTab"
|
|
128
151
|
/>
|
|
129
152
|
</div>
|
|
130
153
|
</div>
|
|
131
154
|
</div>
|
|
132
155
|
</template>
|
|
133
156
|
|
|
134
|
-
<style>
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
position: absolute;
|
|
157
|
+
<style scoped>
|
|
158
|
+
.code-editor-container {
|
|
159
|
+
margin-bottom: 0.5rem;
|
|
160
|
+
height: 100%;
|
|
139
161
|
}
|
|
140
|
-
</style>
|
|
141
162
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
word-wrap: break-word;
|
|
147
|
-
caret-color: var(--bgl-white);
|
|
148
|
-
color: var(--bgl-white);
|
|
163
|
+
.label {
|
|
164
|
+
display: block;
|
|
165
|
+
text-align: left;
|
|
166
|
+
margin-bottom: 0.25rem;
|
|
149
167
|
}
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
168
|
+
|
|
169
|
+
.code-editor-grandpa {
|
|
170
|
+
background: #22252A;
|
|
171
|
+
border-radius: 0.25rem;
|
|
172
|
+
width: 100%;
|
|
173
|
+
height: 100%;
|
|
174
|
+
overflow: auto;
|
|
175
|
+
padding: 1ch;
|
|
176
|
+
padding-inline-start: 2ch;
|
|
153
177
|
}
|
|
154
|
-
.code-editor-wrap:focus-within, .code-editor-wrap:focus-visible, .code-editor-wrap:focus {
|
|
155
|
-
box-shadow: inset 0 0 10px #00000021;
|
|
156
|
-
outline: solid 1px var(--border-color);
|
|
157
|
-
/* outline: -webkit-focus-ring-color auto 1px; */
|
|
158
178
|
|
|
179
|
+
.code-editor-grandpa:focus-within {
|
|
180
|
+
outline: solid 1px var(--border-color, #4f575f);
|
|
181
|
+
box-shadow: inset 0 0 10px rgba(0, 0, 0, 0.13);
|
|
159
182
|
}
|
|
160
183
|
|
|
161
|
-
.
|
|
162
|
-
|
|
163
|
-
|
|
184
|
+
.editor-content-papa {
|
|
185
|
+
position: relative;
|
|
186
|
+
width: 100%;
|
|
187
|
+
padding-bottom: calc(100% - 5lh);
|
|
188
|
+
}
|
|
189
|
+
.code-display,
|
|
190
|
+
.code-input {
|
|
191
|
+
inset: 0;
|
|
192
|
+
margin: 0;
|
|
193
|
+
padding: 0;
|
|
194
|
+
width: 100%;
|
|
195
|
+
height: 100%;
|
|
196
|
+
overflow: auto;
|
|
197
|
+
font-family: monospace;
|
|
198
|
+
font-size: 1em;
|
|
199
|
+
line-height: 1.5;
|
|
200
|
+
tab-size: 2;
|
|
201
|
+
word-break: keep-all;
|
|
202
|
+
text-align: left;
|
|
164
203
|
}
|
|
165
204
|
|
|
166
|
-
.code-
|
|
167
|
-
|
|
168
|
-
|
|
205
|
+
.code-display {
|
|
206
|
+
position: relative;
|
|
207
|
+
color: #fff;
|
|
208
|
+
pointer-events: none;
|
|
209
|
+
z-index: 1;
|
|
169
210
|
}
|
|
170
211
|
|
|
171
|
-
.code-
|
|
172
|
-
|
|
173
|
-
|
|
212
|
+
.code-display code {
|
|
213
|
+
display: block;
|
|
214
|
+
background: transparent !important;
|
|
215
|
+
padding: 0 !important;
|
|
174
216
|
}
|
|
175
217
|
|
|
176
|
-
.code-
|
|
177
|
-
|
|
218
|
+
.code-input {
|
|
219
|
+
position: absolute;
|
|
220
|
+
background: transparent;
|
|
221
|
+
color: transparent;
|
|
222
|
+
caret-color: #fff;
|
|
223
|
+
border: none;
|
|
224
|
+
resize: none;
|
|
225
|
+
outline: none;
|
|
226
|
+
z-index: 2;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
.code-input::selection {
|
|
230
|
+
background-color: rgba(36, 102, 188, 0.3);
|
|
231
|
+
color: transparent;
|
|
232
|
+
}
|
|
233
|
+
</style>
|
|
234
|
+
|
|
235
|
+
<style>
|
|
236
|
+
/* Global styles */
|
|
237
|
+
pre code.hljs {
|
|
238
|
+
padding: 0 !important;
|
|
239
|
+
background: transparent !important;
|
|
178
240
|
}
|
|
179
241
|
</style>
|
|
@@ -15,6 +15,9 @@ const editor = useEditor()
|
|
|
15
15
|
const isInitializing = ref(false)
|
|
16
16
|
const hasInitialized = ref(false)
|
|
17
17
|
|
|
18
|
+
// Initialize content from modelValue
|
|
19
|
+
editor.state.content = props.modelValue
|
|
20
|
+
|
|
18
21
|
// Initialize debugger if debug mode is enabled
|
|
19
22
|
if (props.debug) {
|
|
20
23
|
editor.initDebugger()
|
|
@@ -83,6 +86,9 @@ async function initEditor() {
|
|
|
83
86
|
// Set default direction based on content
|
|
84
87
|
doc.body.dir = hasRTL ? 'rtl' : 'ltr'
|
|
85
88
|
|
|
89
|
+
// Ensure editor.state.content is set to the current HTML content
|
|
90
|
+
editor.state.content = doc.body.innerHTML
|
|
91
|
+
|
|
86
92
|
editor.init(doc)
|
|
87
93
|
useEditorKeyboard(doc, commands)
|
|
88
94
|
|
|
@@ -92,6 +98,8 @@ async function initEditor() {
|
|
|
92
98
|
p.dir = doc.body.dir
|
|
93
99
|
p.innerHTML = '<br>'
|
|
94
100
|
doc.body.appendChild(p)
|
|
101
|
+
// Update state.content after changes
|
|
102
|
+
editor.state.content = doc.body.innerHTML
|
|
95
103
|
} else {
|
|
96
104
|
// Convert any direct text nodes to paragraphs
|
|
97
105
|
const walker = doc.createTreeWalker(doc.body, NodeFilter.SHOW_TEXT)
|
|
@@ -112,6 +120,8 @@ async function initEditor() {
|
|
|
112
120
|
doc.body.removeChild(textNode)
|
|
113
121
|
}
|
|
114
122
|
})
|
|
123
|
+
// Update state.content after cleanup
|
|
124
|
+
editor.state.content = doc.body.innerHTML
|
|
115
125
|
}
|
|
116
126
|
|
|
117
127
|
doc.body.focus()
|
|
@@ -168,7 +178,7 @@ watch(() => editor.state.content, (newValue) => {
|
|
|
168
178
|
v-if="editor.state.isSplitView"
|
|
169
179
|
v-model="editor.state.content"
|
|
170
180
|
language="html"
|
|
171
|
-
|
|
181
|
+
:height="editor.state.isFullscreen ? 'calc(100vh - 4rem)' : '250px'"
|
|
172
182
|
@update:modelValue="editor.updateState.content('html')"
|
|
173
183
|
/>
|
|
174
184
|
</div>
|
|
@@ -196,12 +206,6 @@ watch(() => editor.state.content, (newValue) => {
|
|
|
196
206
|
.content-area li{
|
|
197
207
|
line-height: 1.65;
|
|
198
208
|
}
|
|
199
|
-
|
|
200
|
-
.code-editor {
|
|
201
|
-
flex: 1;
|
|
202
|
-
min-height: 240px !important;
|
|
203
|
-
height: 100%;
|
|
204
|
-
}
|
|
205
209
|
</style>
|
|
206
210
|
|
|
207
211
|
<style scoped>
|
|
@@ -213,7 +217,7 @@ watch(() => editor.state.content, (newValue) => {
|
|
|
213
217
|
|
|
214
218
|
.editor-container {
|
|
215
219
|
display: flex;
|
|
216
|
-
gap:
|
|
220
|
+
gap: 0.5rem;
|
|
217
221
|
}
|
|
218
222
|
|
|
219
223
|
.content-area,
|
|
@@ -274,9 +278,6 @@ watch(() => editor.state.content, (newValue) => {
|
|
|
274
278
|
height: 100%;
|
|
275
279
|
overflow-y: auto;
|
|
276
280
|
}
|
|
277
|
-
.fullscreen-mode .code-editor{
|
|
278
|
-
height: 100% !important;
|
|
279
|
-
}
|
|
280
281
|
|
|
281
282
|
.debug-controls {
|
|
282
283
|
display: flex;
|
package/src/utils/index.ts
CHANGED
|
@@ -124,26 +124,51 @@ export function sleep(ms: number = 100) {
|
|
|
124
124
|
return new Promise(resolve => setTimeout(resolve, ms))
|
|
125
125
|
}
|
|
126
126
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
127
|
+
// Keep track of loading scripts
|
|
128
|
+
const scriptsLoading = new Map<string, Promise<void>>()
|
|
129
|
+
|
|
130
|
+
export async function appendScript(src: string, options?: { id?: string }): Promise<void> {
|
|
131
|
+
const scriptId = options?.id || src
|
|
132
|
+
await sleep(1)
|
|
133
|
+
// If this script is already loading, return the existing promise
|
|
134
|
+
if (scriptsLoading.has(scriptId)) {
|
|
135
|
+
return scriptsLoading.get(scriptId)!
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Check if script is already in the document
|
|
139
|
+
if (options?.id && document.getElementById(options.id)) {
|
|
140
|
+
return Promise.resolve()
|
|
141
|
+
} else if (document.querySelector(`script[src="${src}"]`)) {
|
|
142
|
+
return Promise.resolve()
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Create a new loading promise for this script
|
|
146
|
+
const loadingPromise = new Promise<void>((resolve, reject) => {
|
|
138
147
|
const script = document.createElement('script')
|
|
139
148
|
script.src = src
|
|
140
149
|
if (options?.id) {
|
|
141
150
|
script.id = options.id
|
|
142
151
|
}
|
|
143
|
-
|
|
144
|
-
script.
|
|
152
|
+
|
|
153
|
+
script.onload = () => {
|
|
154
|
+
resolve()
|
|
155
|
+
// Remove from loading scripts map when done
|
|
156
|
+
scriptsLoading.delete(scriptId)
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
script.onerror = (err) => {
|
|
160
|
+
reject(err)
|
|
161
|
+
// Remove from loading scripts map on error
|
|
162
|
+
scriptsLoading.delete(scriptId)
|
|
163
|
+
}
|
|
164
|
+
|
|
145
165
|
document.head.append(script)
|
|
146
166
|
})
|
|
167
|
+
|
|
168
|
+
// Store the loading promise for this script
|
|
169
|
+
scriptsLoading.set(scriptId, loadingPromise)
|
|
170
|
+
|
|
171
|
+
return loadingPromise
|
|
147
172
|
}
|
|
148
173
|
|
|
149
174
|
export function appendStyle(src: string): Promise<void> {
|