From 2cb98ad118f3ceafe8f7a8c0ec2698105035a202 Mon Sep 17 00:00:00 2001 From: LTbinglingfeng Date: Wed, 20 May 2026 01:27:19 +0800 Subject: [PATCH] fix(config): preserve source draft on visual save --- src/pages/ConfigPage.tsx | 38 ++++++++++++++++++++++++++++++-------- 1 file changed, 30 insertions(+), 8 deletions(-) diff --git a/src/pages/ConfigPage.tsx b/src/pages/ConfigPage.tsx index 2e902df..75fd49f 100644 --- a/src/pages/ConfigPage.tsx +++ b/src/pages/ConfigPage.tsx @@ -35,6 +35,15 @@ function readCommercialModeFromYaml(yamlContent: string): boolean { } } +function normalizeYamlForVisualDiff(yamlContent: string): string { + try { + const doc = parseDocument(yamlContent); + return doc.toString({ indent: 2, lineWidth: 120, minContentWidth: 0 }); + } catch { + return yamlContent; + } +} + export function ConfigPage() { const { t } = useTranslation(); const pageTransitionLayer = usePageTransitionLayer(); @@ -178,6 +187,8 @@ export function ConfigPage() { try { const latestServerYaml = await configFileApi.fetchConfigYaml(); + const visualBaseYaml = dirty ? content : latestServerYaml; + if (activeTab !== 'source') { const latestDocument = parseDocument(latestServerYaml); if (latestDocument.errors.length > 0) { @@ -191,23 +202,34 @@ export function ConfigPage() { ); return; } + + if (visualBaseYaml !== latestServerYaml) { + const visualBaseDocument = parseDocument(visualBaseYaml); + if (visualBaseDocument.errors.length > 0) { + showNotification( + t('config_management.visual_mode_latest_yaml_invalid', { + message: + visualBaseDocument.errors[0]?.message ?? + t('config_management.visual_mode_save_blocked'), + }), + 'error' + ); + return; + } + } } - // In source mode, save exactly what the user edited. In visual mode, materialize visual changes into the latest YAML. + // In source mode, save exactly what the user edited. In visual mode, preserve the + // local source draft when it has unsaved edits so source-only backend fields are not dropped. const nextMergedYaml = - activeTab === 'source' ? content : applyVisualChangesToYaml(latestServerYaml); + activeTab === 'source' ? content : applyVisualChangesToYaml(visualBaseYaml); // In visual mode, applyVisualChangesToYaml re-serializes YAML via parseDocument → toString, // which may reformat comments/whitespace. Normalize the server YAML through the same pipeline // so the diff only shows actual value changes, not cosmetic reformatting. let diffOriginal = latestServerYaml; if (activeTab !== 'source') { - try { - const doc = parseDocument(latestServerYaml); - diffOriginal = doc.toString({ indent: 2, lineWidth: 120, minContentWidth: 0 }); - } catch { - /* keep raw on parse failure */ - } + diffOriginal = normalizeYamlForVisualDiff(latestServerYaml); } if (diffOriginal === nextMergedYaml) {