diff --git a/apps/desktop/src/components/grid/DataGrid.vue b/apps/desktop/src/components/grid/DataGrid.vue index 3735a8b26d..27f7618c60 100644 --- a/apps/desktop/src/components/grid/DataGrid.vue +++ b/apps/desktop/src/components/grid/DataGrid.vue @@ -6034,6 +6034,7 @@ const { restoreDetailOriginalValue, setValueEditorNull, formatValueEditorJson, + formatDetailJsonDraft, compactDetailJson, openDetailJsonCompare, setDetailNull, @@ -13613,6 +13614,7 @@ useUpdateBlocker(() => (hasPendingChanges.value || hasPendingDataEditorDraft.val :can-copy-sql-condition="canCopyPreparedDetailSqlCondition" :database-type="resolvedDatabaseType" @start-edit="startDetailEdit" + @format-json="formatDetailJsonDraft" @compact-json="compactDetailJson" @compare-json="openDetailJsonCompare" @toggle-formatted="toggleCellDetailJsonFormatted" diff --git a/apps/desktop/src/components/grid/DataGridCellDetailPanel.vue b/apps/desktop/src/components/grid/DataGridCellDetailPanel.vue index c9aefd92f2..a054e8317e 100644 --- a/apps/desktop/src/components/grid/DataGridCellDetailPanel.vue +++ b/apps/desktop/src/components/grid/DataGridCellDetailPanel.vue @@ -47,6 +47,7 @@ const detailEditValue = defineModel("value", { default: "" }); const emit = defineEmits<{ startEdit: []; + formatJson: []; compactJson: []; compareJson: []; toggleFormatted: []; @@ -153,6 +154,8 @@ defineExpose({ openSearch }); :unsafe-number="typeof detail.value === 'number' && Number.isInteger(detail.value) && !Number.isSafeInteger(detail.value) && (!editing || detailEditValue === detail.rawValue)" /> + + diff --git a/apps/desktop/src/components/grid/__tests__/DataGridSurfaces.spec.ts b/apps/desktop/src/components/grid/__tests__/DataGridSurfaces.spec.ts index 6c6403ea20..d185cdab5c 100644 --- a/apps/desktop/src/components/grid/__tests__/DataGridSurfaces.spec.ts +++ b/apps/desktop/src/components/grid/__tests__/DataGridSurfaces.spec.ts @@ -1864,6 +1864,37 @@ describe("cell detail surfaces", () => { expect(dataGridSource).toContain(':disabled="!canCompareDetailJson" @mousedown.prevent @click="openDetailJsonCompare"'); expect(dataGridSource).toContain('v-model:open="detailValueDiffOpen" :snapshot="detailValueDiffSnapshot"'); }); + + // issue #9832:编辑中点「压缩 JSON」会把草稿压成单行,此时必须仍能点「格式化 JSON」 + // 重新展开,否则 Mongo 这类对象字段的 JSON 一旦压缩就再也无法格式化。 + it("keeps the JSON format action reachable while a compacted draft is being edited", async () => { + const formatJson = vi.fn(); + const mounted = mountComponent(DataGridCellDetailPanel, { + detail: detail(), + panelIsBottom: true, + metadataCollapsed: false, + valueFillsHeight: true, + editing: true, + sideJsonView: false, + showCompactJson: true, + canCompactJson: true, + typeColorClass: () => "", + canDownloadBinaryValue: () => false, + downloadBinaryValue: vi.fn(), + canImportBinaryValue: () => false, + importBinaryValue: vi.fn(), + openImagePreview: vi.fn(), + canCopySqlCondition: () => true, + onFormatJson: formatJson, + }); + + const formatButton = findOne(mounted.root, (node) => node.props.title === "grid.formatJson"); + dispatch(formatButton, "click"); + expect(formatJson).toHaveBeenCalledOnce(); + + await mounted.setProps({ showCompactJson: false }); + expect(findAll(mounted.root, (node) => node.props.title === "grid.formatJson")).toHaveLength(0); + }); }); describe("DataGridCopyColumnNamesDialog", () => { diff --git a/apps/desktop/src/composables/__tests__/useDataGridCellDetailEdit.spec.ts b/apps/desktop/src/composables/__tests__/useDataGridCellDetailEdit.spec.ts index d27b400e2f..65fbc499b9 100644 --- a/apps/desktop/src/composables/__tests__/useDataGridCellDetailEdit.spec.ts +++ b/apps/desktop/src/composables/__tests__/useDataGridCellDetailEdit.spec.ts @@ -152,4 +152,44 @@ describe("useDataGridCellDetailEdit", () => { expect(editor.isEditingDetail.value).toBe(false); expect(editor.detailEditValue.value).toBe(""); }); + + // issue #9832:单元格详情面板的「格式化 JSON」只重排编辑器草稿,不触发 + // 「正在编辑格式化后的 JSON」提示,也不改动原始值。 + it("expands a compacted JSON draft without warning about formatted JSON", async () => { + const compact = '{"owner":"zipg","tags":["a"]}'; + const activeDetail = ref(detail(compact)); + const syncEditor = vi.fn(); + const warnFormattedJsonEdit = vi.fn(); + const editor = useDataGridCellDetailEdit({ + activeDetail: computed(() => activeDetail.value), + activeTab: ref("valueEditor"), + jsonFormatted: computed(() => false), + databaseType: computed(() => "mongodb"), + resultRows: computed(() => [["id", compact]]), + getColumnInfo: () => undefined, + getRowItem: () => ({ sourceIndex: 0, isNew: false, isDeleted: false }), + hydrateLargeValueCell: async () => true, + applyCellValue: vi.fn(), + restoreCellValue: vi.fn(), + syncEditor, + refreshDetail: vi.fn(), + warnFormattedJsonEdit, + }); + + await editor.startDetailEdit(); + syncEditor.mockClear(); + warnFormattedJsonEdit.mockClear(); + editor.formatDetailJsonDraft(); + + expect(editor.detailEditValue.value).toBe('{\n "owner": "zipg",\n "tags": [\n "a"\n ]\n}'); + expect(editor.detailEditOriginalValue.value).toBe(compact); + expect(syncEditor).toHaveBeenLastCalledWith('{\n "owner": "zipg",\n "tags": [\n "a"\n ]\n}', ""); + expect(warnFormattedJsonEdit).not.toHaveBeenCalled(); + + syncEditor.mockClear(); + editor.detailEditValue.value = "not json"; + editor.formatDetailJsonDraft(); + expect(editor.detailEditValue.value).toBe("not json"); + expect(syncEditor).not.toHaveBeenCalled(); + }); }); diff --git a/apps/desktop/src/composables/useDataGridCellDetailEdit.ts b/apps/desktop/src/composables/useDataGridCellDetailEdit.ts index 52dbed06d4..63ec01f9ad 100644 --- a/apps/desktop/src/composables/useDataGridCellDetailEdit.ts +++ b/apps/desktop/src/composables/useDataGridCellDetailEdit.ts @@ -203,6 +203,15 @@ export function useDataGridCellDetailEdit(options: UseDataGridCellDetailEditOpti options.warnFormattedJsonEdit(detail, true); } + // 与 formatValueEditorJson 相同的草稿格式化,但不弹「正在编辑格式化后的 JSON」 + // 提示:用户是在单元格详情面板里主动点「格式化 JSON」展开草稿(issue #9832)。 + function formatDetailJsonDraft() { + const detail = options.activeDetail.value; + if (!detail || !canFormatCellDetailJson(detailEditValue.value, detail.type)) return; + detailEditValue.value = formatJsonText(detailEditValue.value) ?? detailEditValue.value; + syncEditorFromDetailEdit(); + } + function compactDetailJson() { const detail = options.activeDetail.value; if (!detail || !canFormatCellDetailJson(detailEditValue.value, detail.type)) return; @@ -250,6 +259,7 @@ export function useDataGridCellDetailEdit(options: UseDataGridCellDetailEditOpti restoreDetailOriginalValue, setValueEditorNull, formatValueEditorJson, + formatDetailJsonDraft, compactDetailJson, openDetailJsonCompare, setDetailNull, diff --git a/docs/screenshots/issue-9832-json-format-after.png b/docs/screenshots/issue-9832-json-format-after.png new file mode 100644 index 0000000000..5379920907 Binary files /dev/null and b/docs/screenshots/issue-9832-json-format-after.png differ diff --git a/docs/screenshots/issue-9832-json-format-before.png b/docs/screenshots/issue-9832-json-format-before.png new file mode 100644 index 0000000000..072bf4bbab Binary files /dev/null and b/docs/screenshots/issue-9832-json-format-before.png differ