@shwfed/nuxt 0.7.10 → 0.7.11

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.
Files changed (34) hide show
  1. package/dist/module.json +1 -1
  2. package/dist/runtime/components/table.d.vue.ts +68 -2
  3. package/dist/runtime/components/table.vue +0 -1
  4. package/dist/runtime/components/table.vue.d.ts +68 -2
  5. package/dist/runtime/components/ui/dropdown-menu/DropdownMenuItem.vue +1 -1
  6. package/dist/runtime/components/ui/field/FieldLabel.vue +1 -1
  7. package/dist/runtime/components/ui/icon-picker/IconPicker.d.vue.ts +15 -0
  8. package/dist/runtime/components/ui/icon-picker/IconPicker.vue +178 -0
  9. package/dist/runtime/components/ui/icon-picker/IconPicker.vue.d.ts +15 -0
  10. package/dist/runtime/components/ui/icon-picker/index.d.ts +1 -0
  11. package/dist/runtime/components/ui/icon-picker/index.js +1 -0
  12. package/dist/runtime/components/ui/input-group/InputGroupComboboxInput.vue +1 -1
  13. package/dist/runtime/components/ui/input-group/InputGroupInput.vue +1 -1
  14. package/dist/runtime/components/ui/input-group/InputGroupNumberField.vue +1 -1
  15. package/dist/runtime/components/ui/input-group/InputGroupTextarea.vue +1 -1
  16. package/dist/runtime/components/ui/native-select/NativeSelect.d.vue.ts +2 -2
  17. package/dist/runtime/components/ui/native-select/NativeSelect.vue +1 -1
  18. package/dist/runtime/components/ui/native-select/NativeSelect.vue.d.ts +2 -2
  19. package/dist/runtime/components/ui/switch/Switch.vue +2 -2
  20. package/dist/runtime/components/ui/table/Table.d.vue.ts +69 -3
  21. package/dist/runtime/components/ui/table/Table.vue +201 -41
  22. package/dist/runtime/components/ui/table/Table.vue.d.ts +69 -3
  23. package/dist/runtime/components/ui/table/schema.d.ts +107 -4
  24. package/dist/runtime/components/ui/table/schema.js +106 -90
  25. package/dist/runtime/components/ui/table-configurator/TableConfiguratorDialog.d.vue.ts +68 -2
  26. package/dist/runtime/components/ui/table-configurator/TableConfiguratorDialog.vue +590 -104
  27. package/dist/runtime/components/ui/table-configurator/TableConfiguratorDialog.vue.d.ts +68 -2
  28. package/dist/runtime/components/ui/textarea/Textarea.vue +1 -1
  29. package/dist/runtime/plugins/toast/index.d.ts +2 -2
  30. package/dist/runtime/table-renderers/builtins.js +151 -75
  31. package/dist/runtime/table-renderers/registry.d.ts +1 -1
  32. package/dist/runtime/utils/coders.d.ts +2 -0
  33. package/dist/runtime/utils/coders.js +13 -0
  34. package/package.json +6 -6
@@ -6,7 +6,7 @@ import z from "zod";
6
6
  import { computed, nextTick, ref, watch } from "vue";
7
7
  import { useI18n } from "vue-i18n";
8
8
  import { useTableRenderers } from "../../../composables/useTableRenderers";
9
- import { dotPropC, expressionC } from "../../../utils/coders";
9
+ import { dotPropC, expressionC, getLocalizedText, hasVisibleLocaleValue } from "../../../utils/coders";
10
10
  import { cn } from "../../../utils/cn";
11
11
  import ExpressionEditor from "../expression-editor/ExpressionEditor.vue";
12
12
  import { Button } from "../button";
@@ -20,7 +20,7 @@ import {
20
20
  DialogTitle
21
21
  } from "../dialog";
22
22
  import { Input } from "../input";
23
- import { InputGroup, InputGroupNumberField } from "../input-group";
23
+ import { InputGroup, InputGroupAddon, InputGroupNumberField } from "../input-group";
24
24
  import Locale from "../locale/Locale.vue";
25
25
  import { NativeSelect, NativeSelectOption } from "../native-select";
26
26
  import { NumberField, NumberFieldInput } from "../number-field";
@@ -30,7 +30,8 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from "../tabs";
30
30
  import { Toggle } from "../toggle";
