@shwfed/nuxt 0.10.1 → 0.10.3
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/dist/module.json +1 -1
- package/dist/runtime/components/markdown.d.vue.ts +25 -0
- package/dist/runtime/components/markdown.vue +42 -0
- package/dist/runtime/components/markdown.vue.d.ts +25 -0
- package/dist/runtime/components/ui/command/CommandInput.vue +1 -1
- package/dist/runtime/components/ui/fields/Fields.vue +306 -134
- package/dist/runtime/components/ui/markdown/Markdown.d.vue.ts +25 -0
- package/dist/runtime/components/ui/markdown/Markdown.vue +128 -0
- package/dist/runtime/components/ui/markdown/Markdown.vue.d.ts +25 -0
- package/dist/runtime/components/ui/markdown/schema.d.ts +26 -0
- package/dist/runtime/components/ui/markdown/schema.js +8 -0
- package/dist/runtime/components/ui/markdown-configurator/MarkdownConfiguratorDialog.d.vue.ts +27 -0
- package/dist/runtime/components/ui/markdown-configurator/MarkdownConfiguratorDialog.vue +288 -0
- package/dist/runtime/components/ui/markdown-configurator/MarkdownConfiguratorDialog.vue.d.ts +27 -0
- package/package.json +1 -1
package/dist/module.json
CHANGED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { Effect } from 'effect';
|
|
2
|
+
import type { MarkdownConfigInput } from './ui/markdown/Markdown.vue.js';
|
|
3
|
+
export { MarkdownConfigC, MarkdownStyleC } from './ui/markdown/Markdown.vue.js';
|
|
4
|
+
export type { MarkdownConfig, MarkdownConfigInput } from './ui/markdown/Markdown.vue.js';
|
|
5
|
+
declare const _default: typeof __VLS_export;
|
|
6
|
+
export default _default;
|
|
7
|
+
declare const __VLS_export: import("vue").DefineComponent<{
|
|
8
|
+
config?: MarkdownConfigInput | Effect.Effect<MarkdownConfigInput | undefined>;
|
|
9
|
+
context?: Record<string, unknown>;
|
|
10
|
+
}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
11
|
+
"update:config": (args_0: Readonly<{
|
|
12
|
+
locale?: import("../utils/coders.js").LocaleValue;
|
|
13
|
+
inline?: boolean;
|
|
14
|
+
style?: string;
|
|
15
|
+
}>) => any;
|
|
16
|
+
}, string, import("vue").PublicProps, Readonly<{
|
|
17
|
+
config?: MarkdownConfigInput | Effect.Effect<MarkdownConfigInput | undefined>;
|
|
18
|
+
context?: Record<string, unknown>;
|
|
19
|
+
}> & Readonly<{
|
|
20
|
+
"onUpdate:config"?: ((args_0: Readonly<{
|
|
21
|
+
locale?: import("../utils/coders.js").LocaleValue;
|
|
22
|
+
inline?: boolean;
|
|
23
|
+
style?: string;
|
|
24
|
+
}>) => any) | undefined;
|
|
25
|
+
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { Effect } from "effect";
|
|
3
|
+
import { computed } from "vue";
|
|
4
|
+
import UiMarkdown from "./ui/markdown/Markdown.vue";
|
|
5
|
+
defineOptions({
|
|
6
|
+
inheritAttrs: false
|
|
7
|
+
});
|
|
8
|
+
const props = defineProps({
|
|
9
|
+
config: { type: null, required: false },
|
|
10
|
+
context: { type: Object, required: false }
|
|
11
|
+
});
|
|
12
|
+
const emit = defineEmits(["update:config"]);
|
|
13
|
+
const defaultConfig = {};
|
|
14
|
+
function isMarkdownConfigEffect(value) {
|
|
15
|
+
return typeof value === "object" && value !== null && "pipe" in value && typeof value.pipe === "function";
|
|
16
|
+
}
|
|
17
|
+
const config = computed(() => {
|
|
18
|
+
if (!props.config) {
|
|
19
|
+
return Effect.succeed(defaultConfig);
|
|
20
|
+
}
|
|
21
|
+
if (isMarkdownConfigEffect(props.config)) {
|
|
22
|
+
return props.config.pipe(Effect.map((config2) => config2 ?? defaultConfig));
|
|
23
|
+
}
|
|
24
|
+
return Effect.succeed(props.config);
|
|
25
|
+
});
|
|
26
|
+
function handleConfigUpdate(config2) {
|
|
27
|
+
emit("update:config", config2);
|
|
28
|
+
}
|
|
29
|
+
</script>
|
|
30
|
+
|
|
31
|
+
<script>
|
|
32
|
+
export { MarkdownConfigC, MarkdownStyleC } from "./ui/markdown/Markdown.vue";
|
|
33
|
+
</script>
|
|
34
|
+
|
|
35
|
+
<template>
|
|
36
|
+
<UiMarkdown
|
|
37
|
+
v-bind="$attrs"
|
|
38
|
+
:config="config"
|
|
39
|
+
:context="props.context"
|
|
40
|
+
@update:config="handleConfigUpdate"
|
|
41
|
+
/>
|
|
42
|
+
</template>
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { Effect } from 'effect';
|
|
2
|
+
import type { MarkdownConfigInput } from './ui/markdown/Markdown.vue.js';
|
|
3
|
+
export { MarkdownConfigC, MarkdownStyleC } from './ui/markdown/Markdown.vue.js';
|
|
4
|
+
export type { MarkdownConfig, MarkdownConfigInput } from './ui/markdown/Markdown.vue.js';
|
|
5
|
+
declare const _default: typeof __VLS_export;
|
|
6
|
+
export default _default;
|
|
7
|
+
declare const __VLS_export: import("vue").DefineComponent<{
|
|
8
|
+
config?: MarkdownConfigInput | Effect.Effect<MarkdownConfigInput | undefined>;
|
|
9
|
+
context?: Record<string, unknown>;
|
|
10
|
+
}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
11
|
+
"update:config": (args_0: Readonly<{
|
|
12
|
+
locale?: import("../utils/coders.js").LocaleValue;
|
|
13
|
+
inline?: boolean;
|
|
14
|
+
style?: string;
|
|
15
|
+
}>) => any;
|
|
16
|
+
}, string, import("vue").PublicProps, Readonly<{
|
|
17
|
+
config?: MarkdownConfigInput | Effect.Effect<MarkdownConfigInput | undefined>;
|
|
18
|
+
context?: Record<string, unknown>;
|
|
19
|
+
}> & Readonly<{
|
|
20
|
+
"onUpdate:config"?: ((args_0: Readonly<{
|
|
21
|
+
locale?: import("../utils/coders.js").LocaleValue;
|
|
22
|
+
inline?: boolean;
|
|
23
|
+
style?: string;
|
|
24
|
+
}>) => any) | undefined;
|
|
25
|
+
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
@@ -23,7 +23,7 @@ const { filterState } = useCommand();
|
|
|
23
23
|
<template>
|
|
24
24
|
<div
|
|
25
25
|
data-slot="command-input-wrapper"
|
|
26
|
-
class="flex h-
|
|
26
|
+
class="flex h-10 items-center gap-2 border-b border-zinc-200 px-3"
|
|
27
27
|
>
|
|
28
28
|
<Icon
|
|
29
29
|
icon="fluent:search-20-filled"
|
|
@@ -7,15 +7,15 @@ import { Icon } from "@iconify/vue";
|
|
|
7
7
|
import { Effect } from "effect";
|
|
8
8
|
import { format, parse } from "date-fns";
|
|
9
9
|
import { deleteProperty, getProperty, hasProperty, setProperty } from "dot-prop";
|
|
10
|
-
import { ref, toRaw, useId, watch, watchEffect } from "vue";
|
|
10
|
+
import { nextTick, ref, toRaw, useId, watch, watchEffect } from "vue";
|
|
11
11
|
import { useI18n } from "vue-i18n";
|
|
12
12
|
import { useCheating } from "#imports";
|
|
13
13
|
import { Calendar } from "../calendar";
|
|
14
14
|
import { Button } from "../button";
|
|
15
|
-
import { CommandGroup, CommandItem } from "../command";
|
|
15
|
+
import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from "../command";
|
|
16
16
|
import { Field, FieldContent, FieldError, FieldLabel } from "../field";
|
|
17
17
|
import FieldsConfiguratorDialog from "../fields-configurator/FieldsConfiguratorDialog.vue";
|
|
18
|
-
import { InputGroup, InputGroupAddon, InputGroupButton,
|
|
18
|
+
import { InputGroup, InputGroupAddon, InputGroupButton, InputGroupInput, InputGroupNumberField, InputGroupTextarea } from "../input-group";
|
|
19
19
|
import { Popover, PopoverAnchor, PopoverContent, PopoverTrigger } from "../popover";
|
|
20
20
|
import { Skeleton } from "../skeleton";
|
|
21
21
|
import { Tooltip, TooltipContent, TooltipTrigger } from "../tooltip";
|
|
@@ -40,6 +40,7 @@ const isConfiguratorOpen = ref(false);
|
|
|
40
40
|
const displayConfig = ref(defaultConfig);
|
|
41
41
|
const validationErrors = ref({});
|
|
42
42
|
const calendarOpen = ref({});
|
|
43
|
+
const selectOpen = ref({});
|
|
43
44
|
function cloneConfig(config2) {
|
|
44
45
|
const nextConfig = {
|
|
45
46
|
fields: config2.fields.slice()
|
|
@@ -109,6 +110,90 @@ function isFieldDisabled(field) {
|
|
|
109
110
|
function getFieldValue(field) {
|
|
110
111
|
return getProperty(modelValue.value, field.path);
|
|
111
112
|
}
|
|
113
|
+
function stringifySelectValue(value) {
|
|
114
|
+
try {
|
|
115
|
+
return JSON.stringify(value);
|
|
116
|
+
} catch {
|
|
117
|
+
return void 0;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
function isSelectObjectValue(value) {
|
|
121
|
+
return typeof value === "object" && value !== null;
|
|
122
|
+
}
|
|
123
|
+
function isSelectValueEqual(left, right) {
|
|
124
|
+
if (!isSelectObjectValue(left) && !isSelectObjectValue(right)) {
|
|
125
|
+
return Object.is(left, right);
|
|
126
|
+
}
|
|
127
|
+
if (!isSelectObjectValue(left) || !isSelectObjectValue(right)) {
|
|
128
|
+
return false;
|
|
129
|
+
}
|
|
130
|
+
const leftValue = stringifySelectValue(left);
|
|
131
|
+
const rightValue = stringifySelectValue(right);
|
|
132
|
+
if (leftValue === void 0 || rightValue === void 0) {
|
|
133
|
+
return false;
|
|
134
|
+
}
|
|
135
|
+
return leftValue === rightValue;
|
|
136
|
+
}
|
|
137
|
+
function getSelectOptions(field) {
|
|
138
|
+
return $dsl.evaluate`${field.options}`().map((option) => {
|
|
139
|
+
return {
|
|
140
|
+
key: $dsl.evaluate`${field.key}`({ option }),
|
|
141
|
+
label: $dsl.evaluate`${field.label}`({ option }),
|
|
142
|
+
value: $dsl.evaluate`${field.value}`({ option })
|
|
143
|
+
};
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
function getSelectFieldState(field) {
|
|
147
|
+
const options = getSelectOptions(field);
|
|
148
|
+
const currentValue = getFieldValue(field);
|
|
149
|
+
const selectedOption = options.find((option) => isSelectValueEqual(option.value, currentValue));
|
|
150
|
+
return {
|
|
151
|
+
options,
|
|
152
|
+
selectedKey: selectedOption?.key
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
function getSelectDisplayValue(state, value) {
|
|
156
|
+
if (typeof value !== "string") {
|
|
157
|
+
return "";
|
|
158
|
+
}
|
|
159
|
+
return state.options.find((option) => option.key === value)?.label ?? "";
|
|
160
|
+
}
|
|
161
|
+
function clearSelectField(field) {
|
|
162
|
+
deleteProperty(modelValue.value, field.path);
|
|
163
|
+
}
|
|
164
|
+
function handleSelectValueChange(field, state, value) {
|
|
165
|
+
if (typeof value !== "string") {
|
|
166
|
+
clearSelectField(field);
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
const option = state.options.find((candidate) => candidate.key === value);
|
|
170
|
+
if (!option) {
|
|
171
|
+
clearSelectField(field);
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
setProperty(modelValue.value, field.path, option.value);
|
|
175
|
+
}
|
|
176
|
+
function handleSelectOpenChange(field, open) {
|
|
177
|
+
if (open) {
|
|
178
|
+
selectOpen.value[field.path] = true;
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
Reflect.deleteProperty(selectOpen.value, field.path);
|
|
182
|
+
validateField(field);
|
|
183
|
+
}
|
|
184
|
+
function handleSelectBlur(field) {
|
|
185
|
+
window.setTimeout(() => {
|
|
186
|
+
if (!selectOpen.value[field.path]) {
|
|
187
|
+
validateField(field);
|
|
188
|
+
}
|
|
189
|
+
}, 0);
|
|
190
|
+
}
|
|
191
|
+
function handleSelectCommandValueChange(field, state, value) {
|
|
192
|
+
handleSelectValueChange(field, state, value);
|
|
193
|
+
void nextTick().then(() => {
|
|
194
|
+
handleSelectOpenChange(field, false);
|
|
195
|
+
});
|
|
196
|
+
}
|
|
112
197
|
function clearFieldValidation(path) {
|
|
113
198
|
Reflect.deleteProperty(validationErrors.value, path);
|
|
114
199
|
}
|
|
@@ -195,6 +280,11 @@ watchEffect(() => {
|
|
|
195
280
|
Reflect.deleteProperty(calendarOpen.value, path);
|
|
196
281
|
}
|
|
197
282
|
}
|
|
283
|
+
for (const path of Object.keys(selectOpen.value)) {
|
|
284
|
+
if (!activePaths.has(path)) {
|
|
285
|
+
Reflect.deleteProperty(selectOpen.value, path);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
198
288
|
});
|
|
199
289
|
</script>
|
|
200
290
|
|
|
@@ -265,7 +355,7 @@ export {
|
|
|
265
355
|
:orientation="getConfigOrientation(displayConfig)"
|
|
266
356
|
:style="getFieldStyle(field)"
|
|
267
357
|
>
|
|
268
|
-
<FieldLabel :for="['string', 'textarea', 'number'].includes(field.type) ? `${id}:${field.path}` : void 0">
|
|
358
|
+
<FieldLabel :for="['string', 'textarea', 'number', 'select'].includes(field.type) ? `${id}:${field.path}` : void 0">
|
|
269
359
|
<span class="inline-flex items-start gap-0.5">
|
|
270
360
|
<span>{{ getFieldLabel(field) }}</span>
|
|
271
361
|
<sup
|
|
@@ -343,17 +433,132 @@ export {
|
|
|
343
433
|
/>
|
|
344
434
|
</PopoverContent>
|
|
345
435
|
</Popover>
|
|
346
|
-
<
|
|
347
|
-
v-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
436
|
+
<template v-else>
|
|
437
|
+
<template v-if="field.type === 'select'">
|
|
438
|
+
<Popover
|
|
439
|
+
v-for="selectState in [getSelectFieldState(field)]"
|
|
440
|
+
:key="`${field.id}:select:${selectState.selectedKey ?? 'empty'}`"
|
|
441
|
+
:open="selectOpen[field.path] === true"
|
|
442
|
+
@update:open="(open) => handleSelectOpenChange(field, open)"
|
|
443
|
+
>
|
|
444
|
+
<PopoverAnchor as-child>
|
|
445
|
+
<InputGroup :data-disabled="isFieldDisabled(field) ? 'true' : void 0">
|
|
446
|
+
<PopoverTrigger as-child>
|
|
447
|
+
<InputGroupInput
|
|
448
|
+
:id="`${id}:${field.path}`"
|
|
449
|
+
:model-value="getSelectDisplayValue(selectState, selectState.selectedKey)"
|
|
450
|
+
:disabled="isFieldDisabled(field)"
|
|
451
|
+
:aria-invalid="isFieldInvalid(field) ? 'true' : void 0"
|
|
452
|
+
:placeholder="t('select-placeholder')"
|
|
453
|
+
class="text-left"
|
|
454
|
+
readonly
|
|
455
|
+
@blur="handleSelectBlur(field)"
|
|
456
|
+
/>
|
|
457
|
+
</PopoverTrigger>
|
|
458
|
+
<InputGroupAddon v-if="field.icon">
|
|
459
|
+
<Icon
|
|
460
|
+
:icon="field.icon"
|
|
461
|
+
/>
|
|
462
|
+
</InputGroupAddon>
|
|
463
|
+
<InputGroupAddon
|
|
464
|
+
v-if="hasProperty(modelValue, field.path)"
|
|
465
|
+
align="inline-end"
|
|
466
|
+
:class="getConfigOrientation(displayConfig) === 'floating' ? 'group-data-[disabled=true]/input-group:hidden' : void 0"
|
|
467
|
+
>
|
|
468
|
+
<Tooltip :delay-duration="800">
|
|
469
|
+
<TooltipTrigger>
|
|
470
|
+
<InputGroupButton as-child>
|
|
471
|
+
<button
|
|
472
|
+
type="button"
|
|
473
|
+
class="text-zinc-300 hover:text-zinc-500 transition-colors"
|
|
474
|
+
:disabled="isFieldDisabled(field)"
|
|
475
|
+
@click="clearSelectField(field)"
|
|
476
|
+
>
|
|
477
|
+
<Icon
|
|
478
|
+
icon="fluent:dismiss-20-regular"
|
|
479
|
+
/>
|
|
480
|
+
</button>
|
|
481
|
+
</InputGroupButton>
|
|
482
|
+
</TooltipTrigger>
|
|
483
|
+
<TooltipContent>
|
|
484
|
+
{{ t("clear") }}
|
|
485
|
+
</TooltipContent>
|
|
486
|
+
</Tooltip>
|
|
487
|
+
</InputGroupAddon>
|
|
488
|
+
</InputGroup>
|
|
489
|
+
</PopoverAnchor>
|
|
490
|
+
|
|
491
|
+
<PopoverContent class="w-72 p-0">
|
|
492
|
+
<Command
|
|
493
|
+
:model-value="selectState.selectedKey"
|
|
494
|
+
:disabled="isFieldDisabled(field)"
|
|
495
|
+
selection-behavior="toggle"
|
|
496
|
+
@update:model-value="(value) => handleSelectCommandValueChange(field, selectState, value)"
|
|
497
|
+
>
|
|
498
|
+
<CommandInput :placeholder="t('select-search-placeholder')" />
|
|
499
|
+
<CommandList>
|
|
500
|
+
<CommandEmpty as-child>
|
|
501
|
+
<section class="h-32 flex flex-col text-lg items-center justify-center gap-2 select-none">
|
|
502
|
+
<Icon
|
|
503
|
+
icon="fluent:app-recent-20-regular"
|
|
504
|
+
class="text-zinc-400 text-2xl!"
|
|
505
|
+
/>
|
|
506
|
+
<p class="text-zinc-500">
|
|
507
|
+
{{ t("select-empty") }}
|
|
508
|
+
</p>
|
|
509
|
+
</section>
|
|
510
|
+
</CommandEmpty>
|
|
511
|
+
<CommandGroup>
|
|
512
|
+
<CommandItem
|
|
513
|
+
v-for="option in selectState.options"
|
|
514
|
+
:key="option.key"
|
|
515
|
+
data-slot="select-option"
|
|
516
|
+
:value="option.key"
|
|
517
|
+
class="data-highlighted:bg-zinc-50 data-highlighted:text-zinc-700 data-[state=checked]:bg-zinc-100 data-[state=checked]:text-zinc-700 transition cursor-pointer relative flex items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none"
|
|
518
|
+
>
|
|
519
|
+
{{ option.label }}
|
|
520
|
+
</CommandItem>
|
|
521
|
+
</CommandGroup>
|
|
522
|
+
</CommandList>
|
|
523
|
+
</Command>
|
|
524
|
+
</PopoverContent>
|
|
525
|
+
</Popover>
|
|
526
|
+
</template>
|
|
527
|
+
|
|
528
|
+
<InputGroup
|
|
529
|
+
v-else
|
|
530
|
+
:data-disabled="isFieldDisabled(field) ? 'true' : void 0"
|
|
531
|
+
:class="field.type === 'textarea' ? 'h-auto flex-col items-stretch' : void 0"
|
|
354
532
|
>
|
|
355
|
-
<
|
|
533
|
+
<div
|
|
534
|
+
v-if="field.type === 'textarea'"
|
|
535
|
+
class="flex min-w-0 w-full items-center"
|
|
536
|
+
>
|
|
537
|
+
<InputGroupTextarea
|
|
538
|
+
:id="`${id}:${field.path}`"
|
|
539
|
+
:model-value="getProperty(modelValue, field.path)"
|
|
540
|
+
:maxlength="field.maxLength ? $dsl.evaluate`${field.maxLength}`() : void 0"
|
|
541
|
+
:disabled="isFieldDisabled(field)"
|
|
542
|
+
:aria-invalid="isFieldInvalid(field) ? 'true' : void 0"
|
|
543
|
+
@update:model-value="(value) => {
|
|
544
|
+
if (!value && !field.discardEmptyString) {
|
|
545
|
+
deleteProperty(modelValue, field.path);
|
|
546
|
+
} else {
|
|
547
|
+
setProperty(modelValue, field.path, value);
|
|
548
|
+
}
|
|
549
|
+
}"
|
|
550
|
+
@blur="validateField(field)"
|
|
551
|
+
/>
|
|
552
|
+
<InputGroupAddon v-if="field.icon">
|
|
553
|
+
<Icon
|
|
554
|
+
:icon="field.icon"
|
|
555
|
+
/>
|
|
556
|
+
</InputGroupAddon>
|
|
557
|
+
</div>
|
|
558
|
+
<InputGroupInput
|
|
559
|
+
v-if="field.type === 'string'"
|
|
356
560
|
:id="`${id}:${field.path}`"
|
|
561
|
+
:treat-empty-as-different-state-from-null="!!field.discardEmptyString"
|
|
357
562
|
:model-value="getProperty(modelValue, field.path)"
|
|
358
563
|
:maxlength="field.maxLength ? $dsl.evaluate`${field.maxLength}`() : void 0"
|
|
359
564
|
:disabled="isFieldDisabled(field)"
|
|
@@ -367,136 +572,97 @@ export {
|
|
|
367
572
|
}"
|
|
368
573
|
@blur="validateField(field)"
|
|
369
574
|
/>
|
|
370
|
-
<
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
:model-value="getProperty(modelValue, field.path)"
|
|
381
|
-
:maxlength="field.maxLength ? $dsl.evaluate`${field.maxLength}`() : void 0"
|
|
382
|
-
:disabled="isFieldDisabled(field)"
|
|
383
|
-
:aria-invalid="isFieldInvalid(field) ? 'true' : void 0"
|
|
384
|
-
@update:model-value="(value) => {
|
|
385
|
-
if (!value && !field.discardEmptyString) {
|
|
386
|
-
deleteProperty(modelValue, field.path);
|
|
387
|
-
} else {
|
|
388
|
-
setProperty(modelValue, field.path, value);
|
|
389
|
-
}
|
|
390
|
-
}"
|
|
391
|
-
@blur="validateField(field)"
|
|
392
|
-
/>
|
|
393
|
-
<InputGroupNumberField
|
|
394
|
-
v-if="field.type === 'number'"
|
|
395
|
-
:id="`${id}:${field.path}`"
|
|
396
|
-
:model-value="getProperty(modelValue, field.path) ?? null"
|
|
397
|
-
:min="field.min ? $dsl.evaluate`${field.min}`() : void 0"
|
|
398
|
-
:max="field.max ? $dsl.evaluate`${field.max}`() : void 0"
|
|
399
|
-
:step="field.step ? $dsl.evaluate`${field.step}`() : void 0"
|
|
400
|
-
:disabled="isFieldDisabled(field)"
|
|
401
|
-
:invalid="isFieldInvalid(field)"
|
|
402
|
-
@update:model-value="(value) => {
|
|
575
|
+
<InputGroupNumberField
|
|
576
|
+
v-if="field.type === 'number'"
|
|
577
|
+
:id="`${id}:${field.path}`"
|
|
578
|
+
:model-value="getProperty(modelValue, field.path) ?? null"
|
|
579
|
+
:min="field.min ? $dsl.evaluate`${field.min}`() : void 0"
|
|
580
|
+
:max="field.max ? $dsl.evaluate`${field.max}`() : void 0"
|
|
581
|
+
:step="field.step ? $dsl.evaluate`${field.step}`() : void 0"
|
|
582
|
+
:disabled="isFieldDisabled(field)"
|
|
583
|
+
:invalid="isFieldInvalid(field)"
|
|
584
|
+
@update:model-value="(value) => {
|
|
403
585
|
if (!value && value !== 0) {
|
|
404
586
|
deleteProperty(modelValue, field.path);
|
|
405
587
|
} else {
|
|
406
588
|
setProperty(modelValue, field.path, value);
|
|
407
589
|
}
|
|
408
590
|
}"
|
|
409
|
-
|
|
410
|
-
/>
|
|
411
|
-
<InputGroupCombobox
|
|
412
|
-
v-if="field.type === 'select'"
|
|
413
|
-
:disabled="isFieldDisabled(field)"
|
|
414
|
-
:invalid="isFieldInvalid(field)"
|
|
415
|
-
@blur="validateField(field)"
|
|
416
|
-
>
|
|
417
|
-
<CommandGroup>
|
|
418
|
-
<CommandItem
|
|
419
|
-
v-for="option in $dsl.evaluate`${field.options}`()"
|
|
420
|
-
:key="$dsl.evaluate`${field.key}`({ option })"
|
|
421
|
-
:value="$dsl.evaluate`${field.key}`({ option })"
|
|
422
|
-
@select="setProperty(modelValue, field.path, $dsl.evaluate`${field.value}`({ option }))"
|
|
423
|
-
>
|
|
424
|
-
{{ $dsl.evaluate`${field.label}`({ option }) }}
|
|
425
|
-
</CommandItem>
|
|
426
|
-
</CommandGroup>
|
|
427
|
-
</InputGroupCombobox>
|
|
428
|
-
<InputGroupAddon v-if="field.type !== 'textarea' && field.icon">
|
|
429
|
-
<Icon
|
|
430
|
-
:icon="field.icon"
|
|
591
|
+
@blur="validateField(field)"
|
|
431
592
|
/>
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
<button
|
|
442
|
-
type="button"
|
|
443
|
-
class="text-zinc-300 hover:text-zinc-500 transition-colors"
|
|
444
|
-
:disabled="isFieldDisabled(field)"
|
|
445
|
-
@click="deleteProperty(modelValue, field.path)"
|
|
446
|
-
>
|
|
447
|
-
<Icon
|
|
448
|
-
icon="fluent:dismiss-20-regular"
|
|
449
|
-
/>
|
|
450
|
-
</button>
|
|
451
|
-
</InputGroupButton>
|
|
452
|
-
</TooltipTrigger>
|
|
453
|
-
<TooltipContent>
|
|
454
|
-
{{ t("clear") }}
|
|
455
|
-
</TooltipContent>
|
|
456
|
-
</Tooltip>
|
|
457
|
-
</InputGroupAddon>
|
|
458
|
-
<InputGroupAddon
|
|
459
|
-
v-if="field.type === 'string' && field.maxLength && getProperty(modelValue, field.path)"
|
|
460
|
-
align="inline-end"
|
|
461
|
-
>
|
|
462
|
-
<span class="text-xs text-zinc-400 font-mono">
|
|
463
|
-
<span class="inline-block text-right">{{ String(getProperty(modelValue, field.path) ?? "").length }}</span>/{{ field.maxLength }}
|
|
464
|
-
</span>
|
|
465
|
-
</InputGroupAddon>
|
|
466
|
-
<InputGroupAddon
|
|
467
|
-
v-if="field.type === 'textarea' && (hasProperty(modelValue, field.path) || field.maxLength && getProperty(modelValue, field.path))"
|
|
468
|
-
align="block-end"
|
|
469
|
-
>
|
|
470
|
-
<Tooltip
|
|
471
|
-
v-if="hasProperty(modelValue, field.path)"
|
|
472
|
-
:delay-duration="800"
|
|
593
|
+
<InputGroupAddon v-if="field.type !== 'textarea' && field.icon">
|
|
594
|
+
<Icon
|
|
595
|
+
:icon="field.icon"
|
|
596
|
+
/>
|
|
597
|
+
</InputGroupAddon>
|
|
598
|
+
<InputGroupAddon
|
|
599
|
+
v-if="field.type !== 'textarea' && hasProperty(modelValue, field.path)"
|
|
600
|
+
align="inline-end"
|
|
601
|
+
:class="getConfigOrientation(displayConfig) === 'floating' ? 'group-data-[disabled=true]/input-group:hidden' : void 0"
|
|
473
602
|
>
|
|
474
|
-
<
|
|
475
|
-
<
|
|
476
|
-
<
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
603
|
+
<Tooltip :delay-duration="800">
|
|
604
|
+
<TooltipTrigger>
|
|
605
|
+
<InputGroupButton as-child>
|
|
606
|
+
<button
|
|
607
|
+
type="button"
|
|
608
|
+
class="text-zinc-300 hover:text-zinc-500 transition-colors"
|
|
609
|
+
:disabled="isFieldDisabled(field)"
|
|
610
|
+
@click="deleteProperty(modelValue, field.path)"
|
|
611
|
+
>
|
|
612
|
+
<Icon
|
|
613
|
+
icon="fluent:dismiss-20-regular"
|
|
614
|
+
/>
|
|
615
|
+
</button>
|
|
616
|
+
</InputGroupButton>
|
|
617
|
+
</TooltipTrigger>
|
|
618
|
+
<TooltipContent>
|
|
619
|
+
{{ t("clear") }}
|
|
620
|
+
</TooltipContent>
|
|
621
|
+
</Tooltip>
|
|
622
|
+
</InputGroupAddon>
|
|
623
|
+
<InputGroupAddon
|
|
624
|
+
v-if="field.type === 'string' && field.maxLength && getProperty(modelValue, field.path)"
|
|
625
|
+
align="inline-end"
|
|
495
626
|
>
|
|
496
|
-
<span class="
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
627
|
+
<span class="text-xs text-zinc-400 font-mono">
|
|
628
|
+
<span class="inline-block text-right">{{ String(getProperty(modelValue, field.path) ?? "").length }}</span>/{{ field.maxLength }}
|
|
629
|
+
</span>
|
|
630
|
+
</InputGroupAddon>
|
|
631
|
+
<InputGroupAddon
|
|
632
|
+
v-if="field.type === 'textarea' && (hasProperty(modelValue, field.path) || field.maxLength && getProperty(modelValue, field.path))"
|
|
633
|
+
align="block-end"
|
|
634
|
+
>
|
|
635
|
+
<Tooltip
|
|
636
|
+
v-if="hasProperty(modelValue, field.path)"
|
|
637
|
+
:delay-duration="800"
|
|
638
|
+
>
|
|
639
|
+
<TooltipTrigger>
|
|
640
|
+
<InputGroupButton as-child>
|
|
641
|
+
<button
|
|
642
|
+
type="button"
|
|
643
|
+
class="text-zinc-300 hover:text-zinc-500 transition-colors"
|
|
644
|
+
:disabled="isFieldDisabled(field)"
|
|
645
|
+
@click="deleteProperty(modelValue, field.path)"
|
|
646
|
+
>
|
|
647
|
+
<Icon
|
|
648
|
+
icon="fluent:dismiss-20-regular"
|
|
649
|
+
/>
|
|
650
|
+
</button>
|
|
651
|
+
</InputGroupButton>
|
|
652
|
+
</TooltipTrigger>
|
|
653
|
+
<TooltipContent>
|
|
654
|
+
{{ t("clear") }}
|
|
655
|
+
</TooltipContent>
|
|
656
|
+
</Tooltip>
|
|
657
|
+
<span
|
|
658
|
+
v-if="field.maxLength && getProperty(modelValue, field.path)"
|
|
659
|
+
class="text-xs text-zinc-400 font-mono"
|
|
660
|
+
>
|
|
661
|
+
<span class="inline-block text-right">{{ String(getProperty(modelValue, field.path) ?? "").length }}</span>/{{ field.maxLength }}
|
|
662
|
+
</span>
|
|
663
|
+
</InputGroupAddon>
|
|
664
|
+
</InputGroup>
|
|
665
|
+
</template>
|
|
500
666
|
|
|
501
667
|
<FieldError v-if="isFieldInvalid(field)">
|
|
502
668
|
<span v-html="renderValidationMessage(field)" />
|
|
@@ -513,14 +679,20 @@ export {
|
|
|
513
679
|
{
|
|
514
680
|
"zh": {
|
|
515
681
|
"clear": "清空",
|
|
682
|
+
"select-empty": "无搜索结果",
|
|
683
|
+
"select-search-placeholder": "搜索…",
|
|
516
684
|
"select-placeholder": "选择…"
|
|
517
685
|
},
|
|
518
686
|
"ja": {
|
|
519
687
|
"clear": "クリア",
|
|
688
|
+
"select-empty": "結果はありません",
|
|
689
|
+
"select-search-placeholder": "検索…",
|
|
520
690
|
"select-placeholder": "選択…"
|
|
521
691
|
},
|
|
522
692
|
"en": {
|
|
523
693
|
"clear": "Clear",
|
|
694
|
+
"select-empty": "No results",
|
|
695
|
+
"select-search-placeholder": "Search…",
|
|
524
696
|
"select-placeholder": "Select…"
|
|
525
697
|
}
|
|
526
698
|
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { Effect } from 'effect';
|
|
2
|
+
import { type MarkdownConfigInput } from './schema.js';
|
|
3
|
+
export { MarkdownConfigC, MarkdownStyleC } from './schema.js';
|
|
4
|
+
export type { MarkdownConfig, MarkdownConfigInput } from './schema.js';
|
|
5
|
+
declare const _default: typeof __VLS_export;
|
|
6
|
+
export default _default;
|
|
7
|
+
declare const __VLS_export: import("vue").DefineComponent<{
|
|
8
|
+
config: Effect.Effect<MarkdownConfigInput | undefined>;
|
|
9
|
+
context?: Record<string, unknown>;
|
|
10
|
+
}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
11
|
+
"update:config": (args_0: Readonly<{
|
|
12
|
+
locale?: import("../../../utils/coders.js").LocaleValue;
|
|
13
|
+
inline?: boolean;
|
|
14
|
+
style?: string;
|
|
15
|
+
}>) => any;
|
|
16
|
+
}, string, import("vue").PublicProps, Readonly<{
|
|
17
|
+
config: Effect.Effect<MarkdownConfigInput | undefined>;
|
|
18
|
+
context?: Record<string, unknown>;
|
|
19
|
+
}> & Readonly<{
|
|
20
|
+
"onUpdate:config"?: ((args_0: Readonly<{
|
|
21
|
+
locale?: import("../../../utils/coders.js").LocaleValue;
|
|
22
|
+
inline?: boolean;
|
|
23
|
+
style?: string;
|
|
24
|
+
}>) => any) | undefined;
|
|
25
|
+
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { useNuxtApp } from "#app";
|
|
3
|
+
import { useCheating } from "#imports";
|
|
4
|
+
import { Icon } from "@iconify/vue";
|
|
5
|
+
import { computedAsync } from "@vueuse/core";
|
|
6
|
+
import { Effect } from "effect";
|
|
7
|
+
import { computed, ref, watch } from "vue";
|
|
8
|
+
import { useI18n } from "vue-i18n";
|
|
9
|
+
import { getLocalizedText } from "../../../utils/coders";
|
|
10
|
+
import { Button } from "../button";
|
|
11
|
+
import { Skeleton } from "../skeleton";
|
|
12
|
+
import MarkdownConfiguratorDialog from "../markdown-configurator/MarkdownConfiguratorDialog.vue";
|
|
13
|
+
import { MarkdownConfigC } from "./schema";
|
|
14
|
+
const defaultConfig = {};
|
|
15
|
+
const props = defineProps({
|
|
16
|
+
config: { type: null, required: true },
|
|
17
|
+
context: { type: Object, required: false }
|
|
18
|
+
});
|
|
19
|
+
const emit = defineEmits(["update:config"]);
|
|
20
|
+
const resolvedConfig = computedAsync(async () => MarkdownConfigC.parse(await props.config.pipe(Effect.runPromise) ?? defaultConfig));
|
|
21
|
+
const displayConfig = ref(defaultConfig);
|
|
22
|
+
const { $dsl, $md } = useNuxtApp();
|
|
23
|
+
const { t, locale } = useI18n();
|
|
24
|
+
const isCheating = useCheating();
|
|
25
|
+
const isConfiguratorOpen = ref(false);
|
|
26
|
+
function normalizeStyle(value) {
|
|
27
|
+
const style = {};
|
|
28
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
29
|
+
return style;
|
|
30
|
+
}
|
|
31
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
32
|
+
if (typeof entry === "string" || typeof entry === "number") {
|
|
33
|
+
Reflect.set(style, key, entry);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return style;
|
|
37
|
+
}
|
|
38
|
+
const renderedStyle = computed(() => {
|
|
39
|
+
if (!displayConfig.value.style) {
|
|
40
|
+
return {};
|
|
41
|
+
}
|
|
42
|
+
try {
|
|
43
|
+
return normalizeStyle($dsl.evaluate`${displayConfig.value.style}`(props.context ?? {}));
|
|
44
|
+
} catch {
|
|
45
|
+
return {};
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
const markdownSource = computed(() => getLocalizedText(displayConfig.value.locale, locale.value) ?? "");
|
|
49
|
+
const renderedMarkdown = computed(() => {
|
|
50
|
+
if (markdownSource.value.trim().length === 0) {
|
|
51
|
+
return "";
|
|
52
|
+
}
|
|
53
|
+
if (displayConfig.value.inline) {
|
|
54
|
+
return $md.inline`${markdownSource.value}`(props.context);
|
|
55
|
+
}
|
|
56
|
+
return $md.block`${markdownSource.value}`(props.context);
|
|
57
|
+
});
|
|
58
|
+
function handleConfiguratorConfirm(nextConfig) {
|
|
59
|
+
displayConfig.value = nextConfig;
|
|
60
|
+
emit("update:config", nextConfig);
|
|
61
|
+
}
|
|
62
|
+
watch(resolvedConfig, (value) => {
|
|
63
|
+
if (!value) {
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
displayConfig.value = value;
|
|
67
|
+
}, { immediate: true });
|
|
68
|
+
</script>
|
|
69
|
+
|
|
70
|
+
<script>
|
|
71
|
+
export { MarkdownConfigC, MarkdownStyleC } from "./schema";
|
|
72
|
+
</script>
|
|
73
|
+
|
|
74
|
+
<template>
|
|
75
|
+
<div
|
|
76
|
+
:class="[
|
|
77
|
+
'relative p-1 -m-1 border border-dashed',
|
|
78
|
+
isCheating ? 'border-(--primary)/20 rounded hover:border-(--primary)/40 transition-colors duration-150 group cursor-pointer' : 'border-transparent'
|
|
79
|
+
]"
|
|
80
|
+
:style="renderedStyle"
|
|
81
|
+
>
|
|
82
|
+
<Button
|
|
83
|
+
v-if="isCheating"
|
|
84
|
+
data-slot="markdown-configurator-trigger"
|
|
85
|
+
variant="ghost"
|
|
86
|
+
size="sm"
|
|
87
|
+
type="button"
|
|
88
|
+
class="absolute right-3 top-3 z-20 bg-white/90 shadow-xs backdrop-blur-sm hover:bg-white"
|
|
89
|
+
:aria-label="t('markdown-open-configurator')"
|
|
90
|
+
:title="t('markdown-open-configurator')"
|
|
91
|
+
@click="isConfiguratorOpen = true"
|
|
92
|
+
>
|
|
93
|
+
<Icon icon="fluent:settings-20-regular" />
|
|
94
|
+
</Button>
|
|
95
|
+
|
|
96
|
+
<MarkdownConfiguratorDialog
|
|
97
|
+
v-if="resolvedConfig !== void 0"
|
|
98
|
+
v-model:open="isConfiguratorOpen"
|
|
99
|
+
:config="displayConfig"
|
|
100
|
+
:context="props.context"
|
|
101
|
+
@confirm="handleConfiguratorConfirm"
|
|
102
|
+
/>
|
|
103
|
+
|
|
104
|
+
<Skeleton
|
|
105
|
+
v-if="resolvedConfig === void 0"
|
|
106
|
+
class="absolute inset-0 z-10 w-full h-full"
|
|
107
|
+
/>
|
|
108
|
+
|
|
109
|
+
<div
|
|
110
|
+
data-slot="markdown-content"
|
|
111
|
+
v-html="renderedMarkdown"
|
|
112
|
+
/>
|
|
113
|
+
</div>
|
|
114
|
+
</template>
|
|
115
|
+
|
|
116
|
+
<i18n lang="json">
|
|
117
|
+
{
|
|
118
|
+
"zh": {
|
|
119
|
+
"markdown-open-configurator": "打开 Markdown 配置"
|
|
120
|
+
},
|
|
121
|
+
"ja": {
|
|
122
|
+
"markdown-open-configurator": "Markdown 設定を開く"
|
|
123
|
+
},
|
|
124
|
+
"en": {
|
|
125
|
+
"markdown-open-configurator": "Open markdown configurator"
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
</i18n>
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { Effect } from 'effect';
|
|
2
|
+
import { type MarkdownConfigInput } from './schema.js';
|
|
3
|
+
export { MarkdownConfigC, MarkdownStyleC } from './schema.js';
|
|
4
|
+
export type { MarkdownConfig, MarkdownConfigInput } from './schema.js';
|
|
5
|
+
declare const _default: typeof __VLS_export;
|
|
6
|
+
export default _default;
|
|
7
|
+
declare const __VLS_export: import("vue").DefineComponent<{
|
|
8
|
+
config: Effect.Effect<MarkdownConfigInput | undefined>;
|
|
9
|
+
context?: Record<string, unknown>;
|
|
10
|
+
}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
11
|
+
"update:config": (args_0: Readonly<{
|
|
12
|
+
locale?: import("../../../utils/coders.js").LocaleValue;
|
|
13
|
+
inline?: boolean;
|
|
14
|
+
style?: string;
|
|
15
|
+
}>) => any;
|
|
16
|
+
}, string, import("vue").PublicProps, Readonly<{
|
|
17
|
+
config: Effect.Effect<MarkdownConfigInput | undefined>;
|
|
18
|
+
context?: Record<string, unknown>;
|
|
19
|
+
}> & Readonly<{
|
|
20
|
+
"onUpdate:config"?: ((args_0: Readonly<{
|
|
21
|
+
locale?: import("../../../utils/coders.js").LocaleValue;
|
|
22
|
+
inline?: boolean;
|
|
23
|
+
style?: string;
|
|
24
|
+
}>) => any) | undefined;
|
|
25
|
+
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import z from 'zod';
|
|
2
|
+
import type { LocaleValue } from '../../../utils/coders.js';
|
|
3
|
+
export declare const MarkdownStyleC: z.ZodOptional<z.ZodString>;
|
|
4
|
+
export declare const MarkdownConfigC: z.ZodReadonly<z.ZodObject<{
|
|
5
|
+
locale: z.ZodOptional<z.ZodReadonly<z.ZodArray<z.ZodObject<{
|
|
6
|
+
locale: z.ZodEnum<{
|
|
7
|
+
zh: "zh";
|
|
8
|
+
ja: "ja";
|
|
9
|
+
en: "en";
|
|
10
|
+
ko: "ko";
|
|
11
|
+
}>;
|
|
12
|
+
message: z.ZodString;
|
|
13
|
+
}, z.core.$strip>>>>;
|
|
14
|
+
inline: z.ZodOptional<z.ZodBoolean>;
|
|
15
|
+
style: z.ZodOptional<z.ZodString>;
|
|
16
|
+
}, z.core.$strict>>;
|
|
17
|
+
export type MarkdownConfig = Readonly<{
|
|
18
|
+
locale?: LocaleValue;
|
|
19
|
+
inline?: boolean;
|
|
20
|
+
style?: string;
|
|
21
|
+
}>;
|
|
22
|
+
export type MarkdownConfigInput = Readonly<{
|
|
23
|
+
locale?: LocaleValue;
|
|
24
|
+
inline?: boolean;
|
|
25
|
+
style?: string;
|
|
26
|
+
}>;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import z from "zod";
|
|
2
|
+
import { localeC } from "../../../utils/coders.js";
|
|
3
|
+
export const MarkdownStyleC = z.string().optional().describe("\u8FD4\u56DE Markdown \u5BB9\u5668\u6837\u5F0F\u5BF9\u8C61\u7684 CEL \u8868\u8FBE\u5F0F\uFF0C\u53EF\u76F4\u63A5\u8BBF\u95EE\u5F53\u524D\u6E32\u67D3\u4E0A\u4E0B\u6587\u53D8\u91CF\u3002");
|
|
4
|
+
export const MarkdownConfigC = z.object({
|
|
5
|
+
locale: localeC.optional().describe("Markdown \u5185\u5BB9\u7684\u672C\u5730\u5316\u6587\u672C\uFF0C\u652F\u6301 Markdown \u6E32\u67D3\u4E0E {{ expression }} \u8BED\u6CD5\u3002"),
|
|
6
|
+
inline: z.boolean().optional().describe("\u4E3A true \u65F6\u4EE5\u5185\u8054\u6A21\u5F0F\u6E32\u67D3\uFF1B\u7559\u7A7A\u6216 false \u65F6\u6309\u5757\u7EA7\u6A21\u5F0F\u6E32\u67D3\u3002"),
|
|
7
|
+
style: MarkdownStyleC
|
|
8
|
+
}).strict().readonly();
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { type LocaleValue } from '../../../utils/coders.js';
|
|
2
|
+
import { type MarkdownConfig } from '../markdown/schema.js';
|
|
3
|
+
type __VLS_Props = {
|
|
4
|
+
config: MarkdownConfig;
|
|
5
|
+
context?: Record<string, unknown>;
|
|
6
|
+
};
|
|
7
|
+
type __VLS_ModelProps = {
|
|
8
|
+
'open'?: boolean;
|
|
9
|
+
};
|
|
10
|
+
type __VLS_PublicProps = __VLS_Props & __VLS_ModelProps;
|
|
11
|
+
declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
12
|
+
"update:open": (value: boolean) => any;
|
|
13
|
+
confirm: (args_0: Readonly<{
|
|
14
|
+
locale?: LocaleValue;
|
|
15
|
+
inline?: boolean;
|
|
16
|
+
style?: string;
|
|
17
|
+
}>) => any;
|
|
18
|
+
}, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
|
|
19
|
+
"onUpdate:open"?: ((value: boolean) => any) | undefined;
|
|
20
|
+
onConfirm?: ((args_0: Readonly<{
|
|
21
|
+
locale?: LocaleValue;
|
|
22
|
+
inline?: boolean;
|
|
23
|
+
style?: string;
|
|
24
|
+
}>) => any) | undefined;
|
|
25
|
+
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
26
|
+
declare const _default: typeof __VLS_export;
|
|
27
|
+
export default _default;
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { useNuxtApp } from "#app";
|
|
3
|
+
import { ref, watch } from "vue";
|
|
4
|
+
import { useI18n } from "vue-i18n";
|
|
5
|
+
import { hasVisibleLocaleValue } from "../../../utils/coders";
|
|
6
|
+
import { Button } from "../button";
|
|
7
|
+
import {
|
|
8
|
+
Dialog,
|
|
9
|
+
DialogDescription,
|
|
10
|
+
DialogFooter,
|
|
11
|
+
DialogHeader,
|
|
12
|
+
DialogScrollContent,
|
|
13
|
+
DialogTitle
|
|
14
|
+
} from "../dialog";
|
|
15
|
+
import Locale from "../locale/Locale.vue";
|
|
16
|
+
import { Switch } from "../switch";
|
|
17
|
+
import { Textarea } from "../textarea";
|
|
18
|
+
import { MarkdownConfigC } from "../markdown/schema";
|
|
19
|
+
const MARKDOWN_EXPRESSION_EXAMPLE = "{{ expression }}";
|
|
20
|
+
const STYLE_EXAMPLE = '{"display": "grid", "gap": "8px"}';
|
|
21
|
+
const props = defineProps({
|
|
22
|
+
config: { type: Object, required: true },
|
|
23
|
+
context: { type: Object, required: false }
|
|
24
|
+
});
|
|
25
|
+
const emit = defineEmits(["confirm"]);
|
|
26
|
+
const open = defineModel("open", { type: Boolean, ...{
|
|
27
|
+
default: false
|
|
28
|
+
} });
|
|
29
|
+
const { $dsl } = useNuxtApp();
|
|
30
|
+
const { t } = useI18n();
|
|
31
|
+
const draftLocale = ref();
|
|
32
|
+
const draftInline = ref(false);
|
|
33
|
+
const draftStyle = ref("");
|
|
34
|
+
const styleError = ref("");
|
|
35
|
+
function resetDraftState(config) {
|
|
36
|
+
draftLocale.value = config.locale;
|
|
37
|
+
draftInline.value = config.inline === true;
|
|
38
|
+
draftStyle.value = config.style ?? "";
|
|
39
|
+
styleError.value = "";
|
|
40
|
+
}
|
|
41
|
+
function discardChanges() {
|
|
42
|
+
resetDraftState(props.config);
|
|
43
|
+
open.value = false;
|
|
44
|
+
}
|
|
45
|
+
function handleOpenChange(value) {
|
|
46
|
+
if (value) {
|
|
47
|
+
open.value = true;
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
discardChanges();
|
|
51
|
+
}
|
|
52
|
+
function normalizeLocale(value) {
|
|
53
|
+
if (!hasVisibleLocaleValue(value)) {
|
|
54
|
+
return void 0;
|
|
55
|
+
}
|
|
56
|
+
return value;
|
|
57
|
+
}
|
|
58
|
+
function normalizeStyle(value) {
|
|
59
|
+
const trimmedValue = value.trim();
|
|
60
|
+
return trimmedValue.length > 0 ? trimmedValue : void 0;
|
|
61
|
+
}
|
|
62
|
+
function updateDraftStyle(value) {
|
|
63
|
+
draftStyle.value = typeof value === "string" ? value : String(value);
|
|
64
|
+
}
|
|
65
|
+
function isStyleObject(value) {
|
|
66
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
67
|
+
}
|
|
68
|
+
function validateStyle(style) {
|
|
69
|
+
styleError.value = "";
|
|
70
|
+
if (!style) {
|
|
71
|
+
return true;
|
|
72
|
+
}
|
|
73
|
+
try {
|
|
74
|
+
const result = $dsl.evaluate`${style}`(props.context ?? {});
|
|
75
|
+
if (isStyleObject(result)) {
|
|
76
|
+
return true;
|
|
77
|
+
}
|
|
78
|
+
} catch {
|
|
79
|
+
styleError.value = t("style-error");
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
styleError.value = t("style-error");
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
function confirmChanges() {
|
|
86
|
+
const normalizedStyle = normalizeStyle(draftStyle.value);
|
|
87
|
+
if (!validateStyle(normalizedStyle)) {
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
const nextConfig = {};
|
|
91
|
+
const localeValue = normalizeLocale(draftLocale.value);
|
|
92
|
+
if (localeValue) {
|
|
93
|
+
nextConfig.locale = localeValue;
|
|
94
|
+
}
|
|
95
|
+
if (draftInline.value) {
|
|
96
|
+
nextConfig.inline = true;
|
|
97
|
+
}
|
|
98
|
+
if (normalizedStyle) {
|
|
99
|
+
nextConfig.style = normalizedStyle;
|
|
100
|
+
}
|
|
101
|
+
emit("confirm", MarkdownConfigC.parse(nextConfig));
|
|
102
|
+
open.value = false;
|
|
103
|
+
}
|
|
104
|
+
watch(() => props.config, (value) => {
|
|
105
|
+
resetDraftState(value);
|
|
106
|
+
}, { immediate: true });
|
|
107
|
+
</script>
|
|
108
|
+
|
|
109
|
+
<template>
|
|
110
|
+
<Dialog
|
|
111
|
+
:open="open"
|
|
112
|
+
@update:open="handleOpenChange"
|
|
113
|
+
>
|
|
114
|
+
<DialogScrollContent
|
|
115
|
+
class="w-[calc(100%-2rem)] max-w-3xl p-0"
|
|
116
|
+
:show-close-button="true"
|
|
117
|
+
>
|
|
118
|
+
<DialogHeader class="gap-1 border-b border-zinc-200 px-6 py-5">
|
|
119
|
+
<DialogTitle class="text-xl font-semibold text-zinc-800">
|
|
120
|
+
{{ t("configure-markdown") }}
|
|
121
|
+
</DialogTitle>
|
|
122
|
+
<DialogDescription class="text-sm text-zinc-500">
|
|
123
|
+
{{ t("configure-markdown-description") }}
|
|
124
|
+
</DialogDescription>
|
|
125
|
+
</DialogHeader>
|
|
126
|
+
|
|
127
|
+
<div class="flex flex-col gap-6 px-6 py-5">
|
|
128
|
+
<section
|
|
129
|
+
data-slot="markdown-configurator-locale-section"
|
|
130
|
+
class="flex flex-col gap-3"
|
|
131
|
+
>
|
|
132
|
+
<div class="flex flex-col gap-1">
|
|
133
|
+
<p class="text-xs font-medium text-zinc-500">
|
|
134
|
+
{{ t("locale") }}
|
|
135
|
+
</p>
|
|
136
|
+
<p class="text-sm text-zinc-500">
|
|
137
|
+
{{ t("locale-description", { expression: MARKDOWN_EXPRESSION_EXAMPLE }) }}
|
|
138
|
+
</p>
|
|
139
|
+
</div>
|
|
140
|
+
|
|
141
|
+
<Locale
|
|
142
|
+
data-slot="markdown-configurator-locale"
|
|
143
|
+
:model-value="draftLocale"
|
|
144
|
+
:multiline="true"
|
|
145
|
+
@update:model-value="draftLocale = $event"
|
|
146
|
+
/>
|
|
147
|
+
</section>
|
|
148
|
+
|
|
149
|
+
<section
|
|
150
|
+
data-slot="markdown-configurator-render-mode"
|
|
151
|
+
class="flex flex-col gap-3"
|
|
152
|
+
>
|
|
153
|
+
<div class="flex flex-col gap-1">
|
|
154
|
+
<p class="text-xs font-medium text-zinc-500">
|
|
155
|
+
{{ t("render-mode") }}
|
|
156
|
+
</p>
|
|
157
|
+
<p class="text-sm text-zinc-500">
|
|
158
|
+
{{ t("render-mode-description") }}
|
|
159
|
+
</p>
|
|
160
|
+
</div>
|
|
161
|
+
|
|
162
|
+
<label class="flex items-center justify-between gap-4 rounded-md border border-zinc-200 px-4 py-3">
|
|
163
|
+
<div class="flex flex-col gap-1">
|
|
164
|
+
<span class="text-sm font-medium text-zinc-800">
|
|
165
|
+
{{ t("inline") }}
|
|
166
|
+
</span>
|
|
167
|
+
<span
|
|
168
|
+
data-slot="markdown-configurator-render-mode-value"
|
|
169
|
+
class="text-xs text-zinc-500"
|
|
170
|
+
>
|
|
171
|
+
{{ draftInline ? t("inline-enabled") : t("block-enabled") }}
|
|
172
|
+
</span>
|
|
173
|
+
</div>
|
|
174
|
+
|
|
175
|
+
<Switch
|
|
176
|
+
data-slot="markdown-configurator-inline-switch"
|
|
177
|
+
:model-value="draftInline"
|
|
178
|
+
@update:model-value="draftInline = $event === true"
|
|
179
|
+
/>
|
|
180
|
+
</label>
|
|
181
|
+
</section>
|
|
182
|
+
|
|
183
|
+
<section
|
|
184
|
+
data-slot="markdown-configurator-style-section"
|
|
185
|
+
class="flex flex-col gap-3"
|
|
186
|
+
>
|
|
187
|
+
<div class="flex flex-col gap-1">
|
|
188
|
+
<p class="text-xs font-medium text-zinc-500">
|
|
189
|
+
{{ t("style") }}
|
|
190
|
+
</p>
|
|
191
|
+
<p class="text-sm text-zinc-500">
|
|
192
|
+
{{ t("style-description") }}
|
|
193
|
+
</p>
|
|
194
|
+
</div>
|
|
195
|
+
|
|
196
|
+
<Textarea
|
|
197
|
+
data-slot="markdown-configurator-style-input"
|
|
198
|
+
:model-value="draftStyle"
|
|
199
|
+
:aria-invalid="styleError ? 'true' : void 0"
|
|
200
|
+
:placeholder="STYLE_EXAMPLE"
|
|
201
|
+
class="min-h-20 font-mono text-sm"
|
|
202
|
+
@update:model-value="updateDraftStyle"
|
|
203
|
+
/>
|
|
204
|
+
|
|
205
|
+
<p
|
|
206
|
+
v-if="styleError"
|
|
207
|
+
data-slot="markdown-configurator-style-error"
|
|
208
|
+
class="text-xs text-red-500"
|
|
209
|
+
>
|
|
210
|
+
{{ styleError }}
|
|
211
|
+
</p>
|
|
212
|
+
</section>
|
|
213
|
+
</div>
|
|
214
|
+
|
|
215
|
+
<DialogFooter class="border-t border-zinc-200 px-6 py-4">
|
|
216
|
+
<Button
|
|
217
|
+
data-slot="markdown-configurator-cancel"
|
|
218
|
+
type="button"
|
|
219
|
+
variant="ghost"
|
|
220
|
+
@click="discardChanges"
|
|
221
|
+
>
|
|
222
|
+
{{ t("cancel") }}
|
|
223
|
+
</Button>
|
|
224
|
+
<Button
|
|
225
|
+
data-slot="markdown-configurator-confirm"
|
|
226
|
+
type="button"
|
|
227
|
+
variant="primary"
|
|
228
|
+
@click="confirmChanges"
|
|
229
|
+
>
|
|
230
|
+
{{ t("confirm") }}
|
|
231
|
+
</Button>
|
|
232
|
+
</DialogFooter>
|
|
233
|
+
</DialogScrollContent>
|
|
234
|
+
</Dialog>
|
|
235
|
+
</template>
|
|
236
|
+
|
|
237
|
+
<i18n lang="json">
|
|
238
|
+
{
|
|
239
|
+
"zh": {
|
|
240
|
+
"configure-markdown": "配置 Markdown",
|
|
241
|
+
"configure-markdown-description": "编辑 Markdown 文案、渲染模式和容器样式。",
|
|
242
|
+
"locale": "文案",
|
|
243
|
+
"locale-description": "支持 Markdown 编辑和 {expression} 语法。",
|
|
244
|
+
"render-mode": "渲染模式",
|
|
245
|
+
"render-mode-description": "关闭时按 block 渲染,开启后按 inline 渲染。",
|
|
246
|
+
"inline": "Inline",
|
|
247
|
+
"inline-enabled": "当前使用 inline 模式",
|
|
248
|
+
"block-enabled": "当前使用 block 模式",
|
|
249
|
+
"style": "容器样式",
|
|
250
|
+
"style-description": "填写返回样式对象的 CEL 表达式,使用当前 Markdown 上下文求值。",
|
|
251
|
+
"style-error": "样式表达式必须返回对象",
|
|
252
|
+
"cancel": "取消",
|
|
253
|
+
"confirm": "确认"
|
|
254
|
+
},
|
|
255
|
+
"ja": {
|
|
256
|
+
"configure-markdown": "Markdown を設定",
|
|
257
|
+
"configure-markdown-description": "Markdown テキスト、描画モード、コンテナスタイルを編集します。",
|
|
258
|
+
"locale": "テキスト",
|
|
259
|
+
"locale-description": "Markdown と {expression} 構文をサポートします。",
|
|
260
|
+
"render-mode": "描画モード",
|
|
261
|
+
"render-mode-description": "オフでは block、オンでは inline で描画します。",
|
|
262
|
+
"inline": "Inline",
|
|
263
|
+
"inline-enabled": "現在は inline モードです",
|
|
264
|
+
"block-enabled": "現在は block モードです",
|
|
265
|
+
"style": "コンテナスタイル",
|
|
266
|
+
"style-description": "現在の Markdown コンテキストで評価される CEL スタイル式を入力します。",
|
|
267
|
+
"style-error": "スタイル式はオブジェクトを返す必要があります",
|
|
268
|
+
"cancel": "キャンセル",
|
|
269
|
+
"confirm": "確認"
|
|
270
|
+
},
|
|
271
|
+
"en": {
|
|
272
|
+
"configure-markdown": "Configure markdown",
|
|
273
|
+
"configure-markdown-description": "Edit markdown content, render mode, and container style.",
|
|
274
|
+
"locale": "Content",
|
|
275
|
+
"locale-description": "Supports markdown editing and {expression} syntax.",
|
|
276
|
+
"render-mode": "Render mode",
|
|
277
|
+
"render-mode-description": "Off renders as block, on renders as inline.",
|
|
278
|
+
"inline": "Inline",
|
|
279
|
+
"inline-enabled": "Currently using inline mode",
|
|
280
|
+
"block-enabled": "Currently using block mode",
|
|
281
|
+
"style": "Container style",
|
|
282
|
+
"style-description": "Provide a CEL expression that returns a style object using the current markdown context.",
|
|
283
|
+
"style-error": "Style expression must return an object",
|
|
284
|
+
"cancel": "Cancel",
|
|
285
|
+
"confirm": "Confirm"
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
</i18n>
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { type LocaleValue } from '../../../utils/coders.js';
|
|
2
|
+
import { type MarkdownConfig } from '../markdown/schema.js';
|
|
3
|
+
type __VLS_Props = {
|
|
4
|
+
config: MarkdownConfig;
|
|
5
|
+
context?: Record<string, unknown>;
|
|
6
|
+
};
|
|
7
|
+
type __VLS_ModelProps = {
|
|
8
|
+
'open'?: boolean;
|
|
9
|
+
};
|
|
10
|
+
type __VLS_PublicProps = __VLS_Props & __VLS_ModelProps;
|
|
11
|
+
declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
12
|
+
"update:open": (value: boolean) => any;
|
|
13
|
+
confirm: (args_0: Readonly<{
|
|
14
|
+
locale?: LocaleValue;
|
|
15
|
+
inline?: boolean;
|
|
16
|
+
style?: string;
|
|
17
|
+
}>) => any;
|
|
18
|
+
}, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
|
|
19
|
+
"onUpdate:open"?: ((value: boolean) => any) | undefined;
|
|
20
|
+
onConfirm?: ((args_0: Readonly<{
|
|
21
|
+
locale?: LocaleValue;
|
|
22
|
+
inline?: boolean;
|
|
23
|
+
style?: string;
|
|
24
|
+
}>) => any) | undefined;
|
|
25
|
+
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
26
|
+
declare const _default: typeof __VLS_export;
|
|
27
|
+
export default _default;
|