From 12c2963447bb71f3faf6096d9f06baadd1148092 Mon Sep 17 00:00:00 2001 From: cfngc4594 Date: Fri, 29 May 2026 12:12:27 +0800 Subject: [PATCH] feat(settings): add code editor preferences --- messages/en.json | 12 +++- messages/zh.json | 12 +++- src/components/code-editor-settings.tsx | 88 +++++++++++++++++++++++++ src/components/core-diff-editor.tsx | 18 ++++- src/components/core-editor.tsx | 17 ++++- src/components/settings-dialog.tsx | 5 +- src/stores/useSettingsStore.ts | 28 +++++++- 7 files changed, 171 insertions(+), 9 deletions(-) create mode 100644 src/components/code-editor-settings.tsx diff --git a/messages/en.json b/messages/en.json index a27829d..13523fe 100644 --- a/messages/en.json +++ b/messages/en.json @@ -75,6 +75,15 @@ "showPassword": "Show password", "hidePassword": "Hide password" }, + "CodeEditorSettings": { + "fontSize": "Font size", + "wordWrap": "Wrap long lines", + "wordWrapDescription": "Move code to the next line when it does not fit in one line.", + "minimap": "Minimap", + "minimapDescription": "Show a code overview on the right side of the editor.", + "stickyScroll": "Sticky scroll", + "stickyScrollDescription": "Keep the current code scope visible while scrolling." + }, "DetailsPage": { "BackButton": "All Submissions", "Time": "Submitted on", @@ -129,8 +138,7 @@ "nav": { "Appearance": "Appearance", "Language": "Language", - "CodeEditor": "CodeEditor", - "Advanced": "Advanced" + "CodeEditor": "CodeEditor" } }, "SignInForm": { diff --git a/messages/zh.json b/messages/zh.json index 79d966f..a7a02e4 100644 --- a/messages/zh.json +++ b/messages/zh.json @@ -75,6 +75,15 @@ "showPassword": "显示密码", "hidePassword": "隐藏密码" }, + "CodeEditorSettings": { + "fontSize": "字体大小", + "wordWrap": "超长行换行显示", + "wordWrapDescription": "一行代码显示不下时,自动换到下一行显示。", + "minimap": "小地图", + "minimapDescription": "在编辑器右侧显示代码缩略导航。", + "stickyScroll": "粘性滚动", + "stickyScrollDescription": "滚动时固定显示当前所在的代码结构。" + }, "DetailsPage": { "BackButton": "所有提交记录", "Time": "提交于", @@ -129,8 +138,7 @@ "nav": { "Appearance": "外观", "Language": "语言", - "CodeEditor": "代码编辑器", - "Advanced": "高级设置" + "CodeEditor": "代码编辑器" } }, "SignInForm": { diff --git a/src/components/code-editor-settings.tsx b/src/components/code-editor-settings.tsx new file mode 100644 index 0000000..b51c5fd --- /dev/null +++ b/src/components/code-editor-settings.tsx @@ -0,0 +1,88 @@ +"use client"; + +import { useTranslations } from "next-intl"; +import { Label } from "@/components/ui/label"; +import { Switch } from "@/components/ui/switch"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import { useSettingsStore } from "@/stores/useSettingsStore"; + +const fontSizes = [12, 13, 14, 15, 16, 18, 20]; + +export function CodeEditorSettings() { + const t = useTranslations("CodeEditorSettings"); + const { editorSettings, setEditorSetting } = useSettingsStore(); + + return ( +
+
+ + +
+ + setEditorSetting("wordWrap", checked)} + /> + setEditorSetting("minimap", checked)} + /> + setEditorSetting("stickyScroll", checked)} + /> +
+ ); +} + +interface EditorSwitchProps { + label: string; + description: string; + checked: boolean; + onCheckedChange: (checked: boolean) => void; +} + +function EditorSwitch({ + label, + description, + checked, + onCheckedChange, +}: EditorSwitchProps) { + return ( +
+
+ +

{description}

+
+ +
+ ); +} diff --git a/src/components/core-diff-editor.tsx b/src/components/core-diff-editor.tsx index 9af4f9d..5ea680f 100644 --- a/src/components/core-diff-editor.tsx +++ b/src/components/core-diff-editor.tsx @@ -9,6 +9,7 @@ import { shikiToMonaco } from "@shikijs/monaco"; import type { Monaco } from "@monaco-editor/react"; import { DEFAULT_EDITOR_OPTIONS } from "@/config/editor"; import { useMonacoTheme } from "@/hooks/use-monaco-theme"; +import { useSettingsStore } from "@/stores/useSettingsStore"; const MonacoEditor = dynamic( async () => { @@ -38,6 +39,7 @@ export const CoreDiffEditor = ({ className, }: CoreDiffEditorProps) => { const { theme } = useMonacoTheme(); + const { editorSettings } = useSettingsStore(); const editorRef = useRef(null); @@ -62,7 +64,21 @@ export const CoreDiffEditor = ({ modified={modified} beforeMount={handleBeforeMount} onMount={handleOnMount} - options={{ ...DEFAULT_EDITOR_OPTIONS, readOnly: true }} + options={{ + ...DEFAULT_EDITOR_OPTIONS, + readOnly: true, + fontSize: editorSettings.fontSize, + lineHeight: Math.round(editorSettings.fontSize * 1.5), + wordWrap: editorSettings.wordWrap ? "on" : "off", + minimap: { + ...DEFAULT_EDITOR_OPTIONS.minimap, + enabled: editorSettings.minimap, + }, + stickyScroll: { + ...DEFAULT_EDITOR_OPTIONS.stickyScroll, + enabled: editorSettings.stickyScroll, + }, + }} loading={} className={className} /> diff --git a/src/components/core-editor.tsx b/src/components/core-editor.tsx index 9301a7e..1c3132e 100644 --- a/src/components/core-editor.tsx +++ b/src/components/core-editor.tsx @@ -15,6 +15,7 @@ import type { Monaco } from "@monaco-editor/react"; import { DEFAULT_EDITOR_OPTIONS } from "@/config/editor"; import { useMonacoTheme } from "@/hooks/use-monaco-theme"; import { LanguageServerConfig } from "@/generated/client"; +import { useSettingsStore } from "@/stores/useSettingsStore"; import type { MessageTransports } from "vscode-languageclient"; import { useCallback, useEffect, useRef, useState } from "react"; import type { MonacoLanguageClient } from "monaco-languageclient"; @@ -72,6 +73,7 @@ export const CoreEditor = ({ className, }: CoreEditorProps) => { const { theme } = useMonacoTheme(); + const { editorSettings } = useSettingsStore(); const [isEditorMounted, setIsEditorMounted] = useState(false); const editorRef = useRef(null); @@ -159,7 +161,20 @@ export const CoreEditor = ({ onMount={handleOnMount} onChange={handleOnChange} onValidate={handleOnValidate} - options={DEFAULT_EDITOR_OPTIONS} + options={{ + ...DEFAULT_EDITOR_OPTIONS, + fontSize: editorSettings.fontSize, + lineHeight: Math.round(editorSettings.fontSize * 1.5), + wordWrap: editorSettings.wordWrap ? "on" : "off", + minimap: { + ...DEFAULT_EDITOR_OPTIONS.minimap, + enabled: editorSettings.minimap, + }, + stickyScroll: { + ...DEFAULT_EDITOR_OPTIONS.stickyScroll, + enabled: editorSettings.stickyScroll, + }, + }} loading={} className={className} /> diff --git a/src/components/settings-dialog.tsx b/src/components/settings-dialog.tsx index ac462b8..f0f1634 100644 --- a/src/components/settings-dialog.tsx +++ b/src/components/settings-dialog.tsx @@ -30,7 +30,8 @@ import { ScrollArea } from "@/components/ui/scroll-area"; import { useSettingsStore } from "@/stores/useSettingsStore"; import { LocaleSwitcher } from "@/components/locale-switcher"; import AppearanceSettings from "@/components/appearance-settings"; -import { CodeXml, Globe, Paintbrush, Settings } from "lucide-react"; +import { CodeEditorSettings } from "@/components/code-editor-settings"; +import { CodeXml, Globe, Paintbrush } from "lucide-react"; export const SettingsDialog = () => { const t = useTranslations("SettingsDialog"); @@ -39,7 +40,6 @@ export const SettingsDialog = () => { { id: "Appearance", name: t("nav.Appearance"), icon: Paintbrush }, { id: "Language", name: t("nav.Language"), icon: Globe }, { id: "CodeEditor", name: t("nav.CodeEditor"), icon: CodeXml }, - { id: "Advanced", name: t("nav.Advanced"), icon: Settings }, ], }; const { isDialogOpen, activeSetting, setDialogOpen, setActiveSetting } = @@ -99,6 +99,7 @@ export const SettingsDialog = () => {
{activeSetting === "Appearance" && } {activeSetting === "Language" && } + {activeSetting === "CodeEditor" && }
diff --git a/src/stores/useSettingsStore.ts b/src/stores/useSettingsStore.ts index dd66d58..f5e314d 100644 --- a/src/stores/useSettingsStore.ts +++ b/src/stores/useSettingsStore.ts @@ -1,11 +1,23 @@ import { create } from "zustand"; import { persist } from "zustand/middleware"; +export interface EditorSettings { + fontSize: number; + wordWrap: boolean; + minimap: boolean; + stickyScroll: boolean; +} + interface SettingsState { activeSetting: string; isDialogOpen: boolean; + editorSettings: EditorSettings; setActiveSetting: (setting: string) => void; setDialogOpen: (open: boolean) => void; + setEditorSetting: ( + key: K, + value: EditorSettings[K] + ) => void; } export const useSettingsStore = create()( @@ -13,14 +25,28 @@ export const useSettingsStore = create()( (set) => ({ activeSetting: "Appearance", isDialogOpen: false, + editorSettings: { + fontSize: 14, + wordWrap: true, + minimap: false, + stickyScroll: true, + }, setActiveSetting: (setting) => set({ activeSetting: setting }), setDialogOpen: (open) => set({ isDialogOpen: open }), + setEditorSetting: (key, value) => + set((state) => ({ + editorSettings: { + ...state.editorSettings, + [key]: value, + }, + })), }), { name: "settings-state", partialize: (state) => ({ - activeNav: state.activeSetting, + activeSetting: state.activeSetting, isDialogOpen: state.isDialogOpen, + editorSettings: state.editorSettings, }), } )