31
31
  import {
32
32
  getColumnTechnicalKey,
33
- getLocalizedText,
33
+ normalizePaginationPageSizes,
34
+ resolvePaginationPageSize,
34
35
  TableConfigC,
35
36
  TableConfigCellStylesC,
36
37
  TableConfigEnableMultiRowSelectionC,
@@ -66,7 +67,7 @@ const open = defineModel("open", { type: Boolean, ...{
66
67
  } });
67
68
  const { $toast } = useNuxtApp();
68
69
  const { t, locale } = useI18n();
69
- const { listTableRenderers, resolveTableRenderer } = useTableRenderers();
70
+ const { getTableRenderer, listTableRenderers, resolveTableRenderer } = useTableRenderers();
70
71
  const search = ref("");
71
72
  const childSearch = ref("");
72
73
  const selectedItemId = ref("general");
@@ -80,6 +81,7 @@ const draftCellStyles = ref();
80
81
  const draftPaginationLeft = ref();
81
82
  const draftPaginationRight = ref();
82
83
  const draftPageSize = ref();
84
+ const draftPaginationPageSizes = ref([]);
83
85
  const leftPinnedSortableListRef = ref(null);
84
86
  const leftPinnedSortableItemIds = ref([]);
85
87
  const centerSortableListRef = ref(null);
@@ -89,6 +91,8 @@ const rightPinnedSortableItemIds = ref([]);
89
91
  const leftPinnedRootItemIds = ref([]);
90
92
  const centerRootItemIds = ref([]);
91
93
  const rightPinnedRootItemIds = ref([]);
94
+ const paginationPageSizesSortableListRef = ref(null);
95
+ const paginationPageSizesSortableItemIds = ref([]);
92
96
  const selectedChildSortableListRef = ref(null);
93
97
  const selectedChildSortableItemIds = ref([]);
94
98
  const getRowIdEditor = ref(null);
@@ -96,7 +100,6 @@ const getSubRowsEditor = ref(null);
96
100
  const enableRowSelectionEditor = ref(null);
97
101
  const enableMultiRowSelectionEditor = ref(null);
98
102
  const cellStylesEditor = ref(null);
99
- const importInputRef = ref(null);
100
103
  const accessorPathEditor = ref(null);
101
104
  const accessorReadEditor = ref(null);
102
105
  const accessorWriteEditor = ref(null);
@@ -367,6 +370,18 @@ function normalizeOptionalNumber(value) {
367
370
  function getInitialPageSize(config) {
368
371
  return config.props?.initialState?.pagination?.pageSize;
369
372
  }
373
+ function getInitialPaginationPageSizes(config) {
374
+ return normalizePaginationPageSizes(config.paginationPageSizes) ?? [];
375
+ }
376
+ function createPaginationPageSizeItem(value = void 0) {
377
+ return {
378
+ itemId: crypto.randomUUID(),
379
+ value
380
+ };
381
+ }
382
+ function createPaginationPageSizeItems(values) {
383
+ return (normalizePaginationPageSizes(values) ?? []).map((value) => createPaginationPageSizeItem(value));
384
+ }
370
385
  function isRendererConfigExposed(value) {
371
386
  if (typeof value !== "object" || value === null) {
372
387
  return false;
@@ -482,6 +497,32 @@ function reorderItemIds(itemIds, oldIndex, newIndex) {
482
497
  nextItemIds.splice(newIndex, 0, movedItemId);
483
498
  return nextItemIds;
484
499
  }
500
+ function areItemIdsEqual(left, right) {
501
+ if (left.length !== right.length) {
502
+ return false;
503
+ }
504
+ return left.every((itemId, index) => itemId === right[index]);
505
+ }
506
+ function syncDraftColumnTreeRootOrder() {
507
+ const existingRootItemIds = draftColumnTree.value.rootItemIds;
508
+ const nextRootItemIds = [
509
+ ...leftPinnedRootItemIds.value,
510
+ ...centerRootItemIds.value,
511
+ ...rightPinnedRootItemIds.value
512
+ ].filter((itemId, index, itemIds) => existingRootItemIds.includes(itemId) && itemIds.indexOf(itemId) === index);
513
+ for (const itemId of existingRootItemIds) {
514
+ if (!nextRootItemIds.includes(itemId)) {
515
+ nextRootItemIds.push(itemId);
516
+ }
517
+ }
518
+ if (areItemIdsEqual(existingRootItemIds, nextRootItemIds)) {
519
+ return;
520
+ }
521
+ draftColumnTree.value = {
522
+ ...draftColumnTree.value,
523
+ rootItemIds: nextRootItemIds
524
+ };
525
+ }
485
526
  function initializeInitialStateGroups(config) {
486
527
  const leafColumnIdToRootItemId = /* @__PURE__ */ new Map();
487
528
  for (const item of rootInitialStateItems.value) {
@@ -530,6 +571,7 @@ function initializeInitialStateGroups(config) {
530
571
  rightPinnedRootItemIds.value = rightItemIds;
531
572
  }
532
573
  function resetDraftState(config) {
574
+ const nextPaginationPageSizes = getInitialPaginationPageSizes(config);
533
575
  draftBaseConfig.value = config;
534
576
  draftColumnTree.value = buildTableConfiguratorColumnTree(config.columns);
535
577
  draftGetRowId.value = config.getRowId;
@@ -539,8 +581,10 @@ function resetDraftState(config) {
539
581
  draftCellStyles.value = config.cellStyles;
540
582
  draftPaginationLeft.value = config.paginationLeft;
541
583
  draftPaginationRight.value = config.paginationRight;
542
- draftPageSize.value = getInitialPageSize(config);
584
+ draftPageSize.value = resolvePaginationPageSize(getInitialPageSize(config), nextPaginationPageSizes);
585
+ draftPaginationPageSizes.value = createPaginationPageSizeItems(nextPaginationPageSizes);
543
586
  initializeInitialStateGroups(config);
587
+ syncDraftColumnTreeRootOrder();
544
588
  childSearch.value = "";
545
589
  selectedItemId.value = "general";
546
590
  clearExpressionValidation();
@@ -553,11 +597,13 @@ watch(() => props.config, (config) => {
553
597
  const leftPinnedSortable = useSortable(leftPinnedSortableListRef, leftPinnedSortableItemIds);
554
598
  const centerSortable = useSortable(centerSortableListRef, centerSortableItemIds);
555
599
  const rightPinnedSortable = useSortable(rightPinnedSortableListRef, rightPinnedSortableItemIds);
600
+ const paginationPageSizesSortable = useSortable(paginationPageSizesSortableListRef, paginationPageSizesSortableItemIds);
556
601
  const childSortable = useSortable(selectedChildSortableListRef, selectedChildSortableItemIds);
557
602
  function syncSortableItemIds() {
558
603
  leftPinnedSortableItemIds.value = leftPinnedRootItemIds.value.slice();
559
604
  centerSortableItemIds.value = centerRootItemIds.value.slice();
560
605
  rightPinnedSortableItemIds.value = rightPinnedRootItemIds.value.slice();
606
+ paginationPageSizesSortableItemIds.value = draftPaginationPageSizes.value.map((item) => item.itemId);
561
607
  selectedChildSortableItemIds.value = selectedColumnNode.value?.childItemIds.slice() ?? [];
562
608
  }
563
609
  function normalizeInitialStateGroups() {
@@ -632,6 +678,23 @@ function handleRightPinnedSortableUpdate(event) {
632
678
  newIndex
633
679
  );
634
680
  }
681
+ function handlePaginationPageSizesSortableUpdate(event) {
682
+ const oldIndex = event.oldIndex;
683
+ const newIndex = event.newIndex;
684
+ if (selectedItemId.value !== "general" || oldIndex === void 0 || newIndex === void 0 || oldIndex === newIndex) {
685
+ return;
686
+ }
687
+ const nextItemIds = reorderItemIds(
688
+ draftPaginationPageSizes.value.map((item) => item.itemId),
689
+ oldIndex,
690
+ newIndex
691
+ );
692
+ const itemMap = new Map(draftPaginationPageSizes.value.map((item) => [item.itemId, item]));
693
+ draftPaginationPageSizes.value = nextItemIds.flatMap((itemId) => {
694
+ const item = itemMap.get(itemId);
695
+ return item ? [item] : [];
696
+ });
697
+ }
635
698
  function handleChildSortableUpdate(event) {
636
699
  const oldIndex = event.oldIndex;
637
700
  const newIndex = event.newIndex;
@@ -661,6 +724,11 @@ function configureRightPinnedSortable() {
661
724
  rightPinnedSortable.option("handle", '[data-slot="table-configurator-initial-state-right-drag-handle"]');
662
725
  rightPinnedSortable.option("onUpdate", handleRightPinnedSortableUpdate);
663
726
  }
727
+ function configurePaginationPageSizesSortable() {
728
+ paginationPageSizesSortable.option("animation", 150);
729
+ paginationPageSizesSortable.option("handle", '[data-slot="table-configurator-pagination-page-size-drag-handle"]');
730
+ paginationPageSizesSortable.option("onUpdate", handlePaginationPageSizesSortableUpdate);
731
+ }
664
732
  function configureChildSortable() {
665
733
  childSortable.option("animation", 150);
666
734
  childSortable.option("handle", '[data-slot="table-configurator-child-drag-handle"]');
@@ -670,6 +738,7 @@ async function refreshSortable() {
670
738
  leftPinnedSortable.stop();
671
739
  centerSortable.stop();
672
740
  rightPinnedSortable.stop();
741
+ paginationPageSizesSortable.stop();
673
742
  childSortable.stop();
674
743
  if (!open.value) {
675
744
  return;
@@ -679,9 +748,11 @@ async function refreshSortable() {
679
748
  leftPinnedSortable.start();
680
749
  centerSortable.start();
681
750
  rightPinnedSortable.start();
751
+ paginationPageSizesSortable.start();
682
752
  configureLeftPinnedSortable();
683
753
  configureCenterSortable();
684
754
  configureRightPinnedSortable();
755
+ configurePaginationPageSizesSortable();
685
756
  return;
686
757
  }
687
758
  if (selectedColumnNode.value) {
@@ -692,9 +763,18 @@ async function refreshSortable() {
692
763
  watch(rootInitialStateItems, () => {
693
764
  normalizeInitialStateGroups();
694
765
  }, { immediate: true });
695
- watch([draftColumnTree, selectedItemId, leftPinnedRootItemIds, centerRootItemIds, rightPinnedRootItemIds], () => {
766
+ watch([leftPinnedRootItemIds, centerRootItemIds, rightPinnedRootItemIds], () => {
767
+ syncDraftColumnTreeRootOrder();
768
+ }, { immediate: true });
769
+ watch([draftColumnTree, draftPaginationPageSizes, selectedItemId, leftPinnedRootItemIds, centerRootItemIds, rightPinnedRootItemIds], () => {
696
770
  syncSortableItemIds();
697
771
  }, { immediate: true });
772
+ watch(() => draftPaginationPageSizes.value.length, async () => {
773
+ if (!open.value || selectedItemId.value !== "general") {
774
+ return;
775
+ }
776
+ await refreshSortable();
777
+ });
698
778
  watch(open, async (value) => {
699
779
  if (value) {
700
780
  resetDraftState(props.config);
@@ -704,6 +784,7 @@ watch(open, async (value) => {
704
784
  leftPinnedSortable.stop();
705
785
  centerSortable.stop();
706
786
  rightPinnedSortable.stop();
787
+ paginationPageSizesSortable.stop();
707
788
  childSortable.stop();
708
789
  childSearch.value = "";
709
790
  search.value = "";
@@ -744,15 +825,14 @@ watch(filteredColumnItems, (items) => {
744
825
  watch(selectedColumnRendererId, () => {
745
826
  clearRendererValidation();
746
827
  });
747
- function hasVisibleTitle(value) {
748
- return value?.some((item) => item.message.trim().length > 0) ?? false;
749
- }
750
828
  function normalizeColumn(column) {
751
- const title = hasVisibleTitle(column.title) ? column.title : void 0;
752
- const tooltip = hasVisibleTitle(column.tooltip) ? column.tooltip : void 0;
829
+ const title = hasVisibleLocaleValue(column.title) ? column.title : void 0;
830
+ const tooltip = hasVisibleLocaleValue(column.tooltip) ? column.tooltip : void 0;
753
831
  const normalizedChildren = column.columns?.map(normalizeColumn);
832
+ const shouldDropGeneratedId = looksLikeGeneratedColumnId(column.id) && normalizedChildren === void 0 && column.accessor !== void 0;
754
833
  return {
755
834
  ...column,
835
+ id: shouldDropGeneratedId ? void 0 : column.id,
756
836
  title,
757
837
  tooltip,
758
838
  columns: normalizedChildren?.length ? normalizedChildren : void 0
@@ -1077,9 +1157,34 @@ function updateDraftPaginationRight(value) {
1077
1157
  function updateDraftPageSize(value) {
1078
1158
  draftPageSize.value = normalizeOptionalNumber(value);
1079
1159
  }
1160
+ function updateDraftPaginationPageSizeOption(itemId, value) {
1161
+ const normalizedValue = normalizeOptionalNumber(value);
1162
+ draftPaginationPageSizes.value = draftPaginationPageSizes.value.map((item) => item.itemId === itemId ? {
1163
+ ...item,
1164
+ value: normalizedValue
1165
+ } : item);
1166
+ }
1167
+ function addDraftPaginationPageSizeOption() {
1168
+ draftPaginationPageSizes.value = [
1169
+ ...draftPaginationPageSizes.value,
1170
+ createPaginationPageSizeItem()
1171
+ ];
1172
+ }
1173
+ function removeDraftPaginationPageSizeOption(itemId) {
1174
+ draftPaginationPageSizes.value = draftPaginationPageSizes.value.filter((item) => item.itemId !== itemId);
1175
+ }
1176
+ function buildNextPaginationPageSizes() {
1177
+ return normalizePaginationPageSizes(draftPaginationPageSizes.value.flatMap((item) => {
1178
+ if (item.value === void 0) {
1179
+ return [];
1180
+ }
1181
+ return [item.value];
1182
+ }));
1183
+ }
1080
1184
  function buildNextPaginationState() {
1081
1185
  const currentPagination = draftBaseConfig.value.props?.initialState?.pagination;
1082
- const nextPageSize = draftPageSize.value;
1186
+ const nextPaginationPageSizes = buildNextPaginationPageSizes();
1187
+ const nextPageSize = resolvePaginationPageSize(draftPageSize.value, nextPaginationPageSizes);
1083
1188
  if (!currentPagination && nextPageSize === void 0) {
1084
1189
  return void 0;
1085
1190
  }
@@ -1098,6 +1203,7 @@ function buildDraftConfig() {
1098
1203
  cellStyles: draftCellStyles.value,
1099
1204
  paginationLeft: normalizeOptionalText(draftPaginationLeft.value),
1100
1205
  paginationRight: normalizeOptionalText(draftPaginationRight.value),
1206
+ paginationPageSizes: buildNextPaginationPageSizes(),
1101
1207
  columns: materializeTableConfiguratorColumnTree(draftColumnTree.value).map(normalizeColumn),
1102
1208
  props: {
1103
1209
  ...draftBaseConfig.value.props,
@@ -1114,68 +1220,320 @@ function buildDraftConfig() {
1114
1220
  }
1115
1221
  });
1116
1222
  }
1117
- function triggerImport() {
1118
- importInputRef.value?.click();
1119
- }
1120
1223
  function showImportError(message) {
1121
1224
  $toast.error(message);
1122
1225
  }
1123
- function showExportError() {
1124
- $toast.error(t("export-config-failed"));
1226
+ function showCopyError(message) {
1227
+ $toast.error(message);
1125
1228
  }
1126
- async function handleImportChange(event) {
1127
- const target = event.target;
1128
- if (!(target instanceof HTMLInputElement)) {
1129
- return;
1229
+ function showImportErrorWithCopyAction(message, onClick) {
1230
+ $toast.error(message, {
1231
+ action: {
1232
+ label: t("copy-paste-error"),
1233
+ onClick
1234
+ }
1235
+ });
1236
+ }
1237
+ function getValidDraftConfig(errorMessage) {
1238
+ if (!validateExpressionEditors()) {
1239
+ showCopyError(errorMessage);
1240
+ return void 0;
1130
1241
  }
1131
- const file = target.files?.[0];
1132
- try {
1133
- if (!file) {
1134
- return;
1242
+ const result = buildDraftConfig();
1243
+ if (!result.success) {
1244
+ showCopyError(errorMessage);
1245
+ return void 0;
1246
+ }
1247
+ return result.data;
1248
+ }
1249
+ function buildTableConfigJsonSchema() {
1250
+ return z.toJSONSchema(TableConfigC, {
1251
+ io: "input",
1252
+ unrepresentable: "any"
1253
+ });
1254
+ }
1255
+ function collectUsedRendererIds(columns) {
1256
+ const rendererIds = [];
1257
+ for (const column of columns) {
1258
+ if (Array.isArray(column.columns) && column.columns.length > 0) {
1259
+ for (const rendererId2 of collectUsedRendererIds(column.columns)) {
1260
+ if (!rendererIds.includes(rendererId2)) {
1261
+ rendererIds.push(rendererId2);
1262
+ }
1263
+ }
1264
+ continue;
1135
1265
  }
1136
- const source = await file.text();
1137
- let parsedValue;
1138
- try {
1139
- parsedValue = JSON.parse(source);
1140
- } catch {
1141
- showImportError(t("import-config-invalid-json"));
1142
- return;
1266
+ const rendererId = getColumnRendererId(column);
1267
+ if (!rendererIds.includes(rendererId)) {
1268
+ rendererIds.push(rendererId);
1143
1269
  }
1144
- const result = TableConfigC.safeParse(parsedValue);
1145
- if (!result.success) {
1146
- showImportError(t("import-config-invalid-schema"));
1147
- return;
1270
+ }
1271
+ return rendererIds;
1272
+ }
1273
+ function buildRendererSchemaMarkdown(config) {
1274
+ const rendererIds = collectUsedRendererIds(config.columns);
1275
+ if (rendererIds.length === 0) {
1276
+ return "\u5F53\u524D\u8349\u7A3F\u4E2D\u6CA1\u6709\u53F6\u5B50\u5217\u6E32\u67D3\u5668\u3002";
1277
+ }
1278
+ return rendererIds.map((rendererId) => {
1279
+ const renderer = getTableRenderer(rendererId);
1280
+ if (!renderer) {
1281
+ return [
1282
+ `### ${rendererId}`,
1283
+ "\u672A\u627E\u5230\u8BE5\u6E32\u67D3\u5668\u7684 options schema\u3002"
1284
+ ].join("\n");
1148
1285
  }
1149
- resetDraftState(result.data);
1150
- await nextTick();
1151
- await refreshSortable();
1152
- } finally {
1153
- target.value = "";
1286
+ return [
1287
+ `### ${rendererId}`,
1288
+ "```json",
1289
+ JSON.stringify(z.toJSONSchema(renderer.optionsSchema, {
1290
+ io: "input",
1291
+ unrepresentable: "any"
1292
+ }), null, 2),
1293
+ "```"
1294
+ ].join("\n");
1295
+ }).join("\n\n");
1296
+ }
1297
+ function buildDslGuideMarkdown() {
1298
+ return [
1299
+ "## DSL / CEL \u7F16\u5199\u8BF4\u660E",
1300
+ "\u672C\u914D\u7F6E\u4E2D\u7684 DSL \u662F CEL \u8868\u8FBE\u5F0F\uFF1B\u8868\u8FBE\u5F0F\u5B57\u6BB5\u5FC5\u987B\u586B\u5199\u5B57\u7B26\u4E32\uFF0C\u4E0D\u8981\u751F\u6210 JavaScript\u3001TypeScript\u3001\u7BAD\u5934\u51FD\u6570\u6216\u4F2A\u4EE3\u7801\u3002",
1301
+ "",
1302
+ "### 1. \u57FA\u7840\u8BED\u6CD5",
1303
+ '- \u5B57\u9762\u91CF\uFF1A`"text"`\u3001`123`\u3001`123.45`\u3001`true`\u3001`false`\u3001`null`\u3002',
1304
+ '- \u5217\u8868 / \u5BF9\u8C61\uFF1A`[1, 2, 3]`\u3001`{"a": 1, "b": 2}`\u3002',
1305
+ '- \u8BBF\u95EE\uFF1A`.`\u3001`[]`\uFF0C\u4F8B\u5982 `row.id`\u3001`row["id"]`\u3001`row.items[0]`\u3002',
1306
+ '- \u53EF\u9009\u8BBF\u95EE\uFF1A`.?`\u3001`[?]`\uFF0C\u4F8B\u5982 `row.?children`\u3001`row[?"name"]`\u3002',
1307
+ "- \u903B\u8F91\u4E0E\u6761\u4EF6\uFF1A`&&`\u3001`||`\u3001`!`\u3001`condition ? a : b`\u3002",
1308
+ "- \u6BD4\u8F83\uFF1A`==`\u3001`!=`\u3001`<`\u3001`<=`\u3001`>`\u3001`>=`\u3001`in`\u3002",
1309
+ "- \u7B97\u672F\uFF1A`+`\u3001`-`\u3001`*`\u3001`/`\u3001`%`\u3002",
1310
+ '- \u65B9\u6CD5\u8C03\u7528\uFF1A`value.method(args)`\uFF0C\u4F8B\u5982 `now.format("yyyy-MM-dd")`\u3001`row.?children.orValue([])`\u3002',
1311
+ "- `accessor` \u4E3A\u5B57\u7B26\u4E32\u65F6\u4E0D\u662F CEL\uFF0C\u800C\u662F dot-prop \u8DEF\u5F84\uFF0C\u4F8B\u5982 `id`\u3001`foo.bar`\u3001`foo[0].bar`\u3002",
1312
+ "",
1313
+ "### 2. \u5168\u5C40\u53D8\u91CF\u4E0E\u5E38\u91CF",
1314
+ "- `now: Date` \u5F53\u524D\u65F6\u95F4\u3002",
1315
+ "- `today: Date` \u4E0E `now` \u7C7B\u4F3C\uFF0C\u4FDD\u7559\u7528\u4E8E\u517C\u5BB9\u65E7\u914D\u7F6E\u3002",
1316
+ "- `location: URL` \u5F53\u524D\u9875\u9762 URL\u3002",
1317
+ "- `token: string` \u5F53\u524D sessionStorage \u4E2D\u7684 token\uFF0C\u7F3A\u5931\u65F6\u4E3A\u7A7A\u5B57\u7B26\u4E32\u3002",
1318
+ "- `locale: string` \u5F53\u524D\u6D4F\u89C8\u5668\u8BED\u8A00\u3002",
1319
+ "- `git: map<string, string>` \u6765\u81EA\u8FD0\u884C\u65F6\u914D\u7F6E\u7684 git \u5E38\u91CF\u3002",
1320
+ "- `ci: map<string, dyn>` \u6765\u81EA\u8FD0\u884C\u65F6\u914D\u7F6E\u7684 CI \u5E38\u91CF\uFF0C\u5176\u4E2D `ci.build` \u4F1A\u88AB\u8F6C\u6210 `int`\u3002",
1321
+ "- \u8FD8\u4F1A\u6DF7\u5165\u5BBF\u4E3B\u5E94\u7528\u901A\u8FC7 `props.dsl` \u6CE8\u5165\u7684\u5168\u5C40\u52A8\u6001\u4E0A\u4E0B\u6587\uFF1B\u8FD9\u4E9B\u952E\u5B58\u5728\u65F6\u53EF\u76F4\u63A5\u8BBF\u95EE\uFF0C\u4F46\u540D\u79F0\u548C\u7C7B\u578B\u4E0D\u56FA\u5B9A\uFF0C\u5E94\u4EE5\u5F53\u524D\u914D\u7F6E\u548C\u4E1A\u52A1\u4E0A\u4E0B\u6587\u4E3A\u51C6\u3002",
1322
+ "",
1323
+ "### 3. \u8868\u683C\u914D\u7F6E\u5B57\u6BB5\u91CC\u7684\u5C40\u90E8\u53D8\u91CF",
1324
+ "- `accessor.read`: `row: dyn`\u3001`index: int`\u3002",
1325
+ "- `getRowId`: `row: dyn`\u3001`index: int`\u3001`parent: dyn`\uFF1B\u5FC5\u987B\u8FD4\u56DE `string`\u3002",
1326
+ "- `getSubRows`: `row: dyn`\u3001`index: int`\uFF1B\u901A\u5E38\u8FD4\u56DE `list<dyn>`\u3002",
1327
+ "- `enableRowSelection`: `row: dyn`\u3001`index: int`\u3001`id: string`\uFF1B\u5FC5\u987B\u8FD4\u56DE `bool`\u3002",
1328
+ "- `enableMultiRowSelection`: `row: dyn`\u3001`index: int`\u3001`id: string`\uFF1B\u5FC5\u987B\u8FD4\u56DE `bool`\u3002",
1329
+ "- `cellStyles`: `row: dyn`\u3001`index: int`\u3001`id: string`\u3001`selected: bool`\u3001`pinned: dyn`\uFF1B\u5FC5\u987B\u8FD4\u56DE `map`\u3002",
1330
+ "- `table.renderer.text.props.copyExpression`: `row: dyn`\u3001`index: int`\u3002",
1331
+ "",
1332
+ "### 4. \u53EF\u7528\u7C7B\u578B\u4E0E\u6269\u5C55\u80FD\u529B",
1333
+ "- `URL` \u7C7B\u578B\uFF1A\u53EF\u8C03\u7528 `location.searchParams()`\uFF0C\u8FD4\u56DE `URLSearchParams`\u3002",
1334
+ "- `URLSearchParams` \u7C7B\u578B\uFF1A\u53EF\u8C03\u7528 `searchParams({...})` \u521B\u5EFA\uFF0C`URLSearchParams.get(string)` \u8FD4\u56DE `optional<string>`\u3002",
1335
+ "- `Date` \u7C7B\u578B\uFF1A\u7531 `date(string)` \u521B\u5EFA\uFF0C\u4E5F\u53EF\u76F4\u63A5\u4F7F\u7528 `now` / `today`\u3002",
1336
+ "- `Date` \u652F\u6301\u6BD4\u8F83\uFF1A`<`\u3001`<=`\u3001`>`\u3001`>=`\u3001`==`\u3002",
1337
+ "- `Date.startOf(string)` / `Date.endOf(string)`\uFF1A`unit` \u652F\u6301 `day` / `week` / `month` / `year`\u3002",
1338
+ "- `Date.offset(int, string)`\uFF1A`unit` \u652F\u6301 `day|days|week|weeks|month|months|year|years`\u3002",
1339
+ "- `Date.set(string, int)`\uFF1A`unit` \u652F\u6301 `day` / `month` / `year`\uFF0C\u5176\u4E2D `month` \u4F20\u5165\u81EA\u7136\u6708 1-12\u3002",
1340
+ '- `Date.format(string)`\uFF1A\u4F7F\u7528 `date-fns` \u683C\u5F0F\u5316\u5B57\u7B26\u4E32\uFF0C\u4F8B\u5982 `now.format("yyyy-MM-dd")`\u3002',
1341
+ "- `list<double>.sum()` / `list<int>.sum()`\uFF1A\u5BF9\u6570\u5B57\u5217\u8868\u6C42\u548C\uFF0C\u8FD4\u56DE `double`\u3002",
1342
+ "- `double.toLocaleString(dyn)` / `int.toLocaleString(dyn)`\uFF1A\u6309\u5F53\u524D\u8BED\u8A00\u683C\u5F0F\u5316\u6570\u5B57\u3002",
1343
+ "- `double.encodeSimplifiedChineseUppercase()` / `int.encodeSimplifiedChineseUppercase()`\uFF1A\u8F6C\u4E2D\u6587\u5927\u5199\u91D1\u989D/\u6570\u5B57\u3002",
1344
+ "- `parseJSON(string): dyn`\uFF1A\u628A JSON \u5B57\u7B26\u4E32\u89E3\u6790\u6210\u5BF9\u8C61\u6216\u5217\u8868\u3002",
1345
+ "- `string.slice(int)` / `string.slice(int, int)`\uFF1A\u5B57\u7B26\u4E32\u5207\u7247\u3002",
1346
+ "- `session(string): optional<string>`\uFF1A\u4ECE `sessionStorage` \u8BFB\u53D6\u503C\u3002",
1347
+ "- `local(string): optional<string>`\uFF1A\u4ECE `localStorage` \u8BFB\u53D6\u503C\u3002",
1348
+ "",
1349
+ "### 5. Optional / \u7A7A\u503C\u8BED\u4E49",
1350
+ "- \u53EF\u9009\u8BBF\u95EE .? \u548C [?] \u4F1A\u8FD4\u56DE `Optional`\uFF0C\u800C\u4E0D\u662F\u76F4\u63A5\u8FD4\u56DE\u666E\u901A\u503C\u3002",
1351
+ "- `Optional.hasValue()` \u5224\u65AD\u662F\u5426\u5B58\u5728\u503C\u3002",
1352
+ "- `Optional.value()` \u53D6\u503C\uFF1B\u5728\u7A7A\u503C\u65F6\u4F1A\u629B\u9519\uFF0C\u4E00\u822C\u4E0D\u5EFA\u8BAE\u76F4\u63A5\u4F7F\u7528\u3002",
1353
+ "- `Optional.or(optional)` \u5728\u7A7A\u503C\u65F6\u4F7F\u7528\u53E6\u4E00\u4E2A Optional\u3002",
1354
+ "- `Optional.orValue(defaultValue)` \u5728\u7A7A\u503C\u65F6\u8FD4\u56DE\u9ED8\u8BA4\u503C\uFF0C\u6700\u5E38\u7528\u3002\u793A\u4F8B\uFF1A`row.?children.orValue([])`\u3002",
1355
+ "",
1356
+ "### 6. \u7F16\u5199\u8981\u6C42",
1357
+ "- \u751F\u6210\u8868\u8FBE\u5F0F\u65F6\u8981\u4E25\u683C\u9075\u5B88\u5B57\u6BB5\u8FD4\u56DE\u7C7B\u578B\uFF0C\u4E0D\u8981\u6DF7\u7528\u5B57\u7B26\u4E32\u3001\u5E03\u5C14\u503C\u3001\u6570\u7EC4\u548C\u5BF9\u8C61\u3002",
1358
+ "- \u5982\u679C\u5B57\u6BB5 schema \u6216\u5C40\u90E8\u53D8\u91CF\u6CA1\u6709\u58F0\u660E\u67D0\u4E2A\u53D8\u91CF\uFF0C\u5C31\u4E0D\u8981\u81C6\u9020\u65B0\u7684\u53D8\u91CF\u540D\u3002",
1359
+ "- \u4F18\u5148\u590D\u7528\u5F53\u524D\u914D\u7F6E\u4E2D\u5DF2\u7ECF\u51FA\u73B0\u8FC7\u7684\u5B57\u6BB5\u8DEF\u5F84\u3001\u8868\u8FBE\u5F0F\u6A21\u5F0F\u548C\u8FD4\u56DE\u7ED3\u6784\u3002"
1360
+ ].join("\n");
1361
+ }
1362
+ function buildMarkdownNotes() {
1363
+ return [
1364
+ "## \u6CE8\u610F\u4E8B\u9879",
1365
+ "- \u8BF7\u4F18\u5148\u4F9D\u636E schema \u4E2D\u7684 `description` \u7F16\u5199\u914D\u7F6E\uFF0C\u63CF\u8FF0\u8D8A\u8BE6\u7EC6\u8D8A\u91CD\u8981\uFF0C\u4E0D\u8981\u5FFD\u7565\u5B57\u6BB5\u8BF4\u660E\u3002",
1366
+ "- \u53EA\u5141\u8BB8\u4FEE\u6539\u73B0\u6709\u5217\u7684\u914D\u7F6E\uFF1B\u7981\u6B62\u65B0\u589E\u9876\u7EA7\u5217\u3001\u7981\u6B62\u65B0\u589E\u5B50\u5217\u3001\u7981\u6B62\u5220\u9664\u540E\u91CD\u5EFA\u5217\u3002",
1367
+ "- \u7981\u6B62\u751F\u6210\u65B0\u7684\u5217 ID\uFF0C\u4E5F\u4E0D\u8981\u4E3A\u7F3A\u5931 ID \u7684\u65B0\u5217\u8865 ID\uFF1B\u5982\u679C\u7528\u6237\u9700\u8981\u65B0\u589E\u5217\uFF0C\u5FC5\u987B\u5728\u754C\u9762\u4E2D\u624B\u52A8\u5B8C\u6210\u3002",
1368
+ "- \u4FEE\u6539\u5217\u914D\u7F6E\u65F6\u5C3D\u91CF\u4FDD\u7559\u73B0\u6709\u5217\u987A\u5E8F\u3001\u5C42\u7EA7\u7ED3\u6784\u3001`id`\u3001`accessor` \u548C renderer \u8BED\u4E49\uFF0C\u9664\u975E\u7528\u6237\u660E\u786E\u8981\u6C42\u53D8\u66F4\u3002",
1369
+ "- \u5982\u679C\u9700\u6C42\u8D85\u51FA schema \u80FD\u8868\u8FBE\u7684\u8303\u56F4\uFF0C\u5E94\u8BE5\u76F4\u63A5\u8BF4\u660E\u9650\u5236\uFF0C\u800C\u4E0D\u662F\u53D1\u660E schema \u4E2D\u4E0D\u5B58\u5728\u7684\u65B0\u5B57\u6BB5\u3002"
1370
+ ].join("\n");
1371
+ }
1372
+ function buildAiPromptHeaderMarkdown() {
1373
+ return [
1374
+ "# \u8868\u683C\u914D\u7F6E AI \u4E0A\u4E0B\u6587",
1375
+ "\u4F60\u662F\u4E00\u4E2A\u5E2E\u52A9\u7528\u6237\u914D\u7F6E\u8868\u683C\u7EC4\u4EF6\u7684 AI \u52A9\u624B\uFF0C\u8D1F\u8D23\u6839\u636E\u5F53\u524D\u914D\u7F6E\u3001schema \u548C DSL \u89C4\u5219\u56DE\u7B54\u7528\u6237\u95EE\u9898\u3002",
1376
+ "\u4F60\u7684\u76EE\u6807\u662F\u89E3\u91CA\u73B0\u6709\u914D\u7F6E\u3001\u5E2E\u52A9\u7528\u6237\u4FEE\u6539\u73B0\u6709\u914D\u7F6E\uFF0C\u5E76\u5728\u7EA6\u675F\u8303\u56F4\u5185\u751F\u6210\u53EF\u7528\u7684\u914D\u7F6E\u7247\u6BB5\u3002",
1377
+ "\u5982\u679C\u4F60\u4E0D\u786E\u5B9A\u67D0\u4E2A\u5B57\u6BB5\u3001\u53D8\u91CF\u3001\u7C7B\u578B\u3001\u8FD0\u884C\u65F6\u4E0A\u4E0B\u6587\u6216\u4E1A\u52A1\u542B\u4E49\uFF0C\u5FC5\u987B\u660E\u786E\u8BF4\u660E\u4E0D\u786E\u5B9A\u70B9\uFF0C\u5E76\u8981\u6C42\u7528\u6237\u8865\u5145\u4FE1\u606F\uFF1B\u4E0D\u8981\u731C\u6D4B\uFF0C\u4E0D\u8981\u53D1\u660E schema \u4E2D\u4E0D\u5B58\u5728\u7684\u5B57\u6BB5\u3001\u53D8\u91CF\u3001\u51FD\u6570\u6216\u80FD\u529B\u3002",
1378
+ "\u53EA\u6709\u5F53\u7528\u6237\u660E\u786E\u8981\u6C42\u201C\u751F\u6210\u914D\u7F6E\u201D\u201C\u8F93\u51FA\u914D\u7F6E\u201D\u201C\u76F4\u63A5\u7ED9\u6211\u914D\u7F6E\u201D\u201C\u8FD4\u56DE\u53EF\u7C98\u8D34\u914D\u7F6E\u201D\u8FD9\u7C7B\u7ED3\u679C\u65F6\uFF0C\u624D\u8FD4\u56DE\u5B8C\u6574\u914D\u7F6E\u6216\u914D\u7F6E\u7247\u6BB5\uFF1B\u5176\u4ED6\u60C5\u51B5\u4E0B\u4F18\u5148\u89E3\u91CA\u601D\u8DEF\u3001\u6307\u51FA\u4FEE\u6539\u70B9\u548C\u539F\u56E0\u3002",
1379
+ "\u5F53\u6211\u8BF4\u201C\u751F\u6210\u914D\u7F6E\u201D\u65F6\uFF0C\u6211\u4F1A\u628A\u914D\u7F6E\u4EE5 Markdown code block \u7684\u5F62\u5F0F\u53D1\u7ED9\u4F60\uFF1B\u4F60\u8FD4\u56DE\u7684\u914D\u7F6E\u4E5F\u5E94\u8BE5\u653E\u5728 Markdown code block \u4E2D\uFF0C\u8FD9\u6837\u6211\u53EF\u4EE5\u76F4\u63A5\u7C98\u8D34\u5230\u9875\u9762\u914D\u7F6E\u91CC\u3002"
1380
+ ].join("\n");
1381
+ }
1382
+ function buildMarkdownCopyContent(config) {
1383
+ return [
1384
+ buildAiPromptHeaderMarkdown(),
1385
+ "",
1386
+ "## \u5F53\u524D\u914D\u7F6E",
1387
+ "```json",
1388
+ JSON.stringify(config, null, 2),
1389
+ "```",
1390
+ "",
1391
+ buildDslGuideMarkdown(),
1392
+ "",
1393
+ buildMarkdownNotes(),
1394
+ "",
1395
+ "## TableConfig JSON Schema",
1396
+ "```json",
1397
+ JSON.stringify(buildTableConfigJsonSchema(), null, 2),
1398
+ "```",
1399
+ "",
1400
+ "## \u5F53\u524D\u4F7F\u7528\u7684 Renderer Options Schema",
1401
+ buildRendererSchemaMarkdown(config)
1402
+ ].join("\n");
1403
+ }
1404
+ function formatIssuePath(path) {
1405
+ if (path.length === 0) {
1406
+ return "(root)";
1407
+ }
1408
+ return path.map((segment) => {
1409
+ if (typeof segment === "number") {
1410
+ return `${segment}`;
1411
+ }
1412
+ if (typeof segment === "string") {
1413
+ return segment;
1414
+ }
1415
+ return String(segment);
1416
+ }).join(".");
1417
+ }
1418
+ function formatIssueExtraFields(issue) {
1419
+ const lines = [];
1420
+ const entries = Object.entries(issue);
1421
+ for (const [key, value] of entries) {
1422
+ if (key === "code" || key === "message" || key === "path") {
1423
+ continue;
1424
+ }
1425
+ lines.push(` - ${key}: ${JSON.stringify(value)}`);
1426
+ }
1427
+ return lines;
1428
+ }
1429
+ function buildPasteConfigErrorDetails(source, error) {
1430
+ if (error instanceof SyntaxError) {
1431
+ return [
1432
+ "## \u7C98\u8D34\u5931\u8D25\u539F\u56E0",
1433
+ "- \u7C7B\u578B\uFF1AJSON \u89E3\u6790\u5931\u8D25",
1434
+ `- message: ${error.message}`,
1435
+ "",
1436
+ "## \u539F\u59CB\u7C98\u8D34\u5185\u5BB9",
1437
+ "```text",
1438
+ source,
1439
+ "```"
1440
+ ].join("\n");
1441
+ }
1442
+ const issueLines = error.issues.flatMap((issue, index) => {
1443
+ return [
1444
+ `### Issue ${index + 1}`,
1445
+ `- path: ${formatIssuePath(issue.path)}`,
1446
+ `- code: ${issue.code}`,
1447
+ `- message: ${issue.message}`,
1448
+ ...formatIssueExtraFields(issue)
1449
+ ];
1450
+ });
1451
+ return [
1452
+ "## \u7C98\u8D34\u5931\u8D25\u539F\u56E0",
1453
+ "- \u7C7B\u578B\uFF1ASchema \u6821\u9A8C\u5931\u8D25",
1454
+ "",
1455
+ "## \u539F\u59CB\u7C98\u8D34\u5185\u5BB9",
1456
+ "```json",
1457
+ source,
1458
+ "```",
1459
+ "",
1460
+ "## Schema \u62A5\u9519",
1461
+ ...issueLines
1462
+ ].join("\n");
1463
+ }
1464
+ function buildPasteConfigErrorMarkdown(source, error) {
1465
+ return [
1466
+ buildAiPromptHeaderMarkdown(),
1467
+ "",
1468
+ "## \u5F53\u524D\u4EFB\u52A1",
1469
+ "\u7528\u6237\u628A\u4E00\u6BB5\u914D\u7F6E\u7C98\u8D34\u56DE\u8868\u683C\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",
1470
+ "\u4F60\u53EA\u80FD\u4FEE\u590D\u5F53\u524D\u914D\u7F6E\uFF0C\u4E0D\u80FD\u65B0\u589E\u5217\u3001\u4E0D\u80FD\u65B0\u589E\u5B50\u5217\u3001\u4E0D\u80FD\u5220\u9664\u540E\u91CD\u5EFA\u5217\u3001\u4E0D\u80FD\u751F\u6210\u65B0\u7684\u5217 ID\u3002",
1471
+ "\u5982\u679C\u67D0\u4E2A\u5217\u7F3A\u5C11 ID\uFF0C\u4E0D\u8981\u66FF\u7528\u6237\u8865\u4E00\u4E2A\u65B0 ID\uFF1B\u8FD9\u7C7B\u64CD\u4F5C\u5FC5\u987B\u5728\u754C\u9762\u4E2D\u5B8C\u6210\u3002",
1472
+ "\u8BF7\u4F18\u5148\u4FEE\u590D\u6700\u5C0F\u5FC5\u8981\u8303\u56F4\uFF0C\u53EA\u4FEE\u6539\u5BFC\u81F4\u62A5\u9519\u7684\u90E8\u5206\u3002",
1473
+ "\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",
1474
+ "",
1475
+ buildPasteConfigErrorDetails(source, error),
1476
+ "",
1477
+ buildMarkdownNotes()
1478
+ ].join("\n");
1479
+ }
1480
+ async function writeClipboardText(value, errorMessage) {
1481
+ try {
1482
+ await navigator.clipboard.writeText(value);
1483
+ } catch {
1484
+ showCopyError(errorMessage);
1154
1485
  }
1155
1486
  }
1156
- function exportConfig() {
1157
- if (!validateExpressionEditors()) {
1158
- showExportError();
1487
+ async function copyPasteConfigError(source, error) {
1488
+ await writeClipboardText(buildPasteConfigErrorMarkdown(source, error), t("copy-paste-error-failed"));
1489
+ }
1490
+ async function pasteConfigFromClipboard() {
1491
+ let source = "";
1492
+ try {
1493
+ source = await navigator.clipboard.readText();
1494
+ } catch {
1495
+ showImportError(t("paste-config-read-failed"));
1159
1496
  return;
1160
1497
  }
1161
- const result = buildDraftConfig();
1498
+ let parsedValue;
1499
+ try {
1500
+ parsedValue = JSON.parse(source);
1501
+ } catch (error) {
1502
+ if (error instanceof SyntaxError) {
1503
+ showImportErrorWithCopyAction(
1504
+ t("paste-config-invalid-json"),
1505
+ async () => copyPasteConfigError(source, error)
1506
+ );
1507
+ return;
1508
+ }
1509
+ showImportError(t("paste-config-invalid-json"));
1510
+ return;
1511
+ }
1512
+ const result = TableConfigC.safeParse(parsedValue);
1162
1513
  if (!result.success) {
1163
- showExportError();
1514
+ showImportErrorWithCopyAction(
1515
+ t("paste-config-invalid-schema"),
1516
+ async () => copyPasteConfigError(source, result.error)
1517
+ );
1164
1518
  return;
1165
1519
  }
1166
- try {
1167
- const blob = new Blob([JSON.stringify(result.data, null, 2)], { type: "application/json" });
1168
- const url = URL.createObjectURL(blob);
1169
- const link = document.createElement("a");
1170
- link.href = url;
1171
- link.download = "table-config.json";
1172
- document.body.append(link);
1173
- link.click();
1174
- link.remove();
1175
- URL.revokeObjectURL(url);
1176
- } catch {
1177
- showExportError();
1520
+ resetDraftState(result.data);
1521
+ await nextTick();
1522
+ await refreshSortable();
1523
+ }
1524
+ async function copyConfig() {
1525
+ const config = getValidDraftConfig(t("copy-config-failed"));
1526
+ if (!config) {
1527
+ return;
1178
1528
  }
1529
+ await writeClipboardText(JSON.stringify(config, null, 2), t("copy-config-failed"));
1530
+ }
1531
+ async function copyMarkdown() {
1532
+ const config = getValidDraftConfig(t("copy-markdown-failed"));
1533
+ if (!config) {
1534
+ return;
1535
+ }
1536
+ await writeClipboardText(buildMarkdownCopyContent(config), t("copy-markdown-failed"));
1179
1537
  }
1180
1538
  function confirmChanges() {
1181
1539
  if (!validateExpressionEditors()) {
@@ -1199,6 +1557,7 @@ function confirmChanges() {
1199
1557
  <DialogContent
1200
1558
  class="flex h-[min(42rem,calc(100vh-4rem))] w-[calc(100%-2rem)] max-h-[calc(100vh-4rem)] max-w-[calc(100%-2rem)] flex-col overflow-hidden p-0 sm:w-[80vw] sm:max-w-[80vw]"
1201
1559
  :show-close-button="true"
1560
+ @pointer-down-outside="(event) => event.preventDefault()"
1202
1561
  >
1203
1562
  <DialogHeader class="gap-1 border-b border-zinc-200 px-6 py-5">
1204
1563
  <div class="flex items-center gap-3">
@@ -1209,21 +1568,13 @@ function confirmChanges() {
1209
1568
  type="button"
1210
1569
  variant="ghost"
1211
1570
  size="sm"
1212
- data-slot="table-configurator-import"
1571
+ data-slot="table-configurator-paste"
1213
1572
  class="shrink-0"
1214
- @click="triggerImport"
1573
+ @click="pasteConfigFromClipboard"
1215
1574
  >
1216
- <Icon icon="fluent:arrow-upload-20-regular" />
1217
- {{ t("import-config") }}
1575
+ <Icon icon="fluent:clipboard-paste-20-regular" />
1576
+ {{ t("paste-config") }}
1218
1577
  </Button>
1219
- <input
1220
- ref="importInputRef"
1221
- data-slot="table-configurator-import-input"
1222
- class="hidden"
1223
- type="file"
1224
- accept="application/json,.json"
1225
- @change="handleImportChange"
1226
- >
1227
1578
  </div>
1228
1579
  <DialogDescription class="text-sm text-zinc-500">
1229
1580
  {{ t("configure-table-description") }}
@@ -1443,6 +1794,81 @@ function confirmChanges() {
1443
1794
  <NumberFieldInput class="w-full text-left" />
1444
1795
  </NumberField>
1445
1796
  </label>
1797
+
1798
+ <div
1799
+ data-field-key="paginationPageSizes"
1800
+ class="flex flex-col gap-3"
1801
+ >
1802
+ <div class="flex items-center justify-between gap-3">
1803
+ <span class="text-xs font-medium text-zinc-500">{{ t("pagination-page-size-options") }}</span>
1804
+ <Button
1805
+ type="button"
1806
+ variant="ghost"
1807
+ size="sm"
1808
+ data-slot="table-configurator-pagination-page-size-add"
1809
+ class="h-7 px-2 text-xs"
1810
+ @click="addDraftPaginationPageSizeOption"
1811
+ >
1812
+ <Icon icon="fluent:add-20-regular" />
1813
+ {{ t("add-pagination-page-size") }}
1814
+ </Button>
1815
+ </div>
1816
+
1817
+ <div
1818
+ v-if="draftPaginationPageSizes.length > 0"
1819
+ ref="paginationPageSizesSortableListRef"
1820
+ data-slot="table-configurator-pagination-page-size-list"
1821
+ class="flex flex-col gap-2"
1822
+ >
1823
+ <InputGroup
1824
+ v-for="item in draftPaginationPageSizes"
1825
+ :key="item.itemId"
1826
+ :data-item-id="item.itemId"
1827
+ data-slot="table-configurator-pagination-page-size-item"
1828
+ class="overflow-hidden"
1829
+ >
1830
+ <InputGroupNumberField
1831
+ class="flex-1"
1832
+ :model-value="item.value"
1833
+ :min="1"
1834
+ @update:model-value="(value) => updateDraftPaginationPageSizeOption(item.itemId, value)"
1835
+ />
1836
+
1837
+ <InputGroupAddon class="w-12 shrink-0 self-stretch border-r border-zinc-200 px-0 py-0">
1838
+ <div
1839
+ data-slot="table-configurator-pagination-page-size-drag-handle"
1840
+ :title="t('drag-pagination-page-size')"
1841
+ class="flex h-full w-full cursor-grab items-center justify-center text-zinc-400 transition-colors hover:bg-zinc-50 hover:text-zinc-700 active:cursor-grabbing"
1842
+ >
1843
+ <Icon icon="fluent:re-order-dots-vertical-20-regular" />
1844
+ </div>
1845
+ </InputGroupAddon>
1846
+
1847
+ <InputGroupAddon
1848
+ align="inline-end"
1849
+ class="w-12 shrink-0 self-stretch border-l border-zinc-200 px-0 py-0 mr-0"
1850
+ >
1851
+ <button
1852
+ type="button"
1853
+ data-slot="table-configurator-pagination-page-size-remove"
1854
+ class="flex h-full w-full items-center justify-center bg-transparent text-red-600 transition-colors hover:bg-red-50 hover:text-red-700"
1855
+ :aria-label="t('remove-pagination-page-size')"
1856
+ @click="removeDraftPaginationPageSizeOption(item.itemId)"
1857
+ >
1858
+ <Icon icon="fluent:delete-20-regular" />
1859
+ </button>
1860
+ </InputGroupAddon>
1861
+ </InputGroup>
1862
+ </div>
1863
+
1864
+ <p
1865
+ v-else
1866
+ data-slot="table-configurator-pagination-page-size-empty"
1867
+ class="text-xs text-zinc-400"
1868
+ >
1869
+ {{ t("no-pagination-page-sizes") }}
1870
+ </p>
1871
+ </div>
1446
1872
  </div>
1447
1873
  </section>
1448
1874
 
@@ -1674,6 +2100,7 @@ function confirmChanges() {
1674
2100
 
1675
2101
  <div class="flex h-full items-stretch border-l border-zinc-200">
1676
2102
  <Toggle
2103
+ data-field-key="columnGrow"
1677
2104
  class="h-full rounded-none border-0 px-3 text-xs shadow-none"
1678
2105
  :model-value="selectedColumn.grow ?? false"
1679
2106
  @update:model-value="updateSelectedColumnGrow"
@@ -1685,7 +2112,10 @@ function confirmChanges() {
1685
2112
  </InputGroup>
1686
2113
  </label>
1687
2114
 
1688
- <label class="flex items-center justify-between gap-3 py-1">
2115
+ <label
2116
+ data-field-key="columnEnableSorting"
2117
+ class="flex items-center justify-between gap-3 py-1"
2118
+ >
1689
2119
  <div class="flex flex-col gap-1">
1690
2120
  <span class="text-sm font-medium text-zinc-800">{{ t("column-enable-sorting") }}</span>
1691
2121
  <span class="text-xs text-zinc-500">{{ t("column-enable-sorting-description") }}</span>
@@ -1938,34 +2368,50 @@ function confirmChanges() {
1938
2368
  </section>
1939
2369
  </div>
1940
2370
 
1941
- <DialogFooter class="border-t border-zinc-200 px-6 py-4">
1942
- <Button
1943
- type="button"
1944
- data-slot="table-configurator-export"
1945
- variant="ghost"
1946
- @click="exportConfig"
1947
- >
1948
- <Icon icon="fluent:arrow-download-20-regular" />
1949
- {{ t("export-config") }}
1950
- </Button>
1951
- <Button
1952
- type="button"
1953
- data-slot="table-configurator-cancel"
1954
- variant="default"
1955
- @click="discardChanges"
2371
+ <DialogFooter class="border-t border-zinc-200 px-6 py-4 sm:justify-between">
2372
+ <div
2373
+ data-slot="table-configurator-copy-actions"
2374
+ class="flex items-center gap-2"
1956
2375
  >
1957
- <Icon icon="fluent:dismiss-20-regular" />
1958
- {{ t("cancel") }}
1959
- </Button>
1960
- <Button
1961
- type="button"
1962
- data-slot="table-configurator-confirm"
1963
- variant="primary"
1964
- @click="confirmChanges"
1965
- >
1966
- <Icon icon="fluent:checkmark-20-regular" />
1967
- {{ t("confirm") }}
1968
- </Button>
2376
+ <Button
2377
+ type="button"
2378
+ data-slot="table-configurator-copy-markdown"
2379
+ variant="ghost"
2380
+ @click="copyMarkdown"
2381
+ >
2382
+ <Icon icon="simple-icons:markdown" />
2383
+ {{ t("copy-markdown") }}
2384
+ </Button>
2385
+ <Button
2386
+ type="button"
2387
+ data-slot="table-configurator-copy-config"
2388
+ variant="ghost"
2389
+ @click="copyConfig"
2390
+ >
2391
+ <Icon icon="fluent:copy-20-regular" />
2392
+ {{ t("copy-config") }}
2393
+ </Button>
2394
+ </div>
2395
+ <div class="flex items-center gap-2">
2396
+ <Button
2397
+ type="button"
2398
+ data-slot="table-configurator-cancel"
2399
+ variant="default"
2400
+ @click="discardChanges"
2401
+ >
2402
+ <Icon icon="fluent:dismiss-20-regular" />
2403
+ {{ t("cancel") }}
2404
+ </Button>
2405
+ <Button
2406
+ type="button"
2407
+ data-slot="table-configurator-confirm"
2408
+ variant="primary"
2409
+ @click="confirmChanges"
2410
+ >
2411
+ <Icon icon="fluent:checkmark-20-regular" />
2412
+ {{ t("confirm") }}
2413
+ </Button>
2414
+ </div>
1969
2415
  </DialogFooter>
1970
2416
  </DialogContent>
1971
2417
  </Dialog>
@@ -1976,11 +2422,16 @@ function confirmChanges() {
1976
2422
  "zh": {
1977
2423
  "configure-table": "配置表格",
1978
2424
  "configure-table-description": "在这里浏览表格和列配置项。",
1979
- "import-config": "导入",
1980
- "export-config": "导出",
1981
- "import-config-invalid-json": "导入失败,文件不是有效的 JSON。",
1982
- "import-config-invalid-schema": "导入失败,配置格式无效。",
1983
- "export-config-failed": "导出失败,请先修正当前配置中的错误。",
2425
+ "paste-config": "粘贴配置",
2426
+ "paste-config-invalid-json": "粘贴失败,剪贴板不是有效的 JSON。",
2427
+ "paste-config-invalid-schema": "粘贴失败,配置格式无效。",
2428
+ "paste-config-read-failed": "读取剪贴板失败,请检查剪贴板权限。",
2429
+ "copy-paste-error": "复制错误",
2430
+ "copy-paste-error-failed": "复制错误详情失败,请检查剪贴板权限。",
2431
+ "copy-markdown": "复制为 Markdown",
2432
+ "copy-markdown-failed": "复制 Markdown 失败,请先修正当前配置中的错误。",
2433
+ "copy-config": "仅复制配置",
2434
+ "copy-config-failed": "复制配置失败,请先修正当前配置中的错误。",
1984
2435
  "search-columns": "搜索列名称……",
1985
2436
  "general": "通用",
1986
2437
  "no-matches": "没有匹配的列。",
@@ -2016,6 +2467,11 @@ function confirmChanges() {
2016
2467
  "pagination-right": "右侧文案",
2017
2468
  "pagination-right-placeholder": "输入分页右侧 Markdown",
2018
2469
  "pagination-page-size": "每页条数",
2470
+ "pagination-page-size-options": "每页条数选项",
2471
+ "add-pagination-page-size": "新增选项",
2472
+ "drag-pagination-page-size": "拖拽调整每页条数选项顺序",
2473
+ "remove-pagination-page-size": "删除每页条数选项",
2474
+ "no-pagination-page-sizes": "暂未配置每页条数选项。",
2019
2475
  "initial-state": "初始状态",
2020
2476
  "column-options": "列配置",
2021
2477
  "column-main": "基本",
@@ -2065,6 +2521,16 @@ function confirmChanges() {
2065
2521
  "ja": {
2066
2522
  "configure-table": "テーブルを設定",
2067
2523
  "configure-table-description": "ここではテーブルと列の設定項目を確認できます。",
2524
+ "paste-config": "設定を貼り付け",
2525
+ "paste-config-invalid-json": "貼り付けに失敗しました。クリップボードの内容が有効な JSON ではありません。",
2526
+ "paste-config-invalid-schema": "貼り付けに失敗しました。設定形式が無効です。",
2527
+ "paste-config-read-failed": "クリップボードの読み取りに失敗しました。権限を確認してください。",
2528
+ "copy-paste-error": "エラーをコピー",
2529
+ "copy-paste-error-failed": "エラー詳細のコピーに失敗しました。クリップボード権限を確認してください。",
2530
+ "copy-markdown": "Markdown としてコピー",
2531
+ "copy-markdown-failed": "Markdown のコピーに失敗しました。現在の設定エラーを先に修正してください。",
2532
+ "copy-config": "設定のみコピー",
2533
+ "copy-config-failed": "設定のコピーに失敗しました。現在の設定エラーを先に修正してください。",
2068
2534
  "search-columns": "列名を検索…",
2069
2535
  "general": "共通",
2070
2536
  "no-matches": "一致する列がありません。",
@@ -2100,6 +2566,11 @@ function confirmChanges() {
2100
2566
  "pagination-right": "右側テキスト",
2101
2567
  "pagination-right-placeholder": "右側の Markdown を入力",
2102
2568
  "pagination-page-size": "1ページあたりの件数",
2569
+ "pagination-page-size-options": "1ページあたりの件数候補",
2570
+ "add-pagination-page-size": "候補を追加",
2571
+ "drag-pagination-page-size": "ドラッグして1ページあたりの件数候補の順序を変更",
2572
+ "remove-pagination-page-size": "1ページあたりの件数候補を削除",
2573
+ "no-pagination-page-sizes": "1ページあたりの件数候補はまだありません。",
2103
2574
  "initial-state": "初期状態",
2104
2575
  "column-options": "列設定",
2105
2576
  "column-main": "基本",
@@ -2149,6 +2620,16 @@ function confirmChanges() {
2149
2620
  "en": {
2150
2621
  "configure-table": "Configure Table",
2151
2622
  "configure-table-description": "Browse the table and column settings here.",
2623
+ "paste-config": "Paste Config",
2624
+ "paste-config-invalid-json": "Paste failed because the clipboard does not contain valid JSON.",
2625
+ "paste-config-invalid-schema": "Paste failed because the config shape is invalid.",
2626
+ "paste-config-read-failed": "Failed to read from the clipboard. Check clipboard permissions.",
2627
+ "copy-paste-error": "Copy Error",
2628
+ "copy-paste-error-failed": "Failed to copy the error details. Check clipboard permissions.",
2629
+ "copy-markdown": "Copy as Markdown",
2630
+ "copy-markdown-failed": "Failed to copy Markdown. Fix the current config errors first.",
2631
+ "copy-config": "Copy Config Only",
2632
+ "copy-config-failed": "Failed to copy the config. Fix the current config errors first.",
2152
2633
  "search-columns": "Search columns…",
2153
2634
  "general": "General",
2154
2635
  "no-matches": "No matching columns.",
@@ -2184,6 +2665,11 @@ function confirmChanges() {
2184
2665
  "pagination-right": "Right content",
2185
2666
  "pagination-right-placeholder": "Enter right pagination markdown",
2186
2667
  "pagination-page-size": "Page size",
2668
+ "pagination-page-size-options": "Page size options",
2669
+ "add-pagination-page-size": "Add option",
2670
+ "drag-pagination-page-size": "Drag to reorder page size options",
2671
+ "remove-pagination-page-size": "Remove page size option",
2672
+ "no-pagination-page-sizes": "No page size options configured.",
2187
2673
  "initial-state": "Initial State",
2188
2674
  "column-options": "Column options",
2189
2675
  "column-main": "Basics",