From 7b746155f79fd00c81051530a0214c167ab48002 Mon Sep 17 00:00:00 2001 From: landaiqing Date: Sat, 20 Dec 2025 16:43:04 +0800 Subject: [PATCH] :recycle: Refactor keybinding service --- .../voidraft/internal/models/ent/models.ts | 90 +- .../voidraft/internal/models/models.ts | 372 ++++-- .../internal/services/extensionservice.ts | 83 +- .../internal/services/keybindingservice.ts | 58 +- .../internal/services/themeservice.ts | 6 +- frontend/src/common/constant/config.ts | 5 +- frontend/src/i18n/locales/en-US.ts | 38 + frontend/src/i18n/locales/zh-CN.ts | 38 + frontend/src/stores/configStore.ts | 3 + frontend/src/stores/documentStore.ts | 4 +- frontend/src/stores/editorStore.ts | 23 +- frontend/src/stores/extensionStore.ts | 26 +- frontend/src/stores/keybindingStore.ts | 74 +- frontend/src/stores/themeStore.ts | 75 +- .../editor/extensions/contextMenu/index.ts | 206 ++-- .../extensions/contextMenu/menuSchema.ts | 8 +- .../extensions/markdown/plugins/code-block.ts | 22 +- frontend/src/views/editor/keymap/commands.ts | 248 +++- frontend/src/views/editor/keymap/index.ts | 3 +- frontend/src/views/editor/keymap/manager.ts | 31 +- frontend/src/views/editor/keymap/types.ts | 30 - .../src/views/editor/manager/extensions.ts | 38 +- frontend/src/views/editor/manager/manager.ts | 16 +- frontend/src/views/editor/manager/types.ts | 2 +- .../views/settings/pages/ExtensionsPage.vue | 63 +- .../views/settings/pages/KeyBindingsPage.vue | 610 +++++++--- internal/models/config.go | 5 + internal/models/ent/document.go | 11 +- internal/models/ent/document/where.go | 10 - internal/models/ent/document_create.go | 5 +- internal/models/ent/document_update.go | 6 - internal/models/ent/entql.go | 74 +- internal/models/ent/extension.go | 27 +- internal/models/ent/extension/extension.go | 16 +- internal/models/ent/extension/where.go | 94 +- internal/models/ent/extension_create.go | 27 +- internal/models/ent/extension_update.go | 50 +- internal/models/ent/keybinding.go | 107 +- internal/models/ent/keybinding/keybinding.go | 80 +- internal/models/ent/keybinding/where.go | 509 +++++++- internal/models/ent/keybinding_create.go | 203 +++- internal/models/ent/keybinding_update.go | 394 ++++++- internal/models/ent/migrate/schema.go | 43 +- internal/models/ent/mutation.go | 685 ++++++++--- internal/models/ent/runtime/runtime.go | 130 ++- internal/models/ent/theme.go | 27 +- internal/models/ent/theme/theme.go | 16 +- internal/models/ent/theme/where.go | 94 +- internal/models/ent/theme_create.go | 27 +- internal/models/ent/theme_update.go | 50 +- internal/models/extension.go | 59 +- internal/models/key_binding.go | 1024 +++++++++++++---- internal/models/schema/extension.go | 6 +- internal/models/schema/keybinding.go | 65 +- internal/models/schema/theme.go | 6 +- internal/services/backup_service.go | 54 +- internal/services/extension_service.go | 81 +- internal/services/keybinding_service.go | 169 ++- internal/services/theme_service.go | 14 +- version.txt | 2 +- 60 files changed, 4526 insertions(+), 1816 deletions(-) delete mode 100644 frontend/src/views/editor/keymap/types.ts diff --git a/frontend/bindings/voidraft/internal/models/ent/models.ts b/frontend/bindings/voidraft/internal/models/ent/models.ts index 096a7b5..9cd4bcb 100644 --- a/frontend/bindings/voidraft/internal/models/ent/models.ts +++ b/frontend/bindings/voidraft/internal/models/ent/models.ts @@ -21,7 +21,7 @@ export class Document { /** * UUID for cross-device sync (UUIDv7) */ - "uuid": string | null; + "uuid": string; /** * creation time @@ -56,7 +56,7 @@ export class Document { /** Creates a new Document instance. */ constructor($$source: Partial = {}) { if (!("uuid" in $$source)) { - this["uuid"] = null; + this["uuid"] = ""; } if (!("created_at" in $$source)) { this["created_at"] = ""; @@ -98,7 +98,7 @@ export class Extension { /** * UUID for cross-device sync (UUIDv7) */ - "uuid": string | null; + "uuid": string; /** * creation time @@ -116,9 +116,9 @@ export class Extension { "deleted_at"?: string | null; /** - * extension key + * extension name */ - "key": string; + "name": string; /** * extension enabled or not @@ -133,7 +133,7 @@ export class Extension { /** Creates a new Extension instance. */ constructor($$source: Partial = {}) { if (!("uuid" in $$source)) { - this["uuid"] = null; + this["uuid"] = ""; } if (!("created_at" in $$source)) { this["created_at"] = ""; @@ -141,8 +141,8 @@ export class Extension { if (!("updated_at" in $$source)) { this["updated_at"] = ""; } - if (!("key" in $$source)) { - this["key"] = ""; + if (!("name" in $$source)) { + this["name"] = ""; } if (!("enabled" in $$source)) { this["enabled"] = false; @@ -179,7 +179,7 @@ export class KeyBinding { /** * UUID for cross-device sync (UUIDv7) */ - "uuid": string | null; + "uuid": string; /** * creation time @@ -197,29 +197,59 @@ export class KeyBinding { "deleted_at"?: string | null; /** - * key binding key + * command identifier */ - "key": string; + "name": string; /** - * key binding command + * keybinding type: standard or emacs */ - "command": string; + "type": string; /** - * key binding extension + * universal keybinding (cross-platform) */ - "extension"?: string; + "key"?: string; /** - * key binding enabled + * macOS specific keybinding + */ + "macos"?: string; + + /** + * Windows specific keybinding + */ + "windows"?: string; + + /** + * Linux specific keybinding + */ + "linux"?: string; + + /** + * extension name (functional category) + */ + "extension": string; + + /** + * whether this keybinding is enabled */ "enabled": boolean; + /** + * prevent browser default behavior + */ + "preventDefault": boolean; + + /** + * keybinding scope (default: editor) + */ + "scope"?: string; + /** Creates a new KeyBinding instance. */ constructor($$source: Partial = {}) { if (!("uuid" in $$source)) { - this["uuid"] = null; + this["uuid"] = ""; } if (!("created_at" in $$source)) { this["created_at"] = ""; @@ -227,15 +257,21 @@ export class KeyBinding { if (!("updated_at" in $$source)) { this["updated_at"] = ""; } - if (!("key" in $$source)) { - this["key"] = ""; + if (!("name" in $$source)) { + this["name"] = ""; } - if (!("command" in $$source)) { - this["command"] = ""; + if (!("type" in $$source)) { + this["type"] = ""; + } + if (!("extension" in $$source)) { + this["extension"] = ""; } if (!("enabled" in $$source)) { this["enabled"] = false; } + if (!("preventDefault" in $$source)) { + this["preventDefault"] = false; + } Object.assign(this, $$source); } @@ -261,7 +297,7 @@ export class Theme { /** * UUID for cross-device sync (UUIDv7) */ - "uuid": string | null; + "uuid": string; /** * creation time @@ -279,9 +315,9 @@ export class Theme { "deleted_at"?: string | null; /** - * theme key + * theme name */ - "key": string; + "name": string; /** * theme type @@ -296,7 +332,7 @@ export class Theme { /** Creates a new Theme instance. */ constructor($$source: Partial = {}) { if (!("uuid" in $$source)) { - this["uuid"] = null; + this["uuid"] = ""; } if (!("created_at" in $$source)) { this["created_at"] = ""; @@ -304,8 +340,8 @@ export class Theme { if (!("updated_at" in $$source)) { this["updated_at"] = ""; } - if (!("key" in $$source)) { - this["key"] = ""; + if (!("name" in $$source)) { + this["name"] = ""; } if (!("type" in $$source)) { this["type"] = ("" as theme$0.Type); diff --git a/frontend/bindings/voidraft/internal/models/models.ts b/frontend/bindings/voidraft/internal/models/models.ts index fd3ba80..ff1cc73 100644 --- a/frontend/bindings/voidraft/internal/models/models.ts +++ b/frontend/bindings/voidraft/internal/models/models.ts @@ -234,6 +234,12 @@ export class EditingConfig { */ "tabType": TabType; + /** + * 快捷键模式 + * 快捷键模式(standard 或 emacs) + */ + "keymapMode": KeyBindingType; + /** * 保存选项 * 自动保存延迟(毫秒) @@ -263,6 +269,9 @@ export class EditingConfig { if (!("tabType" in $$source)) { this["tabType"] = ("" as TabType); } + if (!("keymapMode" in $$source)) { + this["keymapMode"] = ("" as KeyBindingType); + } if (!("autoSaveDelay" in $$source)) { this["autoSaveDelay"] = 0; } @@ -283,14 +292,14 @@ export class EditingConfig { * Extension 扩展配置 */ export class Extension { - "key": ExtensionKey; + "key": ExtensionName; "enabled": boolean; "config": ExtensionConfig; /** Creates a new Extension instance. */ constructor($$source: Partial = {}) { if (!("key" in $$source)) { - this["key"] = ("" as ExtensionKey); + this["key"] = ("" as ExtensionName); } if (!("enabled" in $$source)) { this["enabled"] = false; @@ -321,79 +330,78 @@ export class Extension { export type ExtensionConfig = { [_: string]: any }; /** - * ExtensionKey 扩展标识符 + * ExtensionName 扩展标识符 */ -export enum ExtensionKey { +export enum ExtensionName { /** * The Go zero value for the underlying type of the enum. */ $zero = "", /** - * 编辑增强扩展 * 彩虹括号 */ - ExtensionRainbowBrackets = "rainbowBrackets", + RainbowBrackets = "rainbowBrackets", /** * 超链接 */ - ExtensionHyperlink = "hyperlink", + Hyperlink = "hyperlink", /** * 颜色选择器 */ - ExtensionColorSelector = "colorSelector", + ColorSelector = "colorSelector", /** * 代码折叠 */ - ExtensionFold = "fold", + Fold = "fold", /** * 划词翻译 */ - ExtensionTranslator = "translator", + Translator = "translator", /** * Markdown渲染 */ - ExtensionMarkdown = "markdown", + Markdown = "markdown", /** * 显示空白字符 */ - ExtensionHighlightWhitespace = "highlightWhitespace", + HighlightWhitespace = "highlightWhitespace", /** * 高亮行尾空白 */ - ExtensionHighlightTrailingWhitespace = "highlightTrailingWhitespace", + HighlightTrailingWhitespace = "highlightTrailingWhitespace", /** * 小地图 */ - ExtensionMinimap = "minimap", + Minimap = "minimap", /** * 行号显示 */ - ExtensionLineNumbers = "lineNumbers", + LineNumbers = "lineNumbers", /** * 上下文菜单 */ - ExtensionContextMenu = "contextMenu", + ContextMenu = "contextMenu", /** * 搜索功能 */ - ExtensionSearch = "search", + Search = "search", /** * HTTP 客户端 */ - ExtensionHttpClient = "httpClient", + HttpClient = "httpClient", }; /** @@ -684,25 +692,73 @@ export class HotkeyCombo { * KeyBinding 单个快捷键绑定 */ export class KeyBinding { - "key": KeyBindingKey; - "command": string; - "extension": ExtensionKey; + /** + * 命令唯一标识符 + */ + "name": KeyBindingName; + + /** + * 快捷键类型(standard 或 "emacs") + */ + "type": KeyBindingType; + + /** + * 通用快捷键(跨平台) + */ + "key"?: string; + + /** + * macOS 专用快捷键 + */ + "macos"?: string; + + /** + * windows 专用快捷键 + */ + "win"?: string; + + /** + * Linux 专用快捷键 + */ + "linux"?: string; + + /** + * 所属扩展 + */ + "extension": ExtensionName; + + /** + * 是否启用 + */ "enabled": boolean; + /** + * 阻止浏览器默认行为 + */ + "preventDefault": boolean; + + /** + * 作用域(默认 "editor") + */ + "scope"?: string; + /** Creates a new KeyBinding instance. */ constructor($$source: Partial = {}) { - if (!("key" in $$source)) { - this["key"] = ("" as KeyBindingKey); + if (!("name" in $$source)) { + this["name"] = ("" as KeyBindingName); } - if (!("command" in $$source)) { - this["command"] = ""; + if (!("type" in $$source)) { + this["type"] = ("" as KeyBindingType); } if (!("extension" in $$source)) { - this["extension"] = ("" as ExtensionKey); + this["extension"] = ("" as ExtensionName); } if (!("enabled" in $$source)) { this["enabled"] = false; } + if (!("preventDefault" in $$source)) { + this["preventDefault"] = false; + } Object.assign(this, $$source); } @@ -717,9 +773,9 @@ export class KeyBinding { } /** - * KeyBindingKey 快捷键命令 + * KeyBindingName 快捷键命令标识符 */ -export enum KeyBindingKey { +export enum KeyBindingName { /** * The Go zero value for the underlying type of the enum. */ @@ -728,247 +784,409 @@ export enum KeyBindingKey { /** * 显示搜索 */ - ShowSearchKeyBindingKey = "showSearch", + ShowSearch = "showSearch", /** * 隐藏搜索 */ - HideSearchKeyBindingKey = "hideSearch", + HideSearch = "hideSearch", /** * 块内选择全部 */ - BlockSelectAllKeyBindingKey = "blockSelectAll", + BlockSelectAll = "blockSelectAll", /** * 在当前块后添加新块 */ - BlockAddAfterCurrentKeyBindingKey = "blockAddAfterCurrent", + BlockAddAfterCurrent = "blockAddAfterCurrent", /** * 在最后添加新块 */ - BlockAddAfterLastKeyBindingKey = "blockAddAfterLast", + BlockAddAfterLast = "blockAddAfterLast", /** * 在当前块前添加新块 */ - BlockAddBeforeCurrentKeyBindingKey = "blockAddBeforeCurrent", + BlockAddBeforeCurrent = "blockAddBeforeCurrent", /** * 跳转到上一个块 */ - BlockGotoPreviousKeyBindingKey = "blockGotoPrevious", + BlockGotoPrevious = "blockGotoPrevious", /** * 跳转到下一个块 */ - BlockGotoNextKeyBindingKey = "blockGotoNext", + BlockGotoNext = "blockGotoNext", /** * 选择上一个块 */ - BlockSelectPreviousKeyBindingKey = "blockSelectPrevious", + BlockSelectPrevious = "blockSelectPrevious", /** * 选择下一个块 */ - BlockSelectNextKeyBindingKey = "blockSelectNext", + BlockSelectNext = "blockSelectNext", /** * 删除当前块 */ - BlockDeleteKeyBindingKey = "blockDelete", + BlockDelete = "blockDelete", /** * 向上移动当前块 */ - BlockMoveUpKeyBindingKey = "blockMoveUp", + BlockMoveUp = "blockMoveUp", /** * 向下移动当前块 */ - BlockMoveDownKeyBindingKey = "blockMoveDown", + BlockMoveDown = "blockMoveDown", /** * 删除行 */ - BlockDeleteLineKeyBindingKey = "blockDeleteLine", + BlockDeleteLine = "blockDeleteLine", /** * 向上移动行 */ - BlockMoveLineUpKeyBindingKey = "blockMoveLineUp", + BlockMoveLineUp = "blockMoveLineUp", /** * 向下移动行 */ - BlockMoveLineDownKeyBindingKey = "blockMoveLineDown", + BlockMoveLineDown = "blockMoveLineDown", /** * 字符转置 */ - BlockTransposeCharsKeyBindingKey = "blockTransposeChars", + BlockTransposeChars = "blockTransposeChars", /** * 格式化代码块 */ - BlockFormatKeyBindingKey = "blockFormat", + BlockFormat = "blockFormat", /** * 复制 */ - BlockCopyKeyBindingKey = "blockCopy", + BlockCopy = "blockCopy", /** * 剪切 */ - BlockCutKeyBindingKey = "blockCut", + BlockCut = "blockCut", /** * 粘贴 */ - BlockPasteKeyBindingKey = "blockPaste", + BlockPaste = "blockPaste", /** * 折叠代码 */ - FoldCodeKeyBindingKey = "foldCode", + FoldCode = "foldCode", /** * 展开代码 */ - UnfoldCodeKeyBindingKey = "unfoldCode", + UnfoldCode = "unfoldCode", /** * 折叠全部 */ - FoldAllKeyBindingKey = "foldAll", + FoldAll = "foldAll", /** * 展开全部 */ - UnfoldAllKeyBindingKey = "unfoldAll", + UnfoldAll = "unfoldAll", /** * 光标按语法左移 */ - CursorSyntaxLeftKeyBindingKey = "cursorSyntaxLeft", + CursorSyntaxLeft = "cursorSyntaxLeft", /** * 光标按语法右移 */ - CursorSyntaxRightKeyBindingKey = "cursorSyntaxRight", + CursorSyntaxRight = "cursorSyntaxRight", /** * 按语法选择左侧 */ - SelectSyntaxLeftKeyBindingKey = "selectSyntaxLeft", + SelectSyntaxLeft = "selectSyntaxLeft", /** * 按语法选择右侧 */ - SelectSyntaxRightKeyBindingKey = "selectSyntaxRight", + SelectSyntaxRight = "selectSyntaxRight", /** * 向上复制行 */ - CopyLineUpKeyBindingKey = "copyLineUp", + CopyLineUp = "copyLineUp", /** * 向下复制行 */ - CopyLineDownKeyBindingKey = "copyLineDown", + CopyLineDown = "copyLineDown", /** * 插入空行 */ - InsertBlankLineKeyBindingKey = "insertBlankLine", + InsertBlankLine = "insertBlankLine", /** * 选择行 */ - SelectLineKeyBindingKey = "selectLine", + SelectLine = "selectLine", /** * 选择父级语法 */ - SelectParentSyntaxKeyBindingKey = "selectParentSyntax", + SelectParentSyntax = "selectParentSyntax", + + /** + * 简化选择 + */ + SimplifySelection = "simplifySelection", + + /** + * 在上方添加光标 + */ + AddCursorAbove = "addCursorAbove", + + /** + * 在下方添加光标 + */ + AddCursorBelow = "addCursorBelow", + + /** + * 光标按单词左移 + */ + CursorGroupLeft = "cursorGroupLeft", + + /** + * 光标按单词右移 + */ + CursorGroupRight = "cursorGroupRight", + + /** + * 按单词选择左侧 + */ + SelectGroupLeft = "selectGroupLeft", + + /** + * 按单词选择右侧 + */ + SelectGroupRight = "selectGroupRight", + + /** + * 删除到行尾 + */ + DeleteToLineEnd = "deleteToLineEnd", + + /** + * 删除到行首 + */ + DeleteToLineStart = "deleteToLineStart", + + /** + * 移动到行首 + */ + CursorLineStart = "cursorLineStart", + + /** + * 移动到行尾 + */ + CursorLineEnd = "cursorLineEnd", + + /** + * 选择到行首 + */ + SelectLineStart = "selectLineStart", + + /** + * 选择到行尾 + */ + SelectLineEnd = "selectLineEnd", + + /** + * 跳转到文档开头 + */ + CursorDocStart = "cursorDocStart", + + /** + * 跳转到文档结尾 + */ + CursorDocEnd = "cursorDocEnd", + + /** + * 选择到文档开头 + */ + SelectDocStart = "selectDocStart", + + /** + * 选择到文档结尾 + */ + SelectDocEnd = "selectDocEnd", + + /** + * 选择到匹配括号 + */ + SelectMatchingBracket = "selectMatchingBracket", + + /** + * 分割行 + */ + SplitLine = "splitLine", + + /** + * 光标左移一个字符 + */ + CursorCharLeft = "cursorCharLeft", + + /** + * 光标右移一个字符 + */ + CursorCharRight = "cursorCharRight", + + /** + * 光标上移一行 + */ + CursorLineUp = "cursorLineUp", + + /** + * 光标下移一行 + */ + CursorLineDown = "cursorLineDown", + + /** + * 向上翻页 + */ + CursorPageUp = "cursorPageUp", + + /** + * 向下翻页 + */ + CursorPageDown = "cursorPageDown", + + /** + * 选择左移一个字符 + */ + SelectCharLeft = "selectCharLeft", + + /** + * 选择右移一个字符 + */ + SelectCharRight = "selectCharRight", + + /** + * 选择上移一行 + */ + SelectLineUp = "selectLineUp", + + /** + * 选择下移一行 + */ + SelectLineDown = "selectLineDown", /** * 减少缩进 */ - IndentLessKeyBindingKey = "indentLess", + IndentLess = "indentLess", /** * 增加缩进 */ - IndentMoreKeyBindingKey = "indentMore", + IndentMore = "indentMore", /** * 缩进选择 */ - IndentSelectionKeyBindingKey = "indentSelection", + IndentSelection = "indentSelection", /** * 光标到匹配括号 */ - CursorMatchingBracketKeyBindingKey = "cursorMatchingBracket", + CursorMatchingBracket = "cursorMatchingBracket", /** * 切换注释 */ - ToggleCommentKeyBindingKey = "toggleComment", + ToggleComment = "toggleComment", /** * 切换块注释 */ - ToggleBlockCommentKeyBindingKey = "toggleBlockComment", + ToggleBlockComment = "toggleBlockComment", /** * 插入新行并缩进 */ - InsertNewlineAndIndentKeyBindingKey = "insertNewlineAndIndent", + InsertNewlineAndIndent = "insertNewlineAndIndent", /** * 向后删除字符 */ - DeleteCharBackwardKeyBindingKey = "deleteCharBackward", + DeleteCharBackward = "deleteCharBackward", /** * 向前删除字符 */ - DeleteCharForwardKeyBindingKey = "deleteCharForward", + DeleteCharForward = "deleteCharForward", /** * 向后删除组 */ - DeleteGroupBackwardKeyBindingKey = "deleteGroupBackward", + DeleteGroupBackward = "deleteGroupBackward", /** * 向前删除组 */ - DeleteGroupForwardKeyBindingKey = "deleteGroupForward", + DeleteGroupForward = "deleteGroupForward", /** * 撤销 */ - HistoryUndoKeyBindingKey = "historyUndo", + HistoryUndo = "historyUndo", /** * 重做 */ - HistoryRedoKeyBindingKey = "historyRedo", + HistoryRedo = "historyRedo", /** * 撤销选择 */ - HistoryUndoSelectionKeyBindingKey = "historyUndoSelection", + HistoryUndoSelection = "historyUndoSelection", /** * 重做选择 */ - HistoryRedoSelectionKeyBindingKey = "historyRedoSelection", + HistoryRedoSelection = "historyRedoSelection", +}; + +export enum KeyBindingType { + /** + * The Go zero value for the underlying type of the enum. + */ + $zero = "", + + /** + * standard 标准快捷键 + */ + Standard = "standard", + + /** + * emacs 快捷键 + */ + Emacs = "emacs", }; /** diff --git a/frontend/bindings/voidraft/internal/services/extensionservice.ts b/frontend/bindings/voidraft/internal/services/extensionservice.ts index b9d4ed2..3aea86d 100644 --- a/frontend/bindings/voidraft/internal/services/extensionservice.ts +++ b/frontend/bindings/voidraft/internal/services/extensionservice.ts @@ -20,35 +20,11 @@ import * as models$0 from "../models/models.js"; // @ts-ignore: Unused imports import * as ent$0 from "../models/ent/models.js"; -/** - * GetAllExtensions 获取所有扩展 - */ -export function GetAllExtensions(): Promise<(ent$0.Extension | null)[]> & { cancel(): void } { - let $resultPromise = $Call.ByID(3094292124) as any; - let $typingPromise = $resultPromise.then(($result: any) => { - return $$createType2($result); - }) as any; - $typingPromise.cancel = $resultPromise.cancel.bind($resultPromise); - return $typingPromise; -} - /** * GetDefaultExtensions 获取默认扩展配置(用于前端绑定生成) */ export function GetDefaultExtensions(): Promise & { cancel(): void } { let $resultPromise = $Call.ByID(4036328166) as any; - let $typingPromise = $resultPromise.then(($result: any) => { - return $$createType4($result); - }) as any; - $typingPromise.cancel = $resultPromise.cancel.bind($resultPromise); - return $typingPromise; -} - -/** - * GetExtensionByKey 根据Key获取扩展 - */ -export function GetExtensionByKey(key: string): Promise & { cancel(): void } { - let $resultPromise = $Call.ByID(2551065776, key) as any; let $typingPromise = $resultPromise.then(($result: any) => { return $$createType1($result); }) as any; @@ -56,11 +32,47 @@ export function GetExtensionByKey(key: string): Promise return $typingPromise; } +/** + * GetExtensionByID 根据ID获取扩展 + */ +export function GetExtensionByID(id: number): Promise & { cancel(): void } { + let $resultPromise = $Call.ByID(1521424252, id) as any; + let $typingPromise = $resultPromise.then(($result: any) => { + return $$createType3($result); + }) as any; + $typingPromise.cancel = $resultPromise.cancel.bind($resultPromise); + return $typingPromise; +} + +/** + * GetExtensionConfig 获取扩展配置 + */ +export function GetExtensionConfig(id: number): Promise<{ [_: string]: any }> & { cancel(): void } { + let $resultPromise = $Call.ByID(1629559882, id) as any; + let $typingPromise = $resultPromise.then(($result: any) => { + return $$createType4($result); + }) as any; + $typingPromise.cancel = $resultPromise.cancel.bind($resultPromise); + return $typingPromise; +} + +/** + * GetExtensions 获取所有扩展 + */ +export function GetExtensions(): Promise<(ent$0.Extension | null)[]> & { cancel(): void } { + let $resultPromise = $Call.ByID(3179289021) as any; + let $typingPromise = $resultPromise.then(($result: any) => { + return $$createType5($result); + }) as any; + $typingPromise.cancel = $resultPromise.cancel.bind($resultPromise); + return $typingPromise; +} + /** * ResetExtensionConfig 重置单个扩展到默认状态 */ -export function ResetExtensionConfig(key: string): Promise & { cancel(): void } { - let $resultPromise = $Call.ByID(3990780299, key) as any; +export function ResetExtensionConfig(id: number): Promise & { cancel(): void } { + let $resultPromise = $Call.ByID(3990780299, id) as any; return $resultPromise; } @@ -83,22 +95,23 @@ export function SyncExtensions(): Promise & { cancel(): void } { /** * UpdateExtensionConfig 更新扩展配置 */ -export function UpdateExtensionConfig(key: string, config: { [_: string]: any }): Promise & { cancel(): void } { - let $resultPromise = $Call.ByID(3184142503, key, config) as any; +export function UpdateExtensionConfig(id: number, config: { [_: string]: any }): Promise & { cancel(): void } { + let $resultPromise = $Call.ByID(3184142503, id, config) as any; return $resultPromise; } /** * UpdateExtensionEnabled 更新扩展启用状态 */ -export function UpdateExtensionEnabled(key: string, enabled: boolean): Promise & { cancel(): void } { - let $resultPromise = $Call.ByID(1067300094, key, enabled) as any; +export function UpdateExtensionEnabled(id: number, enabled: boolean): Promise & { cancel(): void } { + let $resultPromise = $Call.ByID(1067300094, id, enabled) as any; return $resultPromise; } // Private type creation functions -const $$createType0 = ent$0.Extension.createFrom; -const $$createType1 = $Create.Nullable($$createType0); -const $$createType2 = $Create.Array($$createType1); -const $$createType3 = models$0.Extension.createFrom; -const $$createType4 = $Create.Array($$createType3); +const $$createType0 = models$0.Extension.createFrom; +const $$createType1 = $Create.Array($$createType0); +const $$createType2 = ent$0.Extension.createFrom; +const $$createType3 = $Create.Nullable($$createType2); +const $$createType4 = $Create.Map($Create.Any, $Create.Any); +const $$createType5 = $Create.Array($$createType3); diff --git a/frontend/bindings/voidraft/internal/services/keybindingservice.ts b/frontend/bindings/voidraft/internal/services/keybindingservice.ts index d4d086d..c73ee8c 100644 --- a/frontend/bindings/voidraft/internal/services/keybindingservice.ts +++ b/frontend/bindings/voidraft/internal/services/keybindingservice.ts @@ -21,22 +21,34 @@ import * as models$0 from "../models/models.js"; import * as ent$0 from "../models/ent/models.js"; /** - * GetAllKeyBindings 获取所有快捷键 + * GetDefaultKeyBindings 获取默认快捷键配置 */ -export function GetAllKeyBindings(): Promise<(ent$0.KeyBinding | null)[]> & { cancel(): void } { - let $resultPromise = $Call.ByID(1633502882) as any; +export function GetDefaultKeyBindings(): Promise & { cancel(): void } { + let $resultPromise = $Call.ByID(3843471588) as any; let $typingPromise = $resultPromise.then(($result: any) => { - return $$createType2($result); + return $$createType1($result); }) as any; $typingPromise.cancel = $resultPromise.cancel.bind($resultPromise); return $typingPromise; } /** - * GetDefaultKeyBindings 获取默认快捷键配置 + * GetKeyBindingByID 根据ID获取快捷键 */ -export function GetDefaultKeyBindings(): Promise & { cancel(): void } { - let $resultPromise = $Call.ByID(3843471588) as any; +export function GetKeyBindingByID(id: number): Promise & { cancel(): void } { + let $resultPromise = $Call.ByID(1578192526, id) as any; + let $typingPromise = $resultPromise.then(($result: any) => { + return $$createType3($result); + }) as any; + $typingPromise.cancel = $resultPromise.cancel.bind($resultPromise); + return $typingPromise; +} + +/** + * GetKeyBindings 根据类型获取快捷键 + */ +export function GetKeyBindings(kbType: models$0.KeyBindingType): Promise<(ent$0.KeyBinding | null)[]> & { cancel(): void } { + let $resultPromise = $Call.ByID(4253885163, kbType) as any; let $typingPromise = $resultPromise.then(($result: any) => { return $$createType4($result); }) as any; @@ -45,15 +57,11 @@ export function GetDefaultKeyBindings(): Promise & { canc } /** - * GetKeyBindingByKey 根据Key获取快捷键 + * ResetKeyBindings 重置所有快捷键到默认值 */ -export function GetKeyBindingByKey(key: string): Promise & { cancel(): void } { - let $resultPromise = $Call.ByID(852938650, key) as any; - let $typingPromise = $resultPromise.then(($result: any) => { - return $$createType1($result); - }) as any; - $typingPromise.cancel = $resultPromise.cancel.bind($resultPromise); - return $typingPromise; +export function ResetKeyBindings(): Promise & { cancel(): void } { + let $resultPromise = $Call.ByID(4251626010) as any; + return $resultPromise; } /** @@ -73,24 +81,24 @@ export function SyncKeyBindings(): Promise & { cancel(): void } { } /** - * UpdateKeyBindingCommand 更新快捷键命令 + * UpdateKeyBindingEnabled 更新快捷键启用状态 */ -export function UpdateKeyBindingCommand(key: string, command: string): Promise & { cancel(): void } { - let $resultPromise = $Call.ByID(1293670628, key, command) as any; +export function UpdateKeyBindingEnabled(id: number, enabled: boolean): Promise & { cancel(): void } { + let $resultPromise = $Call.ByID(843626124, id, enabled) as any; return $resultPromise; } /** - * UpdateKeyBindingEnabled 更新快捷键启用状态 + * UpdateKeyBindingKeys 更新快捷键绑定(根据操作系统自动判断更新哪个字段) */ -export function UpdateKeyBindingEnabled(key: string, enabled: boolean): Promise & { cancel(): void } { - let $resultPromise = $Call.ByID(843626124, key, enabled) as any; +export function UpdateKeyBindingKeys(id: number, key: string): Promise & { cancel(): void } { + let $resultPromise = $Call.ByID(3432755175, id, key) as any; return $resultPromise; } // Private type creation functions -const $$createType0 = ent$0.KeyBinding.createFrom; -const $$createType1 = $Create.Nullable($$createType0); -const $$createType2 = $Create.Array($$createType1); -const $$createType3 = models$0.KeyBinding.createFrom; +const $$createType0 = models$0.KeyBinding.createFrom; +const $$createType1 = $Create.Array($$createType0); +const $$createType2 = ent$0.KeyBinding.createFrom; +const $$createType3 = $Create.Nullable($$createType2); const $$createType4 = $Create.Array($$createType3); diff --git a/frontend/bindings/voidraft/internal/services/themeservice.ts b/frontend/bindings/voidraft/internal/services/themeservice.ts index 7f03c00..a8241d2 100644 --- a/frontend/bindings/voidraft/internal/services/themeservice.ts +++ b/frontend/bindings/voidraft/internal/services/themeservice.ts @@ -18,10 +18,10 @@ import * as application$0 from "../../../github.com/wailsapp/wails/v3/pkg/applic import * as ent$0 from "../models/ent/models.js"; /** - * GetThemeByKey 根据Key获取主题 + * GetThemeByName 根据Key获取主题 */ -export function GetThemeByKey(key: string): Promise & { cancel(): void } { - let $resultPromise = $Call.ByID(808794256, key) as any; +export function GetThemeByName(name: string): Promise & { cancel(): void } { + let $resultPromise = $Call.ByID(1938954770, name) as any; let $typingPromise = $resultPromise.then(($result: any) => { return $$createType1($result); }) as any; diff --git a/frontend/src/common/constant/config.ts b/frontend/src/common/constant/config.ts index f34e7a7..6d9aba3 100644 --- a/frontend/src/common/constant/config.ts +++ b/frontend/src/common/constant/config.ts @@ -1,6 +1,7 @@ import { AppConfig, AuthMethod, + KeyBindingType, LanguageType, SystemThemeType, TabType, @@ -31,6 +32,7 @@ export const CONFIG_KEY_MAP = { enableTabIndent: 'editing.enableTabIndent', tabSize: 'editing.tabSize', tabType: 'editing.tabType', + keymapMode: 'editing.keymapMode', autoSaveDelay: 'editing.autoSaveDelay', // appearance language: 'appearance.language', @@ -95,11 +97,12 @@ export const DEFAULT_CONFIG: AppConfig = { enableTabIndent: true, tabSize: CONFIG_LIMITS.tabSize.default, tabType: CONFIG_LIMITS.tabType.default, + keymapMode: KeyBindingType.Standard, autoSaveDelay: 5000 }, appearance: { language: LanguageType.LangZhCN, - systemTheme: SystemThemeType.SystemThemeAuto, + systemTheme: SystemThemeType.SystemThemeDark, currentTheme: 'default-dark' }, updates: { diff --git a/frontend/src/i18n/locales/en-US.ts b/frontend/src/i18n/locales/en-US.ts index 99b020a..f128e7f 100644 --- a/frontend/src/i18n/locales/en-US.ts +++ b/frontend/src/i18n/locales/en-US.ts @@ -44,11 +44,18 @@ export default { auto: 'Follow System' }, keybindings: { + keymapMode: 'Keymap Mode', + modes: { + standard: 'Standard Mode', + emacs: 'Emacs Mode' + }, headers: { shortcut: 'Shortcut', extension: 'Extension', description: 'Description' }, + resetToDefault: 'Reset to Default', + confirmReset: 'Confirm Reset?', commands: { showSearch: 'Show search panel', hideSearch: 'Hide search panel', @@ -93,6 +100,25 @@ export default { insertBlankLine: 'Insert blank line', selectLine: 'Select line', selectParentSyntax: 'Select parent syntax', + simplifySelection: 'Simplify selection', + addCursorAbove: 'Add cursor above', + addCursorBelow: 'Add cursor below', + cursorGroupLeft: 'Cursor word left', + cursorGroupRight: 'Cursor word right', + selectGroupLeft: 'Select word left', + selectGroupRight: 'Select word right', + deleteToLineEnd: 'Delete to line end', + deleteToLineStart: 'Delete to line start', + cursorLineStart: 'Cursor to line start', + cursorLineEnd: 'Cursor to line end', + selectLineStart: 'Select to line start', + selectLineEnd: 'Select to line end', + cursorDocStart: 'Cursor to document start', + cursorDocEnd: 'Cursor to document end', + selectDocStart: 'Select to document start', + selectDocEnd: 'Select to document end', + selectMatchingBracket: 'Select to matching bracket', + splitLine: 'Split line', indentLess: 'Indent less', indentMore: 'Indent more', indentSelection: 'Indent selection', @@ -104,6 +130,18 @@ export default { deleteCharForward: 'Delete character forward', deleteGroupBackward: 'Delete group backward', deleteGroupForward: 'Delete group forward', + + // Emacs mode additional basic navigation commands + cursorCharLeft: 'Cursor left one character', + cursorCharRight: 'Cursor right one character', + cursorLineUp: 'Cursor up one line', + cursorLineDown: 'Cursor down one line', + cursorPageUp: 'Page up', + cursorPageDown: 'Page down', + selectCharLeft: 'Select left one character', + selectCharRight: 'Select right one character', + selectLineUp: 'Select up one line', + selectLineDown: 'Select down one line', } }, tabs: { diff --git a/frontend/src/i18n/locales/zh-CN.ts b/frontend/src/i18n/locales/zh-CN.ts index 87bbd0b..48576fd 100644 --- a/frontend/src/i18n/locales/zh-CN.ts +++ b/frontend/src/i18n/locales/zh-CN.ts @@ -44,11 +44,18 @@ export default { auto: '跟随系统' }, keybindings: { + keymapMode: '快捷键模式', + modes: { + standard: '标准模式', + emacs: 'Emacs 模式' + }, headers: { shortcut: '快捷键', extension: '扩展', description: '描述' }, + resetToDefault: '重置为默认', + confirmReset: '确认重置?', commands: { showSearch: '显示搜索面板', hideSearch: '隐藏搜索面板', @@ -93,6 +100,25 @@ export default { insertBlankLine: '插入空行', selectLine: '选择行', selectParentSyntax: '选择父级语法', + simplifySelection: '简化选择', + addCursorAbove: '在上方添加光标', + addCursorBelow: '在下方添加光标', + cursorGroupLeft: '光标按单词左移', + cursorGroupRight: '光标按单词右移', + selectGroupLeft: '按单词选择左侧', + selectGroupRight: '按单词选择右侧', + deleteToLineEnd: '删除到行尾', + deleteToLineStart: '删除到行首', + cursorLineStart: '移动到行首', + cursorLineEnd: '移动到行尾', + selectLineStart: '选择到行首', + selectLineEnd: '选择到行尾', + cursorDocStart: '跳转到文档开头', + cursorDocEnd: '跳转到文档结尾', + selectDocStart: '选择到文档开头', + selectDocEnd: '选择到文档结尾', + selectMatchingBracket: '选择到匹配括号', + splitLine: '分割行', indentLess: '减少缩进', indentMore: '增加缩进', indentSelection: '缩进选择', @@ -104,6 +130,18 @@ export default { deleteCharForward: '向前删除字符', deleteGroupBackward: '向后删除组', deleteGroupForward: '向前删除组', + + // Emacs 模式额外的基础导航命令 + cursorCharLeft: '光标左移一个字符', + cursorCharRight: '光标右移一个字符', + cursorLineUp: '光标上移一行', + cursorLineDown: '光标下移一行', + cursorPageUp: '向上翻页', + cursorPageDown: '向下翻页', + selectCharLeft: '选择左移一个字符', + selectCharRight: '选择右移一个字符', + selectLineUp: '选择上移一行', + selectLineDown: '选择下移一行', } }, tabs: { diff --git a/frontend/src/stores/configStore.ts b/frontend/src/stores/configStore.ts index 253ba5a..eb51e92 100644 --- a/frontend/src/stores/configStore.ts +++ b/frontend/src/stores/configStore.ts @@ -275,6 +275,9 @@ export const useConfigStore = defineStore('config', () => { // 标签页配置相关方法 setEnableTabs: (value: boolean) => updateConfig('enableTabs', value), + // 快捷键模式配置相关方法 + setKeymapMode: (value: any) => updateConfig('keymapMode', value), + // 更新配置相关方法 setAutoUpdate: (value: boolean) => updateConfig('autoUpdate', value), diff --git a/frontend/src/stores/documentStore.ts b/frontend/src/stores/documentStore.ts index 9b9163f..c5ee635 100644 --- a/frontend/src/stores/documentStore.ts +++ b/frontend/src/stores/documentStore.ts @@ -204,8 +204,8 @@ export const useDocumentStore = defineStore('document', () => { await openDocument(currentDocumentId.value); } else { // 否则打开第一个文档 - if (documents.value[0].id) { - await openDocument(documents.value[0].id); + if (documentList.value[0].id) { + await openDocument(documentList.value[0].id); } } } catch (error) { diff --git a/frontend/src/stores/editorStore.ts b/frontend/src/stores/editorStore.ts index 54b56c5..0688d95 100644 --- a/frontend/src/stores/editorStore.ts +++ b/frontend/src/stores/editorStore.ts @@ -29,6 +29,7 @@ import {generateContentHash} from "@/common/utils/hashUtils"; import {createTimerManager, type TimerManager} from '@/common/utils/timerUtils'; import {EDITOR_CONFIG} from '@/common/constant/editor'; import {createDebounce} from '@/common/utils/debounce'; +import {useKeybindingStore} from "@/stores/keybindingStore"; export interface DocumentStats { lines: number; @@ -602,28 +603,30 @@ export const useEditorStore = defineStore('editor', () => { }; // 更新扩展 - const updateExtension = async (key: string, enabled: boolean, config?: any) => { + const updateExtension = async (id: number, enabled: boolean, config?: any) => { // 更新启用状态 - await ExtensionService.UpdateExtensionEnabled(key, enabled); + await ExtensionService.UpdateExtensionEnabled(id, enabled); // 如果需要更新配置 if (config !== undefined) { - await ExtensionService.UpdateExtensionConfig(key, config); + await ExtensionService.UpdateExtensionConfig(id, config); } + // 重新加载扩展配置 + await extensionStore.loadExtensions(); + + // 获取更新后的扩展名称 + const extension = extensionStore.extensions.find(ext => ext.id === id); + if (!extension) return; + // 更新前端编辑器扩展 - 应用于所有实例 const manager = getExtensionManager(); if (manager) { // 直接更新前端扩展至所有视图 - manager.updateExtension(key, enabled, config); - } - - // 重新加载扩展配置 - await extensionStore.loadExtensions(); - if (manager) { - manager.initExtensions(extensionStore.extensions); + manager.updateExtension(extension.name, enabled, config); } + await useKeybindingStore().loadKeyBindings(); await applyKeymapSettings(); }; diff --git a/frontend/src/stores/extensionStore.ts b/frontend/src/stores/extensionStore.ts index 6ad4bb3..5e9f93a 100644 --- a/frontend/src/stores/extensionStore.ts +++ b/frontend/src/stores/extensionStore.ts @@ -7,22 +7,12 @@ export const useExtensionStore = defineStore('extension', () => { // 扩展配置数据 const extensions = ref([]); - // 获取启用的扩展 - const enabledExtensions = computed(() => - extensions.value.filter(ext => ext.enabled) - ); - - // 获取启用的扩展ID列表 (key) - const enabledExtensionIds = computed(() => - enabledExtensions.value.map(ext => ext.key).filter((k): k is string => k !== undefined) - ); - /** * 从后端加载扩展配置 */ const loadExtensions = async (): Promise => { try { - const result = await ExtensionService.GetAllExtensions(); + const result = await ExtensionService.GetExtensions(); extensions.value = result.filter((ext): ext is Extension => ext !== null); } catch (err) { console.error('[ExtensionStore] Failed to load extensions:', err); @@ -32,17 +22,19 @@ export const useExtensionStore = defineStore('extension', () => { /** * 获取扩展配置 */ - const getExtensionConfig = (key: string): any => { - const extension = extensions.value.find(ext => ext.key === key); - return extension?.config ?? {}; + const getExtensionConfig = async (id: number): Promise => { + try { + const config = await ExtensionService.GetExtensionConfig(id); + return config ?? {}; + } catch (err) { + console.error('[ExtensionStore] Failed to get extension config:', err); + return {}; + } }; return { // 状态 extensions, - enabledExtensions, - enabledExtensionIds, - // 方法 loadExtensions, getExtensionConfig, diff --git a/frontend/src/stores/keybindingStore.ts b/frontend/src/stores/keybindingStore.ts index 3960cae..b32a711 100644 --- a/frontend/src/stores/keybindingStore.ts +++ b/frontend/src/stores/keybindingStore.ts @@ -1,86 +1,38 @@ import {defineStore} from 'pinia'; import {computed, ref} from 'vue'; import {KeyBinding} from '@/../bindings/voidraft/internal/models/ent/models'; -import {GetAllKeyBindings} from '@/../bindings/voidraft/internal/services/keybindingservice'; +import {KeyBindingService} from '@/../bindings/voidraft/internal/services'; +import {KeyBindingType} from '@/../bindings/voidraft/internal/models/models'; +import {useConfigStore} from './configStore'; export const useKeybindingStore = defineStore('keybinding', () => { + const configStore = useConfigStore(); + // 快捷键配置数据 const keyBindings = ref([]); - // 获取启用的快捷键 - const enabledKeyBindings = computed(() => - keyBindings.value.filter(kb => kb.enabled) - ); - - // 按扩展分组的快捷键 - const keyBindingsByExtension = computed(() => { - const groups = new Map(); - - for (const binding of keyBindings.value) { - const ext = binding.extension || ''; - if (!groups.has(ext)) { - groups.set(ext, []); - } - groups.get(ext)!.push(binding); - } - - return groups; - }); - - // 获取指定扩展的快捷键 - const getKeyBindingsByExtension = computed(() => - (extension: string) => - keyBindings.value.filter(kb => kb.extension === extension) - ); - - // 按命令获取快捷键 - const getKeyBindingByCommand = computed(() => - (command: string) => - keyBindings.value.find(kb => kb.command === command) - ); - /** - * 从后端加载快捷键配置 + * 从后端加载快捷键配置(根据当前配置的模式) */ const loadKeyBindings = async (): Promise => { - const result = await GetAllKeyBindings(); + const keymapMode = configStore.config.editing.keymapMode || KeyBindingType.Standard; + const result = await KeyBindingService.GetKeyBindings(keymapMode); keyBindings.value = result.filter((kb): kb is KeyBinding => kb !== null); }; /** - * 检查是否存在指定命令的快捷键 + * 更新快捷键绑定 */ - const hasCommand = (command: string): boolean => { - return keyBindings.value.some(kb => kb.command === command && kb.enabled); + const updateKeyBinding = async (id: number, key: string): Promise => { + await KeyBindingService.UpdateKeyBindingKeys(id, key); + await loadKeyBindings(); }; - - /** - * 获取扩展相关的所有扩展ID - */ - const getAllExtensionIds = computed(() => { - const extensionIds = new Set(); - for (const binding of keyBindings.value) { - if (binding.extension) { - extensionIds.add(binding.extension); - } - } - return Array.from(extensionIds); - }); - return { // 状态 keyBindings, - enabledKeyBindings, - keyBindingsByExtension, - getAllExtensionIds, - - // 计算属性 - getKeyBindingByCommand, - getKeyBindingsByExtension, - // 方法 loadKeyBindings, - hasCommand, + updateKeyBinding, }; }); \ No newline at end of file diff --git a/frontend/src/stores/themeStore.ts b/frontend/src/stores/themeStore.ts index 500c8a0..9d584e5 100644 --- a/frontend/src/stores/themeStore.ts +++ b/frontend/src/stores/themeStore.ts @@ -8,45 +8,19 @@ import {useEditorStore} from './editorStore'; import type {ThemeColors} from '@/views/editor/theme/types'; import {cloneThemeColors, FALLBACK_THEME_NAME, themePresetList, themePresetMap} from '@/views/editor/theme/presets'; -type ThemeColorConfig = { [_: string]: any }; -type ThemeOption = { name: string; type: ThemeType }; +// 类型定义 +type ThemeOption = {name: string; type: ThemeType}; -const resolveThemeName = (name?: string) => +// 解析主题名称,确保返回有效的主题 +const resolveThemeName = (name?: string): string => name && themePresetMap[name] ? name : FALLBACK_THEME_NAME; +// 根据主题类型创建主题选项列表 const createThemeOptions = (type: ThemeType): ThemeOption[] => themePresetList .filter(preset => preset.type === type) .map(preset => ({name: preset.name, type: preset.type})); -const darkThemeOptions = createThemeOptions(ThemeType.TypeDark); -const lightThemeOptions = createThemeOptions(ThemeType.TypeLight); - -const cloneColors = (colors: ThemeColorConfig): ThemeColors => - JSON.parse(JSON.stringify(colors)) as ThemeColors; - -const getPresetColors = (name: string): ThemeColors => { - const preset = themePresetMap[name] ?? themePresetMap[FALLBACK_THEME_NAME]; - const colors = cloneThemeColors(preset.colors); - colors.themeName = name; - return colors; -}; - -const fetchThemeColors = async (themeName: string): Promise => { - const safeName = resolveThemeName(themeName); - try { - const theme = await ThemeService.GetThemeByKey(safeName); - if (theme?.colors) { - const colors = cloneColors(theme.colors); - colors.themeName = safeName; - return colors; - } - } catch (error) { - console.error('Failed to load theme override:', error); - } - return getPresetColors(safeName); -}; - export const useThemeStore = defineStore('theme', () => { const configStore = useConfigStore(); const currentColors = ref(null); @@ -62,10 +36,12 @@ export const useThemeStore = defineStore('theme', () => { window.matchMedia('(prefers-color-scheme: dark)').matches) ); + // 根据当前模式动态计算可用主题列表 const availableThemes = computed(() => - isDarkMode.value ? darkThemeOptions : lightThemeOptions + createThemeOptions(isDarkMode.value ? ThemeType.TypeDark : ThemeType.TypeLight) ); + // 应用主题到 DOM const applyThemeToDOM = (theme: SystemThemeType) => { const themeMap = { [SystemThemeType.SystemThemeAuto]: 'auto', @@ -75,6 +51,31 @@ export const useThemeStore = defineStore('theme', () => { document.documentElement.setAttribute('data-theme', themeMap[theme]); }; + // 获取预设主题颜色 + const getPresetColors = (name: string): ThemeColors => { + const preset = themePresetMap[name] ?? themePresetMap[FALLBACK_THEME_NAME]; + const colors = cloneThemeColors(preset.colors); + colors.themeName = name; + return colors; + }; + + // 从服务器获取主题颜色 + const fetchThemeColors = async (themeName: string): Promise => { + const safeName = resolveThemeName(themeName); + try { + const theme = await ThemeService.GetThemeByName(safeName); + if (theme?.colors) { + const colors = cloneThemeColors(theme.colors as ThemeColors); + colors.themeName = safeName; + return colors; + } + } catch (error) { + console.error('Failed to load theme override:', error); + } + return getPresetColors(safeName); + }; + + // 加载主题颜色 const loadThemeColors = async (themeName?: string) => { const targetName = resolveThemeName( themeName || configStore.config?.appearance?.currentTheme @@ -82,17 +83,21 @@ export const useThemeStore = defineStore('theme', () => { currentColors.value = await fetchThemeColors(targetName); }; + // 初始化主题 const initTheme = async () => { applyThemeToDOM(currentTheme.value); await loadThemeColors(); + refreshEditorTheme(); }; + // 设置系统主题 const setTheme = async (theme: SystemThemeType) => { await configStore.setSystemTheme(theme); applyThemeToDOM(theme); refreshEditorTheme(); }; + // 切换到指定主题 const switchToTheme = async (themeName: string) => { if (!themePresetMap[themeName]) { console.error('Theme not found:', themeName); @@ -105,11 +110,13 @@ export const useThemeStore = defineStore('theme', () => { return true; }; + // 更新当前主题颜色 const updateCurrentColors = (colors: Partial) => { if (!currentColors.value) return; Object.assign(currentColors.value, colors); }; + // 保存当前主题 const saveCurrentTheme = async () => { if (!currentColors.value) { throw new Error('No theme selected'); @@ -118,13 +125,14 @@ export const useThemeStore = defineStore('theme', () => { const themeName = resolveThemeName(currentColors.value.themeName); currentColors.value.themeName = themeName; - await ThemeService.UpdateTheme(themeName, currentColors.value as ThemeColorConfig); + await ThemeService.UpdateTheme(themeName, currentColors.value); await loadThemeColors(themeName); refreshEditorTheme(); return true; }; + // 重置当前主题到默认值 const resetCurrentTheme = async () => { if (!currentColors.value) { throw new Error('No theme selected'); @@ -138,6 +146,7 @@ export const useThemeStore = defineStore('theme', () => { return true; }; + // 刷新编辑器主题 const refreshEditorTheme = () => { applyThemeToDOM(currentTheme.value); const editorStore = useEditorStore(); diff --git a/frontend/src/views/editor/extensions/contextMenu/index.ts b/frontend/src/views/editor/extensions/contextMenu/index.ts index de6c459..8ba50e9 100644 --- a/frontend/src/views/editor/extensions/contextMenu/index.ts +++ b/frontend/src/views/editor/extensions/contextMenu/index.ts @@ -1,140 +1,136 @@ -import { EditorView } from '@codemirror/view'; -import { Extension } from '@codemirror/state'; -import { copyCommand, cutCommand, pasteCommand } from '../codeblock/copyPaste'; -import { KeyBindingKey } from '@/../bindings/voidraft/internal/models/models'; -import { useKeybindingStore } from '@/stores/keybindingStore'; -import { undo, redo } from '@codemirror/commands'; +import {EditorView} from '@codemirror/view'; +import {Extension} from '@codemirror/state'; +import {copyCommand, cutCommand, pasteCommand} from '../codeblock/copyPaste'; +import {KeyBindingName} from '@/../bindings/voidraft/internal/models/models'; +import {useKeybindingStore} from '@/stores/keybindingStore'; +import {redo, undo} from '@codemirror/commands'; import i18n from '@/i18n'; -import { useSystemStore } from '@/stores/systemStore'; -import { showContextMenu } from './manager'; -import { - buildRegisteredMenu, - createMenuContext, - registerMenuNodes -} from './menuSchema'; -import type { MenuSchemaNode } from './menuSchema'; +import {useSystemStore} from '@/stores/systemStore'; +import {showContextMenu} from './manager'; +import type {MenuSchemaNode} from './menuSchema'; +import {buildRegisteredMenu, createMenuContext, registerMenuNodes} from './menuSchema'; function t(key: string): string { - return i18n.global.t(key); + return i18n.global.t(key); } function formatKeyBinding(keyBinding: string): string { - const systemStore = useSystemStore(); - const isMac = systemStore.isMacOS; + const systemStore = useSystemStore(); + const isMac = systemStore.isMacOS; - return keyBinding - .replace("Mod", isMac ? "Cmd" : "Ctrl") - .replace("Alt", isMac ? "Option" : "Alt") - .replace(/-/g, " + "); + return keyBinding + .replace("Mod", isMac ? "Cmd" : "Ctrl") + .replace("Alt", isMac ? "Option" : "Alt") + .replace(/-/g, " + "); } -const shortcutCache = new Map(); +const shortcutCache = new Map(); -function getShortcutText(keyBindingKey?: KeyBindingKey): string { - if (keyBindingKey === undefined) { - return ""; - } - - const cached = shortcutCache.get(keyBindingKey); - if (cached !== undefined) { - return cached; - } - - try { - const keybindingStore = useKeybindingStore(); - // binding.key 是命令标识符,binding.command 是快捷键组合 - const binding = keybindingStore.keyBindings.find( - (kb) => kb.key === keyBindingKey && kb.enabled - ); - - if (binding?.command) { - const formatted = formatKeyBinding(binding.command); - shortcutCache.set(keyBindingKey, formatted); - return formatted; +function getShortcutText(keyBindingKey?: KeyBindingName): string { + if (keyBindingKey === undefined) { + return ""; } - } catch (error) { - console.warn("An error occurred while getting the shortcut:", error); - } - shortcutCache.set(keyBindingKey, ""); - return ""; + const cached = shortcutCache.get(keyBindingKey); + if (cached !== undefined) { + return cached; + } + + try { + const keybindingStore = useKeybindingStore(); + // binding.key 是命令标识符,binding.command 是快捷键组合 + const binding = keybindingStore.keyBindings.find( + (kb) => kb.key === keyBindingKey && kb.enabled + ); + + if (binding?.key) { + const formatted = formatKeyBinding(binding.key); + shortcutCache.set(keyBindingKey, formatted); + return formatted; + } + } catch (error) { + console.warn("An error occurred while getting the shortcut:", error); + } + + shortcutCache.set(keyBindingKey, ""); + return ""; } function builtinMenuNodes(): MenuSchemaNode[] { - return [ - { - id: "copy", - labelKey: "keybindings.commands.blockCopy", - command: copyCommand, - keyBindingKey: KeyBindingKey.BlockCopyKeyBindingKey, - enabled: (context) => context.hasSelection - }, - { - id: "cut", - labelKey: "keybindings.commands.blockCut", - command: cutCommand, - keyBindingKey: KeyBindingKey.BlockCutKeyBindingKey, - visible: (context) => context.isEditable, - enabled: (context) => context.hasSelection && context.isEditable - }, - { - id: "paste", - labelKey: "keybindings.commands.blockPaste", - command: pasteCommand, - keyBindingKey: KeyBindingKey.BlockPasteKeyBindingKey, - visible: (context) => context.isEditable - }, - { - id: "undo", - labelKey: "keybindings.commands.historyUndo", - command: undo, - keyBindingKey: KeyBindingKey.HistoryUndoKeyBindingKey, - visible: (context) => context.isEditable - }, - { - id: "redo", - labelKey: "keybindings.commands.historyRedo", - command: redo, - keyBindingKey: KeyBindingKey.HistoryRedoKeyBindingKey, - visible: (context) => context.isEditable - } - ]; + return [ + { + id: "copy", + labelKey: "keybindings.commands.blockCopy", + command: copyCommand, + keyBindingName: KeyBindingName.BlockCopy, + enabled: (context) => context.hasSelection + }, + { + id: "cut", + labelKey: "keybindings.commands.blockCut", + command: cutCommand, + keyBindingName: KeyBindingName.BlockCut, + visible: (context) => context.isEditable, + enabled: (context) => context.hasSelection && context.isEditable + }, + { + id: "paste", + labelKey: "keybindings.commands.blockPaste", + command: pasteCommand, + keyBindingName: KeyBindingName.BlockPaste, + visible: (context) => context.isEditable + }, + { + id: "undo", + labelKey: "keybindings.commands.historyUndo", + command: undo, + keyBindingName: KeyBindingName.HistoryUndo, + visible: (context) => context.isEditable + }, + { + id: "redo", + labelKey: "keybindings.commands.historyRedo", + command: redo, + keyBindingName: KeyBindingName.HistoryRedo, + visible: (context) => context.isEditable + } + ]; } let builtinMenuRegistered = false; function ensureBuiltinMenuRegistered(): void { - if (builtinMenuRegistered) return; - registerMenuNodes(builtinMenuNodes()); - builtinMenuRegistered = true; + if (builtinMenuRegistered) return; + registerMenuNodes(builtinMenuNodes()); + builtinMenuRegistered = true; } export function createEditorContextMenu(): Extension { - ensureBuiltinMenuRegistered(); + ensureBuiltinMenuRegistered(); - return EditorView.domEventHandlers({ - contextmenu: (event, view) => { - event.preventDefault(); + return EditorView.domEventHandlers({ + contextmenu: (event, view) => { + event.preventDefault(); - const context = createMenuContext(view, event as MouseEvent); - const menuItems = buildRegisteredMenu(context, { - translate: t, - formatShortcut: getShortcutText - }); + const context = createMenuContext(view, event as MouseEvent); + const menuItems = buildRegisteredMenu(context, { + translate: t, + formatShortcut: getShortcutText + }); - if (menuItems.length === 0) { - return false; - } + if (menuItems.length === 0) { + return false; + } - showContextMenu(view, event.clientX, event.clientY, menuItems); - return true; - } - }); + showContextMenu(view, event.clientX, event.clientY, menuItems); + return true; + } + }); } export default createEditorContextMenu; diff --git a/frontend/src/views/editor/extensions/contextMenu/menuSchema.ts b/frontend/src/views/editor/extensions/contextMenu/menuSchema.ts index 4da2beb..88b1adf 100644 --- a/frontend/src/views/editor/extensions/contextMenu/menuSchema.ts +++ b/frontend/src/views/editor/extensions/contextMenu/menuSchema.ts @@ -1,6 +1,6 @@ import type { EditorView } from '@codemirror/view'; import { EditorState } from '@codemirror/state'; -import { KeyBindingKey } from '@/../bindings/voidraft/internal/models/models'; +import { KeyBindingName } from '@/../bindings/voidraft/internal/models/models'; export interface MenuContext { view: EditorView; @@ -16,7 +16,7 @@ export type MenuSchemaNode = type?: "action"; labelKey: string; command?: (view: EditorView) => boolean; - keyBindingKey?: KeyBindingKey; + keyBindingName?: KeyBindingName; visible?: (context: MenuContext) => boolean; enabled?: (context: MenuContext) => boolean; } @@ -37,7 +37,7 @@ export interface RenderMenuItem { interface MenuBuildOptions { translate: (key: string) => string; - formatShortcut: (keyBindingKey?: KeyBindingKey) => string; + formatShortcut: (keyBindingKey?: KeyBindingName) => string; } const menuRegistry: MenuSchemaNode[] = []; @@ -89,7 +89,7 @@ function convertNode( } const disabled = node.enabled ? !node.enabled(context) : false; - const shortcut = options.formatShortcut(node.keyBindingKey); + const shortcut = options.formatShortcut(node.keyBindingName); return { id: node.id, diff --git a/frontend/src/views/editor/extensions/markdown/plugins/code-block.ts b/frontend/src/views/editor/extensions/markdown/plugins/code-block.ts index 939f1e7..be6580d 100644 --- a/frontend/src/views/editor/extensions/markdown/plugins/code-block.ts +++ b/frontend/src/views/editor/extensions/markdown/plugins/code-block.ts @@ -65,9 +65,15 @@ export function handleCodeBlock( if (ctx.seen.has(nf)) return; ctx.seen.add(nf); ranges.push([nf, nt]); + + // When cursor/selection is in this code block, don't add any decorations + // This allows the selection background to be visible + if (inCursor) return; const startLine = ctx.view.state.doc.lineAt(nf); const endLine = ctx.view.state.doc.lineAt(nt); + + // Add background decorations for each line for (let num = startLine.number; num <= endLine.number; num++) { const line = ctx.view.state.doc.line(num); let deco = DECO_CODEBLOCK_LINE; @@ -76,14 +82,14 @@ export function handleCodeBlock( else if (num === endLine.number) deco = DECO_CODEBLOCK_END; ctx.items.push({ from: line.from, to: line.from, deco }); } - if (!inCursor) { - const codeInfo = node.getChild('CodeInfo'); - const codeMarks = node.getChildren('CodeMark'); - const language = codeInfo ? ctx.view.state.doc.sliceString(codeInfo.from, codeInfo.to).trim() : null; - ctx.items.push({ from: startLine.to, to: startLine.to, deco: Decoration.widget({ widget: new CodeBlockInfoWidget(nf, nt, language), side: 1 }), priority: 1 }); - if (codeInfo) ctx.items.push({ from: codeInfo.from, to: codeInfo.to, deco: invisibleDecoration }); - for (const mark of codeMarks) ctx.items.push({ from: mark.from, to: mark.to, deco: invisibleDecoration }); - } + + // Add language info widget and hide code marks + const codeInfo = node.getChild('CodeInfo'); + const codeMarks = node.getChildren('CodeMark'); + const language = codeInfo ? ctx.view.state.doc.sliceString(codeInfo.from, codeInfo.to).trim() : null; + ctx.items.push({ from: startLine.to, to: startLine.to, deco: Decoration.widget({ widget: new CodeBlockInfoWidget(nf, nt, language), side: 1 }), priority: 1 }); + if (codeInfo) ctx.items.push({ from: codeInfo.from, to: codeInfo.to, deco: invisibleDecoration }); + for (const mark of codeMarks) ctx.items.push({ from: mark.from, to: mark.to, deco: invisibleDecoration }); } /** diff --git a/frontend/src/views/editor/keymap/commands.ts b/frontend/src/views/editor/keymap/commands.ts index 478fedf..1dcf17e 100644 --- a/frontend/src/views/editor/keymap/commands.ts +++ b/frontend/src/views/editor/keymap/commands.ts @@ -18,8 +18,22 @@ import {moveLineDown, moveLineUp} from '../extensions/codeblock/moveLines'; import {transposeChars} from '../extensions/codeblock'; import {copyCommand, cutCommand, pasteCommand} from '../extensions/codeblock/copyPaste'; import { + addCursorAbove, + addCursorBelow, copyLineDown, copyLineUp, + cursorCharLeft, + cursorCharRight, + cursorLineDown, + cursorLineUp, + cursorPageDown, + cursorPageUp, + cursorDocEnd, + cursorDocStart, + cursorGroupLeft, + cursorGroupRight, + cursorLineEnd, + cursorLineStart, cursorMatchingBracket, cursorSyntaxLeft, cursorSyntaxRight, @@ -27,6 +41,8 @@ import { deleteCharForward, deleteGroupBackward, deleteGroupForward, + deleteToLineEnd, + deleteToLineStart, indentLess, indentMore, indentSelection, @@ -34,10 +50,23 @@ import { insertNewlineAndIndent, redo, redoSelection, + selectCharLeft, + selectCharRight, + selectLineDown, + selectLineUp, + selectDocEnd, + selectDocStart, + selectGroupLeft, + selectGroupRight, selectLine, + selectLineEnd, + selectLineStart, + selectMatchingBracket, selectParentSyntax, selectSyntaxLeft, selectSyntaxRight, + simplifySelection, + splitLine, toggleBlockComment, toggleComment, undo, @@ -45,9 +74,8 @@ import { } from '@codemirror/commands'; import {foldAll, foldCode, unfoldAll, unfoldCode} from '@codemirror/language'; import i18n from '@/i18n'; -import {KeyBindingKey} from '@/../bindings/voidraft/internal/models/models'; +import {KeyBindingName} from '@/../bindings/voidraft/internal/models/models'; -// 默认代码块扩展选项 const defaultBlockExtensionOptions = { defaultBlockToken: 'text', defaultBlockAutoDetect: true, @@ -58,202 +86,320 @@ const defaultBlockExtensionOptions = { * 将后端定义的key字段映射到具体的前端方法和翻译键 */ export const commands: Record = { - [KeyBindingKey.ShowSearchKeyBindingKey]: { + [KeyBindingName.ShowSearch]: { handler: openSearchPanel, descriptionKey: 'keybindings.commands.showSearch' }, - [KeyBindingKey.HideSearchKeyBindingKey]: { + [KeyBindingName.HideSearch]: { handler: closeSearchPanel, descriptionKey: 'keybindings.commands.hideSearch' }, - [KeyBindingKey.BlockSelectAllKeyBindingKey]: { + [KeyBindingName.BlockSelectAll]: { handler: selectAll, descriptionKey: 'keybindings.commands.blockSelectAll' }, - [KeyBindingKey.BlockAddAfterCurrentKeyBindingKey]: { + [KeyBindingName.BlockAddAfterCurrent]: { handler: addNewBlockAfterCurrent(defaultBlockExtensionOptions), descriptionKey: 'keybindings.commands.blockAddAfterCurrent' }, - [KeyBindingKey.BlockAddAfterLastKeyBindingKey]: { + [KeyBindingName.BlockAddAfterLast]: { handler: addNewBlockAfterLast(defaultBlockExtensionOptions), descriptionKey: 'keybindings.commands.blockAddAfterLast' }, - [KeyBindingKey.BlockAddBeforeCurrentKeyBindingKey]: { + [KeyBindingName.BlockAddBeforeCurrent]: { handler: addNewBlockBeforeCurrent(defaultBlockExtensionOptions), descriptionKey: 'keybindings.commands.blockAddBeforeCurrent' }, - [KeyBindingKey.BlockGotoPreviousKeyBindingKey]: { + [KeyBindingName.BlockGotoPrevious]: { handler: gotoPreviousBlock, descriptionKey: 'keybindings.commands.blockGotoPrevious' }, - [KeyBindingKey.BlockGotoNextKeyBindingKey]: { + [KeyBindingName.BlockGotoNext]: { handler: gotoNextBlock, descriptionKey: 'keybindings.commands.blockGotoNext' }, - [KeyBindingKey.BlockSelectPreviousKeyBindingKey]: { + [KeyBindingName.BlockSelectPrevious]: { handler: selectPreviousBlock, descriptionKey: 'keybindings.commands.blockSelectPrevious' }, - [KeyBindingKey.BlockSelectNextKeyBindingKey]: { + [KeyBindingName.BlockSelectNext]: { handler: selectNextBlock, descriptionKey: 'keybindings.commands.blockSelectNext' }, - [KeyBindingKey.BlockDeleteKeyBindingKey]: { + [KeyBindingName.BlockDelete]: { handler: deleteBlock(defaultBlockExtensionOptions), descriptionKey: 'keybindings.commands.blockDelete' }, - [KeyBindingKey.BlockMoveUpKeyBindingKey]: { + [KeyBindingName.BlockMoveUp]: { handler: moveCurrentBlockUp, descriptionKey: 'keybindings.commands.blockMoveUp' }, - [KeyBindingKey.BlockMoveDownKeyBindingKey]: { + [KeyBindingName.BlockMoveDown]: { handler: moveCurrentBlockDown, descriptionKey: 'keybindings.commands.blockMoveDown' }, - [KeyBindingKey.BlockDeleteLineKeyBindingKey]: { + [KeyBindingName.BlockDeleteLine]: { handler: deleteLineCommand, descriptionKey: 'keybindings.commands.blockDeleteLine' }, - [KeyBindingKey.BlockMoveLineUpKeyBindingKey]: { + [KeyBindingName.BlockMoveLineUp]: { handler: moveLineUp, descriptionKey: 'keybindings.commands.blockMoveLineUp' }, - [KeyBindingKey.BlockMoveLineDownKeyBindingKey]: { + [KeyBindingName.BlockMoveLineDown]: { handler: moveLineDown, descriptionKey: 'keybindings.commands.blockMoveLineDown' }, - [KeyBindingKey.BlockTransposeCharsKeyBindingKey]: { + [KeyBindingName.BlockTransposeChars]: { handler: transposeChars, descriptionKey: 'keybindings.commands.blockTransposeChars' }, - [KeyBindingKey.BlockFormatKeyBindingKey]: { + [KeyBindingName.BlockFormat]: { handler: formatCurrentBlock, descriptionKey: 'keybindings.commands.blockFormat' }, - [KeyBindingKey.BlockCopyKeyBindingKey]: { + [KeyBindingName.BlockCopy]: { handler: copyCommand, descriptionKey: 'keybindings.commands.blockCopy' }, - [KeyBindingKey.BlockCutKeyBindingKey]: { + [KeyBindingName.BlockCut]: { handler: cutCommand, descriptionKey: 'keybindings.commands.blockCut' }, - [KeyBindingKey.BlockPasteKeyBindingKey]: { + [KeyBindingName.BlockPaste]: { handler: pasteCommand, descriptionKey: 'keybindings.commands.blockPaste' }, - [KeyBindingKey.HistoryUndoKeyBindingKey]: { + [KeyBindingName.HistoryUndo]: { handler: undo, descriptionKey: 'keybindings.commands.historyUndo' }, - [KeyBindingKey.HistoryRedoKeyBindingKey]: { + [KeyBindingName.HistoryRedo]: { handler: redo, descriptionKey: 'keybindings.commands.historyRedo' }, - [KeyBindingKey.HistoryUndoSelectionKeyBindingKey]: { + [KeyBindingName.HistoryUndoSelection]: { handler: undoSelection, descriptionKey: 'keybindings.commands.historyUndoSelection' }, - [KeyBindingKey.HistoryRedoSelectionKeyBindingKey]: { + [KeyBindingName.HistoryRedoSelection]: { handler: redoSelection, descriptionKey: 'keybindings.commands.historyRedoSelection' }, - [KeyBindingKey.FoldCodeKeyBindingKey]: { + [KeyBindingName.FoldCode]: { handler: foldCode, descriptionKey: 'keybindings.commands.foldCode' }, - [KeyBindingKey.UnfoldCodeKeyBindingKey]: { + [KeyBindingName.UnfoldCode]: { handler: unfoldCode, descriptionKey: 'keybindings.commands.unfoldCode' }, - [KeyBindingKey.FoldAllKeyBindingKey]: { + [KeyBindingName.FoldAll]: { handler: foldAll, descriptionKey: 'keybindings.commands.foldAll' }, - [KeyBindingKey.UnfoldAllKeyBindingKey]: { + [KeyBindingName.UnfoldAll]: { handler: unfoldAll, descriptionKey: 'keybindings.commands.unfoldAll' }, - [KeyBindingKey.CursorSyntaxLeftKeyBindingKey]: { + [KeyBindingName.CursorSyntaxLeft]: { handler: cursorSyntaxLeft, descriptionKey: 'keybindings.commands.cursorSyntaxLeft' }, - [KeyBindingKey.CursorSyntaxRightKeyBindingKey]: { + [KeyBindingName.CursorSyntaxRight]: { handler: cursorSyntaxRight, descriptionKey: 'keybindings.commands.cursorSyntaxRight' }, - [KeyBindingKey.SelectSyntaxLeftKeyBindingKey]: { + [KeyBindingName.SelectSyntaxLeft]: { handler: selectSyntaxLeft, descriptionKey: 'keybindings.commands.selectSyntaxLeft' }, - [KeyBindingKey.SelectSyntaxRightKeyBindingKey]: { + [KeyBindingName.SelectSyntaxRight]: { handler: selectSyntaxRight, descriptionKey: 'keybindings.commands.selectSyntaxRight' }, - [KeyBindingKey.CopyLineUpKeyBindingKey]: { + [KeyBindingName.CopyLineUp]: { handler: copyLineUp, descriptionKey: 'keybindings.commands.copyLineUp' }, - [KeyBindingKey.CopyLineDownKeyBindingKey]: { + [KeyBindingName.CopyLineDown]: { handler: copyLineDown, descriptionKey: 'keybindings.commands.copyLineDown' }, - [KeyBindingKey.InsertBlankLineKeyBindingKey]: { + [KeyBindingName.InsertBlankLine]: { handler: insertBlankLine, descriptionKey: 'keybindings.commands.insertBlankLine' }, - [KeyBindingKey.SelectLineKeyBindingKey]: { + [KeyBindingName.SelectLine]: { handler: selectLine, descriptionKey: 'keybindings.commands.selectLine' }, - [KeyBindingKey.SelectParentSyntaxKeyBindingKey]: { + [KeyBindingName.SelectParentSyntax]: { handler: selectParentSyntax, descriptionKey: 'keybindings.commands.selectParentSyntax' }, - [KeyBindingKey.IndentLessKeyBindingKey]: { + [KeyBindingName.SimplifySelection]: { + handler: simplifySelection, + descriptionKey: 'keybindings.commands.simplifySelection' + }, + [KeyBindingName.AddCursorAbove]: { + handler: addCursorAbove, + descriptionKey: 'keybindings.commands.addCursorAbove' + }, + [KeyBindingName.AddCursorBelow]: { + handler: addCursorBelow, + descriptionKey: 'keybindings.commands.addCursorBelow' + }, + [KeyBindingName.CursorGroupLeft]: { + handler: cursorGroupLeft, + descriptionKey: 'keybindings.commands.cursorGroupLeft' + }, + [KeyBindingName.CursorGroupRight]: { + handler: cursorGroupRight, + descriptionKey: 'keybindings.commands.cursorGroupRight' + }, + [KeyBindingName.SelectGroupLeft]: { + handler: selectGroupLeft, + descriptionKey: 'keybindings.commands.selectGroupLeft' + }, + [KeyBindingName.SelectGroupRight]: { + handler: selectGroupRight, + descriptionKey: 'keybindings.commands.selectGroupRight' + }, + [KeyBindingName.DeleteToLineEnd]: { + handler: deleteToLineEnd, + descriptionKey: 'keybindings.commands.deleteToLineEnd' + }, + [KeyBindingName.DeleteToLineStart]: { + handler: deleteToLineStart, + descriptionKey: 'keybindings.commands.deleteToLineStart' + }, + [KeyBindingName.CursorLineStart]: { + handler: cursorLineStart, + descriptionKey: 'keybindings.commands.cursorLineStart' + }, + [KeyBindingName.CursorLineEnd]: { + handler: cursorLineEnd, + descriptionKey: 'keybindings.commands.cursorLineEnd' + }, + [KeyBindingName.SelectLineStart]: { + handler: selectLineStart, + descriptionKey: 'keybindings.commands.selectLineStart' + }, + [KeyBindingName.SelectLineEnd]: { + handler: selectLineEnd, + descriptionKey: 'keybindings.commands.selectLineEnd' + }, + [KeyBindingName.CursorDocStart]: { + handler: cursorDocStart, + descriptionKey: 'keybindings.commands.cursorDocStart' + }, + [KeyBindingName.CursorDocEnd]: { + handler: cursorDocEnd, + descriptionKey: 'keybindings.commands.cursorDocEnd' + }, + [KeyBindingName.SelectDocStart]: { + handler: selectDocStart, + descriptionKey: 'keybindings.commands.selectDocStart' + }, + [KeyBindingName.SelectDocEnd]: { + handler: selectDocEnd, + descriptionKey: 'keybindings.commands.selectDocEnd' + }, + [KeyBindingName.SelectMatchingBracket]: { + handler: selectMatchingBracket, + descriptionKey: 'keybindings.commands.selectMatchingBracket' + }, + [KeyBindingName.SplitLine]: { + handler: splitLine, + descriptionKey: 'keybindings.commands.splitLine' + }, + [KeyBindingName.IndentLess]: { handler: indentLess, descriptionKey: 'keybindings.commands.indentLess' }, - [KeyBindingKey.IndentMoreKeyBindingKey]: { + [KeyBindingName.IndentMore]: { handler: indentMore, descriptionKey: 'keybindings.commands.indentMore' }, - [KeyBindingKey.IndentSelectionKeyBindingKey]: { + [KeyBindingName.IndentSelection]: { handler: indentSelection, descriptionKey: 'keybindings.commands.indentSelection' }, - [KeyBindingKey.CursorMatchingBracketKeyBindingKey]: { + [KeyBindingName.CursorMatchingBracket]: { handler: cursorMatchingBracket, descriptionKey: 'keybindings.commands.cursorMatchingBracket' }, - [KeyBindingKey.ToggleCommentKeyBindingKey]: { + [KeyBindingName.ToggleComment]: { handler: toggleComment, descriptionKey: 'keybindings.commands.toggleComment' }, - [KeyBindingKey.ToggleBlockCommentKeyBindingKey]: { + [KeyBindingName.ToggleBlockComment]: { handler: toggleBlockComment, descriptionKey: 'keybindings.commands.toggleBlockComment' }, - [KeyBindingKey.InsertNewlineAndIndentKeyBindingKey]: { + [KeyBindingName.InsertNewlineAndIndent]: { handler: insertNewlineAndIndent, descriptionKey: 'keybindings.commands.insertNewlineAndIndent' }, - [KeyBindingKey.DeleteCharBackwardKeyBindingKey]: { + [KeyBindingName.DeleteCharBackward]: { handler: deleteCharBackward, descriptionKey: 'keybindings.commands.deleteCharBackward' }, - [KeyBindingKey.DeleteCharForwardKeyBindingKey]: { + [KeyBindingName.DeleteCharForward]: { handler: deleteCharForward, descriptionKey: 'keybindings.commands.deleteCharForward' }, - [KeyBindingKey.DeleteGroupBackwardKeyBindingKey]: { + [KeyBindingName.DeleteGroupBackward]: { handler: deleteGroupBackward, descriptionKey: 'keybindings.commands.deleteGroupBackward' }, - [KeyBindingKey.DeleteGroupForwardKeyBindingKey]: { + [KeyBindingName.DeleteGroupForward]: { handler: deleteGroupForward, descriptionKey: 'keybindings.commands.deleteGroupForward' }, + + // Emacs 模式额外的基础导航命令 + [KeyBindingName.CursorCharLeft]: { + handler: cursorCharLeft, + descriptionKey: 'keybindings.commands.cursorCharLeft' + }, + [KeyBindingName.CursorCharRight]: { + handler: cursorCharRight, + descriptionKey: 'keybindings.commands.cursorCharRight' + }, + [KeyBindingName.CursorLineUp]: { + handler: cursorLineUp, + descriptionKey: 'keybindings.commands.cursorLineUp' + }, + [KeyBindingName.CursorLineDown]: { + handler: cursorLineDown, + descriptionKey: 'keybindings.commands.cursorLineDown' + }, + [KeyBindingName.CursorPageUp]: { + handler: cursorPageUp, + descriptionKey: 'keybindings.commands.cursorPageUp' + }, + [KeyBindingName.CursorPageDown]: { + handler: cursorPageDown, + descriptionKey: 'keybindings.commands.cursorPageDown' + }, + [KeyBindingName.SelectCharLeft]: { + handler: selectCharLeft, + descriptionKey: 'keybindings.commands.selectCharLeft' + }, + [KeyBindingName.SelectCharRight]: { + handler: selectCharRight, + descriptionKey: 'keybindings.commands.selectCharRight' + }, + [KeyBindingName.SelectLineUp]: { + handler: selectLineUp, + descriptionKey: 'keybindings.commands.selectLineUp' + }, + [KeyBindingName.SelectLineDown]: { + handler: selectLineDown, + descriptionKey: 'keybindings.commands.selectLineDown' + }, }; /** diff --git a/frontend/src/views/editor/keymap/index.ts b/frontend/src/views/editor/keymap/index.ts index 7ea01aa..d1d9367 100644 --- a/frontend/src/views/editor/keymap/index.ts +++ b/frontend/src/views/editor/keymap/index.ts @@ -27,5 +27,4 @@ export const updateKeymapExtension = (view: any): void => { // 导出相关模块 export { Manager } from './manager'; -export { commands, getCommandHandler, getCommandDescription, isCommandRegistered, getRegisteredCommands } from './commands'; -export type { KeyBinding, CommandHandler, CommandDefinition, KeymapResult } from './types'; \ No newline at end of file +export { commands, getCommandHandler, getCommandDescription, isCommandRegistered, getRegisteredCommands } from './commands'; \ No newline at end of file diff --git a/frontend/src/views/editor/keymap/manager.ts b/frontend/src/views/editor/keymap/manager.ts index c92fd0e..4bd24ec 100644 --- a/frontend/src/views/editor/keymap/manager.ts +++ b/frontend/src/views/editor/keymap/manager.ts @@ -1,7 +1,6 @@ -import {keymap} from '@codemirror/view'; -import {Extension, Compartment} from '@codemirror/state'; +import {KeyBinding, keymap} from '@codemirror/view'; +import {Compartment, Extension} from '@codemirror/state'; import {KeyBinding as KeyBindingConfig} from '@/../bindings/voidraft/internal/models/ent/models'; -import {KeyBinding, KeymapResult} from './types'; import {getCommandHandler, isCommandRegistered} from './commands'; /** @@ -10,13 +9,13 @@ import {getCommandHandler, isCommandRegistered} from './commands'; */ export class Manager { private static compartment = new Compartment(); - + /** * 将后端快捷键配置转换为CodeMirror快捷键绑定 * @param keyBindings 后端快捷键配置列表 * @returns 转换结果 */ - static convertToKeyBindings(keyBindings: KeyBindingConfig[]): KeymapResult { + static convertToKeyBindings(keyBindings: KeyBindingConfig[]): KeyBinding[] { const result: KeyBinding[] = []; for (const binding of keyBindings) { @@ -25,29 +24,31 @@ export class Manager { continue; } - // 检查命令是否已注册(使用 key 字段作为命令标识符) - if (!binding.key || !isCommandRegistered(binding.key)) { + // 检查命令是否已注册 + if (!binding.name || !isCommandRegistered(binding.name)) { continue; } // 获取命令处理函数 - const handler = getCommandHandler(binding.key); + const handler = getCommandHandler(binding.name); if (!handler) { continue; } - // 转换为CodeMirror快捷键格式 - // binding.command 是快捷键组合 (如 "Mod-f"),binding.key 是命令标识符 const keyBinding: KeyBinding = { - key: binding.command || '', + key: binding.key || '', + mac: binding.macos || undefined, + win: binding.windows || undefined, + linux: binding.linux || undefined, run: handler, - preventDefault: true + preventDefault: binding.preventDefault, + scope: binding.scope || undefined }; result.push(keyBinding); } - return {keyBindings: result}; + return result; } /** @@ -56,7 +57,7 @@ export class Manager { * @returns CodeMirror扩展 */ static createKeymapExtension(keyBindings: KeyBindingConfig[]): Extension { - const {keyBindings: cmKeyBindings} = this.convertToKeyBindings(keyBindings); + const cmKeyBindings = this.convertToKeyBindings(keyBindings); return this.compartment.of(keymap.of(cmKeyBindings)); } @@ -66,7 +67,7 @@ export class Manager { * @param keyBindings 后端快捷键配置列表 */ static updateKeymap(view: any, keyBindings: KeyBindingConfig[]): void { - const {keyBindings: cmKeyBindings} = this.convertToKeyBindings(keyBindings); + const cmKeyBindings = this.convertToKeyBindings(keyBindings); view.dispatch({ effects: this.compartment.reconfigure(keymap.of(cmKeyBindings)) }); diff --git a/frontend/src/views/editor/keymap/types.ts b/frontend/src/views/editor/keymap/types.ts deleted file mode 100644 index a5bf1e8..0000000 --- a/frontend/src/views/editor/keymap/types.ts +++ /dev/null @@ -1,30 +0,0 @@ -import {Command} from '@codemirror/view'; - -/** - * CodeMirror快捷键绑定格式 - */ -export interface KeyBinding { - key: string - run: Command - preventDefault?: boolean -} - -/** - * 命令处理函数类型 - */ -export type CommandHandler = Command - -/** - * 命令定义接口 - */ -export interface CommandDefinition { - handler: CommandHandler - descriptionKey: string // 翻译键 -} - -/** - * 快捷键转换结果 - */ -export interface KeymapResult { - keyBindings: KeyBinding[] -} \ No newline at end of file diff --git a/frontend/src/views/editor/manager/extensions.ts b/frontend/src/views/editor/manager/extensions.ts index 53d7777..5bc7282 100644 --- a/frontend/src/views/editor/manager/extensions.ts +++ b/frontend/src/views/editor/manager/extensions.ts @@ -15,7 +15,7 @@ import {highlightActiveLineGutter, highlightWhitespace, highlightTrailingWhitesp import createEditorContextMenu from '../extensions/contextMenu'; import {blockLineNumbers} from '../extensions/codeblock'; import {createHttpClientExtension} from '../extensions/httpclient'; -import {ExtensionKey} from '@/../bindings/voidraft/internal/models/models'; +import {ExtensionName} from '@/../bindings/voidraft/internal/models/models'; type ExtensionEntry = { definition: ExtensionDefinition @@ -24,35 +24,35 @@ type ExtensionEntry = { }; // 排除 $zero 的有效扩展 Key 类型 -type ValidExtensionKey = Exclude; +type ValidExtensionName = Exclude; const defineExtension = (create: (config: any) => any, defaultConfig: Record = {}): ExtensionDefinition => ({ create, defaultConfig }); -const EXTENSION_REGISTRY: Record = { - [ExtensionKey.ExtensionRainbowBrackets]: { +const EXTENSION_REGISTRY: Record = { + [ExtensionName.RainbowBrackets]: { definition: defineExtension(() => rainbowBrackets()), displayNameKey: 'extensions.rainbowBrackets.name', descriptionKey: 'extensions.rainbowBrackets.description' }, - [ExtensionKey.ExtensionHyperlink]: { + [ExtensionName.Hyperlink]: { definition: defineExtension(() => hyperLink), displayNameKey: 'extensions.hyperlink.name', descriptionKey: 'extensions.hyperlink.description' }, - [ExtensionKey.ExtensionColorSelector]: { + [ExtensionName.ColorSelector]: { definition: defineExtension(() => color), displayNameKey: 'extensions.colorSelector.name', descriptionKey: 'extensions.colorSelector.description' }, - [ExtensionKey.ExtensionTranslator]: { + [ExtensionName.Translator]: { definition: defineExtension(() => createTranslatorExtension()), displayNameKey: 'extensions.translator.name', descriptionKey: 'extensions.translator.description' }, - [ExtensionKey.ExtensionMinimap]: { + [ExtensionName.Minimap]: { definition: defineExtension((config: any) => minimap({ displayText: config?.displayText ?? 'characters', showOverlay: config?.showOverlay ?? 'always', @@ -65,49 +65,49 @@ const EXTENSION_REGISTRY: Record = { displayNameKey: 'extensions.minimap.name', descriptionKey: 'extensions.minimap.description' }, - [ExtensionKey.ExtensionSearch]: { + [ExtensionName.Search]: { definition: defineExtension(() => vscodeSearch), displayNameKey: 'extensions.search.name', descriptionKey: 'extensions.search.description' }, - [ExtensionKey.ExtensionFold]: { + [ExtensionName.Fold]: { definition: defineExtension(() => Prec.low(foldGutter())), displayNameKey: 'extensions.fold.name', descriptionKey: 'extensions.fold.description' }, - [ExtensionKey.ExtensionMarkdown]: { + [ExtensionName.Markdown]: { definition: defineExtension(() => markdownExtensions), displayNameKey: 'extensions.markdown.name', descriptionKey: 'extensions.markdown.description' }, - [ExtensionKey.ExtensionLineNumbers]: { + [ExtensionName.LineNumbers]: { definition: defineExtension(() => Prec.high([blockLineNumbers, highlightActiveLineGutter()])), displayNameKey: 'extensions.lineNumbers.name', descriptionKey: 'extensions.lineNumbers.description' }, - [ExtensionKey.ExtensionContextMenu]: { + [ExtensionName.ContextMenu]: { definition: defineExtension(() => createEditorContextMenu()), displayNameKey: 'extensions.contextMenu.name', descriptionKey: 'extensions.contextMenu.description' }, - [ExtensionKey.ExtensionHighlightWhitespace]: { + [ExtensionName.HighlightWhitespace]: { definition: defineExtension(() => highlightWhitespace()), displayNameKey: 'extensions.highlightWhitespace.name', descriptionKey: 'extensions.highlightWhitespace.description' }, - [ExtensionKey.ExtensionHighlightTrailingWhitespace]: { + [ExtensionName.HighlightTrailingWhitespace]: { definition: defineExtension(() => highlightTrailingWhitespace()), displayNameKey: 'extensions.highlightTrailingWhitespace.name', descriptionKey: 'extensions.highlightTrailingWhitespace.description' }, - [ExtensionKey.ExtensionHttpClient]: { + [ExtensionName.HttpClient]: { definition: defineExtension(() => createHttpClientExtension()), displayNameKey: 'extensions.httpClient.name', descriptionKey: 'extensions.httpClient.description' } }; -const isRegisteredExtension = (key: string): key is ValidExtensionKey => +const isRegisteredExtension = (key: string): key is ValidExtensionName => Object.prototype.hasOwnProperty.call(EXTENSION_REGISTRY, key); const getRegistryEntry = (key: string): ExtensionEntry | undefined => { @@ -118,7 +118,7 @@ const getRegistryEntry = (key: string): ExtensionEntry | undefined => { }; export function registerAllExtensions(manager: Manager): void { - (Object.entries(EXTENSION_REGISTRY) as [ValidExtensionKey, ExtensionEntry][]).forEach(([id, entry]) => { + (Object.entries(EXTENSION_REGISTRY) as [ValidExtensionName, ExtensionEntry][]).forEach(([id, entry]) => { manager.registerExtension(id, entry.definition); }); } @@ -147,7 +147,7 @@ export function hasExtensionConfig(key: string): boolean { return Object.keys(getExtensionDefaultConfig(key)).length > 0; } -export function getAllExtensionIds(): string[] { +export function getExtensionsMap(): string[] { return Object.keys(EXTENSION_REGISTRY); } diff --git a/frontend/src/views/editor/manager/manager.ts b/frontend/src/views/editor/manager/manager.ts index 5285ddd..27c1e90 100644 --- a/frontend/src/views/editor/manager/manager.ts +++ b/frontend/src/views/editor/manager/manager.ts @@ -11,8 +11,8 @@ export class Manager { private extensionStates = new Map(); private views = new Map(); - registerExtension(id: string, definition: ExtensionDefinition): void { - const existingState = this.extensionStates.get(id); + registerExtension(name: string, definition: ExtensionDefinition): void { + const existingState = this.extensionStates.get(name); if (existingState) { existingState.definition = definition; if (existingState.config === undefined) { @@ -21,8 +21,8 @@ export class Manager { } else { const compartment = new Compartment(); const defaultConfig = this.cloneConfig(definition.defaultConfig ?? {}); - this.extensionStates.set(id, { - id, + this.extensionStates.set(name, { + name, definition, config: defaultConfig, enabled: false, @@ -34,8 +34,8 @@ export class Manager { initExtensions(extensionConfigs: ExtensionConfig[]): void { for (const config of extensionConfigs) { - if (!config.key) continue; - const state = this.extensionStates.get(config.key); + if (!config.name) continue; + const state = this.extensionStates.get(config.name); if (!state) continue; const resolvedConfig = this.cloneConfig(config.config ?? state.definition.defaultConfig ?? {}); this.commitExtensionState(state, config.enabled ?? false, resolvedConfig); @@ -88,9 +88,9 @@ export class Manager { state.enabled = enabled; state.config = config; state.extension = runtimeExtension; - this.applyExtensionToAllViews(state.id); + this.applyExtensionToAllViews(state.name); } catch (error) { - console.error(`Failed to update extension ${state.id}:`, error); + console.error(`Failed to update extension ${state.name}:`, error); } } diff --git a/frontend/src/views/editor/manager/types.ts b/frontend/src/views/editor/manager/types.ts index 7bdbaf8..cef1066 100644 --- a/frontend/src/views/editor/manager/types.ts +++ b/frontend/src/views/editor/manager/types.ts @@ -13,7 +13,7 @@ export interface ExtensionDefinition { * 扩展运行时状态 */ export interface ExtensionState { - id: string // 扩展 key + name: string definition: ExtensionDefinition config: any enabled: boolean diff --git a/frontend/src/views/settings/pages/ExtensionsPage.vue b/frontend/src/views/settings/pages/ExtensionsPage.vue index a2c464c..cecafab 100644 --- a/frontend/src/views/settings/pages/ExtensionsPage.vue +++ b/frontend/src/views/settings/pages/ExtensionsPage.vue @@ -1,14 +1,13 @@