pimelon-ui 0.1.91 → 0.1.92
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pimelon-ui",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.92",
|
|
4
4
|
"description": "A set of components and utilities for rapid UI development",
|
|
5
5
|
"main": "./src/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -51,6 +51,7 @@
|
|
|
51
51
|
"feather-icons": "^4.28.0",
|
|
52
52
|
"husky": "^9.1.7",
|
|
53
53
|
"idb-keyval": "^6.2.0",
|
|
54
|
+
"ora": "5.4.1",
|
|
54
55
|
"prettier": "^3.3.2",
|
|
55
56
|
"radix-vue": "^1.5.3",
|
|
56
57
|
"showdown": "^2.1.0",
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { ref } from 'vue'
|
|
3
3
|
import Autocomplete from './Autocomplete.vue'
|
|
4
4
|
|
|
5
|
-
const single = ref(
|
|
5
|
+
const single = ref()
|
|
6
6
|
const people = ref(null)
|
|
7
7
|
const options = [
|
|
8
8
|
{
|
|
@@ -74,7 +74,7 @@ const options = [
|
|
|
74
74
|
:options="options"
|
|
75
75
|
v-model="single"
|
|
76
76
|
placeholder="Select person"
|
|
77
|
-
|
|
77
|
+
:hideSearch="true"
|
|
78
78
|
/>
|
|
79
79
|
</div>
|
|
80
80
|
</Variant>
|
|
@@ -84,7 +84,7 @@ const options = [
|
|
|
84
84
|
:options="options"
|
|
85
85
|
v-model="people"
|
|
86
86
|
placeholder="Select people"
|
|
87
|
-
multiple="true"
|
|
87
|
+
:multiple="true"
|
|
88
88
|
/>
|
|
89
89
|
</div>
|
|
90
90
|
</Variant>
|
|
@@ -94,8 +94,8 @@ const options = [
|
|
|
94
94
|
:options="options"
|
|
95
95
|
v-model="people"
|
|
96
96
|
placeholder="Select people"
|
|
97
|
-
multiple="true"
|
|
98
|
-
|
|
97
|
+
:multiple="true"
|
|
98
|
+
:hideSearch="true"
|
|
99
99
|
/>
|
|
100
100
|
</div>
|
|
101
101
|
</Variant>
|
|
@@ -18,9 +18,9 @@
|
|
|
18
18
|
<slot name="prefix" />
|
|
19
19
|
<span
|
|
20
20
|
class="truncate text-base leading-5 text-ink-gray-8"
|
|
21
|
-
v-if="
|
|
21
|
+
v-if="displayValue"
|
|
22
22
|
>
|
|
23
|
-
{{ displayValue
|
|
23
|
+
{{ displayValue }}
|
|
24
24
|
</span>
|
|
25
25
|
<span class="text-base leading-5 text-ink-gray-4" v-else>
|
|
26
26
|
{{ placeholder || '' }}
|
|
@@ -55,18 +55,14 @@
|
|
|
55
55
|
ref="searchInput"
|
|
56
56
|
class="form-input w-full focus:bg-surface-gray-3 hover:bg-surface-gray-4 text-ink-gray-8"
|
|
57
57
|
type="text"
|
|
58
|
-
@change="
|
|
59
|
-
(e) => {
|
|
60
|
-
query = e.target.value
|
|
61
|
-
}
|
|
62
|
-
"
|
|
63
58
|
:value="query"
|
|
59
|
+
@change="query = $event.target.value"
|
|
64
60
|
autocomplete="off"
|
|
65
61
|
placeholder="Search"
|
|
66
62
|
/>
|
|
67
63
|
<button
|
|
68
64
|
class="absolute right-0 inline-flex h-7 w-7 items-center justify-center"
|
|
69
|
-
@click="
|
|
65
|
+
@click="clearAll"
|
|
70
66
|
>
|
|
71
67
|
<FeatherIcon name="x" class="w-4 text-ink-gray-8" />
|
|
72
68
|
</button>
|
|
@@ -86,7 +82,7 @@
|
|
|
86
82
|
<ComboboxOption
|
|
87
83
|
as="template"
|
|
88
84
|
v-for="(option, idx) in group.items.slice(0, 50)"
|
|
89
|
-
:key="
|
|
85
|
+
:key="idx"
|
|
90
86
|
:value="option"
|
|
91
87
|
v-slot="{ active, selected }"
|
|
92
88
|
>
|
|
@@ -167,185 +163,222 @@
|
|
|
167
163
|
</Combobox>
|
|
168
164
|
</template>
|
|
169
165
|
|
|
170
|
-
<script>
|
|
166
|
+
<script setup lang="ts">
|
|
171
167
|
import {
|
|
172
168
|
Combobox,
|
|
173
|
-
ComboboxButton,
|
|
174
169
|
ComboboxInput,
|
|
175
170
|
ComboboxOption,
|
|
176
171
|
ComboboxOptions,
|
|
177
172
|
} from '@headlessui/vue'
|
|
178
|
-
import { nextTick } from 'vue'
|
|
173
|
+
import { computed, nextTick, ref, watch } from 'vue'
|
|
179
174
|
import Popover from './Popover.vue'
|
|
180
175
|
import { Button } from './Button'
|
|
181
176
|
import FeatherIcon from './FeatherIcon.vue'
|
|
182
177
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
Button,
|
|
212
|
-
FeatherIcon,
|
|
213
|
-
Combobox,
|
|
214
|
-
ComboboxInput,
|
|
215
|
-
ComboboxOptions,
|
|
216
|
-
ComboboxOption,
|
|
217
|
-
ComboboxButton,
|
|
218
|
-
},
|
|
219
|
-
expose: ['togglePopover', 'rootRef'],
|
|
220
|
-
data() {
|
|
221
|
-
return {
|
|
222
|
-
query: '',
|
|
223
|
-
showOptions: false,
|
|
178
|
+
type Option = {
|
|
179
|
+
label: string
|
|
180
|
+
value: OptionValue
|
|
181
|
+
description?: string
|
|
182
|
+
[key: string]: any
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
type OptionValue = string | number | boolean
|
|
186
|
+
|
|
187
|
+
type AutocompleteOption = OptionValue | Option
|
|
188
|
+
|
|
189
|
+
type AutocompleteOptionGroup = {
|
|
190
|
+
group: string
|
|
191
|
+
items: AutocompleteOption[]
|
|
192
|
+
hideLabel?: boolean
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
type AutocompleteOptions = AutocompleteOption[] | AutocompleteOptionGroup[]
|
|
196
|
+
|
|
197
|
+
type AutocompleteProps = {
|
|
198
|
+
options: AutocompleteOptions
|
|
199
|
+
hideSearch?: boolean
|
|
200
|
+
placeholder?: string
|
|
201
|
+
bodyClasses?: string | string[]
|
|
202
|
+
} & (
|
|
203
|
+
| {
|
|
204
|
+
multiple: true
|
|
205
|
+
modelValue?: AutocompleteOption[] | null
|
|
224
206
|
}
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
207
|
+
| {
|
|
208
|
+
multiple?: false
|
|
209
|
+
modelValue?: AutocompleteOption | null
|
|
210
|
+
}
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
const props = withDefaults(defineProps<AutocompleteProps>(), {
|
|
214
|
+
multiple: false,
|
|
215
|
+
hideSearch: false,
|
|
216
|
+
})
|
|
217
|
+
const emit = defineEmits(['update:modelValue', 'update:query', 'change'])
|
|
218
|
+
|
|
219
|
+
const searchInput = ref()
|
|
220
|
+
const showOptions = ref(false)
|
|
221
|
+
const query = ref('')
|
|
222
|
+
|
|
223
|
+
const groups = computed(() => {
|
|
224
|
+
if (!props.options?.length) return []
|
|
225
|
+
|
|
226
|
+
let groups: AutocompleteOptionGroup[]
|
|
227
|
+
if (isOptionGroup(props.options[0])) {
|
|
228
|
+
groups = props.options as AutocompleteOptionGroup[]
|
|
229
|
+
} else {
|
|
230
|
+
groups = [
|
|
231
|
+
{
|
|
232
|
+
group: '',
|
|
233
|
+
items: sanitizeOptions(props.options as AutocompleteOption[]),
|
|
234
|
+
hideLabel: false,
|
|
246
235
|
},
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
if (!this.options || this.options.length == 0) return []
|
|
250
|
-
|
|
251
|
-
let groups = this.options[0]?.group
|
|
252
|
-
? this.options
|
|
253
|
-
: [{ group: '', items: this.sanitizeOptions(this.options) }]
|
|
254
|
-
|
|
255
|
-
return groups
|
|
256
|
-
.map((group, i) => {
|
|
257
|
-
return {
|
|
258
|
-
key: i,
|
|
259
|
-
group: group.group,
|
|
260
|
-
hideLabel: group.hideLabel || false,
|
|
261
|
-
items: this.filterOptions(this.sanitizeOptions(group.items)),
|
|
262
|
-
}
|
|
263
|
-
})
|
|
264
|
-
.filter((group) => group.items.length > 0)
|
|
265
|
-
},
|
|
266
|
-
allOptions() {
|
|
267
|
-
return this.groups.flatMap((group) => group.items)
|
|
268
|
-
},
|
|
269
|
-
areAllOptionsSelected() {
|
|
270
|
-
if (!this.multiple) return false
|
|
271
|
-
return this.allOptions.length === this.selectedValue?.length
|
|
272
|
-
},
|
|
273
|
-
},
|
|
274
|
-
watch: {
|
|
275
|
-
query(q) {
|
|
276
|
-
this.$emit('update:query', q)
|
|
277
|
-
},
|
|
278
|
-
showOptions(val) {
|
|
279
|
-
if (val) nextTick(() => this.$refs.searchInput?.$el?.focus())
|
|
280
|
-
},
|
|
281
|
-
},
|
|
282
|
-
methods: {
|
|
283
|
-
rootRef() {
|
|
284
|
-
return this.$refs['rootRef']
|
|
285
|
-
},
|
|
286
|
-
togglePopover(val) {
|
|
287
|
-
this.showOptions = val ?? !this.showOptions
|
|
288
|
-
},
|
|
289
|
-
findOption(option) {
|
|
290
|
-
if (!option) return option
|
|
291
|
-
const value = isOptionOrValue(option) === 'value' ? option : option.value
|
|
292
|
-
return this.allOptions.find((o) => o.value === value)
|
|
293
|
-
},
|
|
294
|
-
filterOptions(options) {
|
|
295
|
-
if (!this.query) return options
|
|
296
|
-
return options.filter((option) => {
|
|
297
|
-
return (
|
|
298
|
-
option.label
|
|
299
|
-
.toLowerCase()
|
|
300
|
-
.includes(this.query.trim().toLowerCase()) ||
|
|
301
|
-
option.value.toLowerCase().includes(this.query.trim().toLowerCase())
|
|
302
|
-
)
|
|
303
|
-
})
|
|
304
|
-
},
|
|
305
|
-
displayValue(option) {
|
|
306
|
-
if (!option) return ''
|
|
307
|
-
|
|
308
|
-
if (!this.multiple) {
|
|
309
|
-
return this.getLabel(this.findOption(option))
|
|
310
|
-
}
|
|
236
|
+
]
|
|
237
|
+
}
|
|
311
238
|
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
if (isOptionOrValue(option) === 'value') return option
|
|
320
|
-
return option?.label || option?.value || 'No label'
|
|
321
|
-
},
|
|
322
|
-
sanitizeOptions(options) {
|
|
323
|
-
if (!options) return []
|
|
324
|
-
// in case the options are just values, convert them to objects
|
|
325
|
-
return options.map((option) => {
|
|
326
|
-
return isOptionOrValue(option) === 'option'
|
|
327
|
-
? option
|
|
328
|
-
: { label: option, value: option }
|
|
329
|
-
})
|
|
330
|
-
},
|
|
331
|
-
isOptionSelected(option) {
|
|
332
|
-
if (!this.selectedValue) return false
|
|
333
|
-
const value = isOptionOrValue(option) === 'value' ? option : option.value
|
|
334
|
-
if (!this.multiple) {
|
|
335
|
-
return this.selectedValue?.value === value
|
|
239
|
+
return groups
|
|
240
|
+
.map((group, i) => {
|
|
241
|
+
return {
|
|
242
|
+
key: i,
|
|
243
|
+
group: group.group,
|
|
244
|
+
hideLabel: group.hideLabel,
|
|
245
|
+
items: filterOptions(sanitizeOptions(group.items || [])),
|
|
336
246
|
}
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
247
|
+
})
|
|
248
|
+
.filter((group) => group.items.length > 0)
|
|
249
|
+
})
|
|
250
|
+
|
|
251
|
+
const allOptions = computed(() => {
|
|
252
|
+
return groups.value.flatMap((group) => group.items)
|
|
253
|
+
})
|
|
254
|
+
|
|
255
|
+
const sanitizeOptions = (options: AutocompleteOption[]) => {
|
|
256
|
+
if (!options) return []
|
|
257
|
+
// in case the options are just values, convert them to objects
|
|
258
|
+
return options.map((option) => {
|
|
259
|
+
return isOption(option)
|
|
260
|
+
? option
|
|
261
|
+
: { label: option.toString(), value: option }
|
|
262
|
+
})
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
const filterOptions = (options: Option[]) => {
|
|
266
|
+
if (!query.value) return options
|
|
267
|
+
return options.filter((option) => {
|
|
268
|
+
return (
|
|
269
|
+
option.label.toLowerCase().includes(query.value.trim().toLowerCase()) ||
|
|
270
|
+
option.value
|
|
271
|
+
.toString()
|
|
272
|
+
.toLowerCase()
|
|
273
|
+
.includes(query.value.trim().toLowerCase())
|
|
274
|
+
)
|
|
275
|
+
})
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
const selectedValue = computed({
|
|
279
|
+
get() {
|
|
280
|
+
if (!props.multiple) {
|
|
281
|
+
return findOption(props.modelValue as AutocompleteOption)
|
|
282
|
+
}
|
|
283
|
+
// in case of `multiple`, modelValue is an array of values
|
|
284
|
+
// if the modelValue is a list of values, convert them to options
|
|
285
|
+
let values = props.modelValue as AutocompleteOption[]
|
|
286
|
+
if (!values) return []
|
|
287
|
+
return isOption(values[0]) ? values : values.map((v) => findOption(v))
|
|
288
|
+
},
|
|
289
|
+
set(val) {
|
|
290
|
+
query.value = ''
|
|
291
|
+
if (val && !props.multiple) showOptions.value = false
|
|
292
|
+
if (!props.multiple) {
|
|
293
|
+
emit('update:modelValue', val)
|
|
294
|
+
return
|
|
295
|
+
}
|
|
296
|
+
emit('update:modelValue', val)
|
|
345
297
|
},
|
|
298
|
+
})
|
|
299
|
+
|
|
300
|
+
const findOption = (option: AutocompleteOption) => {
|
|
301
|
+
if (!option) return option
|
|
302
|
+
const value = isOption(option) ? option.value : option
|
|
303
|
+
return allOptions.value.find((o) => o.value === value)
|
|
346
304
|
}
|
|
347
305
|
|
|
348
|
-
|
|
349
|
-
|
|
306
|
+
const getLabel = (option: AutocompleteOption) => {
|
|
307
|
+
if (isOption(option)) {
|
|
308
|
+
return option?.label || option?.value || 'No label'
|
|
309
|
+
}
|
|
310
|
+
return option
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
const displayValue = computed(() => {
|
|
314
|
+
if (!selectedValue.value) return ''
|
|
315
|
+
if (!props.multiple) {
|
|
316
|
+
return getLabel(selectedValue.value as AutocompleteOption)
|
|
317
|
+
}
|
|
318
|
+
return (selectedValue.value as AutocompleteOption[])
|
|
319
|
+
.map((v) => getLabel(v))
|
|
320
|
+
.join(', ')
|
|
321
|
+
})
|
|
322
|
+
|
|
323
|
+
const isOptionSelected = (option: AutocompleteOption) => {
|
|
324
|
+
if (!selectedValue.value) return false
|
|
325
|
+
const value = isOption(option) ? option.value : option
|
|
326
|
+
if (!props.multiple) {
|
|
327
|
+
return selectedValue.value === value
|
|
328
|
+
}
|
|
329
|
+
return (selectedValue.value as AutocompleteOption[]).find((v) =>
|
|
330
|
+
isOption(v) ? v.value === value : v === value,
|
|
331
|
+
)
|
|
350
332
|
}
|
|
333
|
+
|
|
334
|
+
const areAllOptionsSelected = computed(() => {
|
|
335
|
+
if (!props.multiple) return false
|
|
336
|
+
return (
|
|
337
|
+
allOptions.value.length ===
|
|
338
|
+
(selectedValue.value as AutocompleteOption[])?.length
|
|
339
|
+
)
|
|
340
|
+
})
|
|
341
|
+
|
|
342
|
+
const selectAll = () => {
|
|
343
|
+
selectedValue.value = allOptions.value
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
const clearAll = () => {
|
|
347
|
+
selectedValue.value = props.multiple ? [] : undefined
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
const isOption = (option: AutocompleteOption) => {
|
|
351
|
+
return typeof option === 'object'
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
const isOptionGroup = (option: any) => {
|
|
355
|
+
return typeof option === 'object' && 'items' in option && 'group' in option
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
watch(
|
|
359
|
+
() => query.value,
|
|
360
|
+
() => {
|
|
361
|
+
emit('update:query', query.value)
|
|
362
|
+
},
|
|
363
|
+
)
|
|
364
|
+
|
|
365
|
+
watch(
|
|
366
|
+
() => showOptions.value,
|
|
367
|
+
() => {
|
|
368
|
+
if (showOptions.value) {
|
|
369
|
+
nextTick(() => searchInput.value?.$el.focus())
|
|
370
|
+
}
|
|
371
|
+
},
|
|
372
|
+
)
|
|
373
|
+
|
|
374
|
+
const rootRef = ref()
|
|
375
|
+
|
|
376
|
+
const togglePopover = () => {
|
|
377
|
+
showOptions.value = !showOptions.value
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
defineExpose({
|
|
381
|
+
rootRef,
|
|
382
|
+
togglePopover,
|
|
383
|
+
})
|
|
351
384
|
</script>
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { ref } from 'vue'
|
|
3
|
+
import { EditorContent } from '@tiptap/vue-3'
|
|
4
|
+
import TextEditor from './TextEditor.vue'
|
|
5
|
+
import TextEditorFixedMenu from './TextEditorFixedMenu.vue'
|
|
6
|
+
import { Button } from '../Button'
|
|
7
|
+
|
|
8
|
+
const value = ref('')
|
|
9
|
+
const customValue = ref('')
|
|
10
|
+
const customButtons = [
|
|
11
|
+
'Paragraph',
|
|
12
|
+
['Heading 2', 'Heading 3', 'Heading 4'],
|
|
13
|
+
'Separator',
|
|
14
|
+
'Bold',
|
|
15
|
+
'Italic',
|
|
16
|
+
'Separator',
|
|
17
|
+
'Bullet List',
|
|
18
|
+
'Numbered List',
|
|
19
|
+
'Separator',
|
|
20
|
+
'Link',
|
|
21
|
+
'Image',
|
|
22
|
+
]
|
|
23
|
+
</script>
|
|
24
|
+
<template>
|
|
25
|
+
<Story :layout="{ width: 600, type: 'grid' }" autoPropsDisabled>
|
|
26
|
+
<Variant title="Basic">
|
|
27
|
+
<div class="p-2">
|
|
28
|
+
<TextEditor
|
|
29
|
+
editor-class="prose-sm min-h-[4rem] border rounded-b-lg border-t-0 p-2"
|
|
30
|
+
:content="value"
|
|
31
|
+
placeholder="Type something..."
|
|
32
|
+
@change="(val) => (value = val)"
|
|
33
|
+
:bubbleMenu="true"
|
|
34
|
+
:fixed-menu="true"
|
|
35
|
+
/>
|
|
36
|
+
</div>
|
|
37
|
+
</Variant>
|
|
38
|
+
<Variant title="Comment Editor">
|
|
39
|
+
<div class="p-2">
|
|
40
|
+
<TextEditor
|
|
41
|
+
ref="textEditor"
|
|
42
|
+
editor-class="prose-sm max-w-none min-h-[4rem]"
|
|
43
|
+
:content="customValue"
|
|
44
|
+
@change="(val) => (customValue = val)"
|
|
45
|
+
:starterkit-options="{ heading: { levels: [2, 3, 4] } }"
|
|
46
|
+
placeholder="Write something amazing..."
|
|
47
|
+
>
|
|
48
|
+
<template v-slot:editor="{ editor }">
|
|
49
|
+
<EditorContent
|
|
50
|
+
class="max-h-[50vh] overflow-y-auto border rounded-lg p-4"
|
|
51
|
+
:editor="editor"
|
|
52
|
+
/>
|
|
53
|
+
</template>
|
|
54
|
+
<template v-slot:bottom>
|
|
55
|
+
<div
|
|
56
|
+
class="mt-2 flex flex-col justify-between sm:flex-row sm:items-center"
|
|
57
|
+
>
|
|
58
|
+
<TextEditorFixedMenu
|
|
59
|
+
class="-ml-1 overflow-x-auto"
|
|
60
|
+
:buttons="customButtons"
|
|
61
|
+
/>
|
|
62
|
+
<div class="mt-2 flex items-center justify-end space-x-2 sm:mt-0">
|
|
63
|
+
<Button>Cancel</Button>
|
|
64
|
+
<Button variant="solid">Submit</Button>
|
|
65
|
+
</div>
|
|
66
|
+
</div>
|
|
67
|
+
</template>
|
|
68
|
+
</TextEditor>
|
|
69
|
+
</div>
|
|
70
|
+
</Variant>
|
|
71
|
+
</Story>
|
|
72
|
+
</template>
|
package/src/utils/markdown.js
CHANGED
|
@@ -15,6 +15,15 @@ export function detectMarkdown(text) {
|
|
|
15
15
|
const lines = text.split('\n')
|
|
16
16
|
const markdown = lines.filter(
|
|
17
17
|
(line) =>
|
|
18
|
+
// check for inline markdown content like images, links, italic, bold, etc.
|
|
19
|
+
/!\[.*\]\(.*\)/.test(line) ||
|
|
20
|
+
/\[.*\]\(.*\)/.test(line) ||
|
|
21
|
+
/(^|\s)\*.*\*(\s|$)/.test(line) ||
|
|
22
|
+
/(^|\s)_.*_(\s|$)/.test(line) ||
|
|
23
|
+
/(^|\s)\*\*.*\*\*(\s|$)/.test(line) ||
|
|
24
|
+
/(^|\s)__.*__(\s|$)/.test(line) ||
|
|
25
|
+
/(^|\s)~~.*~~(\s|$)/.test(line) ||
|
|
26
|
+
// check for block markdown content like headings, code blocks, lists, etc.
|
|
18
27
|
line.startsWith('![') ||
|
|
19
28
|
line.startsWith('#') ||
|
|
20
29
|
line.startsWith('> ') ||
|
package/vite.js
CHANGED
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
const path = require('path')
|
|
2
2
|
const fs = require('fs')
|
|
3
|
+
const DocTypeInterfaceGenerator = require('./scripts/generateInterface')
|
|
3
4
|
|
|
4
5
|
module.exports = function proxyOptions({
|
|
5
6
|
port = 8080,
|
|
6
7
|
source = '^/(app|login|api|assets|files|private)',
|
|
7
8
|
} = {}) {
|
|
8
|
-
const
|
|
9
|
-
const webserver_port =
|
|
10
|
-
|
|
9
|
+
const commonSiteConfig = getCommonSiteConfig()
|
|
10
|
+
const webserver_port = commonSiteConfig
|
|
11
|
+
? commonSiteConfig.webserver_port
|
|
12
|
+
: 8000
|
|
13
|
+
if (!commonSiteConfig) {
|
|
11
14
|
console.log('No common_site_config.json found, using default port 8000')
|
|
12
15
|
}
|
|
13
16
|
let proxy = {}
|
|
@@ -19,15 +22,44 @@ module.exports = function proxyOptions({
|
|
|
19
22
|
return `http://${site_name}:${webserver_port}`
|
|
20
23
|
},
|
|
21
24
|
}
|
|
25
|
+
|
|
22
26
|
return {
|
|
23
27
|
name: 'melonui-vite-plugin',
|
|
24
|
-
config: () =>
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
28
|
+
config: async () => {
|
|
29
|
+
await generateDocTypeInterfaces()
|
|
30
|
+
|
|
31
|
+
return {
|
|
32
|
+
server: {
|
|
33
|
+
port: port,
|
|
34
|
+
proxy: proxy,
|
|
35
|
+
},
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async function generateDocTypeInterfaces() {
|
|
42
|
+
const config = getConfig()
|
|
43
|
+
if (!(config && config.typeGeneration && config.typeGeneration.input)) return
|
|
44
|
+
|
|
45
|
+
const frontendFolder = process.cwd()
|
|
46
|
+
let outputPath = config.typeGeneration.output || 'src/types/doctypes.ts'
|
|
47
|
+
if (!path.isAbsolute(outputPath)) {
|
|
48
|
+
outputPath = path.join(frontendFolder, outputPath)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const appsFolder = findAppsFolder()
|
|
52
|
+
if (!appsFolder) {
|
|
53
|
+
console.error('Could not find pimelon/apps folder')
|
|
54
|
+
return
|
|
30
55
|
}
|
|
56
|
+
|
|
57
|
+
const generator = new DocTypeInterfaceGenerator(
|
|
58
|
+
appsFolder,
|
|
59
|
+
config.typeGeneration.input,
|
|
60
|
+
outputPath,
|
|
61
|
+
)
|
|
62
|
+
await generator.generate()
|
|
31
63
|
}
|
|
32
64
|
|
|
33
65
|
function getCommonSiteConfig() {
|
|
@@ -48,3 +80,24 @@ function getCommonSiteConfig() {
|
|
|
48
80
|
}
|
|
49
81
|
return null
|
|
50
82
|
}
|
|
83
|
+
|
|
84
|
+
function findAppsFolder() {
|
|
85
|
+
let currentDir = process.cwd()
|
|
86
|
+
while (currentDir !== '/') {
|
|
87
|
+
if (
|
|
88
|
+
fs.existsSync(path.join(currentDir, 'apps')) &&
|
|
89
|
+
fs.existsSync(path.join(currentDir, 'sites'))
|
|
90
|
+
) {
|
|
91
|
+
return path.join(currentDir, 'apps')
|
|
92
|
+
}
|
|
93
|
+
currentDir = path.resolve(currentDir, '..')
|
|
94
|
+
}
|
|
95
|
+
return null
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function getConfig() {
|
|
99
|
+
let configPath = path.join(process.cwd(), 'melonui.json')
|
|
100
|
+
if (fs.existsSync(configPath)) {
|
|
101
|
+
return JSON.parse(fs.readFileSync(configPath))
|
|
102
|
+
}
|
|
103
|
+
}
|