feat(copy): add camel-case JSON field names

Closes #4784
This commit is contained in:
t8y2 committed 2026-08-14 23:07:21 +00:00
1 parent a0de029a23
commit dc2d3ea1dc
14 files changed
+136 -12

No files matched your search

@@ -69,6 +69,10 @@ function updateSql<K extends keyof DataGridExtractorOptions["sql"]>(key: K, valu
draftOptions.value = { ...draftOptions.value, sql: { ...draftOptions.value.sql, [key]: value } };
}
function updateJson<K extends keyof DataGridExtractorOptions["json"]>(key: K, value: DataGridExtractorOptions["json"][K]) {
draftOptions.value = { ...draftOptions.value, json: { ...draftOptions.value.json, [key]: value } };
}
async function refreshPreview() {
const sequence = ++previewSequence;
previewLoading.value = true;
@@ -217,7 +221,10 @@ function save() {
</div>
</template>
<label v-if="isJson" class="flex items-center gap-2 text-sm"><input type="checkbox" :checked="draftOptions.json.pretty" @change="draftOptions = { ...draftOptions, json: { pretty: ($event.target as HTMLInputElement).checked } }" />{{ t("grid.copyExtractorPrettyJson") }}</label>
<template v-if="isJson">
<label class="flex items-center gap-2 text-sm"><input type="checkbox" :checked="draftOptions.json.pretty" @change="updateJson('pretty', ($event.target as HTMLInputElement).checked)" />{{ t("grid.copyExtractorPrettyJson") }}</label>
<label class="flex items-center gap-2 text-sm"><input type="checkbox" :checked="draftOptions.json.camelCaseFieldNames" @change="updateJson('camelCaseFieldNames', ($event.target as HTMLInputElement).checked)" />{{ t("grid.copyExtractorCamelCaseJsonFields") }}</label>
</template>
</div>
<div class="min-w-0 space-y-1.5">
+1
View File
@@ -1521,6 +1521,7 @@ export default {
copyExtractorInsertMerged: "Merge rows",
copyExtractorInsertRowByRow: "One statement per row",
copyExtractorPrettyJson: "Pretty-print JSON",
copyExtractorCamelCaseJsonFields: "Convert snake_case fields to camelCase",
copyExtractorPreview: "Preview (current selection)",
copyExtractorEmptySelection: "Select data to preview the extractor output.",
copyExtractorUnsupportedSelection: "This extractor requires the same columns to be selected in every row.",
+1
View File
@@ -1867,6 +1867,7 @@ export default withEnglishFallback({
copyExtractorInsertMerged: "Combinar varias filas",
copyExtractorInsertRowByRow: "Una sentencia por fila",
copyExtractorPrettyJson: "JSON formateado",
copyExtractorCamelCaseJsonFields: "Convertir campos snake_case a camelCase",
copyExtractorPreview: "Vista previa (selección actual)",
copyExtractorEmptySelection: "Seleccione datos para previsualizar el resultado de la extracción.",
copyExtractorUnsupportedSelection: "El extractor actual requiere seleccionar las mismas columnas en cada fila.",
+1
View File
@@ -1865,6 +1865,7 @@ export default withEnglishFallback({
copyExtractorInsertMerged: "Unisci più righe",
copyExtractorInsertRowByRow: "Una istruzione per riga",
copyExtractorPrettyJson: "JSON formattato",
copyExtractorCamelCaseJsonFields: "Converti i campi snake_case in camelCase",
copyExtractorPreview: "Anteprima (selezione corrente)",
copyExtractorEmptySelection: "Seleziona i dati per visualizzare l'anteprima dell'estrazione.",
copyExtractorUnsupportedSelection: "L'estrattore corrente richiede la stessa selezione di colonne per ogni riga.",
+1
View File
@@ -1887,6 +1887,7 @@ export default withEnglishFallback({
copyExtractorInsertMerged: "複数行をマージ",
copyExtractorInsertRowByRow: "1行ごとに1文",
copyExtractorPrettyJson: "JSONを整形",
copyExtractorCamelCaseJsonFields: "snake_caseのフィールド名をcamelCaseに変換",
copyExtractorPreview: "プレビュー(現在の選択)",
copyExtractorEmptySelection: "データを選択してから抽出結果をプレビューしてください。",
copyExtractorUnsupportedSelection: "現在の抽出器では各行で同じ列を選択する必要があります。",
+1
View File
@@ -1405,6 +1405,7 @@ export default withEnglishFallback({
copyExtractorInsertMerged: "행 병합",
copyExtractorInsertRowByRow: "행별 한 구문",
copyExtractorPrettyJson: "JSON 보기 좋게",
copyExtractorCamelCaseJsonFields: "snake_case 필드 이름을 camelCase로 변환",
copyExtractorPreview: "미리보기 (현재 선택)",
copyExtractorEmptySelection: "추출기 출력을 미리보려면 데이터를 선택하세요.",
copyExtractorUnsupportedSelection: "이 추출기는 모든 행에서 동일한 컬럼이 선택되어야 합니다.",
+1
View File
@@ -1867,6 +1867,7 @@ export default withEnglishFallback({
copyExtractorInsertMerged: "Mesclar várias linhas",
copyExtractorInsertRowByRow: "Uma instrução por linha",
copyExtractorPrettyJson: "JSON formatado",
copyExtractorCamelCaseJsonFields: "Converter campos snake_case para camelCase",
copyExtractorPreview: "Visualizar (seleção atual)",
copyExtractorEmptySelection: "Selecione dados para visualizar os resultados da extração.",
copyExtractorUnsupportedSelection: "O extrator atual exige que as mesmas colunas sejam selecionadas para cada linha.",
+1
View File
@@ -1445,6 +1445,7 @@ export default withEnglishFallback({
copyExtractorInsertMerged: "合并多行",
copyExtractorInsertRowByRow: "每行一条语句",
copyExtractorPrettyJson: "格式化 JSON",
copyExtractorCamelCaseJsonFields: "下划线字段名转为小驼峰",
copyExtractorPreview: "预览(当前选择)",
copyExtractorEmptySelection: "请选择数据后预览提取结果。",
copyExtractorUnsupportedSelection: "当前提取器要求每一行选择相同的列。",
+1
View File
@@ -1871,6 +1871,7 @@ export default withEnglishFallback({
copyExtractorInsertMerged: "合併多行",
copyExtractorInsertRowByRow: "每行一條語句",
copyExtractorPrettyJson: "格式化 JSON",
copyExtractorCamelCaseJsonFields: "將底線欄位名稱轉為小駝峰",
copyExtractorPreview: "預覽(目前選擇)",
copyExtractorEmptySelection: "請選擇資料後預覽提取結果。",
copyExtractorUnsupportedSelection: "目前提取器要求每一行選擇相同的列。",
@@ -1,3 +1,4 @@
import { readFileSync } from "node:fs";
import { describe, expect, it } from "vitest";
import { DEFAULT_DATA_GRID_EXTRACTOR_OPTIONS, normalizeDataGridCopyPreference, normalizeDataGridExtractorOptions, resolveDataGridCopyPreference, validateDataGridExtractorOptions } from "@/lib/dataGrid/dataGridCopyExtractor";
@@ -18,6 +19,20 @@ describe("data-grid extractor options", () => {
expect(normalized.dsv).not.toBe(DEFAULT_DATA_GRID_EXTRACTOR_OPTIONS.dsv);
});
it("keeps camel-case JSON field names explicit and disabled by default", () => {
expect(normalizeDataGridExtractorOptions({ json: {} }).json.camelCaseFieldNames).toBe(false);
expect(normalizeDataGridExtractorOptions({ json: { camelCaseFieldNames: true } }).json.camelCaseFieldNames).toBe(true);
expect(normalizeDataGridExtractorOptions({ json: { camelCaseFieldNames: "true" } }).json.camelCaseFieldNames).toBe(false);
});
it("exposes the camel-case field-name switch only with JSON extractor options", () => {
const dialog = readFileSync(new URL("../../../components/grid/DataGridExtractorDialog.vue", import.meta.url), "utf8");
expect(dialog).toContain('v-if="isJson"');
expect(dialog).toContain("draftOptions.json.camelCaseFieldNames");
expect(dialog).toContain('t("grid.copyExtractorCamelCaseJsonFields")');
});
it("rejects overlapping effective row and column separators", () => {
const options = normalizeDataGridExtractorOptions(DEFAULT_DATA_GRID_EXTRACTOR_OPTIONS);
options.dsv.rowSeparator = ",";
@@ -88,7 +88,10 @@ export interface DataGridExtractorOptions {
insertMode: DataGridCopyInsertMode;
excludePrimaryKeysFromInsert: boolean;
};
json: { pretty: boolean };
json: {
pretty: boolean;
camelCaseFieldNames: boolean;
};
}
export interface DataGridExtractColumn {
@@ -145,7 +148,7 @@ export const DEFAULT_DATA_GRID_EXTRACTOR_OPTIONS: DataGridExtractorOptions = {
insertMode: "merged",
excludePrimaryKeysFromInsert: false,
},
json: { pretty: true },
json: { pretty: true, camelCaseFieldNames: false },
};
function unicodeCodePointLength(value: string): number {
@@ -175,7 +178,10 @@ export function normalizeDataGridExtractorOptions(value: unknown): DataGridExtra
insertMode: sql.insertMode === "row-by-row" ? "row-by-row" : "merged",
excludePrimaryKeysFromInsert: sql.excludePrimaryKeysFromInsert === true,
},
json: { pretty: json.pretty !== false },
json: {
pretty: json.pretty !== false,
camelCaseFieldNames: json.camelCaseFieldNames === true,
},
};
}