@shwfed/nuxt 0.9.1 → 0.10.0

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,13 +1,18 @@
1
1
  <script setup>
2
+ import { useNuxtApp } from "#app";
3
+ import z, {} from "zod";
2
4
  import { useSortable } from "@vueuse/integrations/useSortable";
3
5
  import { Icon } from "@iconify/vue";
4
6
  import { computed, nextTick, ref, toRaw, watch } from "vue";
5
7
  import { useI18n } from "vue-i18n";
6
8
  import {
7
9
  CalendarFieldC,
10
+ FieldsConfigC,
8
11
  NumberFieldC,
9
12
  SelectFieldC,
10
- StringFieldC
13
+ SlotFieldC,
14
+ StringFieldC,
15
+ FieldsStyleC
11
16
  } from "../fields/schema";
12
17
  import { cn } from "../../../utils/cn";
13
18
  import { Button } from "../button";
@@ -25,7 +30,6 @@ import {
25
30
  DropdownMenuItem,
26
31
  DropdownMenuTrigger
27
32
  } from "../dropdown-menu";
28
- import { IconPicker } from "../icon-picker";
29
33
  import { Input } from "../input";
30
34
  import Locale from "../locale/Locale.vue";
31
35
  import { NativeSelect, NativeSelectOption } from "../native-select";
@@ -38,8 +42,11 @@ const emit = defineEmits(["confirm"]);
38
42
  const open = defineModel("open", { type: Boolean, ...{
39
43
  default: false
40
44
  } });
45
+ const { $toast } = useNuxtApp();
41
46
  const { t } = useI18n();
42
47
  const draftOrientation = ref("horizontal");
48
+ const draftStyle = ref();
49
+ const search = ref("");
43
50
  const selectedItemId = ref("general");
44
51
  const draftFields = ref([]);
45
52
  const sortableListRef = ref(null);
@@ -49,17 +56,28 @@ const fieldTypeOptions = computed(() => [
49
56
  { type: "string", label: t("field-type-string") },
50
57
  { type: "number", label: t("field-type-number") },
51
58
  { type: "select", label: t("field-type-select") },
52
- { type: "calendar", label: t("field-type-calendar") }
59
+ { type: "calendar", label: t("field-type-calendar") },
60
+ { type: "slot", label: t("field-type-slot") }
53
61
  ]);
54
62
  const generalItem = computed(() => ({
55
63
  id: "general",
56
64
  label: t("general")
57
65
  }));
66
+ const normalizedSearch = computed(() => search.value.trim().toLocaleLowerCase());
58
67
  const selectedField = computed(() => draftFields.value.find((field) => field.draftId === selectedItemId.value));
59
- const selectedFieldValidationRules = computed(() => selectedField.value?.field.validation ?? []);
68
+ const selectedFieldValidationRules = computed(() => {
69
+ const field = selectedField.value?.field;
70
+ if (!field || field.type === "slot") {
71
+ return [];
72
+ }
73
+ return field.validation ?? [];
74
+ });
60
75
  function createDraftId() {
61
76
  return crypto.randomUUID();
62
77
  }
78
+ function createFieldId() {
79
+ return crypto.randomUUID();
80
+ }
63
81
  function createDefaultLocaleValue() {
64
82
  return [{ locale: "zh", message: "" }];
65
83
  }
