From 87f408c163fed4437ee07fe1d3cf2a49284b312a Mon Sep 17 00:00:00 2001 From: Jason Date: Sat, 1 Nov 2025 21:05:01 +0800 Subject: [PATCH] feat(editor): add JSON format button to editors Add one-click format functionality for JSON editors with toast notifications: - Add format button to JsonEditor (CodeMirror) - Add format button to CodexAuthSection (auth.json) - Add format button to CommonConfigEditor (settings + modal) - Add formatJSON utility function - Add i18n keys: format, formatSuccess, formatError Note: TOML formatting was intentionally NOT added to avoid losing comments during parse/stringify operations. TOML validation remains available via the existing useCodexTomlValidation hook. --- src/components/JsonEditor.tsx | 42 +++++++++- .../providers/forms/CodexConfigSections.tsx | 55 +++++++++++-- .../providers/forms/CommonConfigEditor.tsx | 82 ++++++++++++++++--- src/i18n/locales/en.json | 5 +- src/i18n/locales/zh.json | 5 +- src/utils/formatters.ts | 28 +++++++ 6 files changed, 194 insertions(+), 23 deletions(-) create mode 100644 src/utils/formatters.ts diff --git a/src/components/JsonEditor.tsx b/src/components/JsonEditor.tsx index 8f16ca7..5764ea4 100644 --- a/src/components/JsonEditor.tsx +++ b/src/components/JsonEditor.tsx @@ -7,6 +7,9 @@ import { EditorState } from "@codemirror/state"; import { placeholder } from "@codemirror/view"; import { linter, Diagnostic } from "@codemirror/lint"; import { useTranslation } from "react-i18next"; +import { Wand2 } from "lucide-react"; +import { toast } from "sonner"; +import { formatJSON } from "@/utils/formatters"; interface JsonEditorProps { value: string; @@ -170,7 +173,44 @@ const JsonEditor: React.FC = ({ } }, [value]); - return
; + // 格式化处理函数 + const handleFormat = () => { + if (!viewRef.current) return; + + const currentValue = viewRef.current.state.doc.toString(); + if (!currentValue.trim()) return; + + try { + const formatted = formatJSON(currentValue); + onChange(formatted); + toast.success(t("common.formatSuccess", { defaultValue: "格式化成功" })); + } catch (error) { + const errorMessage = + error instanceof Error ? error.message : String(error); + toast.error( + t("common.formatError", { + defaultValue: "格式化失败:{{error}}", + error: errorMessage, + }), + ); + } + }; + + return ( +
+
+ {language === "json" && ( + + )} +
+ ); }; export default JsonEditor; diff --git a/src/components/providers/forms/CodexConfigSections.tsx b/src/components/providers/forms/CodexConfigSections.tsx index 4d0f1c0..73a057e 100644 --- a/src/components/providers/forms/CodexConfigSections.tsx +++ b/src/components/providers/forms/CodexConfigSections.tsx @@ -1,5 +1,8 @@ import React from "react"; import { useTranslation } from "react-i18next"; +import { Wand2 } from "lucide-react"; +import { toast } from "sonner"; +import { formatJSON } from "@/utils/formatters"; interface CodexAuthSectionProps { value: string; @@ -19,6 +22,25 @@ export const CodexAuthSection: React.FC = ({ }) => { const { t } = useTranslation(); + const handleFormat = () => { + if (!value.trim()) return; + + try { + const formatted = formatJSON(value); + onChange(formatted); + toast.success(t("common.formatSuccess", { defaultValue: "格式化成功" })); + } catch (error) { + const errorMessage = + error instanceof Error ? error.message : String(error); + toast.error( + t("common.formatError", { + defaultValue: "格式化失败:{{error}}", + error: errorMessage, + }), + ); + } + }; + return (
); }; @@ -141,9 +176,11 @@ export const CodexConfigSection: React.FC = ({

{configError}

)} -

- {t("codexConfig.configTomlHint")} -

+ {!configError && ( +

+ {t("codexConfig.configTomlHint")} +

+ )}
); }; diff --git a/src/components/providers/forms/CommonConfigEditor.tsx b/src/components/providers/forms/CommonConfigEditor.tsx index 1afa84f..78c8341 100644 --- a/src/components/providers/forms/CommonConfigEditor.tsx +++ b/src/components/providers/forms/CommonConfigEditor.tsx @@ -8,7 +8,9 @@ import { } from "@/components/ui/dialog"; import { Label } from "@/components/ui/label"; import { Button } from "@/components/ui/button"; -import { Save } from "lucide-react"; +import { Save, Wand2 } from "lucide-react"; +import { toast } from "sonner"; +import { formatJSON } from "@/utils/formatters"; interface CommonConfigEditorProps { value: string; @@ -37,6 +39,44 @@ export function CommonConfigEditor({ }: CommonConfigEditorProps) { const { t } = useTranslation(); + const handleFormatMain = () => { + if (!value.trim()) return; + + try { + const formatted = formatJSON(value); + onChange(formatted); + toast.success(t("common.formatSuccess", { defaultValue: "格式化成功" })); + } catch (error) { + const errorMessage = + error instanceof Error ? error.message : String(error); + toast.error( + t("common.formatError", { + defaultValue: "格式化失败:{{error}}", + error: errorMessage, + }), + ); + } + }; + + const handleFormatModal = () => { + if (!commonConfigSnippet.trim()) return; + + try { + const formatted = formatJSON(commonConfigSnippet); + onCommonConfigSnippetChange(formatted); + toast.success(t("common.formatSuccess", { defaultValue: "格式化成功" })); + } catch (error) { + const errorMessage = + error instanceof Error ? error.message : String(error); + toast.error( + t("common.formatError", { + defaultValue: "格式化失败:{{error}}", + error: errorMessage, + }), + ); + } + }; + return ( <>
@@ -97,11 +137,21 @@ export function CommonConfigEditor({ data-gramm_editor="false" data-enable-grammarly="false" /> -

- {t("claudeConfig.fullSettingsHint", { - defaultValue: "请填写完整的 Claude Code 配置", - })} -

+
+ +

+ {t("claudeConfig.fullSettingsHint", { + defaultValue: "请填写完整的 Claude Code 配置", + })} +

+
- {commonConfigError && ( -

- {commonConfigError} -

- )} +
+ + {commonConfigError && ( +

+ {commonConfigError} +

+ )} +