@@ -81,12 +99,20 @@ function normalizeOrientation(value) {
81
99
  function getFieldTypeLabel(type) {
82
100
  return t(`field-type-${type}`);
83
101
  }
102
+ function getSlotFieldLabel(field) {
103
+ return t("slot-field", {
104
+ id: field.id.slice(0, 8)
105
+ });
106
+ }
84
107
  function getUnnamedFieldLabel(field) {
85
108
  return t("unnamed-field", {
86
109
  type: getFieldTypeLabel(field.type)
87
110
  });
88
111
  }
89
112
  function getFieldChineseTitle(field) {
113
+ if (field.type === "slot") {
114
+ return void 0;
115
+ }
90
116
  const zhTitle = field.title.find((item) => item.locale === "zh");
91
117
  if (!zhTitle) {
92
118
  return void 0;
@@ -95,14 +121,28 @@ function getFieldChineseTitle(field) {
95
121
  return message.length > 0 ? message : void 0;
96
122
  }
97
123
  function getFieldListLabel(field) {
124
+ if (field.type === "slot") {
125
+ return getSlotFieldLabel(field);
126
+ }
98
127
  return getFieldChineseTitle(field) ?? getUnnamedFieldLabel(field);
99
128
  }
100
129
  const fieldItems = computed(() => draftFields.value.map((item) => ({
101
130
  itemId: item.draftId,
131
+ fieldId: item.field.id,
102
132
  label: getFieldListLabel(item.field),
103
- path: item.field.path,
133
+ path: item.field.type === "slot" ? void 0 : item.field.path,
134
+ searchMeta: item.field.type === "slot" ? item.field.id : [item.field.path, item.field.id].filter(Boolean).join(" "),
104
135
  type: item.field.type
105
136
  })));
137
+ const filteredFieldItems = computed(() => {
138
+ if (!normalizedSearch.value) {
139
+ return fieldItems.value;
140
+ }
141
+ return fieldItems.value.filter((item) => {
142
+ const haystack = [item.label, item.searchMeta].filter(Boolean).join(" ").toLocaleLowerCase();
143
+ return haystack.includes(normalizedSearch.value);
144
+ });
145
+ });
106
146
  const selectedItemLabel = computed(() => selectedField.value ? getFieldListLabel(selectedField.value.field) : generalItem.value.label);
107
147
  const sortable = useSortable(sortableListRef, sortableItemIds);
108
148
  function getFieldErrorKey(draftId, fieldKey) {
@@ -111,6 +151,9 @@ function getFieldErrorKey(draftId, fieldKey) {
111
151
  function getValidationRuleErrorKey(draftId, index, control) {
112
152
  return `${draftId}:validation:${index}:${control}`;
113
153
  }
154
+ function getGeneralErrorKey(fieldKey) {
155
+ return `general:${fieldKey}`;
156
+ }
114
157
  function clearError(key) {
115
158
  Reflect.deleteProperty(validationErrors.value, key);
116
159
  }
@@ -209,25 +252,34 @@ function normalizeField(field) {
209
252
  disabled: normalizeOptionalString(field.disabled ?? ""),
210
253
  validation: normalizeValidationRules(field.validation)
211
254
  };
255
+ case "slot":
256
+ return {
257
+ ...field,
258
+ style: normalizeOptionalString(field.style ?? "")
259
+ };
212
260
  }
213
261
  }
214
262
  function createField(type) {
215
263
  const title = createDefaultLocaleValue();
264
+ const id = createFieldId();
216
265
  switch (type) {
217
266
  case "string":
218
267
  return {
268
+ id,
219
269
  type,
220
270
  path: "",
221
271
  title
222
272
  };
223
273
  case "number":
224
274
  return {
275
+ id,
225
276
  type,
226
277
  path: "",
227
278
  title
228
279
  };
229
280
  case "select":
230
281
  return {
282
+ id,
231
283
  type,
232
284
  path: "",
233
285
  title,
@@ -238,17 +290,28 @@ function createField(type) {
238
290
  };
239
291
  case "calendar":
240
292
  return {
293
+ id,
241
294
  type,
242
295
  path: "",
243
296
  title,
244
297
  mode: "date",
245
298
  value: "yyyy-MM-dd"
246
299
  };
300
+ case "slot":
301
+ return {
302
+ id,
303
+ type
304
+ };
247
305
  }
248
306
  }
249
307
  function resetDraftConfig() {
250
- draftOrientation.value = props.config.orientation ?? "horizontal";
251
- draftFields.value = cloneFields(props.config.fields);
308
+ applyDraftConfig(props.config);
309
+ }
310
+ function applyDraftConfig(config) {
311
+ draftOrientation.value = config.orientation ?? "horizontal";
312
+ draftStyle.value = normalizeOptionalString(config.style ?? "");
313
+ search.value = "";
314
+ draftFields.value = cloneFields(config.fields);
252
315
  selectedItemId.value = "general";
253
316
  validationErrors.value = {};
254
317
  }
@@ -283,7 +346,7 @@ function configureSortable() {
283
346
  }
284
347
  async function refreshSortable() {
285
348
  sortable.stop();
286
- if (!open.value || draftFields.value.length === 0) {
349
+ if (!open.value || draftFields.value.length === 0 || normalizedSearch.value) {
287
350
  return;
288
351
  }
289
352
  await nextTick();
@@ -318,6 +381,21 @@ watch(fieldItems, async (items) => {
318
381
  await refreshSortable();
319
382
  }
320
383
  }, { immediate: true });
384
+ watch(filteredFieldItems, (items) => {
385
+ if (!normalizedSearch.value || selectedItemId.value === "general") {
386
+ return;
387
+ }
388
+ if (items.some((item) => item.itemId === selectedItemId.value)) {
389
+ return;
390
+ }
391
+ selectedItemId.value = items[0]?.itemId ?? "general";
392
+ }, { immediate: true });
393
+ watch(normalizedSearch, async () => {
394
+ if (!open.value) {
395
+ return;
396
+ }
397
+ await refreshSortable();
398
+ });
321
399
  function discardChanges() {
322
400
  resetDraftConfig();
323
401
  open.value = false;
@@ -338,6 +416,10 @@ function selectItem(itemId) {
338
416
  function updateDraftOrientation(value) {
339
417
  draftOrientation.value = normalizeOrientation(value);
340
418
  }
419
+ function updateDraftStyle(value) {
420
+ clearError(getGeneralErrorKey("style"));
421
+ draftStyle.value = normalizeOptionalString(String(value));
422
+ }
341
423
  function updateDraftField(draftId, updater) {
342
424
  draftFields.value = draftFields.value.map((item) => item.draftId === draftId ? {
343
425
  draftId: item.draftId,
@@ -375,26 +457,25 @@ function updateSelectedFieldTitle(value) {
375
457
  title: value
376
458
  }));
377
459
  }
378
- function updateSelectedFieldPath(value) {
460
+ async function copySelectedFieldId() {
379
461
  const selected = selectedField.value;
380
462
  if (!selected) {
381
463
  return;
382
464
  }
383
- clearFieldError(selected.draftId, "path");
384
- updateDraftField(selected.draftId, (field) => ({
385
- ...field,
386
- path: String(value).trim()
387
- }));
465
+ await copyFieldId(selected.field.id);
466
+ }
467
+ async function copyFieldId(fieldId) {
468
+ await writeClipboardText(fieldId, t("copy-field-id-failed"));
388
469
  }
389
- function updateSelectedFieldIcon(value) {
470
+ function updateSelectedFieldPath(value) {
390
471
  const selected = selectedField.value;
391
- if (!selected) {
472
+ if (!selected || selected.field.type === "slot") {
392
473
  return;
393
474
  }
394
- clearFieldError(selected.draftId, "icon");
475
+ clearFieldError(selected.draftId, "path");
395
476
  updateDraftField(selected.draftId, (field) => ({
396
477
  ...field,
397
- icon: normalizeOptionalString(String(value ?? ""))
478
+ path: String(value).trim()
398
479
  }));
399
480
  }
400
481
  function updateSelectedFieldStyle(value) {
@@ -643,23 +724,28 @@ function updateSelectedCalendarDisableDate(value) {
643
724
  }
644
725
  function addValidationRule() {
645
726
  const selected = selectedField.value;
646
- if (!selected) {
727
+ if (!selected || selected.field.type === "slot") {
647
728
  return;
648
729
  }
649
- updateDraftField(selected.draftId, (field) => ({
650
- ...field,
651
- validation: [
652
- ...field.validation ?? [],
653
- {
654
- expression: "",
655
- message: ""
656
- }
657
- ]
658
- }));
730
+ updateDraftField(selected.draftId, (field) => {
731
+ if (field.type === "slot") {
732
+ return field;
733
+ }
734
+ return {
735
+ ...field,
736
+ validation: [
737
+ ...field.validation ?? [],
738
+ {
739
+ expression: "",
740
+ message: ""
741
+ }
742
+ ]
743
+ };
744
+ });
659
745
  }
660
746
  function updateValidationRule(index, updater) {
661
747
  const selected = selectedField.value;
662
- if (!selected) {
748
+ if (!selected || selected.field.type === "slot") {
663
749
  return;
664
750
  }
665
751
  const validation = selected.field.validation ?? [];
@@ -668,10 +754,15 @@ function updateValidationRule(index, updater) {
668
754
  return;
669
755
  }
670
756
  const nextValidation = validation.map((rule, ruleIndex) => ruleIndex === index ? updater(rule) : rule);
671
- updateDraftField(selected.draftId, (field) => ({
672
- ...field,
673
- validation: nextValidation
674
- }));
757
+ updateDraftField(selected.draftId, (field) => {
758
+ if (field.type === "slot") {
759
+ return field;
760
+ }
761
+ return {
762
+ ...field,
763
+ validation: nextValidation
764
+ };
765
+ });
675
766
  }
676
767
  function updateSelectedValidationExpression(index, value) {
677
768
  const selected = selectedField.value;
@@ -697,7 +788,7 @@ function updateSelectedValidationMessage(index, value) {
697
788
  }
698
789
  function moveValidationRule(index, offset) {
699
790
  const selected = selectedField.value;
700
- if (!selected) {
791
+ if (!selected || selected.field.type === "slot") {
701
792
  return;
702
793
  }
703
794
  const validation = selected.field.validation ?? [];
@@ -713,15 +804,20 @@ function moveValidationRule(index, offset) {
713
804
  }
714
805
  nextValidation[index] = targetRule;
715
806
  nextValidation[nextIndex] = currentRule;
716
- updateDraftField(selected.draftId, (field) => ({
717
- ...field,
718
- validation: nextValidation
719
- }));
807
+ updateDraftField(selected.draftId, (field) => {
808
+ if (field.type === "slot") {
809
+ return field;
810
+ }
811
+ return {
812
+ ...field,
813
+ validation: nextValidation
814
+ };
815
+ });
720
816
  clearValidationRuleErrors(selected.draftId);
721
817
  }
722
818
  function deleteValidationRule(index) {
723
819
  const selected = selectedField.value;
724
- if (!selected) {
820
+ if (!selected || selected.field.type === "slot") {
725
821
  return;
726
822
  }
727
823
  const validation = selected.field.validation ?? [];
@@ -731,7 +827,14 @@ function deleteValidationRule(index) {
731
827
  clearValidationRuleError(selected.draftId, index, "expression");
732
828
  clearValidationRuleError(selected.draftId, index, "message");
733
829
  updateDraftField(selected.draftId, (field) => {
734
- const nextValidation = (field.validation ?? []).filter((_, ruleIndex) => ruleIndex !== index);
830
+ if (field.type === "slot") {
831
+ return field;
832
+ }
833
+ const validation2 = field.validation ?? [];
834
+ const nextValidation = [
835
+ ...validation2.slice(0, index),
836
+ ...validation2.slice(index + 1)
837
+ ];
735
838
  return {
736
839
  ...field,
737
840
  validation: nextValidation.length > 0 ? nextValidation : void 0
@@ -757,6 +860,10 @@ function getSchemaIssues(field) {
757
860
  const result = CalendarFieldC.safeParse(field);
758
861
  return result.success ? [] : result.error.issues;
759
862
  }
863
+ case "slot": {
864
+ const result = SlotFieldC.safeParse(field);
865
+ return result.success ? [] : result.error.issues;
866
+ }
760
867
  }
761
868
  }
762
869
  function normalizeIssuePath(path) {
@@ -784,19 +891,30 @@ function validateDraftFields() {
784
891
  draftId: item.draftId,
785
892
  field: normalizeField(item.field)
786
893
  }));
894
+ const idOwners = {};
787
895
  const pathOwners = {};
788
896
  let firstInvalidItemId;
789
897
  for (const item of normalizedFields) {
790
- const existingOwner = pathOwners[item.field.path];
791
- if (item.field.path.length === 0) {
792
- setError(errors, getFieldErrorKey(item.draftId, "path"), t("field-path-required"));
793
- firstInvalidItemId = firstInvalidItemId ?? item.draftId;
794
- } else if (existingOwner !== void 0) {
795
- setError(errors, getFieldErrorKey(item.draftId, "path"), t("field-path-duplicate"));
796
- setError(errors, getFieldErrorKey(existingOwner, "path"), t("field-path-duplicate"));
898
+ const existingIdOwner = idOwners[item.field.id];
899
+ if (existingIdOwner !== void 0) {
900
+ setError(errors, getFieldErrorKey(item.draftId, "id"), t("field-id-duplicate"));
901
+ setError(errors, getFieldErrorKey(existingIdOwner, "id"), t("field-id-duplicate"));
797
902
  firstInvalidItemId = firstInvalidItemId ?? item.draftId;
798
903
  } else {
799
- pathOwners[item.field.path] = item.draftId;
904
+ idOwners[item.field.id] = item.draftId;
905
+ }
906
+ if (item.field.type !== "slot") {
907
+ const existingPathOwner = pathOwners[item.field.path];
908
+ if (item.field.path.length === 0) {
909
+ setError(errors, getFieldErrorKey(item.draftId, "path"), t("field-path-required"));
910
+ firstInvalidItemId = firstInvalidItemId ?? item.draftId;
911
+ } else if (existingPathOwner !== void 0) {
912
+ setError(errors, getFieldErrorKey(item.draftId, "path"), t("field-path-duplicate"));
913
+ setError(errors, getFieldErrorKey(existingPathOwner, "path"), t("field-path-duplicate"));
914
+ firstInvalidItemId = firstInvalidItemId ?? item.draftId;
915
+ } else {
916
+ pathOwners[item.field.path] = item.draftId;
917
+ }
800
918
  }
801
919
  if (item.field.type === "calendar" && item.field.value.length === 0) {
802
920
  setError(errors, getFieldErrorKey(item.draftId, "value"), t("calendar-value-required"));
@@ -818,22 +936,258 @@ function validateDraftFields() {
818
936
  }
819
937
  return normalizedFields;
820
938
  }
821
- function confirmChanges() {
939
+ function buildDraftConfig() {
822
940
  const normalizedFields = validateDraftFields();
823
941
  if (!normalizedFields) {
942
+ return void 0;
943
+ }
944
+ const generalStyleResult = FieldsStyleC.safeParse(draftStyle.value);
945
+ if (!generalStyleResult.success) {
946
+ validationErrors.value = {
947
+ ...validationErrors.value,
948
+ [getGeneralErrorKey("style")]: generalStyleResult.error.issues[0]?.message ?? t("general-style-invalid")
949
+ };
950
+ selectedItemId.value = "general";
951
+ return void 0;
952
+ }
953
+ const nextConfig = {
954
+ fields: normalizedFields.map((item) => item.field)
955
+ };
956
+ if (draftOrientation.value !== "horizontal") {
957
+ nextConfig.orientation = draftOrientation.value;
958
+ }
959
+ if (generalStyleResult.data) {
960
+ nextConfig.style = generalStyleResult.data;
961
+ }
962
+ return {
963
+ config: nextConfig,
964
+ normalizedFields
965
+ };
966
+ }
967
+ function showImportError(message) {
968
+ $toast.error(message);
969
+ }
970
+ function showCopyError(message) {
971
+ $toast.error(message);
972
+ }
973
+ function showImportErrorWithCopyAction(message, onClick) {
974
+ $toast.error(message, {
975
+ action: {
976
+ label: t("copy-paste-error"),
977
+ onClick
978
+ }
979
+ });
980
+ }
981
+ function getValidDraftConfig(errorMessage) {
982
+ const result = buildDraftConfig();
983
+ if (!result) {
984
+ showCopyError(errorMessage);
985
+ return void 0;
986
+ }
987
+ return result.config;
988
+ }
989
+ function buildFieldsConfigJsonSchema() {
990
+ return z.toJSONSchema(FieldsConfigC, {
991
+ io: "input",
992
+ unrepresentable: "any"
993
+ });
994
+ }
995
+ function buildAiPromptHeaderMarkdown() {
996
+ return [
997
+ "# \u5B57\u6BB5\u914D\u7F6E AI \u4E0A\u4E0B\u6587",
998
+ "\u4F60\u662F\u4E00\u4E2A\u5E2E\u52A9\u7528\u6237\u914D\u7F6E\u5B57\u6BB5\u7EC4\u4EF6\u7684 AI \u52A9\u624B\uFF0C\u8D1F\u8D23\u89E3\u91CA\u3001\u4FEE\u6539\u5E76\u751F\u6210\u53EF\u7C98\u8D34\u7684\u5B57\u6BB5\u914D\u7F6E\u3002",
999
+ "\u5982\u679C\u4F60\u4E0D\u786E\u5B9A\u67D0\u4E2A\u5B57\u6BB5\u3001\u8868\u8FBE\u5F0F\u53D8\u91CF\u3001\u8FD0\u884C\u65F6\u4E0A\u4E0B\u6587\u6216\u4E1A\u52A1\u542B\u4E49\uFF0C\u5FC5\u987B\u660E\u786E\u8BF4\u660E\u4E0D\u786E\u5B9A\u70B9\uFF0C\u4E0D\u8981\u731C\u6D4B\u3002",
1000
+ "\u53EA\u6709\u5F53\u7528\u6237\u660E\u786E\u8981\u6C42\u751F\u6210\u914D\u7F6E\u65F6\uFF0C\u624D\u8FD4\u56DE\u5B8C\u6574\u914D\u7F6E\u6216\u914D\u7F6E\u7247\u6BB5\uFF1B\u5982\u679C\u8FD4\u56DE\u914D\u7F6E\uFF0C\u5FC5\u987B\u653E\u5728 Markdown code block \u4E2D\u3002"
1001
+ ].join("\n");
1002
+ }
1003
+ function buildDslGuideMarkdown() {
1004
+ return [
1005
+ "## DSL / CEL \u7F16\u5199\u8BF4\u660E",
1006
+ "\u672C\u914D\u7F6E\u4E2D\u7684\u8868\u8FBE\u5F0F\u5B57\u6BB5\u5FC5\u987B\u586B\u5199 CEL \u5B57\u7B26\u4E32\uFF0C\u4E0D\u8981\u751F\u6210 JavaScript\u3001TypeScript\u3001\u7BAD\u5934\u51FD\u6570\u6216\u4F2A\u4EE3\u7801\u3002",
1007
+ "",
1008
+ "### 1. \u57FA\u7840\u8BED\u6CD5",
1009
+ '- \u5B57\u9762\u91CF\uFF1A`"text"`\u3001`123`\u3001`true`\u3001`false`\u3001`null`\u3002',
1010
+ '- \u5217\u8868 / \u5BF9\u8C61\uFF1A`[1, 2]`\u3001`{"display": "grid"}`\u3002',
1011
+ "- \u8BBF\u95EE\uFF1A`.`\u3001`[]`\u3001`.?`\u3001`[?]`\uFF0C\u4F8B\u5982 `form.name`\u3001`ctx.?user.orValue(null)`\u3002",
1012
+ "- \u6761\u4EF6\u4E0E\u903B\u8F91\uFF1A`&&`\u3001`||`\u3001`!`\u3001`condition ? a : b`\u3002",
1013
+ '- \u65B9\u6CD5\u8C03\u7528\uFF1A`value.method(args)`\uFF0C\u4F8B\u5982 `now.format("yyyy-MM-dd")`\u3002',
1014
+ "",
1015
+ "### 2. \u5E38\u89C1\u5B57\u6BB5\u4E0E\u7528\u9014",
1016
+ '- `style` \u76F8\u5173\u5B57\u6BB5\u9700\u8981\u8FD4\u56DE style map\uFF0C\u4F8B\u5982 `{"display": "grid"}`\u3002',
1017
+ "- `hidden` / `disabled` / `disableDate` \u9700\u8981\u8FD4\u56DE `bool`\u3002",
1018
+ "- `validation[].expression` \u9700\u8981\u8FD4\u56DE `bool`\uFF1B\u8FD4\u56DE `false` \u65F6\u5C55\u793A\u5BF9\u5E94\u6D88\u606F\u3002",
1019
+ "- `select.options` \u901A\u5E38\u8FD4\u56DE\u5217\u8868\uFF1B`label` / `value` / `key` \u7528\u6765\u63CF\u8FF0\u5355\u4E2A\u9009\u9879\u5982\u4F55\u6620\u5C04\u3002",
1020
+ "",
1021
+ "### 3. \u5B57\u6BB5\u7C7B\u578B\u7EA6\u675F",
1022
+ "- \u6240\u6709\u5B57\u6BB5\u90FD\u5FC5\u987B\u5305\u542B\u7A33\u5B9A\u7684 UUID `id`\u3002",
1023
+ "- \u53EA\u6709\u975E `slot` \u5B57\u6BB5\u53EF\u4EE5\u914D\u7F6E `path`\uFF0C\u5E76\u53C2\u4E0E\u8868\u5355\u503C\u8BFB\u5199\u3002",
1024
+ "- `slot` \u5B57\u6BB5\u4E0D\u4F1A\u7ED1\u5B9A\u6A21\u578B\u503C\uFF0C\u53EA\u901A\u8FC7 `field.id` \u5BF9\u5E94\u7684\u5177\u540D\u63D2\u69FD\u6E32\u67D3\u3002"
1025
+ ].join("\n");
1026
+ }
1027
+ function buildMarkdownNotes() {
1028
+ return [
1029
+ "## \u6CE8\u610F\u4E8B\u9879",
1030
+ "- \u6240\u6709\u5B57\u6BB5\u90FD\u5FC5\u987B\u4FDD\u7559\u73B0\u6709 `id`\uFF0C\u4E0D\u8981\u751F\u6210\u65B0\u7684 `id` \u66FF\u6362\u5DF2\u6709\u5B57\u6BB5\u3002",
1031
+ "- `slot` \u5B57\u6BB5\u53EA\u80FD\u4F7F\u7528 `id`\u3001`type` \u548C\u53EF\u9009\u7684 `style`\u3002",
1032
+ "- \u975E `slot` \u5B57\u6BB5\u7684 `path` \u5FC5\u987B\u552F\u4E00\u4E14\u4E0D\u80FD\u4E3A\u7A7A\u3002",
1033
+ "- \u8868\u8FBE\u5F0F\u5B57\u6BB5\u5FC5\u987B\u4E25\u683C\u9075\u5B88 schema \u7EA6\u675F\uFF1B\u5982\u679C schema \u4E0D\u652F\u6301\uFF0C\u5C31\u76F4\u63A5\u8BF4\u660E\u9650\u5236\u3002"
1034
+ ].join("\n");
1035
+ }
1036
+ function buildMarkdownCopyContent(config) {
1037
+ return [
1038
+ buildAiPromptHeaderMarkdown(),
1039
+ "",
1040
+ "## \u5F53\u524D\u914D\u7F6E",
1041
+ "```json",
1042
+ JSON.stringify(config, null, 2),
1043
+ "```",
1044
+ "",
1045
+ buildDslGuideMarkdown(),
1046
+ "",
1047
+ buildMarkdownNotes(),
1048
+ "",
1049
+ "## FieldsConfig JSON Schema",
1050
+ "```json",
1051
+ JSON.stringify(buildFieldsConfigJsonSchema(), null, 2),
1052
+ "```"
1053
+ ].join("\n");
1054
+ }
1055
+ function formatIssuePath(path) {
1056
+ if (path.length === 0) {
1057
+ return "(root)";
1058
+ }
1059
+ return path.map((segment) => {
1060
+ if (typeof segment === "number") {
1061
+ return `${segment}`;
1062
+ }
1063
+ if (typeof segment === "string") {
1064
+ return segment;
1065
+ }
1066
+ return String(segment);
1067
+ }).join(".");
1068
+ }
1069
+ function formatIssueExtraFields(issue) {
1070
+ const lines = [];
1071
+ for (const [key, value] of Object.entries(issue)) {
1072
+ if (key === "code" || key === "message" || key === "path") {
1073
+ continue;
1074
+ }
1075
+ lines.push(` - ${key}: ${JSON.stringify(value)}`);
1076
+ }
1077
+ return lines;
1078
+ }
1079
+ function buildPasteConfigErrorDetails(source, error) {
1080
+ if (error instanceof SyntaxError) {
1081
+ return [
1082
+ "## \u7C98\u8D34\u5931\u8D25\u539F\u56E0",
1083
+ "- \u7C7B\u578B\uFF1AJSON \u89E3\u6790\u5931\u8D25",
1084
+ `- message: ${error.message}`,
1085
+ "",
1086
+ "## \u539F\u59CB\u7C98\u8D34\u5185\u5BB9",
1087
+ "```text",
1088
+ source,
1089
+ "```"
1090
+ ].join("\n");
1091
+ }
1092
+ const issueLines = error.issues.flatMap((issue, index) => [
1093
+ `### Issue ${index + 1}`,
1094
+ `- path: ${formatIssuePath(issue.path)}`,
1095
+ `- code: ${issue.code}`,
1096
+ `- message: ${issue.message}`,
1097
+ ...formatIssueExtraFields(issue)
1098
+ ]);
1099
+ return [
1100
+ "## \u7C98\u8D34\u5931\u8D25\u539F\u56E0",
1101
+ "- \u7C7B\u578B\uFF1ASchema \u6821\u9A8C\u5931\u8D25",
1102
+ "",
1103
+ "## \u539F\u59CB\u7C98\u8D34\u5185\u5BB9",
1104
+ "```json",
1105
+ source,
1106
+ "```",
1107
+ "",
1108
+ "## Schema \u62A5\u9519",
1109
+ ...issueLines
1110
+ ].join("\n");
1111
+ }
1112
+ function buildPasteConfigErrorMarkdown(source, error) {
1113
+ return [
1114
+ buildAiPromptHeaderMarkdown(),
1115
+ "",
1116
+ "## \u5F53\u524D\u4EFB\u52A1",
1117
+ "\u7528\u6237\u628A\u4E00\u6BB5\u914D\u7F6E\u7C98\u8D34\u56DE\u5B57\u6BB5\u914D\u7F6E\u5668\u65F6\u5931\u8D25\u4E86\u3002\u8BF7\u57FA\u4E8E\u4E0B\u9762\u7684\u539F\u59CB\u5185\u5BB9\u548C\u62A5\u9519\u4FEE\u590D\u5F53\u524D\u914D\u7F6E\u3002",
1118
+ "\u8BF7\u4F18\u5148\u4FEE\u590D\u6700\u5C0F\u5FC5\u8981\u8303\u56F4\uFF0C\u4E0D\u8981\u53D1\u660E schema \u4E2D\u4E0D\u5B58\u5728\u7684\u5B57\u6BB5\uFF0C\u4E5F\u4E0D\u8981\u66FF\u73B0\u6709\u5B57\u6BB5\u751F\u6210\u65B0\u7684 ID\u3002",
1119
+ "\u53EA\u6709\u5F53\u7528\u6237\u660E\u786E\u8981\u6C42\u8F93\u51FA\u914D\u7F6E\u65F6\uFF0C\u624D\u8FD4\u56DE\u5B8C\u6574\u914D\u7F6E\uFF1B\u5982\u679C\u8FD4\u56DE\u914D\u7F6E\uFF0C\u5FC5\u987B\u653E\u5728 Markdown code block \u4E2D\u3002",
1120
+ "",
1121
+ buildPasteConfigErrorDetails(source, error),
1122
+ "",
1123
+ buildMarkdownNotes()
1124
+ ].join("\n");
1125
+ }
1126
+ async function writeClipboardText(value, errorMessage) {
1127
+ try {
1128
+ await navigator.clipboard.writeText(value);
1129
+ } catch {
1130
+ showCopyError(errorMessage);
1131
+ }
1132
+ }
1133
+ async function copyPasteConfigError(source, error) {
1134
+ await writeClipboardText(buildPasteConfigErrorMarkdown(source, error), t("copy-paste-error-failed"));
1135
+ }
1136
+ async function pasteConfigFromClipboard() {
1137
+ let source = "";
1138
+ try {
1139
+ source = await navigator.clipboard.readText();
1140
+ } catch {
1141
+ showImportError(t("paste-config-read-failed"));
1142
+ return;
1143
+ }
1144
+ let parsedValue;
1145
+ try {
1146
+ parsedValue = JSON.parse(source);
1147
+ } catch (error) {
1148
+ if (error instanceof SyntaxError) {
1149
+ showImportErrorWithCopyAction(
1150
+ t("paste-config-invalid-json"),
1151
+ async () => copyPasteConfigError(source, error)
1152
+ );
1153
+ return;
1154
+ }
1155
+ showImportError(t("paste-config-invalid-json"));
1156
+ return;
1157
+ }
1158
+ const result = FieldsConfigC.safeParse(parsedValue);
1159
+ if (!result.success) {
1160
+ showImportErrorWithCopyAction(
1161
+ t("paste-config-invalid-schema"),
1162
+ async () => copyPasteConfigError(source, result.error)
1163
+ );
1164
+ return;
1165
+ }
1166
+ applyDraftConfig(result.data);
1167
+ await nextTick();
1168
+ await refreshSortable();
1169
+ }
1170
+ async function copyConfig() {
1171
+ const config = getValidDraftConfig(t("copy-config-failed"));
1172
+ if (!config) {
824
1173
  return;
825
1174
  }
826
- draftFields.value = normalizedFields.map((item) => createDraftField(item.field));
827
- if (draftOrientation.value === "horizontal") {
828
- emit("confirm", {
829
- fields: normalizedFields.map((item) => item.field)
830
- });
831
- } else {
832
- emit("confirm", {
833
- orientation: draftOrientation.value,
834
- fields: normalizedFields.map((item) => item.field)
835
- });
1175
+ await writeClipboardText(JSON.stringify(config, null, 2), t("copy-config-failed"));
1176
+ }
1177
+ async function copyMarkdown() {
1178
+ const config = getValidDraftConfig(t("copy-markdown-failed"));
1179
+ if (!config) {
1180
+ return;
1181
+ }
1182
+ await writeClipboardText(buildMarkdownCopyContent(config), t("copy-markdown-failed"));
1183
+ }
1184
+ function confirmChanges() {
1185
+ const result = buildDraftConfig();
1186
+ if (!result) {
1187
+ return;
836
1188
  }
1189
+ draftFields.value = result.normalizedFields.map((item) => createDraftField(item.field));
1190
+ emit("confirm", result.config);
837
1191
  open.value = false;
838
1192
  }
839
1193
  </script>
@@ -848,9 +1202,22 @@ function confirmChanges() {
848
1202
  :show-close-button="true"
849
1203
  >
850
1204
  <DialogHeader class="gap-1 border-b border-zinc-200 px-6 py-5">
851
- <DialogTitle class="text-xl font-semibold text-zinc-800">
852
- {{ t("configure-fields") }}
853
- </DialogTitle>
1205
+ <div class="flex items-center gap-3">
1206
+ <DialogTitle class="text-xl font-semibold text-zinc-800">
1207
+ {{ t("configure-fields") }}
1208
+ </DialogTitle>
1209
+ <Button
1210
+ type="button"
1211
+ variant="ghost"
1212
+ size="sm"
1213
+ data-slot="fields-configurator-paste"
1214
+ class="shrink-0"
1215
+ @click="pasteConfigFromClipboard"
1216
+ >
1217
+ <Icon icon="fluent:clipboard-paste-20-regular" />
1218
+ {{ t("paste-config") }}
1219
+ </Button>
1220
+ </div>
854
1221
  <DialogDescription class="text-sm text-zinc-500">
855
1222
  {{ t("configure-fields-description") }}
856
1223
  </DialogDescription>
@@ -858,7 +1225,42 @@ function confirmChanges() {
858
1225
 
859
1226
  <div class="grid min-h-0 flex-1 grid-cols-[19rem_minmax(0,1fr)]">
860
1227
  <section class="flex min-h-0 flex-col border-r border-zinc-200 px-4 py-4">
861
- <div class="flex min-h-0 flex-1 flex-col overflow-hidden">
1228
+ <Input
1229
+ v-model="search"
1230
+ data-slot="fields-configurator-search"
1231
+ :placeholder="t('search-fields')"
1232
+ />
1233
+
1234
+ <DropdownMenu>
1235
+ <DropdownMenuTrigger as-child>
1236
+ <Button
1237
+ type="button"
1238
+ data-slot="fields-configurator-add"
1239
+ class="mt-3 w-full justify-center"
1240
+ >
1241
+ <Icon icon="fluent:add-20-regular" />
1242
+ {{ t("add-field") }}
1243
+ </Button>
1244
+ </DropdownMenuTrigger>
1245
+
1246
+ <DropdownMenuContent
1247
+ align="start"
1248
+ :style="{
1249
+ width: 'var(--reka-dropdown-menu-trigger-width)'
1250
+ }"
1251
+ >
1252
+ <DropdownMenuItem
1253
+ v-for="option in fieldTypeOptions"
1254
+ :key="option.type"
1255
+ :data-slot="`fields-configurator-add-item-${option.type}`"
1256
+ @select="addField(option.type)"
1257
+ >
1258
+ {{ option.label }}
1259
+ </DropdownMenuItem>
1260
+ </DropdownMenuContent>
1261
+ </DropdownMenu>
1262
+
1263
+ <div class="mt-4 flex min-h-0 flex-1 flex-col overflow-hidden">
862
1264
  <div class="flex min-h-0 flex-1 flex-col gap-1 overflow-y-auto pr-1">
863
1265
  <button
864
1266
  type="button"
@@ -880,16 +1282,17 @@ function confirmChanges() {
880
1282
  </button>
881
1283
 
882
1284
  <div
883
- v-if="fieldItems.length > 0"
1285
+ v-if="filteredFieldItems.length > 0"
884
1286
  ref="sortableListRef"
885
1287
  data-slot="fields-configurator-list"
886
1288
  class="flex flex-col gap-1"
887
1289
  >
888
1290
  <div
889
- v-for="item in fieldItems"
1291
+ v-for="item in filteredFieldItems"
890
1292
  :key="item.itemId"
891
1293
  data-slot="fields-configurator-field-item"
892
1294
  :data-item-id="item.itemId"
1295
+ :data-field-id="item.fieldId"
893
1296
  :data-field-path="item.path"
894
1297
  :data-selected="selectedItemId === item.itemId ? 'true' : 'false'"
895
1298
  :class="cn(
@@ -933,6 +1336,14 @@ function confirmChanges() {
933
1336
  </div>
934
1337
  </div>
935
1338
 
1339
+ <p
1340
+ v-else-if="normalizedSearch"
1341
+ data-slot="fields-configurator-empty"
1342
+ class="px-1 pt-2 text-xs text-zinc-400"
1343
+ >
1344
+ {{ t("no-matches") }}
1345
+ </p>
1346
+
936
1347
  <p
937
1348
  v-else
938
1349
  data-slot="fields-configurator-empty"
@@ -942,46 +1353,30 @@ function confirmChanges() {
942
1353
  </p>
943
1354
  </div>
944
1355
  </div>
945
-
946
- <div
947
- data-slot="fields-configurator-add-container"
948
- class="mt-4"
949
- >
950
- <DropdownMenu>
951
- <DropdownMenuTrigger as-child>
952
- <Button
953
- type="button"
954
- data-slot="fields-configurator-add"
955
- size="sm"
956
- variant="default"
957
- class="w-full justify-center"
958
- >
959
- <Icon icon="fluent:add-20-regular" />
960
- {{ t("add-field") }}
961
- </Button>
962
- </DropdownMenuTrigger>
963
-
964
- <DropdownMenuContent align="start">
965
- <DropdownMenuItem
966
- v-for="option in fieldTypeOptions"
967
- :key="option.type"
968
- :data-slot="`fields-configurator-add-item-${option.type}`"
969
- @select="addField(option.type)"
970
- >
971
- {{ option.label }}
972
- </DropdownMenuItem>
973
- </DropdownMenuContent>
974
- </DropdownMenu>
975
- </div>
976
1356
  </section>
977
1357
 
978
1358
  <section class="flex min-h-0 flex-col overflow-y-auto px-6 py-6">
979
- <h3
980
- data-slot="fields-configurator-detail-title"
981
- class="text-lg font-semibold text-zinc-800"
982
- >
983
- {{ selectedItemLabel }}
984
- </h3>
1359
+ <div class="flex items-center gap-2">
1360
+ <h3
1361
+ data-slot="fields-configurator-detail-title"
1362
+ class="text-lg font-semibold text-zinc-800"
1363
+ >
1364
+ {{ selectedItemLabel }}
1365
+ </h3>
1366
+ <Button
1367
+ v-if="selectedField"
1368
+ type="button"
1369
+ variant="ghost"
1370
+ size="sm"
1371
+ data-slot="fields-configurator-copy-id"
1372
+ class="size-7 p-0 text-zinc-400 hover:text-zinc-700"
1373
+ :aria-label="t('copy-field-id', { field: selectedItemLabel })"
1374
+ :title="selectedField.field.id"
1375
+ @click="copySelectedFieldId"
1376
+ >
1377
+ <Icon icon="fluent:copy-20-regular" />
1378
+ </Button>
1379
+ </div>
985
1380
 
986
1381
  <p
987
1382
  v-if="selectedItemId === 'general'"
@@ -1019,6 +1414,30 @@ function confirmChanges() {
1019
1414
  </NativeSelectOption>
1020
1415
  </NativeSelect>
1021
1416
  </label>
1417
+
1418
+ <label
1419
+ data-slot="fields-configurator-general-style-section"
1420
+ class="flex flex-col gap-2 md:col-span-2"
1421
+ >
1422
+ <span class="text-xs font-medium text-zinc-500">
1423
+ {{ t("general-style") }}
1424
+ </span>
1425
+ <Textarea
1426
+ data-slot="fields-configurator-general-style-input"
1427
+ :model-value="draftStyle ?? ''"
1428
+ :aria-invalid="validationErrors[getGeneralErrorKey('style')] ? 'true' : void 0"
1429
+ :placeholder="t('general-style-placeholder')"
1430
+ class="min-h-20 font-mono text-sm"
1431
+ @update:model-value="updateDraftStyle"
1432
+ />
1433
+ <p
1434
+ v-if="validationErrors[getGeneralErrorKey('style')]"
1435
+ data-slot="fields-configurator-general-style-error"
1436
+ class="text-xs text-red-500"
1437
+ >
1438
+ {{ validationErrors[getGeneralErrorKey("style")] }}
1439
+ </p>
1440
+ </label>
1022
1441
  </section>
1023
1442
 
1024
1443
  <div
@@ -1026,24 +1445,20 @@ function confirmChanges() {
1026
1445
  data-slot="fields-configurator-field-main"
1027
1446
  class="mt-6 flex flex-col gap-6"
1028
1447
  >
1029
- <section class="grid gap-4 md:grid-cols-2">
1030
- <label
1031
- data-slot="fields-configurator-field-type-section"
1032
- class="flex flex-col gap-2"
1033
- >
1034
- <span class="text-xs font-medium text-zinc-500">
1035
- {{ t("field-type") }}
1036
- </span>
1037
- <div
1038
- data-slot="fields-configurator-field-type"
1039
- class="flex h-9 items-center rounded-md border border-zinc-200 bg-zinc-50 px-3 text-sm text-zinc-600"
1040
- >
1041
- {{ getFieldTypeLabel(selectedField.field.type) }}
1042
- </div>
1043
- </label>
1448
+ <p
1449
+ v-if="validationErrors[getFieldErrorKey(selectedField.draftId, 'id')]"
1450
+ data-slot="fields-configurator-field-id-error"
1451
+ class="-mt-4 text-xs text-red-500"
1452
+ >
1453
+ {{ validationErrors[getFieldErrorKey(selectedField.draftId, "id")] }}
1454
+ </p>
1044
1455
 
1456
+ <section
1457
+ v-if="selectedField.field.type !== 'slot'"
1458
+ data-slot="fields-configurator-field-path-section"
1459
+ class="flex flex-col gap-2"
1460
+ >
1045
1461
  <label
1046
- data-slot="fields-configurator-field-path-section"
1047
1462
  class="flex flex-col gap-2"
1048
1463
  >
1049
1464
  <span class="text-xs font-medium text-zinc-500">
@@ -1068,6 +1483,7 @@ function confirmChanges() {
1068
1483
  </section>
1069
1484
 
1070
1485
  <section
1486
+ v-if="selectedField.field.type !== 'slot'"
1071
1487
  data-slot="fields-configurator-field-label-section"
1072
1488
  class="flex flex-col gap-2"
1073
1489
  >
@@ -1091,28 +1507,36 @@ function confirmChanges() {
1091
1507
  </section>
1092
1508
 
1093
1509
  <section
1094
- data-slot="fields-configurator-field-general-options"
1510
+ v-if="selectedField.field.type === 'slot'"
1511
+ data-slot="fields-configurator-slot-options"
1095
1512
  class="grid gap-4 md:grid-cols-2"
1096
1513
  >
1097
- <label class="flex flex-col gap-2">
1514
+ <label class="flex flex-col gap-2 md:col-span-2">
1098
1515
  <span class="text-xs font-medium text-zinc-500">
1099
- {{ t("field-icon") }}
1516
+ {{ t("field-style") }}
1100
1517
  </span>
1101
- <IconPicker
1102
- data-slot="fields-configurator-field-icon-picker"
1103
- :model-value="selectedField.field.icon ?? ''"
1104
- :invalid="validationErrors[getFieldErrorKey(selectedField.draftId, 'icon')] !== void 0"
1105
- :placeholder="t('field-icon-placeholder')"
1106
- @update:model-value="updateSelectedFieldIcon"
1518
+ <Textarea
1519
+ data-slot="fields-configurator-field-style-input"
1520
+ :model-value="selectedField.field.style ?? ''"
1521
+ :aria-invalid="validationErrors[getFieldErrorKey(selectedField.draftId, 'style')] ? 'true' : void 0"
1522
+ :placeholder="t('field-style-placeholder')"
1523
+ class="min-h-20 font-mono text-sm"
1524
+ @update:model-value="updateSelectedFieldStyle"
1107
1525
  />
1108
1526
  <p
1109
- v-if="validationErrors[getFieldErrorKey(selectedField.draftId, 'icon')]"
1527
+ v-if="validationErrors[getFieldErrorKey(selectedField.draftId, 'style')]"
1110
1528
  class="text-xs text-red-500"
1111
1529
  >
1112
- {{ validationErrors[getFieldErrorKey(selectedField.draftId, "icon")] }}
1530
+ {{ validationErrors[getFieldErrorKey(selectedField.draftId, "style")] }}
1113
1531
  </p>
1114
1532
  </label>
1533
+ </section>
1115
1534
 
1535
+ <section
1536
+ v-else
1537
+ data-slot="fields-configurator-field-general-options"
1538
+ class="grid gap-4 md:grid-cols-2"
1539
+ >
1116
1540
  <label class="flex flex-col gap-2">
1117
1541
  <span class="text-xs font-medium text-zinc-500">
1118
1542
  {{ t("field-style") }}
@@ -1443,6 +1867,7 @@ function confirmChanges() {
1443
1867
  </section>
1444
1868
 
1445
1869
  <section
1870
+ v-if="selectedField.field.type !== 'slot'"
1446
1871
  data-slot="fields-configurator-validation"
1447
1872
  class="flex flex-col gap-4"
1448
1873
  >
@@ -1574,25 +1999,50 @@ function confirmChanges() {
1574
1999
  </section>
1575
2000
  </div>
1576
2001
 
1577
- <DialogFooter class="border-t border-zinc-200 px-6 py-4">
1578
- <Button
1579
- type="button"
1580
- data-slot="fields-configurator-cancel"
1581
- variant="default"
1582
- @click="discardChanges"
1583
- >
1584
- <Icon icon="fluent:dismiss-20-regular" />
1585
- {{ t("cancel") }}
1586
- </Button>
1587
- <Button
1588
- type="button"
1589
- data-slot="fields-configurator-confirm"
1590
- variant="primary"
1591
- @click="confirmChanges"
2002
+ <DialogFooter class="border-t border-zinc-200 px-6 py-4 sm:justify-between">
2003
+ <div
2004
+ data-slot="fields-configurator-copy-actions"
2005
+ class="flex items-center gap-2"
1592
2006
  >
1593
- <Icon icon="fluent:checkmark-20-regular" />
1594
- {{ t("confirm") }}
1595
- </Button>
2007
+ <Button
2008
+ type="button"
2009
+ data-slot="fields-configurator-copy-markdown"
2010
+ variant="ghost"
2011
+ @click="copyMarkdown"
2012
+ >
2013
+ <Icon icon="simple-icons:markdown" />
2014
+ {{ t("copy-markdown") }}
2015
+ </Button>
2016
+ <Button
2017
+ type="button"
2018
+ data-slot="fields-configurator-copy-config"
2019
+ variant="ghost"
2020
+ @click="copyConfig"
2021
+ >
2022
+ <Icon icon="fluent:copy-20-regular" />
2023
+ {{ t("copy-config") }}
2024
+ </Button>
2025
+ </div>
2026
+ <div class="flex items-center gap-2">
2027
+ <Button
2028
+ type="button"
2029
+ data-slot="fields-configurator-cancel"
2030
+ variant="default"
2031
+ @click="discardChanges"
2032
+ >
2033
+ <Icon icon="fluent:dismiss-20-regular" />
2034
+ {{ t("cancel") }}
2035
+ </Button>
2036
+ <Button
2037
+ type="button"
2038
+ data-slot="fields-configurator-confirm"
2039
+ variant="primary"
2040
+ @click="confirmChanges"
2041
+ >
2042
+ <Icon icon="fluent:checkmark-20-regular" />
2043
+ {{ t("confirm") }}
2044
+ </Button>
2045
+ </div>
1596
2046
  </DialogFooter>
1597
2047
  </DialogContent>
1598
2048
  </Dialog>
@@ -1602,24 +2052,44 @@ function confirmChanges() {
1602
2052
  {
1603
2053
  "zh": {
1604
2054
  "configure-fields": "配置字段",
1605
- "configure-fields-description": "在这里浏览通用项和字段配置项。",
2055
+ "configure-fields-description": "在这里管理字段列表,并编辑当前选中的通用项或字段配置。",
1606
2056
  "field-list": "字段列表",
1607
2057
  "general": "通用",
1608
2058
  "general-description": "在这里配置字段集合级别的公共选项。",
2059
+ "general-style": "通用样式表达式",
2060
+ "general-style-placeholder": "例如返回一个 style map,例如 display: grid",
2061
+ "general-style-invalid": "样式表达式无效",
1609
2062
  "fields-orientation": "布局方向",
1610
2063
  "fields-orientation-horizontal": "水平",
1611
2064
  "fields-orientation-vertical": "垂直",
1612
2065
  "fields-orientation-floating": "浮动标签",
2066
+ "paste-config": "粘贴配置",
2067
+ "paste-config-invalid-json": "粘贴失败,剪贴板不是有效的 JSON。",
2068
+ "paste-config-invalid-schema": "粘贴失败,配置格式无效。",
2069
+ "paste-config-read-failed": "读取剪贴板失败,请检查剪贴板权限。",
2070
+ "copy-paste-error": "复制错误",
2071
+ "copy-paste-error-failed": "复制错误详情失败,请检查剪贴板权限。",
2072
+ "copy-markdown": "复制为 Markdown",
2073
+ "copy-markdown-failed": "复制 Markdown 失败,请先修正当前配置中的错误。",
2074
+ "copy-config": "仅复制配置",
2075
+ "copy-config-failed": "复制配置失败,请先修正当前配置中的错误。",
2076
+ "search-fields": "搜索字段名称……",
1613
2077
  "add-field": "新增字段",
1614
2078
  "field-type": "字段类型",
1615
2079
  "field-type-string": "文本",
1616
2080
  "field-type-number": "数字",
1617
2081
  "field-type-select": "选择",
1618
2082
  "field-type-calendar": "日期",
2083
+ "field-type-slot": "插槽",
2084
+ "field-id": "字段 ID",
2085
+ "field-id-duplicate": "字段 ID 不能重复",
1619
2086
  "field-path": "字段路径",
1620
2087
  "field-path-placeholder": "例如 profile.name",
1621
2088
  "field-path-required": "字段路径不能为空",
1622
2089
  "field-path-duplicate": "字段路径不能重复",
2090
+ "copy-field-id": "复制字段 ID:{field}",
2091
+ "copy-field-id-short": "复制 ID",
2092
+ "copy-field-id-failed": "复制字段 ID 失败,请检查剪贴板权限。",
1623
2093
  "field-label": "字段标题",
1624
2094
  "field-icon": "图标",
1625
2095
  "field-icon-placeholder": "例如 fluent:person-20-regular",
@@ -1666,8 +2136,10 @@ function confirmChanges() {
1666
2136
  "validation-expression-placeholder": "返回 false 时展示下面的消息",
1667
2137
  "validation-message": "失败消息",
1668
2138
  "validation-message-placeholder": "支持 Markdown 与双花括号表达式",
2139
+ "no-matches": "没有匹配的字段。",
1669
2140
  "no-validation-rules": "暂未配置校验规则。",
1670
2141
  "unnamed-field": "未命名{type}字段",
2142
+ "slot-field": "插槽 {id}",
1671
2143
  "no-fields": "还没有字段。",
1672
2144
  "drag-field": "拖拽调整字段顺序:{field}",
1673
2145
  "delete-field": "删除字段:{field}",
@@ -1676,24 +2148,44 @@ function confirmChanges() {
1676
2148
  },
1677
2149
  "ja": {
1678
2150
  "configure-fields": "フィールドを設定",
1679
- "configure-fields-description": "共通項目とフィールド設定をここで確認できます。",
2151
+ "configure-fields-description": "ここではフィールド一覧を管理し、選択中の共通設定またはフィールド設定を編集できます。",
1680
2152
  "field-list": "フィールド一覧",
1681
2153
  "general": "共通",
1682
2154
  "general-description": "ここではフィールド群全体に適用される共通設定を編集できます。",
2155
+ "general-style": "共通スタイル式",
2156
+ "general-style-placeholder": "例: style map を返す式。例: display: grid",
2157
+ "general-style-invalid": "スタイル式が無効です",
1683
2158
  "fields-orientation": "レイアウト方向",
1684
2159
  "fields-orientation-horizontal": "横並び",
1685
2160
  "fields-orientation-vertical": "縦並び",
1686
2161
  "fields-orientation-floating": "フローティングラベル",
2162
+ "paste-config": "設定を貼り付け",
2163
+ "paste-config-invalid-json": "貼り付けに失敗しました。クリップボードの内容が有効な JSON ではありません。",
2164
+ "paste-config-invalid-schema": "貼り付けに失敗しました。設定形式が無効です。",
2165
+ "paste-config-read-failed": "クリップボードの読み取りに失敗しました。権限を確認してください。",
2166
+ "copy-paste-error": "エラーをコピー",
2167
+ "copy-paste-error-failed": "エラー詳細のコピーに失敗しました。クリップボード権限を確認してください。",
2168
+ "copy-markdown": "Markdown としてコピー",
2169
+ "copy-markdown-failed": "Markdown のコピーに失敗しました。現在の設定エラーを先に修正してください。",
2170
+ "copy-config": "設定のみコピー",
2171
+ "copy-config-failed": "設定のコピーに失敗しました。現在の設定エラーを先に修正してください。",
2172
+ "search-fields": "フィールド名を検索…",
1687
2173
  "add-field": "フィールドを追加",
1688
2174
  "field-type": "フィールド種別",
1689
2175
  "field-type-string": "テキスト",
1690
2176
  "field-type-number": "数値",
1691
2177
  "field-type-select": "選択",
1692
2178
  "field-type-calendar": "日付",
2179
+ "field-type-slot": "スロット",
2180
+ "field-id": "フィールド ID",
2181
+ "field-id-duplicate": "フィールド ID は重複できません",
1693
2182
  "field-path": "フィールドパス",
1694
2183
  "field-path-placeholder": "例: profile.name",
1695
2184
  "field-path-required": "フィールドパスは必須です",
1696
2185
  "field-path-duplicate": "フィールドパスは重複できません",
2186
+ "copy-field-id": "{field} のフィールド ID をコピー",
2187
+ "copy-field-id-short": "ID をコピー",
2188
+ "copy-field-id-failed": "フィールド ID のコピーに失敗しました。クリップボード権限を確認してください。",
1697
2189
  "field-label": "フィールドラベル",
1698
2190
  "field-icon": "アイコン",
1699
2191
  "field-icon-placeholder": "例: fluent:person-20-regular",
@@ -1740,8 +2232,10 @@ function confirmChanges() {
1740
2232
  "validation-expression-placeholder": "false を返すと下のメッセージを表示します",
1741
2233
  "validation-message": "失敗メッセージ",
1742
2234
  "validation-message-placeholder": "Markdown と二重波括弧式を利用できます",
2235
+ "no-matches": "一致するフィールドがありません。",
1743
2236
  "no-validation-rules": "検証ルールはまだありません。",
1744
2237
  "unnamed-field": "未命名の{type}フィールド",
2238
+ "slot-field": "スロット {id}",
1745
2239
  "no-fields": "フィールドがありません。",
1746
2240
  "drag-field": "{field} の順序をドラッグで変更",
1747
2241
  "delete-field": "{field} を削除",
@@ -1750,24 +2244,44 @@ function confirmChanges() {
1750
2244
  },
1751
2245
  "en": {
1752
2246
  "configure-fields": "Configure Fields",
1753
- "configure-fields-description": "Browse the shared and field-level settings here.",
2247
+ "configure-fields-description": "Manage the field list and edit the selected general or field settings here.",
1754
2248
  "field-list": "Field list",
1755
2249
  "general": "General",
1756
2250
  "general-description": "Edit the shared settings that apply to the whole field group here.",
2251
+ "general-style": "Shared style expression",
2252
+ "general-style-placeholder": "Return a style map, for example display: grid",
2253
+ "general-style-invalid": "The style expression is invalid",
1757
2254
  "fields-orientation": "Layout orientation",
1758
2255
  "fields-orientation-horizontal": "Horizontal",
1759
2256
  "fields-orientation-vertical": "Vertical",
1760
2257
  "fields-orientation-floating": "Floating label",
2258
+ "paste-config": "Paste Config",
2259
+ "paste-config-invalid-json": "Paste failed because the clipboard does not contain valid JSON.",
2260
+ "paste-config-invalid-schema": "Paste failed because the config shape is invalid.",
2261
+ "paste-config-read-failed": "Failed to read from the clipboard. Check clipboard permissions.",
2262
+ "copy-paste-error": "Copy Error",
2263
+ "copy-paste-error-failed": "Failed to copy the error details. Check clipboard permissions.",
2264
+ "copy-markdown": "Copy as Markdown",
2265
+ "copy-markdown-failed": "Failed to copy Markdown. Fix the current config errors first.",
2266
+ "copy-config": "Copy Config Only",
2267
+ "copy-config-failed": "Failed to copy the config. Fix the current config errors first.",
2268
+ "search-fields": "Search fields…",
1761
2269
  "add-field": "Add field",
1762
2270
  "field-type": "Field type",
1763
2271
  "field-type-string": "Text",
1764
2272
  "field-type-number": "Number",
1765
2273
  "field-type-select": "Select",
1766
2274
  "field-type-calendar": "Date",
2275
+ "field-type-slot": "Slot",
2276
+ "field-id": "Field ID",
2277
+ "field-id-duplicate": "Field ID must be unique",
1767
2278
  "field-path": "Field path",
1768
2279
  "field-path-placeholder": "For example profile.name",
1769
2280
  "field-path-required": "Field path is required",
1770
2281
  "field-path-duplicate": "Field path must be unique",
2282
+ "copy-field-id": "Copy field ID: {field}",
2283
+ "copy-field-id-short": "Copy ID",
2284
+ "copy-field-id-failed": "Failed to copy the field ID. Check clipboard permissions.",
1771
2285
  "field-label": "Field label",
1772
2286
  "field-icon": "Icon",
1773
2287
  "field-icon-placeholder": "For example fluent:person-20-regular",
@@ -1814,8 +2328,10 @@ function confirmChanges() {
1814
2328
  "validation-expression-placeholder": "Return false to show the message below",
1815
2329
  "validation-message": "Failure message",
1816
2330
  "validation-message-placeholder": "Supports Markdown and double-curly expressions",
2331
+ "no-matches": "No matching fields.",
1817
2332
  "no-validation-rules": "No validation rules yet.",
1818
2333
  "unnamed-field": "Untitled {type} field",
2334
+ "slot-field": "Slot {id}",
1819
2335
  "no-fields": "No fields yet.",
1820
2336
  "drag-field": "Drag to reorder field {field}",
1821
2337
  "delete-field": "Delete field {field}",