🚧 Refactor basic services
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import { EditorView } from '@codemirror/view';
|
||||
import { Extension } from '@codemirror/state';
|
||||
import { copyCommand, cutCommand, pasteCommand } from '../codeblock/copyPaste';
|
||||
import { KeyBindingCommand } from '../../../../../bindings/voidraft/internal/models/models';
|
||||
import { KeyBindingKey } from '@/../bindings/voidraft/internal/models/models';
|
||||
import { useKeybindingStore } from '@/stores/keybindingStore';
|
||||
import { undo, redo } from '@codemirror/commands';
|
||||
import i18n from '@/i18n';
|
||||
@@ -32,53 +32,54 @@ function formatKeyBinding(keyBinding: string): string {
|
||||
.replace(/-/g, " + ");
|
||||
}
|
||||
|
||||
const shortcutCache = new Map<KeyBindingCommand, string>();
|
||||
const shortcutCache = new Map<KeyBindingKey, string>();
|
||||
|
||||
|
||||
function getShortcutText(command?: KeyBindingCommand): string {
|
||||
if (command === undefined) {
|
||||
function getShortcutText(keyBindingKey?: KeyBindingKey): string {
|
||||
if (keyBindingKey === undefined) {
|
||||
return "";
|
||||
}
|
||||
|
||||
const cached = shortcutCache.get(command);
|
||||
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.command === command && kb.enabled
|
||||
(kb) => kb.key === keyBindingKey && kb.enabled
|
||||
);
|
||||
|
||||
if (binding?.key) {
|
||||
const formatted = formatKeyBinding(binding.key);
|
||||
shortcutCache.set(command, formatted);
|
||||
if (binding?.command) {
|
||||
const formatted = formatKeyBinding(binding.command);
|
||||
shortcutCache.set(keyBindingKey, formatted);
|
||||
return formatted;
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn("An error occurred while getting the shortcut:", error);
|
||||
}
|
||||
|
||||
shortcutCache.set(command, "");
|
||||
shortcutCache.set(keyBindingKey, "");
|
||||
return "";
|
||||
}
|
||||
|
||||
|
||||
function getBuiltinMenuNodes(): MenuSchemaNode[] {
|
||||
function builtinMenuNodes(): MenuSchemaNode[] {
|
||||
return [
|
||||
{
|
||||
id: "copy",
|
||||
labelKey: "keybindings.commands.blockCopy",
|
||||
command: copyCommand,
|
||||
shortcutCommand: KeyBindingCommand.BlockCopyCommand,
|
||||
keyBindingKey: KeyBindingKey.BlockCopyKeyBindingKey,
|
||||
enabled: (context) => context.hasSelection
|
||||
},
|
||||
{
|
||||
id: "cut",
|
||||
labelKey: "keybindings.commands.blockCut",
|
||||
command: cutCommand,
|
||||
shortcutCommand: KeyBindingCommand.BlockCutCommand,
|
||||
keyBindingKey: KeyBindingKey.BlockCutKeyBindingKey,
|
||||
visible: (context) => context.isEditable,
|
||||
enabled: (context) => context.hasSelection && context.isEditable
|
||||
},
|
||||
@@ -86,21 +87,21 @@ function getBuiltinMenuNodes(): MenuSchemaNode[] {
|
||||
id: "paste",
|
||||
labelKey: "keybindings.commands.blockPaste",
|
||||
command: pasteCommand,
|
||||
shortcutCommand: KeyBindingCommand.BlockPasteCommand,
|
||||
keyBindingKey: KeyBindingKey.BlockPasteKeyBindingKey,
|
||||
visible: (context) => context.isEditable
|
||||
},
|
||||
{
|
||||
id: "undo",
|
||||
labelKey: "keybindings.commands.historyUndo",
|
||||
command: undo,
|
||||
shortcutCommand: KeyBindingCommand.HistoryUndoCommand,
|
||||
keyBindingKey: KeyBindingKey.HistoryUndoKeyBindingKey,
|
||||
visible: (context) => context.isEditable
|
||||
},
|
||||
{
|
||||
id: "redo",
|
||||
labelKey: "keybindings.commands.historyRedo",
|
||||
command: redo,
|
||||
shortcutCommand: KeyBindingCommand.HistoryRedoCommand,
|
||||
keyBindingKey: KeyBindingKey.HistoryRedoKeyBindingKey,
|
||||
visible: (context) => context.isEditable
|
||||
}
|
||||
];
|
||||
@@ -110,7 +111,7 @@ let builtinMenuRegistered = false;
|
||||
|
||||
function ensureBuiltinMenuRegistered(): void {
|
||||
if (builtinMenuRegistered) return;
|
||||
registerMenuNodes(getBuiltinMenuNodes());
|
||||
registerMenuNodes(builtinMenuNodes());
|
||||
builtinMenuRegistered = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { EditorView } from '@codemirror/view';
|
||||
import { EditorState } from '@codemirror/state';
|
||||
import type { KeyBindingCommand } from '../../../../../bindings/voidraft/internal/models/models';
|
||||
import { KeyBindingKey } 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;
|
||||
shortcutCommand?: KeyBindingCommand;
|
||||
keyBindingKey?: KeyBindingKey;
|
||||
visible?: (context: MenuContext) => boolean;
|
||||
enabled?: (context: MenuContext) => boolean;
|
||||
}
|
||||
@@ -37,7 +37,7 @@ export interface RenderMenuItem {
|
||||
|
||||
interface MenuBuildOptions {
|
||||
translate: (key: string) => string;
|
||||
formatShortcut: (command?: KeyBindingCommand) => string;
|
||||
formatShortcut: (keyBindingKey?: KeyBindingKey) => string;
|
||||
}
|
||||
|
||||
const menuRegistry: MenuSchemaNode[] = [];
|
||||
@@ -89,7 +89,7 @@ function convertNode(
|
||||
}
|
||||
|
||||
const disabled = node.enabled ? !node.enabled(context) : false;
|
||||
const shortcut = options.formatShortcut(node.shortcutCommand);
|
||||
const shortcut = options.formatShortcut(node.keyBindingKey);
|
||||
|
||||
return {
|
||||
id: node.id,
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
import { Extension, StateField } from '@codemirror/state';
|
||||
import { EditorView, showTooltip, Tooltip } from '@codemirror/view';
|
||||
import { translatorManager } from './manager';
|
||||
import { TRANSLATION_ICON_SVG } from '@/common/constant/translation';
|
||||
|
||||
const TRANSLATION_ICON_SVG = `
|
||||
<svg class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" width="24" height="24">
|
||||
<path d="M599.68 485.056h-8l30.592 164.672c20.352-7.04 38.72-17.344 54.912-31.104a271.36 271.36 0 0 1-40.704-64.64l32.256-4.032c8.896 17.664 19.072 33.28 30.592 46.72 23.872-27.968 42.24-65.152 55.04-111.744l-154.688 0.128z m121.92 133.76c18.368 15.36 39.36 26.56 62.848 33.472l14.784 4.416-8.64 30.336-14.72-4.352a205.696 205.696 0 0 1-76.48-41.728c-20.672 17.92-44.928 31.552-71.232 40.064l20.736 110.912H519.424l-9.984 72.512h385.152c18.112 0 32.704-14.144 32.704-31.616V295.424a32.128 32.128 0 0 0-32.704-31.552H550.528l35.2 189.696h79.424v-31.552h61.44v31.552h102.4v31.616h-42.688c-14.272 55.488-35.712 100.096-64.64 133.568zM479.36 791.68H193.472c-36.224 0-65.472-28.288-65.472-63.168V191.168C128 156.16 157.312 128 193.472 128h327.68l20.544 104.32h352.832c36.224 0 65.472 28.224 65.472 63.104v537.408c0 34.944-29.312 63.168-65.472 63.168H468.608l10.688-104.32zM337.472 548.352v-33.28H272.768v-48.896h60.16V433.28h-60.16v-41.728h64.704v-32.896h-102.4v189.632h102.4z m158.272 0V453.76c0-17.216-4.032-30.272-12.16-39.488-8.192-9.152-20.288-13.696-36.032-13.696a55.04 55.04 0 0 0-24.768 5.376 39.04 39.04 0 0 0-17.088 15.936h-1.984l-5.056-18.56h-28.352V548.48h37.12V480c0-17.088 2.304-29.376 6.912-36.736 4.608-7.424 12.16-11.072 22.528-11.072 7.616 0 13.248 2.56 16.64 7.872 3.52 5.248 5.312 13.056 5.312 23.488v84.736h36.928z" fill="currentColor"></path>
|
||||
</svg>`;
|
||||
|
||||
function TranslationTooltips(state: any): readonly Tooltip[] {
|
||||
const selection = state.selection.main;
|
||||
|
||||
@@ -1,8 +1,4 @@
|
||||
import {KeyBindingCommand} from '@/../bindings/voidraft/internal/models/models';
|
||||
import {
|
||||
openSearchPanel,
|
||||
closeSearchPanel,
|
||||
} from '@codemirror/search';
|
||||
import {closeSearchPanel, openSearchPanel,} from '@codemirror/search';
|
||||
import {
|
||||
addNewBlockAfterCurrent,
|
||||
addNewBlockAfterLast,
|
||||
@@ -49,249 +45,249 @@ import {
|
||||
} from '@codemirror/commands';
|
||||
import {foldAll, foldCode, unfoldAll, unfoldCode} from '@codemirror/language';
|
||||
import i18n from '@/i18n';
|
||||
import {KeyBindingKey} from '@/../bindings/voidraft/internal/models/models';
|
||||
|
||||
// 默认编辑器选项
|
||||
const defaultEditorOptions = {
|
||||
// 默认代码块扩展选项
|
||||
const defaultBlockExtensionOptions = {
|
||||
defaultBlockToken: 'text',
|
||||
defaultBlockAutoDetect: true,
|
||||
};
|
||||
|
||||
/**
|
||||
* 前端命令注册表
|
||||
* 将后端定义的command字段映射到具体的前端方法和翻译键
|
||||
* 将后端定义的key字段映射到具体的前端方法和翻译键
|
||||
*/
|
||||
export const commands = {
|
||||
[KeyBindingCommand.ShowSearchCommand]: {
|
||||
export const commands: Record<string, { handler: any; descriptionKey: string }> = {
|
||||
[KeyBindingKey.ShowSearchKeyBindingKey]: {
|
||||
handler: openSearchPanel,
|
||||
descriptionKey: 'keybindings.commands.showSearch'
|
||||
},
|
||||
[KeyBindingCommand.HideSearchCommand]: {
|
||||
[KeyBindingKey.HideSearchKeyBindingKey]: {
|
||||
handler: closeSearchPanel,
|
||||
descriptionKey: 'keybindings.commands.hideSearch'
|
||||
},
|
||||
// 代码块操作命令
|
||||
[KeyBindingCommand.BlockSelectAllCommand]: {
|
||||
[KeyBindingKey.BlockSelectAllKeyBindingKey]: {
|
||||
handler: selectAll,
|
||||
descriptionKey: 'keybindings.commands.blockSelectAll'
|
||||
},
|
||||
[KeyBindingCommand.BlockAddAfterCurrentCommand]: {
|
||||
handler: addNewBlockAfterCurrent(defaultEditorOptions),
|
||||
[KeyBindingKey.BlockAddAfterCurrentKeyBindingKey]: {
|
||||
handler: addNewBlockAfterCurrent(defaultBlockExtensionOptions),
|
||||
descriptionKey: 'keybindings.commands.blockAddAfterCurrent'
|
||||
},
|
||||
[KeyBindingCommand.BlockAddAfterLastCommand]: {
|
||||
handler: addNewBlockAfterLast(defaultEditorOptions),
|
||||
[KeyBindingKey.BlockAddAfterLastKeyBindingKey]: {
|
||||
handler: addNewBlockAfterLast(defaultBlockExtensionOptions),
|
||||
descriptionKey: 'keybindings.commands.blockAddAfterLast'
|
||||
},
|
||||
[KeyBindingCommand.BlockAddBeforeCurrentCommand]: {
|
||||
handler: addNewBlockBeforeCurrent(defaultEditorOptions),
|
||||
[KeyBindingKey.BlockAddBeforeCurrentKeyBindingKey]: {
|
||||
handler: addNewBlockBeforeCurrent(defaultBlockExtensionOptions),
|
||||
descriptionKey: 'keybindings.commands.blockAddBeforeCurrent'
|
||||
},
|
||||
[KeyBindingCommand.BlockGotoPreviousCommand]: {
|
||||
[KeyBindingKey.BlockGotoPreviousKeyBindingKey]: {
|
||||
handler: gotoPreviousBlock,
|
||||
descriptionKey: 'keybindings.commands.blockGotoPrevious'
|
||||
},
|
||||
[KeyBindingCommand.BlockGotoNextCommand]: {
|
||||
[KeyBindingKey.BlockGotoNextKeyBindingKey]: {
|
||||
handler: gotoNextBlock,
|
||||
descriptionKey: 'keybindings.commands.blockGotoNext'
|
||||
},
|
||||
[KeyBindingCommand.BlockSelectPreviousCommand]: {
|
||||
[KeyBindingKey.BlockSelectPreviousKeyBindingKey]: {
|
||||
handler: selectPreviousBlock,
|
||||
descriptionKey: 'keybindings.commands.blockSelectPrevious'
|
||||
},
|
||||
[KeyBindingCommand.BlockSelectNextCommand]: {
|
||||
[KeyBindingKey.BlockSelectNextKeyBindingKey]: {
|
||||
handler: selectNextBlock,
|
||||
descriptionKey: 'keybindings.commands.blockSelectNext'
|
||||
},
|
||||
[KeyBindingCommand.BlockDeleteCommand]: {
|
||||
handler: deleteBlock(defaultEditorOptions),
|
||||
[KeyBindingKey.BlockDeleteKeyBindingKey]: {
|
||||
handler: deleteBlock(defaultBlockExtensionOptions),
|
||||
descriptionKey: 'keybindings.commands.blockDelete'
|
||||
},
|
||||
[KeyBindingCommand.BlockMoveUpCommand]: {
|
||||
[KeyBindingKey.BlockMoveUpKeyBindingKey]: {
|
||||
handler: moveCurrentBlockUp,
|
||||
descriptionKey: 'keybindings.commands.blockMoveUp'
|
||||
},
|
||||
[KeyBindingCommand.BlockMoveDownCommand]: {
|
||||
[KeyBindingKey.BlockMoveDownKeyBindingKey]: {
|
||||
handler: moveCurrentBlockDown,
|
||||
descriptionKey: 'keybindings.commands.blockMoveDown'
|
||||
},
|
||||
[KeyBindingCommand.BlockDeleteLineCommand]: {
|
||||
[KeyBindingKey.BlockDeleteLineKeyBindingKey]: {
|
||||
handler: deleteLineCommand,
|
||||
descriptionKey: 'keybindings.commands.blockDeleteLine'
|
||||
},
|
||||
[KeyBindingCommand.BlockMoveLineUpCommand]: {
|
||||
[KeyBindingKey.BlockMoveLineUpKeyBindingKey]: {
|
||||
handler: moveLineUp,
|
||||
descriptionKey: 'keybindings.commands.blockMoveLineUp'
|
||||
},
|
||||
[KeyBindingCommand.BlockMoveLineDownCommand]: {
|
||||
[KeyBindingKey.BlockMoveLineDownKeyBindingKey]: {
|
||||
handler: moveLineDown,
|
||||
descriptionKey: 'keybindings.commands.blockMoveLineDown'
|
||||
},
|
||||
[KeyBindingCommand.BlockTransposeCharsCommand]: {
|
||||
[KeyBindingKey.BlockTransposeCharsKeyBindingKey]: {
|
||||
handler: transposeChars,
|
||||
descriptionKey: 'keybindings.commands.blockTransposeChars'
|
||||
},
|
||||
[KeyBindingCommand.BlockFormatCommand]: {
|
||||
[KeyBindingKey.BlockFormatKeyBindingKey]: {
|
||||
handler: formatCurrentBlock,
|
||||
descriptionKey: 'keybindings.commands.blockFormat'
|
||||
},
|
||||
[KeyBindingCommand.BlockCopyCommand]: {
|
||||
[KeyBindingKey.BlockCopyKeyBindingKey]: {
|
||||
handler: copyCommand,
|
||||
descriptionKey: 'keybindings.commands.blockCopy'
|
||||
},
|
||||
[KeyBindingCommand.BlockCutCommand]: {
|
||||
[KeyBindingKey.BlockCutKeyBindingKey]: {
|
||||
handler: cutCommand,
|
||||
descriptionKey: 'keybindings.commands.blockCut'
|
||||
},
|
||||
[KeyBindingCommand.BlockPasteCommand]: {
|
||||
[KeyBindingKey.BlockPasteKeyBindingKey]: {
|
||||
handler: pasteCommand,
|
||||
descriptionKey: 'keybindings.commands.blockPaste'
|
||||
},
|
||||
[KeyBindingCommand.HistoryUndoCommand]: {
|
||||
[KeyBindingKey.HistoryUndoKeyBindingKey]: {
|
||||
handler: undo,
|
||||
descriptionKey: 'keybindings.commands.historyUndo'
|
||||
},
|
||||
[KeyBindingCommand.HistoryRedoCommand]: {
|
||||
[KeyBindingKey.HistoryRedoKeyBindingKey]: {
|
||||
handler: redo,
|
||||
descriptionKey: 'keybindings.commands.historyRedo'
|
||||
},
|
||||
[KeyBindingCommand.HistoryUndoSelectionCommand]: {
|
||||
[KeyBindingKey.HistoryUndoSelectionKeyBindingKey]: {
|
||||
handler: undoSelection,
|
||||
descriptionKey: 'keybindings.commands.historyUndoSelection'
|
||||
},
|
||||
[KeyBindingCommand.HistoryRedoSelectionCommand]: {
|
||||
[KeyBindingKey.HistoryRedoSelectionKeyBindingKey]: {
|
||||
handler: redoSelection,
|
||||
descriptionKey: 'keybindings.commands.historyRedoSelection'
|
||||
},
|
||||
[KeyBindingCommand.FoldCodeCommand]: {
|
||||
[KeyBindingKey.FoldCodeKeyBindingKey]: {
|
||||
handler: foldCode,
|
||||
descriptionKey: 'keybindings.commands.foldCode'
|
||||
},
|
||||
[KeyBindingCommand.UnfoldCodeCommand]: {
|
||||
[KeyBindingKey.UnfoldCodeKeyBindingKey]: {
|
||||
handler: unfoldCode,
|
||||
descriptionKey: 'keybindings.commands.unfoldCode'
|
||||
},
|
||||
[KeyBindingCommand.FoldAllCommand]: {
|
||||
[KeyBindingKey.FoldAllKeyBindingKey]: {
|
||||
handler: foldAll,
|
||||
descriptionKey: 'keybindings.commands.foldAll'
|
||||
},
|
||||
[KeyBindingCommand.UnfoldAllCommand]: {
|
||||
[KeyBindingKey.UnfoldAllKeyBindingKey]: {
|
||||
handler: unfoldAll,
|
||||
descriptionKey: 'keybindings.commands.unfoldAll'
|
||||
},
|
||||
[KeyBindingCommand.CursorSyntaxLeftCommand]: {
|
||||
[KeyBindingKey.CursorSyntaxLeftKeyBindingKey]: {
|
||||
handler: cursorSyntaxLeft,
|
||||
descriptionKey: 'keybindings.commands.cursorSyntaxLeft'
|
||||
},
|
||||
[KeyBindingCommand.CursorSyntaxRightCommand]: {
|
||||
[KeyBindingKey.CursorSyntaxRightKeyBindingKey]: {
|
||||
handler: cursorSyntaxRight,
|
||||
descriptionKey: 'keybindings.commands.cursorSyntaxRight'
|
||||
},
|
||||
[KeyBindingCommand.SelectSyntaxLeftCommand]: {
|
||||
[KeyBindingKey.SelectSyntaxLeftKeyBindingKey]: {
|
||||
handler: selectSyntaxLeft,
|
||||
descriptionKey: 'keybindings.commands.selectSyntaxLeft'
|
||||
},
|
||||
[KeyBindingCommand.SelectSyntaxRightCommand]: {
|
||||
[KeyBindingKey.SelectSyntaxRightKeyBindingKey]: {
|
||||
handler: selectSyntaxRight,
|
||||
descriptionKey: 'keybindings.commands.selectSyntaxRight'
|
||||
},
|
||||
[KeyBindingCommand.CopyLineUpCommand]: {
|
||||
[KeyBindingKey.CopyLineUpKeyBindingKey]: {
|
||||
handler: copyLineUp,
|
||||
descriptionKey: 'keybindings.commands.copyLineUp'
|
||||
},
|
||||
[KeyBindingCommand.CopyLineDownCommand]: {
|
||||
[KeyBindingKey.CopyLineDownKeyBindingKey]: {
|
||||
handler: copyLineDown,
|
||||
descriptionKey: 'keybindings.commands.copyLineDown'
|
||||
},
|
||||
[KeyBindingCommand.InsertBlankLineCommand]: {
|
||||
[KeyBindingKey.InsertBlankLineKeyBindingKey]: {
|
||||
handler: insertBlankLine,
|
||||
descriptionKey: 'keybindings.commands.insertBlankLine'
|
||||
},
|
||||
[KeyBindingCommand.SelectLineCommand]: {
|
||||
[KeyBindingKey.SelectLineKeyBindingKey]: {
|
||||
handler: selectLine,
|
||||
descriptionKey: 'keybindings.commands.selectLine'
|
||||
},
|
||||
[KeyBindingCommand.SelectParentSyntaxCommand]: {
|
||||
[KeyBindingKey.SelectParentSyntaxKeyBindingKey]: {
|
||||
handler: selectParentSyntax,
|
||||
descriptionKey: 'keybindings.commands.selectParentSyntax'
|
||||
},
|
||||
[KeyBindingCommand.IndentLessCommand]: {
|
||||
[KeyBindingKey.IndentLessKeyBindingKey]: {
|
||||
handler: indentLess,
|
||||
descriptionKey: 'keybindings.commands.indentLess'
|
||||
},
|
||||
[KeyBindingCommand.IndentMoreCommand]: {
|
||||
[KeyBindingKey.IndentMoreKeyBindingKey]: {
|
||||
handler: indentMore,
|
||||
descriptionKey: 'keybindings.commands.indentMore'
|
||||
},
|
||||
[KeyBindingCommand.IndentSelectionCommand]: {
|
||||
[KeyBindingKey.IndentSelectionKeyBindingKey]: {
|
||||
handler: indentSelection,
|
||||
descriptionKey: 'keybindings.commands.indentSelection'
|
||||
},
|
||||
[KeyBindingCommand.CursorMatchingBracketCommand]: {
|
||||
[KeyBindingKey.CursorMatchingBracketKeyBindingKey]: {
|
||||
handler: cursorMatchingBracket,
|
||||
descriptionKey: 'keybindings.commands.cursorMatchingBracket'
|
||||
},
|
||||
[KeyBindingCommand.ToggleCommentCommand]: {
|
||||
[KeyBindingKey.ToggleCommentKeyBindingKey]: {
|
||||
handler: toggleComment,
|
||||
descriptionKey: 'keybindings.commands.toggleComment'
|
||||
},
|
||||
[KeyBindingCommand.ToggleBlockCommentCommand]: {
|
||||
[KeyBindingKey.ToggleBlockCommentKeyBindingKey]: {
|
||||
handler: toggleBlockComment,
|
||||
descriptionKey: 'keybindings.commands.toggleBlockComment'
|
||||
},
|
||||
[KeyBindingCommand.InsertNewlineAndIndentCommand]: {
|
||||
[KeyBindingKey.InsertNewlineAndIndentKeyBindingKey]: {
|
||||
handler: insertNewlineAndIndent,
|
||||
descriptionKey: 'keybindings.commands.insertNewlineAndIndent'
|
||||
},
|
||||
[KeyBindingCommand.DeleteCharBackwardCommand]: {
|
||||
[KeyBindingKey.DeleteCharBackwardKeyBindingKey]: {
|
||||
handler: deleteCharBackward,
|
||||
descriptionKey: 'keybindings.commands.deleteCharBackward'
|
||||
},
|
||||
[KeyBindingCommand.DeleteCharForwardCommand]: {
|
||||
[KeyBindingKey.DeleteCharForwardKeyBindingKey]: {
|
||||
handler: deleteCharForward,
|
||||
descriptionKey: 'keybindings.commands.deleteCharForward'
|
||||
},
|
||||
[KeyBindingCommand.DeleteGroupBackwardCommand]: {
|
||||
[KeyBindingKey.DeleteGroupBackwardKeyBindingKey]: {
|
||||
handler: deleteGroupBackward,
|
||||
descriptionKey: 'keybindings.commands.deleteGroupBackward'
|
||||
},
|
||||
[KeyBindingCommand.DeleteGroupForwardCommand]: {
|
||||
[KeyBindingKey.DeleteGroupForwardKeyBindingKey]: {
|
||||
handler: deleteGroupForward,
|
||||
descriptionKey: 'keybindings.commands.deleteGroupForward'
|
||||
},
|
||||
} as const;
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取命令处理函数
|
||||
* @param command 命令名称
|
||||
* @param key 命令标识符
|
||||
* @returns 对应的处理函数,如果不存在则返回 undefined
|
||||
*/
|
||||
export const getCommandHandler = (command: KeyBindingCommand) => {
|
||||
return commands[command]?.handler;
|
||||
export const getCommandHandler = (key: string) => {
|
||||
return commands[key]?.handler;
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取命令描述
|
||||
* @param command 命令名称
|
||||
* @param key 命令标识符
|
||||
* @returns 对应的描述,如果不存在则返回 undefined
|
||||
*/
|
||||
export const getCommandDescription = (command: KeyBindingCommand) => {
|
||||
const descriptionKey = commands[command]?.descriptionKey;
|
||||
export const getCommandDescription = (key: string) => {
|
||||
const descriptionKey = commands[key]?.descriptionKey;
|
||||
return descriptionKey ? i18n.global.t(descriptionKey) : undefined;
|
||||
};
|
||||
|
||||
/**
|
||||
* 检查命令是否已注册
|
||||
* @param command 命令名称
|
||||
* @param key 命令标识符
|
||||
* @returns 是否已注册
|
||||
*/
|
||||
export const isCommandRegistered = (command: KeyBindingCommand): boolean => {
|
||||
return command in commands;
|
||||
export const isCommandRegistered = (key: string): boolean => {
|
||||
return key in commands;
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取所有已注册的命令
|
||||
* @returns 已注册的命令列表
|
||||
*/
|
||||
export const getRegisteredCommands = (): KeyBindingCommand[] => {
|
||||
return Object.keys(commands) as KeyBindingCommand[];
|
||||
};
|
||||
export const getRegisteredCommands = (): string[] => {
|
||||
return Object.keys(commands);
|
||||
};
|
||||
|
||||
@@ -20,10 +20,12 @@ export const createDynamicKeymapExtension = async (): Promise<Extension> => {
|
||||
await extensionStore.loadExtensions();
|
||||
}
|
||||
|
||||
// 获取启用的扩展ID列表
|
||||
const enabledExtensionIds = extensionStore.enabledExtensions.map(ext => ext.id);
|
||||
// 获取启用的扩展key列表
|
||||
const enabledExtensionKeys = extensionStore.enabledExtensions
|
||||
.map(ext => ext.key)
|
||||
.filter((key): key is string => key !== undefined);
|
||||
|
||||
return Manager.createKeymapExtension(keybindingStore.keyBindings, enabledExtensionIds);
|
||||
return Manager.createKeymapExtension(keybindingStore.keyBindings, enabledExtensionKeys);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -34,10 +36,12 @@ export const updateKeymapExtension = (view: any): void => {
|
||||
const keybindingStore = useKeybindingStore();
|
||||
const extensionStore = useExtensionStore();
|
||||
|
||||
// 获取启用的扩展ID列表
|
||||
const enabledExtensionIds = extensionStore.enabledExtensions.map(ext => ext.id);
|
||||
// 获取启用的扩展key列表
|
||||
const enabledExtensionKeys = extensionStore.enabledExtensions
|
||||
.map(ext => ext.key)
|
||||
.filter((key): key is string => key !== undefined);
|
||||
|
||||
Manager.updateKeymap(view, keybindingStore.keyBindings, enabledExtensionIds);
|
||||
Manager.updateKeymap(view, keybindingStore.keyBindings, enabledExtensionKeys);
|
||||
};
|
||||
|
||||
// 导出相关模块
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import {keymap} from '@codemirror/view';
|
||||
import {Extension, Compartment} from '@codemirror/state';
|
||||
import {KeyBinding as KeyBindingConfig, ExtensionID} from '@/../bindings/voidraft/internal/models/models';
|
||||
import {KeyBinding as KeyBindingConfig} from '@/../bindings/voidraft/internal/models/ent/models';
|
||||
import {KeyBinding, KeymapResult} from './types';
|
||||
import {getCommandHandler, isCommandRegistered} from './commands';
|
||||
|
||||
@@ -14,10 +14,10 @@ export class Manager {
|
||||
/**
|
||||
* 将后端快捷键配置转换为CodeMirror快捷键绑定
|
||||
* @param keyBindings 后端快捷键配置列表
|
||||
* @param enabledExtensions 启用的扩展ID列表,如果不提供则使用所有启用的快捷键
|
||||
* @param enabledExtensions 启用的扩展key列表,如果不提供则使用所有启用的快捷键
|
||||
* @returns 转换结果
|
||||
*/
|
||||
static convertToKeyBindings(keyBindings: KeyBindingConfig[], enabledExtensions?: ExtensionID[]): KeymapResult {
|
||||
static convertToKeyBindings(keyBindings: KeyBindingConfig[], enabledExtensions?: string[]): KeymapResult {
|
||||
const result: KeyBinding[] = [];
|
||||
|
||||
for (const binding of keyBindings) {
|
||||
@@ -27,24 +27,25 @@ export class Manager {
|
||||
}
|
||||
|
||||
// 如果提供了扩展列表,则只处理启用扩展的快捷键
|
||||
if (enabledExtensions && !enabledExtensions.includes(binding.extension)) {
|
||||
if (enabledExtensions && binding.extension && !enabledExtensions.includes(binding.extension)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 检查命令是否已注册
|
||||
if (!isCommandRegistered(binding.command)) {
|
||||
// 检查命令是否已注册(使用 key 字段作为命令标识符)
|
||||
if (!binding.key || !isCommandRegistered(binding.key)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 获取命令处理函数
|
||||
const handler = getCommandHandler(binding.command);
|
||||
const handler = getCommandHandler(binding.key);
|
||||
if (!handler) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 转换为CodeMirror快捷键格式
|
||||
// binding.command 是快捷键组合 (如 "Mod-f"),binding.key 是命令标识符
|
||||
const keyBinding: KeyBinding = {
|
||||
key: binding.key,
|
||||
key: binding.command || '',
|
||||
run: handler,
|
||||
preventDefault: true
|
||||
};
|
||||
@@ -58,10 +59,10 @@ export class Manager {
|
||||
/**
|
||||
* 创建CodeMirror快捷键扩展
|
||||
* @param keyBindings 后端快捷键配置列表
|
||||
* @param enabledExtensions 启用的扩展ID列表
|
||||
* @param enabledExtensions 启用的扩展key列表
|
||||
* @returns CodeMirror扩展
|
||||
*/
|
||||
static createKeymapExtension(keyBindings: KeyBindingConfig[], enabledExtensions?: ExtensionID[]): Extension {
|
||||
static createKeymapExtension(keyBindings: KeyBindingConfig[], enabledExtensions?: string[]): Extension {
|
||||
const {keyBindings: cmKeyBindings} =
|
||||
this.convertToKeyBindings(keyBindings, enabledExtensions);
|
||||
|
||||
@@ -72,9 +73,9 @@ export class Manager {
|
||||
* 动态更新快捷键扩展
|
||||
* @param view 编辑器视图
|
||||
* @param keyBindings 后端快捷键配置列表
|
||||
* @param enabledExtensions 启用的扩展ID列表
|
||||
* @param enabledExtensions 启用的扩展key列表
|
||||
*/
|
||||
static updateKeymap(view: any, keyBindings: KeyBindingConfig[], enabledExtensions: ExtensionID[]): void {
|
||||
static updateKeymap(view: any, keyBindings: KeyBindingConfig[], enabledExtensions: string[]): void {
|
||||
const {keyBindings: cmKeyBindings} =
|
||||
this.convertToKeyBindings(keyBindings, enabledExtensions);
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import {Manager} from './manager';
|
||||
import {ExtensionID} from '@/../bindings/voidraft/internal/models/models';
|
||||
import i18n from '@/i18n';
|
||||
import {ExtensionDefinition} from './types';
|
||||
import {Prec} from '@codemirror/state';
|
||||
@@ -15,6 +14,8 @@ import {foldGutter} from "@codemirror/language";
|
||||
import {highlightActiveLineGutter, highlightWhitespace, highlightTrailingWhitespace} from "@codemirror/view";
|
||||
import createEditorContextMenu from '../extensions/contextMenu';
|
||||
import {blockLineNumbers} from '../extensions/codeblock';
|
||||
import {createHttpClientExtension} from '../extensions/httpclient';
|
||||
import {ExtensionKey} from '@/../bindings/voidraft/internal/models/models';
|
||||
|
||||
type ExtensionEntry = {
|
||||
definition: ExtensionDefinition
|
||||
@@ -22,35 +23,36 @@ type ExtensionEntry = {
|
||||
descriptionKey: string
|
||||
};
|
||||
|
||||
type RegisteredExtensionID = Exclude<ExtensionID, ExtensionID.$zero | ExtensionID.ExtensionEditor>;
|
||||
// 排除 $zero 的有效扩展 Key 类型
|
||||
type ValidExtensionKey = Exclude<ExtensionKey, ExtensionKey.$zero>;
|
||||
|
||||
const defineExtension = (create: (config: any) => any, defaultConfig: Record<string, any> = {}): ExtensionDefinition => ({
|
||||
create,
|
||||
defaultConfig
|
||||
});
|
||||
|
||||
const EXTENSION_REGISTRY: Record<RegisteredExtensionID, ExtensionEntry> = {
|
||||
[ExtensionID.ExtensionRainbowBrackets]: {
|
||||
const EXTENSION_REGISTRY: Record<ValidExtensionKey, ExtensionEntry> = {
|
||||
[ExtensionKey.ExtensionRainbowBrackets]: {
|
||||
definition: defineExtension(() => rainbowBrackets()),
|
||||
displayNameKey: 'extensions.rainbowBrackets.name',
|
||||
descriptionKey: 'extensions.rainbowBrackets.description'
|
||||
},
|
||||
[ExtensionID.ExtensionHyperlink]: {
|
||||
[ExtensionKey.ExtensionHyperlink]: {
|
||||
definition: defineExtension(() => hyperLink),
|
||||
displayNameKey: 'extensions.hyperlink.name',
|
||||
descriptionKey: 'extensions.hyperlink.description'
|
||||
},
|
||||
[ExtensionID.ExtensionColorSelector]: {
|
||||
[ExtensionKey.ExtensionColorSelector]: {
|
||||
definition: defineExtension(() => color),
|
||||
displayNameKey: 'extensions.colorSelector.name',
|
||||
descriptionKey: 'extensions.colorSelector.description'
|
||||
},
|
||||
[ExtensionID.ExtensionTranslator]: {
|
||||
[ExtensionKey.ExtensionTranslator]: {
|
||||
definition: defineExtension(() => createTranslatorExtension()),
|
||||
displayNameKey: 'extensions.translator.name',
|
||||
descriptionKey: 'extensions.translator.description'
|
||||
},
|
||||
[ExtensionID.ExtensionMinimap]: {
|
||||
[ExtensionKey.ExtensionMinimap]: {
|
||||
definition: defineExtension((config: any) => minimap({
|
||||
displayText: config?.displayText ?? 'characters',
|
||||
showOverlay: config?.showOverlay ?? 'always',
|
||||
@@ -63,85 +65,90 @@ const EXTENSION_REGISTRY: Record<RegisteredExtensionID, ExtensionEntry> = {
|
||||
displayNameKey: 'extensions.minimap.name',
|
||||
descriptionKey: 'extensions.minimap.description'
|
||||
},
|
||||
[ExtensionID.ExtensionSearch]: {
|
||||
[ExtensionKey.ExtensionSearch]: {
|
||||
definition: defineExtension(() => vscodeSearch),
|
||||
displayNameKey: 'extensions.search.name',
|
||||
descriptionKey: 'extensions.search.description'
|
||||
},
|
||||
[ExtensionID.ExtensionFold]: {
|
||||
[ExtensionKey.ExtensionFold]: {
|
||||
definition: defineExtension(() => Prec.low(foldGutter())),
|
||||
displayNameKey: 'extensions.fold.name',
|
||||
descriptionKey: 'extensions.fold.description'
|
||||
},
|
||||
[ExtensionID.ExtensionMarkdown]: {
|
||||
[ExtensionKey.ExtensionMarkdown]: {
|
||||
definition: defineExtension(() => markdownExtensions),
|
||||
displayNameKey: 'extensions.markdown.name',
|
||||
descriptionKey: 'extensions.markdown.description'
|
||||
},
|
||||
[ExtensionID.ExtensionLineNumbers]: {
|
||||
[ExtensionKey.ExtensionLineNumbers]: {
|
||||
definition: defineExtension(() => Prec.high([blockLineNumbers, highlightActiveLineGutter()])),
|
||||
displayNameKey: 'extensions.lineNumbers.name',
|
||||
descriptionKey: 'extensions.lineNumbers.description'
|
||||
},
|
||||
[ExtensionID.ExtensionContextMenu]: {
|
||||
[ExtensionKey.ExtensionContextMenu]: {
|
||||
definition: defineExtension(() => createEditorContextMenu()),
|
||||
displayNameKey: 'extensions.contextMenu.name',
|
||||
descriptionKey: 'extensions.contextMenu.description'
|
||||
},
|
||||
[ExtensionID.ExtensionHighlightWhitespace]: {
|
||||
[ExtensionKey.ExtensionHighlightWhitespace]: {
|
||||
definition: defineExtension(() => highlightWhitespace()),
|
||||
displayNameKey: 'extensions.highlightWhitespace.name',
|
||||
descriptionKey: 'extensions.highlightWhitespace.description'
|
||||
},
|
||||
[ExtensionID.ExtensionHighlightTrailingWhitespace]: {
|
||||
[ExtensionKey.ExtensionHighlightTrailingWhitespace]: {
|
||||
definition: defineExtension(() => highlightTrailingWhitespace()),
|
||||
displayNameKey: 'extensions.highlightTrailingWhitespace.name',
|
||||
descriptionKey: 'extensions.highlightTrailingWhitespace.description'
|
||||
},
|
||||
[ExtensionKey.ExtensionHttpClient]: {
|
||||
definition: defineExtension(() => createHttpClientExtension()),
|
||||
displayNameKey: 'extensions.httpClient.name',
|
||||
descriptionKey: 'extensions.httpClient.description'
|
||||
}
|
||||
} as const;
|
||||
};
|
||||
|
||||
const isRegisteredExtension = (id: ExtensionID): id is RegisteredExtensionID =>
|
||||
Object.prototype.hasOwnProperty.call(EXTENSION_REGISTRY, id);
|
||||
const isRegisteredExtension = (key: string): key is ValidExtensionKey =>
|
||||
Object.prototype.hasOwnProperty.call(EXTENSION_REGISTRY, key);
|
||||
|
||||
const getRegistryEntry = (id: ExtensionID): ExtensionEntry | undefined => {
|
||||
if (!isRegisteredExtension(id)) {
|
||||
const getRegistryEntry = (key: string): ExtensionEntry | undefined => {
|
||||
if (!isRegisteredExtension(key)) {
|
||||
return undefined;
|
||||
}
|
||||
return EXTENSION_REGISTRY[id];
|
||||
return EXTENSION_REGISTRY[key];
|
||||
};
|
||||
|
||||
export function registerAllExtensions(manager: Manager): void {
|
||||
(Object.entries(EXTENSION_REGISTRY) as [RegisteredExtensionID, ExtensionEntry][]).forEach(([id, entry]) => {
|
||||
(Object.entries(EXTENSION_REGISTRY) as [ValidExtensionKey, ExtensionEntry][]).forEach(([id, entry]) => {
|
||||
manager.registerExtension(id, entry.definition);
|
||||
});
|
||||
}
|
||||
|
||||
export function getExtensionDisplayName(id: ExtensionID): string {
|
||||
const entry = getRegistryEntry(id);
|
||||
return entry?.displayNameKey ? i18n.global.t(entry.displayNameKey) : id;
|
||||
export function getExtensionDisplayName(key: string): string {
|
||||
const entry = getRegistryEntry(key);
|
||||
return entry?.displayNameKey ? i18n.global.t(entry.displayNameKey) : key;
|
||||
}
|
||||
|
||||
export function getExtensionDescription(id: ExtensionID): string {
|
||||
const entry = getRegistryEntry(id);
|
||||
export function getExtensionDescription(key: string): string {
|
||||
const entry = getRegistryEntry(key);
|
||||
return entry?.descriptionKey ? i18n.global.t(entry.descriptionKey) : '';
|
||||
}
|
||||
|
||||
function getExtensionDefinition(id: ExtensionID): ExtensionDefinition | undefined {
|
||||
return getRegistryEntry(id)?.definition;
|
||||
function getExtensionDefinition(key: string): ExtensionDefinition | undefined {
|
||||
return getRegistryEntry(key)?.definition;
|
||||
}
|
||||
|
||||
export function getExtensionDefaultConfig(id: ExtensionID): any {
|
||||
const definition = getExtensionDefinition(id);
|
||||
export function getExtensionDefaultConfig(key: string): any {
|
||||
const definition = getExtensionDefinition(key);
|
||||
if (!definition) return {};
|
||||
return cloneConfig(definition.defaultConfig);
|
||||
}
|
||||
|
||||
export function hasExtensionConfig(id: ExtensionID): boolean {
|
||||
return Object.keys(getExtensionDefaultConfig(id)).length > 0;
|
||||
export function hasExtensionConfig(key: string): boolean {
|
||||
return Object.keys(getExtensionDefaultConfig(key)).length > 0;
|
||||
}
|
||||
|
||||
export function getAllExtensionIds(): ExtensionID[] {
|
||||
return Object.keys(EXTENSION_REGISTRY) as RegisteredExtensionID[];
|
||||
export function getAllExtensionIds(): string[] {
|
||||
return Object.keys(EXTENSION_REGISTRY);
|
||||
}
|
||||
|
||||
const cloneConfig = (config: any) => {
|
||||
|
||||
@@ -12,9 +12,8 @@ const extensionManager = new Manager();
|
||||
/**
|
||||
* 异步创建动态扩展
|
||||
* 确保扩展配置已加载
|
||||
* @param _documentId 可选的文档ID,用于提前初始化视图
|
||||
*/
|
||||
export const createDynamicExtensions = async (_documentId?: number): Promise<Extension[]> => {
|
||||
export const createDynamicExtensions = async (): Promise<Extension[]> => {
|
||||
const extensionStore = useExtensionStore();
|
||||
|
||||
// 注册所有扩展工厂
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import {Compartment, Extension} from '@codemirror/state';
|
||||
import {EditorView} from '@codemirror/view';
|
||||
import {Extension as ExtensionConfig, ExtensionID} from '@/../bindings/voidraft/internal/models/models';
|
||||
import {Extension as ExtensionConfig} from '@/../bindings/voidraft/internal/models/ent/models';
|
||||
import {ExtensionDefinition, ExtensionState} from './types';
|
||||
|
||||
/**
|
||||
@@ -8,10 +8,10 @@ import {ExtensionDefinition, ExtensionState} from './types';
|
||||
* 负责注册、初始化与同步所有动态扩展
|
||||
*/
|
||||
export class Manager {
|
||||
private extensionStates = new Map<ExtensionID, ExtensionState>();
|
||||
private extensionStates = new Map<string, ExtensionState>();
|
||||
private views = new Map<number, EditorView>();
|
||||
|
||||
registerExtension(id: ExtensionID, definition: ExtensionDefinition): void {
|
||||
registerExtension(id: string, definition: ExtensionDefinition): void {
|
||||
const existingState = this.extensionStates.get(id);
|
||||
if (existingState) {
|
||||
existingState.definition = definition;
|
||||
@@ -34,10 +34,11 @@ export class Manager {
|
||||
|
||||
initExtensions(extensionConfigs: ExtensionConfig[]): void {
|
||||
for (const config of extensionConfigs) {
|
||||
const state = this.extensionStates.get(config.id);
|
||||
if (!config.key) continue;
|
||||
const state = this.extensionStates.get(config.key);
|
||||
if (!state) continue;
|
||||
const resolvedConfig = this.cloneConfig(config.config ?? state.definition.defaultConfig ?? {});
|
||||
this.commitExtensionState(state, config.enabled, resolvedConfig);
|
||||
this.commitExtensionState(state, config.enabled ?? false, resolvedConfig);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,7 +55,7 @@ export class Manager {
|
||||
this.applyAllExtensionsToView(view);
|
||||
}
|
||||
|
||||
updateExtension(id: ExtensionID, enabled: boolean, config?: any): void {
|
||||
updateExtension(id: string, enabled: boolean, config?: any): void {
|
||||
const state = this.extensionStates.get(id);
|
||||
if (!state) return;
|
||||
|
||||
@@ -93,7 +94,7 @@ export class Manager {
|
||||
}
|
||||
}
|
||||
|
||||
private applyExtensionToAllViews(id: ExtensionID): void {
|
||||
private applyExtensionToAllViews(id: string): void {
|
||||
const state = this.extensionStates.get(id);
|
||||
if (!state) return;
|
||||
|
||||
|
||||
@@ -1 +1,23 @@
|
||||
import {Compartment, Extension} from '@codemirror/state';
|
||||
import {Compartment, Extension} from '@codemirror/state';
|
||||
|
||||
/**
|
||||
* 扩展定义
|
||||
* 标准化 create 方法和默认配置
|
||||
*/
|
||||
export interface ExtensionDefinition {
|
||||
create(config: any): Extension
|
||||
defaultConfig: Record<string, any>
|
||||
}
|
||||
|
||||
/**
|
||||
* 扩展运行时状态
|
||||
*/
|
||||
export interface ExtensionState {
|
||||
id: string // 扩展 key
|
||||
definition: ExtensionDefinition
|
||||
config: any
|
||||
enabled: boolean
|
||||
compartment: Compartment
|
||||
extension: Extension
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type {ThemeColors} from './types';
|
||||
import {ThemeType} from '@/../bindings/voidraft/internal/models/models';
|
||||
import {Type as ThemeType} from '@/../bindings/voidraft/internal/models/ent/theme/models';
|
||||
import {defaultDarkColors} from './dark/default-dark';
|
||||
import {defaultLightColors} from './light/default-light';
|
||||
import {config as draculaColors} from './dark/dracula';
|
||||
@@ -24,20 +24,20 @@ export interface ThemePreset {
|
||||
export const FALLBACK_THEME_NAME = defaultDarkColors.themeName;
|
||||
|
||||
export const themePresetList: ThemePreset[] = [
|
||||
{name: defaultDarkColors.themeName, type: ThemeType.ThemeTypeDark, colors: defaultDarkColors},
|
||||
{name: draculaColors.themeName, type: ThemeType.ThemeTypeDark, colors: draculaColors},
|
||||
{name: auraColors.themeName, type: ThemeType.ThemeTypeDark, colors: auraColors},
|
||||
{name: githubDarkColors.themeName, type: ThemeType.ThemeTypeDark, colors: githubDarkColors},
|
||||
{name: materialDarkColors.themeName, type: ThemeType.ThemeTypeDark, colors: materialDarkColors},
|
||||
{name: oneDarkColors.themeName, type: ThemeType.ThemeTypeDark, colors: oneDarkColors},
|
||||
{name: solarizedDarkColors.themeName, type: ThemeType.ThemeTypeDark, colors: solarizedDarkColors},
|
||||
{name: tokyoNightColors.themeName, type: ThemeType.ThemeTypeDark, colors: tokyoNightColors},
|
||||
{name: tokyoNightStormColors.themeName, type: ThemeType.ThemeTypeDark, colors: tokyoNightStormColors},
|
||||
{name: defaultLightColors.themeName, type: ThemeType.ThemeTypeLight, colors: defaultLightColors},
|
||||
{name: githubLightColors.themeName, type: ThemeType.ThemeTypeLight, colors: githubLightColors},
|
||||
{name: materialLightColors.themeName, type: ThemeType.ThemeTypeLight, colors: materialLightColors},
|
||||
{name: solarizedLightColors.themeName, type: ThemeType.ThemeTypeLight, colors: solarizedLightColors},
|
||||
{name: tokyoNightDayColors.themeName, type: ThemeType.ThemeTypeLight, colors: tokyoNightDayColors},
|
||||
{name: defaultDarkColors.themeName, type: ThemeType.TypeDark, colors: defaultDarkColors},
|
||||
{name: draculaColors.themeName, type: ThemeType.TypeDark, colors: draculaColors},
|
||||
{name: auraColors.themeName, type: ThemeType.TypeDark, colors: auraColors},
|
||||
{name: githubDarkColors.themeName, type: ThemeType.TypeDark, colors: githubDarkColors},
|
||||
{name: materialDarkColors.themeName, type: ThemeType.TypeDark, colors: materialDarkColors},
|
||||
{name: oneDarkColors.themeName, type: ThemeType.TypeDark, colors: oneDarkColors},
|
||||
{name: solarizedDarkColors.themeName, type: ThemeType.TypeDark, colors: solarizedDarkColors},
|
||||
{name: tokyoNightColors.themeName, type: ThemeType.TypeDark, colors: tokyoNightColors},
|
||||
{name: tokyoNightStormColors.themeName, type: ThemeType.TypeDark, colors: tokyoNightStormColors},
|
||||
{name: defaultLightColors.themeName, type: ThemeType.TypeLight, colors: defaultLightColors},
|
||||
{name: githubLightColors.themeName, type: ThemeType.TypeLight, colors: githubLightColors},
|
||||
{name: materialLightColors.themeName, type: ThemeType.TypeLight, colors: materialLightColors},
|
||||
{name: solarizedLightColors.themeName, type: ThemeType.TypeLight, colors: solarizedLightColors},
|
||||
{name: tokyoNightDayColors.themeName, type: ThemeType.TypeLight, colors: tokyoNightDayColors},
|
||||
];
|
||||
|
||||
export const themePresetMap: Record<string, ThemePreset> = themePresetList.reduce(
|
||||
|
||||
@@ -7,7 +7,7 @@ import SettingSection from '../components/SettingSection.vue';
|
||||
import SettingItem from '../components/SettingItem.vue';
|
||||
import { SystemThemeType, LanguageType } from '@/../bindings/voidraft/internal/models/models';
|
||||
import { createDebounce } from '@/common/utils/debounce';
|
||||
import { createTimerManager } from '@/common/utils/timerUtils';
|
||||
import { useConfirm } from '@/composables/useConfirm';
|
||||
import PickColors from 'vue-pick-colors';
|
||||
import type { ThemeColors } from '@/views/editor/theme/types';
|
||||
|
||||
@@ -21,31 +21,22 @@ const { debouncedFn: debouncedUpdateColor } = createDebounce(
|
||||
{ delay: 100 }
|
||||
);
|
||||
|
||||
const { debouncedFn: debouncedResetTheme } = createDebounce(
|
||||
async () => {
|
||||
const success = await themeStore.resetCurrentTheme();
|
||||
|
||||
if (success) {
|
||||
// 重新加载临时颜色
|
||||
syncTempColors();
|
||||
hasUnsavedChanges.value = false;
|
||||
}
|
||||
},
|
||||
{ delay: 300 }
|
||||
);
|
||||
|
||||
// 创建定时器管理器
|
||||
const resetTimer = createTimerManager();
|
||||
|
||||
// 临时颜色状态(用于编辑)
|
||||
const tempColors = ref<ThemeColors | null>(null);
|
||||
|
||||
// 标记是否有未保存的更改
|
||||
const hasUnsavedChanges = ref(false);
|
||||
|
||||
// 重置按钮状态
|
||||
const resetButtonState = ref({
|
||||
confirming: false
|
||||
// 重置主题确认
|
||||
const { isConfirming: isResetConfirming, requestConfirm: requestResetConfirm } = useConfirm({
|
||||
timeout: 3000,
|
||||
onConfirm: async () => {
|
||||
const success = await themeStore.resetCurrentTheme();
|
||||
if (success) {
|
||||
syncTempColors();
|
||||
hasUnsavedChanges.value = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 当前选中的主题名称
|
||||
@@ -125,23 +116,6 @@ const toggleSearch = async () => {
|
||||
}
|
||||
};
|
||||
|
||||
// 处理重置按钮点击
|
||||
const handleResetClick = () => {
|
||||
if (resetButtonState.value.confirming) {
|
||||
|
||||
debouncedResetTheme();
|
||||
|
||||
resetButtonState.value.confirming = false;
|
||||
resetTimer.clear();
|
||||
} else {
|
||||
resetButtonState.value.confirming = true;
|
||||
// 设置3秒后自动恢复
|
||||
resetTimer.set(() => {
|
||||
resetButtonState.value.confirming = false;
|
||||
}, 3000);
|
||||
}
|
||||
};
|
||||
|
||||
// 更新本地颜色配置
|
||||
const updateLocalColor = (colorKey: string, value: string) => {
|
||||
if (!tempColors.value) return;
|
||||
@@ -295,10 +269,10 @@ const handlePickerClose = () => {
|
||||
</button>
|
||||
<button
|
||||
v-if="!hasUnsavedChanges"
|
||||
:class="['reset-button', resetButtonState.confirming ? 'reset-button-confirming' : '']"
|
||||
@click="handleResetClick"
|
||||
:class="['reset-button', isResetConfirming('theme') ? 'reset-button-confirming' : '']"
|
||||
@click="requestResetConfirm('theme')"
|
||||
>
|
||||
{{ resetButtonState.confirming ? t('settings.confirmReset') : t('settings.resetToDefault') }}
|
||||
{{ isResetConfirming('theme') ? t('settings.confirmReset') : t('settings.resetToDefault') }}
|
||||
</button>
|
||||
<template v-else>
|
||||
<button class="apply-button" @click="applyChanges">
|
||||
|
||||
@@ -4,7 +4,6 @@ import {useI18n} from 'vue-i18n';
|
||||
import {useEditorStore} from '@/stores/editorStore';
|
||||
import {useExtensionStore} from '@/stores/extensionStore';
|
||||
import {ExtensionService} from '@/../bindings/voidraft/internal/services';
|
||||
import {ExtensionID} from '@/../bindings/voidraft/internal/models/models';
|
||||
import {
|
||||
getAllExtensionIds,
|
||||
getExtensionDefaultConfig,
|
||||
@@ -21,59 +20,58 @@ const editorStore = useEditorStore();
|
||||
const extensionStore = useExtensionStore();
|
||||
|
||||
// 展开状态管理
|
||||
const expandedExtensions = ref<Set<ExtensionID>>(new Set());
|
||||
const expandedExtensions = ref<Set<string>>(new Set());
|
||||
|
||||
// 获取所有可用的扩展
|
||||
const availableExtensions = computed(() => {
|
||||
return getAllExtensionIds().map(id => {
|
||||
const extension = extensionStore.extensions.find(ext => ext.id === id);
|
||||
return getAllExtensionIds().map(key => {
|
||||
const extension = extensionStore.extensions.find(ext => ext.key === key);
|
||||
return {
|
||||
id,
|
||||
displayName: getExtensionDisplayName(id),
|
||||
description: getExtensionDescription(id),
|
||||
id: key,
|
||||
displayName: getExtensionDisplayName(key),
|
||||
description: getExtensionDescription(key),
|
||||
enabled: extension?.enabled || false,
|
||||
isDefault: extension?.isDefault || false,
|
||||
hasConfig: hasExtensionConfig(id),
|
||||
hasConfig: hasExtensionConfig(key),
|
||||
config: extension?.config || {},
|
||||
defaultConfig: getExtensionDefaultConfig(id)
|
||||
defaultConfig: getExtensionDefaultConfig(key)
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
// 切换展开状态
|
||||
const toggleExpanded = (extensionId: ExtensionID) => {
|
||||
if (expandedExtensions.value.has(extensionId)) {
|
||||
expandedExtensions.value.delete(extensionId);
|
||||
const toggleExpanded = (extensionKey: string) => {
|
||||
if (expandedExtensions.value.has(extensionKey)) {
|
||||
expandedExtensions.value.delete(extensionKey);
|
||||
} else {
|
||||
expandedExtensions.value.add(extensionId);
|
||||
expandedExtensions.value.add(extensionKey);
|
||||
}
|
||||
};
|
||||
|
||||
// 更新扩展状态
|
||||
const updateExtension = async (extensionId: ExtensionID, enabled: boolean) => {
|
||||
const updateExtension = async (extensionKey: string, enabled: boolean) => {
|
||||
try {
|
||||
await editorStore.updateExtension(extensionId, enabled);
|
||||
await editorStore.updateExtension(extensionKey, enabled);
|
||||
} catch (error) {
|
||||
console.error('Failed to update extension:', error);
|
||||
}
|
||||
};
|
||||
|
||||
// 更新扩展配置
|
||||
const updateExtensionConfig = async (extensionId: ExtensionID, configKey: string, value: any) => {
|
||||
const updateExtensionConfig = async (extensionKey: string, configKey: string, value: any) => {
|
||||
try {
|
||||
// 获取当前扩展状态
|
||||
const extension = extensionStore.extensions.find(ext => ext.id === extensionId);
|
||||
const extension = extensionStore.extensions.find(ext => ext.key === extensionKey);
|
||||
if (!extension) return;
|
||||
|
||||
// 更新配置
|
||||
const updatedConfig = {...extension.config};
|
||||
const updatedConfig = {...(extension.config || {})};
|
||||
if (value === undefined) {
|
||||
delete updatedConfig[configKey];
|
||||
} else {
|
||||
updatedConfig[configKey] = value;
|
||||
}
|
||||
// 使用editorStore的updateExtension方法更新,确保应用到所有编辑器实例
|
||||
await editorStore.updateExtension(extensionId, extension.enabled, updatedConfig);
|
||||
await editorStore.updateExtension(extensionKey, extension.enabled ?? false, updatedConfig);
|
||||
|
||||
} catch (error) {
|
||||
console.error('Failed to update extension config:', error);
|
||||
@@ -81,19 +79,19 @@ const updateExtensionConfig = async (extensionId: ExtensionID, configKey: string
|
||||
};
|
||||
|
||||
// 重置扩展到默认配置
|
||||
const resetExtension = async (extensionId: ExtensionID) => {
|
||||
const resetExtension = async (extensionKey: string) => {
|
||||
try {
|
||||
// 重置到默认配置
|
||||
await ExtensionService.ResetExtensionToDefault(extensionId);
|
||||
await ExtensionService.ResetExtensionConfig(extensionKey);
|
||||
|
||||
// 重新加载扩展状态以获取最新配置
|
||||
await extensionStore.loadExtensions();
|
||||
|
||||
// 获取重置后的状态,立即应用到所有编辑器视图
|
||||
const extension = extensionStore.extensions.find(ext => ext.id === extensionId);
|
||||
const extension = extensionStore.extensions.find(ext => ext.key === extensionKey);
|
||||
if (extension) {
|
||||
// 通过editorStore更新,确保所有视图都能同步
|
||||
await editorStore.updateExtension(extensionId, extension.enabled, extension.config);
|
||||
await editorStore.updateExtension(extensionKey, extension.enabled ?? false, extension.config);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to reset extension:', error);
|
||||
@@ -127,7 +125,7 @@ const formatConfigValue = (value: any): string => {
|
||||
|
||||
|
||||
const handleConfigInput = async (
|
||||
extensionId: ExtensionID,
|
||||
extensionKey: string,
|
||||
configKey: string,
|
||||
defaultValue: any,
|
||||
event: Event
|
||||
@@ -137,15 +135,15 @@ const handleConfigInput = async (
|
||||
const rawValue = target.value;
|
||||
const trimmedValue = rawValue.trim();
|
||||
if (!trimmedValue.length) {
|
||||
await updateExtensionConfig(extensionId, configKey, undefined);
|
||||
await updateExtensionConfig(extensionKey, configKey, undefined);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const parsedValue = JSON.parse(trimmedValue);
|
||||
await updateExtensionConfig(extensionId, configKey, parsedValue);
|
||||
await updateExtensionConfig(extensionKey, configKey, parsedValue);
|
||||
} catch (_error) {
|
||||
const extension = extensionStore.extensions.find(ext => ext.id === extensionId);
|
||||
const extension = extensionStore.extensions.find(ext => ext.key === extensionKey);
|
||||
const fallbackValue = getConfigValue(extension?.config, configKey, defaultValue);
|
||||
target.value = formatConfigValue(fallbackValue);
|
||||
|
||||
|
||||
@@ -2,133 +2,67 @@
|
||||
import {useConfigStore} from '@/stores/configStore';
|
||||
import {useTabStore} from '@/stores/tabStore';
|
||||
import {useI18n} from 'vue-i18n';
|
||||
import {computed, onUnmounted, ref} from 'vue';
|
||||
import {computed, ref} from 'vue';
|
||||
import SettingSection from '../components/SettingSection.vue';
|
||||
import SettingItem from '../components/SettingItem.vue';
|
||||
import ToggleSwitch from '../components/ToggleSwitch.vue';
|
||||
import {
|
||||
DialogService,
|
||||
MigrationProgress,
|
||||
MigrationService,
|
||||
MigrationStatus
|
||||
} from '@/../bindings/voidraft/internal/services';
|
||||
import {DialogService, MigrationService} from '@/../bindings/voidraft/internal/services';
|
||||
import {useSystemStore} from "@/stores/systemStore";
|
||||
import {useConfirm, usePolling} from '@/composables';
|
||||
|
||||
const {t} = useI18n();
|
||||
const configStore = useConfigStore();
|
||||
const systemStore = useSystemStore();
|
||||
const tabStore = useTabStore();
|
||||
// 迁移进度状态
|
||||
const migrationProgress = ref<MigrationProgress>(new MigrationProgress({
|
||||
status: MigrationStatus.MigrationStatusCompleted,
|
||||
progress: 0
|
||||
}));
|
||||
|
||||
// 轮询相关
|
||||
let pollingTimer: number | null = null;
|
||||
const isPolling = ref(false);
|
||||
|
||||
// 进度条显示控制
|
||||
const showProgress = ref(false);
|
||||
const progressError = ref('');
|
||||
let hideProgressTimer: any = null;
|
||||
const showBar = ref(false);
|
||||
const manualError = ref(''); // 用于捕获 MigrateDirectory 抛出的错误
|
||||
let hideTimer = 0;
|
||||
|
||||
// 开始轮询迁移进度
|
||||
const startPolling = () => {
|
||||
if (isPolling.value) return;
|
||||
|
||||
isPolling.value = true;
|
||||
showProgress.value = true;
|
||||
progressError.value = '';
|
||||
|
||||
// 立即重置迁移进度状态,避免从之前的失败状态渐变
|
||||
migrationProgress.value = new MigrationProgress({
|
||||
status: MigrationStatus.MigrationStatusMigrating,
|
||||
progress: 0
|
||||
});
|
||||
|
||||
pollingTimer = window.setInterval(async () => {
|
||||
try {
|
||||
const progress = await MigrationService.GetProgress();
|
||||
migrationProgress.value = progress;
|
||||
|
||||
const {status, error} = progress;
|
||||
const isCompleted = [MigrationStatus.MigrationStatusCompleted, MigrationStatus.MigrationStatusFailed].includes(status);
|
||||
|
||||
if (isCompleted) {
|
||||
stopPolling();
|
||||
|
||||
// 设置错误信息(如果是失败状态)
|
||||
progressError.value = (status === MigrationStatus.MigrationStatusFailed) ? (error || 'Migration failed') : '';
|
||||
|
||||
const delay = status === MigrationStatus.MigrationStatusCompleted ? 3000 : 5000;
|
||||
hideProgressTimer = setTimeout(hideProgress, delay);
|
||||
// 轮询迁移进度
|
||||
const {data: progress, error: pollError, isActive: migrating, start, stop, reset} = usePolling(
|
||||
() => MigrationService.GetProgress(),
|
||||
{
|
||||
interval: 300,
|
||||
shouldStop: ({progress, error}) => !!error || progress >= 100,
|
||||
onStop: () => {
|
||||
const hasError = pollError.value || progress.value?.error;
|
||||
hideTimer = window.setTimeout(hideAll, hasError ? 5000 : 3000);
|
||||
}
|
||||
} catch (_error) {
|
||||
stopPolling();
|
||||
|
||||
// 使用常量简化错误处理
|
||||
const errorMsg = 'Failed to get migration progress';
|
||||
Object.assign(migrationProgress.value, {
|
||||
status: MigrationStatus.MigrationStatusFailed,
|
||||
progress: 0,
|
||||
error: errorMsg
|
||||
});
|
||||
progressError.value = errorMsg;
|
||||
|
||||
hideProgressTimer = setTimeout(hideProgress, 5000);
|
||||
}
|
||||
}, 200);
|
||||
};
|
||||
|
||||
// 停止轮询
|
||||
const stopPolling = () => {
|
||||
if (pollingTimer) {
|
||||
clearInterval(pollingTimer);
|
||||
pollingTimer = null;
|
||||
}
|
||||
isPolling.value = false;
|
||||
};
|
||||
|
||||
// 隐藏进度条
|
||||
const hideProgress = () => {
|
||||
showProgress.value = false;
|
||||
progressError.value = '';
|
||||
|
||||
// 重置迁移状态,避免下次显示时状态不正确
|
||||
migrationProgress.value = new MigrationProgress({
|
||||
status: MigrationStatus.MigrationStatusCompleted,
|
||||
progress: 0
|
||||
});
|
||||
|
||||
if (hideProgressTimer) {
|
||||
clearTimeout(hideProgressTimer);
|
||||
hideProgressTimer = null;
|
||||
}
|
||||
};
|
||||
|
||||
// 简化的迁移状态管理
|
||||
const isMigrating = computed(() => migrationProgress.value.status === MigrationStatus.MigrationStatusMigrating);
|
||||
|
||||
// 进度条样式 - 使用 Map 简化条件判断
|
||||
const statusClassMap = new Map([
|
||||
[MigrationStatus.MigrationStatusMigrating, 'migrating'],
|
||||
[MigrationStatus.MigrationStatusCompleted, 'success'],
|
||||
[MigrationStatus.MigrationStatusFailed, 'error']
|
||||
]);
|
||||
|
||||
const progressBarClass = computed(() =>
|
||||
showProgress.value ? statusClassMap.get(migrationProgress.value.status) ?? '' : ''
|
||||
);
|
||||
|
||||
const progressBarWidth = computed(() => {
|
||||
if (!showProgress.value) return '0%';
|
||||
return isMigrating.value ? `${migrationProgress.value.progress}%` : '100%';
|
||||
// 派生状态
|
||||
const migrationError = computed(() => manualError.value || pollError.value || progress.value?.error || '');
|
||||
const currentProgress = computed(() => progress.value?.progress ?? 0);
|
||||
|
||||
const barClass = computed(() => {
|
||||
if (!showBar.value) return '';
|
||||
return migrationError.value ? 'error' : currentProgress.value >= 100 ? 'success' : 'migrating';
|
||||
});
|
||||
|
||||
// 重置确认状态
|
||||
const resetConfirmState = ref<'idle' | 'confirming'>('idle');
|
||||
let resetConfirmTimer: any = null;
|
||||
const barWidth = computed(() => {
|
||||
if (!showBar.value) return '0%';
|
||||
return (migrationError.value || currentProgress.value >= 100) ? '100%' : `${currentProgress.value}%`;
|
||||
});
|
||||
|
||||
// 隐藏进度条并清除所有状态
|
||||
const hideAll = () => {
|
||||
clearTimeout(hideTimer);
|
||||
hideTimer = 0;
|
||||
showBar.value = false;
|
||||
manualError.value = '';
|
||||
reset(); // 清除轮询状态
|
||||
};
|
||||
|
||||
// 重置设置确认
|
||||
const {isConfirming: isResetConfirming, requestConfirm: requestResetConfirm} = useConfirm({
|
||||
timeout: 3000,
|
||||
onConfirm: async () => {
|
||||
await configStore.resetConfig();
|
||||
}
|
||||
});
|
||||
|
||||
// 可选键列表
|
||||
const keyOptions = [
|
||||
@@ -201,7 +135,7 @@ const modifierKeys = computed(() => ({
|
||||
win: configStore.config.general.globalHotkey.win
|
||||
}));
|
||||
|
||||
// 主键配置 - 只读计算属性
|
||||
// 主键配置
|
||||
const selectedKey = computed(() => configStore.config.general.globalHotkey.key);
|
||||
|
||||
// 切换修饰键
|
||||
@@ -218,29 +152,8 @@ const updateSelectedKey = (event: Event) => {
|
||||
configStore.setGlobalHotkey(newHotkey);
|
||||
};
|
||||
|
||||
// 重置设置
|
||||
const resetSettings = () => {
|
||||
if (resetConfirmState.value === 'idle') {
|
||||
// 第一次点击,进入确认状态
|
||||
resetConfirmState.value = 'confirming';
|
||||
// 3秒后自动返回idle状态
|
||||
resetConfirmTimer = setTimeout(() => {
|
||||
resetConfirmState.value = 'idle';
|
||||
}, 3000);
|
||||
} else if (resetConfirmState.value === 'confirming') {
|
||||
// 第二次点击,执行重置
|
||||
clearTimeout(resetConfirmTimer);
|
||||
resetConfirmState.value = 'idle';
|
||||
confirmReset();
|
||||
}
|
||||
};
|
||||
|
||||
// 确认重置
|
||||
const confirmReset = async () => {
|
||||
await configStore.resetConfig();
|
||||
};
|
||||
|
||||
// 计算热键预览文本 - 使用现代语法简化
|
||||
// 计算热键预览文本
|
||||
const hotkeyPreview = computed(() => {
|
||||
if (!enableGlobalHotkey.value) return '';
|
||||
|
||||
@@ -261,54 +174,30 @@ const currentDataPath = computed(() => configStore.config.general.dataPath);
|
||||
|
||||
// 选择数据存储目录
|
||||
const selectDataDirectory = async () => {
|
||||
if (isMigrating.value) return;
|
||||
|
||||
if (migrating.value) return;
|
||||
|
||||
const selectedPath = await DialogService.SelectDirectory();
|
||||
if (!selectedPath?.trim() || selectedPath === currentDataPath.value) return;
|
||||
|
||||
// 检查用户是否取消了选择或路径为空
|
||||
if (!selectedPath || !selectedPath.trim() || selectedPath === currentDataPath.value) {
|
||||
return;
|
||||
}
|
||||
const oldPath = currentDataPath.value;
|
||||
const newPath = selectedPath.trim();
|
||||
const [oldPath, newPath] = [currentDataPath.value, selectedPath.trim()];
|
||||
|
||||
// 清除之前的进度状态
|
||||
hideProgress();
|
||||
// 清除之前的状态并开始轮询
|
||||
hideAll();
|
||||
showBar.value = true;
|
||||
manualError.value = '';
|
||||
start();
|
||||
|
||||
// 开始轮询迁移进度
|
||||
startPolling();
|
||||
|
||||
// 开始迁移
|
||||
try {
|
||||
await MigrationService.MigrateDirectory(oldPath, newPath);
|
||||
await configStore.setDataPath(newPath);
|
||||
} catch (error) {
|
||||
stopPolling();
|
||||
|
||||
// 使用解构和默认值简化错误处理
|
||||
const errorMsg = error?.toString() || 'Migration failed';
|
||||
showProgress.value = true;
|
||||
|
||||
Object.assign(migrationProgress.value, {
|
||||
status: MigrationStatus.MigrationStatusFailed,
|
||||
progress: 0,
|
||||
error: errorMsg
|
||||
});
|
||||
progressError.value = errorMsg;
|
||||
|
||||
hideProgressTimer = setTimeout(hideProgress, 5000);
|
||||
} catch (e) {
|
||||
stop();
|
||||
// 设置手动捕获的错误(当轮询还没获取到错误时)
|
||||
manualError.value = String(e).replace(/^Error:\s*/i, '') || 'Migration failed';
|
||||
showBar.value = true;
|
||||
hideTimer = window.setTimeout(hideAll, 5000);
|
||||
}
|
||||
};
|
||||
|
||||
// 清理定时器
|
||||
onUnmounted(() => {
|
||||
stopPolling();
|
||||
hideProgress();
|
||||
if (resetConfirmTimer) {
|
||||
clearTimeout(resetConfirmTimer);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -394,24 +283,15 @@ onUnmounted(() => {
|
||||
class="path-display-input"
|
||||
@click="selectDataDirectory"
|
||||
:title="t('settings.clickToSelectPath')"
|
||||
:disabled="isMigrating"
|
||||
:disabled="migrating"
|
||||
/>
|
||||
<!-- 简洁的进度条 -->
|
||||
<div
|
||||
class="progress-bar"
|
||||
:class="[
|
||||
{ 'active': showProgress },
|
||||
progressBarClass
|
||||
]"
|
||||
:style="{ width: progressBarWidth }"
|
||||
></div>
|
||||
<!-- 进度条 -->
|
||||
<div class="progress-bar" :class="[{'active': showBar}, barClass]" :style="{width: barWidth}"/>
|
||||
</div>
|
||||
|
||||
<!-- 错误提示 -->
|
||||
<Transition name="error-fade">
|
||||
<div v-if="progressError" class="progress-error">
|
||||
{{ progressError }}
|
||||
</div>
|
||||
<div v-if="migrationError" class="progress-error">{{ migrationError }}</div>
|
||||
</Transition>
|
||||
</div>
|
||||
</div>
|
||||
@@ -421,15 +301,10 @@ onUnmounted(() => {
|
||||
<SettingItem :title="t('settings.resetAllSettings')">
|
||||
<button
|
||||
class="reset-button"
|
||||
:class="{ 'confirming': resetConfirmState === 'confirming' }"
|
||||
@click="resetSettings"
|
||||
:class="{ 'confirming': isResetConfirming('reset') }"
|
||||
@click="requestResetConfirm('reset')"
|
||||
>
|
||||
<template v-if="resetConfirmState === 'idle'">
|
||||
{{ t('settings.reset') }}
|
||||
</template>
|
||||
<template v-else-if="resetConfirmState === 'confirming'">
|
||||
{{ t('settings.confirmReset') }}
|
||||
</template>
|
||||
{{ isResetConfirming('reset') ? t('settings.confirmReset') : t('settings.reset') }}
|
||||
</button>
|
||||
</SettingItem>
|
||||
</SettingSection>
|
||||
@@ -656,11 +531,8 @@ onUnmounted(() => {
|
||||
}
|
||||
|
||||
.progress-error {
|
||||
margin-top: 6px;
|
||||
font-size: 12px;
|
||||
color: #ef4444;
|
||||
padding: 0 2px;
|
||||
line-height: 1.4;
|
||||
opacity: 1;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import { useKeybindingStore } from '@/stores/keybindingStore';
|
||||
import { useExtensionStore } from '@/stores/extensionStore';
|
||||
import { useSystemStore } from '@/stores/systemStore';
|
||||
import { getCommandDescription } from '@/views/editor/keymap/commands';
|
||||
import {KeyBindingCommand} from "@/../bindings/voidraft/internal/models";
|
||||
import { KeyBindingKey } from '@/../bindings/voidraft/internal/models/models';
|
||||
|
||||
const { t } = useI18n();
|
||||
const keybindingStore = useKeybindingStore();
|
||||
@@ -25,21 +25,21 @@ const keyBindings = computed(() => {
|
||||
const enabledExtensionIds = new Set(extensionStore.enabledExtensionIds);
|
||||
|
||||
return keybindingStore.keyBindings
|
||||
.filter(kb => kb.enabled && enabledExtensionIds.has(kb.extension))
|
||||
.filter(kb => kb.enabled && (!kb.extension || enabledExtensionIds.has(kb.extension)))
|
||||
.map(kb => ({
|
||||
id: kb.command,
|
||||
keys: parseKeyBinding(kb.key, kb.command),
|
||||
category: kb.extension,
|
||||
description: getCommandDescription(kb.command) || kb.command
|
||||
id: kb.key,
|
||||
keys: parseKeyBinding(kb.command || '', kb.key),
|
||||
category: kb.extension || '',
|
||||
description: kb.key ? (getCommandDescription(kb.key) || kb.key) : ''
|
||||
}));
|
||||
});
|
||||
|
||||
// 解析快捷键字符串为显示数组
|
||||
const parseKeyBinding = (keyStr: string, command?: string): string[] => {
|
||||
const parseKeyBinding = (keyStr: string, keyBindingKey?: string): string[] => {
|
||||
if (!keyStr) return [];
|
||||
|
||||
// 特殊处理重做快捷键的操作系统差异
|
||||
if (command === KeyBindingCommand.HistoryRedoCommand && keyStr === 'Mod-Shift-z') {
|
||||
if (keyBindingKey === KeyBindingKey.HistoryRedoKeyBindingKey && keyStr === 'Mod-Shift-z') {
|
||||
if (systemStore.isMacOS) {
|
||||
return ['⌘', '⇧', 'Z']; // macOS: Cmd+Shift+Z
|
||||
} else {
|
||||
@@ -48,7 +48,7 @@ const parseKeyBinding = (keyStr: string, command?: string): string[] => {
|
||||
}
|
||||
|
||||
// 特殊处理重做选择快捷键的操作系统差异
|
||||
if (command === KeyBindingCommand.HistoryRedoSelectionCommand && keyStr === 'Mod-Shift-u') {
|
||||
if (keyBindingKey === KeyBindingKey.HistoryRedoSelectionKeyBindingKey && keyStr === 'Mod-Shift-u') {
|
||||
if (systemStore.isMacOS) {
|
||||
return ['⌘', '⇧', 'U']; // macOS: Cmd+Shift+U
|
||||
} else {
|
||||
@@ -57,7 +57,7 @@ const parseKeyBinding = (keyStr: string, command?: string): string[] => {
|
||||
}
|
||||
|
||||
// 特殊处理代码折叠快捷键的操作系统差异
|
||||
if (command === KeyBindingCommand.FoldCodeCommand && keyStr === 'Ctrl-Shift-[') {
|
||||
if (keyBindingKey === KeyBindingKey.FoldCodeKeyBindingKey && keyStr === 'Ctrl-Shift-[') {
|
||||
if (systemStore.isMacOS) {
|
||||
return ['⌘', '⌥', '[']; // macOS: Cmd+Alt+[
|
||||
} else {
|
||||
@@ -65,7 +65,7 @@ const parseKeyBinding = (keyStr: string, command?: string): string[] => {
|
||||
}
|
||||
}
|
||||
|
||||
if (command === KeyBindingCommand.UnfoldCodeCommand && keyStr === 'Ctrl-Shift-]') {
|
||||
if (keyBindingKey === KeyBindingKey.UnfoldCodeKeyBindingKey && keyStr === 'Ctrl-Shift-]') {
|
||||
if (systemStore.isMacOS) {
|
||||
return ['⌘', '⌥', ']']; // macOS: Cmd+Alt+]
|
||||
} else {
|
||||
@@ -74,7 +74,7 @@ const parseKeyBinding = (keyStr: string, command?: string): string[] => {
|
||||
}
|
||||
|
||||
// 特殊处理编辑快捷键的操作系统差异
|
||||
if (command === KeyBindingCommand.CursorSyntaxLeftCommand && keyStr === 'Alt-ArrowLeft') {
|
||||
if (keyBindingKey === KeyBindingKey.CursorSyntaxLeftKeyBindingKey && keyStr === 'Alt-ArrowLeft') {
|
||||
if (systemStore.isMacOS) {
|
||||
return ['Ctrl', '←']; // macOS: Ctrl+ArrowLeft
|
||||
} else {
|
||||
@@ -82,7 +82,7 @@ const parseKeyBinding = (keyStr: string, command?: string): string[] => {
|
||||
}
|
||||
}
|
||||
|
||||
if (command === KeyBindingCommand.CursorSyntaxRightCommand && keyStr === 'Alt-ArrowRight') {
|
||||
if (keyBindingKey === KeyBindingKey.CursorSyntaxRightKeyBindingKey && keyStr === 'Alt-ArrowRight') {
|
||||
if (systemStore.isMacOS) {
|
||||
return ['Ctrl', '→']; // macOS: Ctrl+ArrowRight
|
||||
} else {
|
||||
@@ -90,7 +90,7 @@ const parseKeyBinding = (keyStr: string, command?: string): string[] => {
|
||||
}
|
||||
}
|
||||
|
||||
if (command === KeyBindingCommand.InsertBlankLineCommand && keyStr === 'Ctrl-Enter') {
|
||||
if (keyBindingKey === KeyBindingKey.InsertBlankLineKeyBindingKey && keyStr === 'Ctrl-Enter') {
|
||||
if (systemStore.isMacOS) {
|
||||
return ['⌘', 'Enter']; // macOS: Cmd+Enter
|
||||
} else {
|
||||
@@ -98,7 +98,7 @@ const parseKeyBinding = (keyStr: string, command?: string): string[] => {
|
||||
}
|
||||
}
|
||||
|
||||
if (command === KeyBindingCommand.SelectLineCommand && keyStr === 'Alt-l') {
|
||||
if (keyBindingKey === KeyBindingKey.SelectLineKeyBindingKey && keyStr === 'Alt-l') {
|
||||
if (systemStore.isMacOS) {
|
||||
return ['Ctrl', 'L']; // macOS: Ctrl+l
|
||||
} else {
|
||||
@@ -106,7 +106,7 @@ const parseKeyBinding = (keyStr: string, command?: string): string[] => {
|
||||
}
|
||||
}
|
||||
|
||||
if (command === KeyBindingCommand.SelectParentSyntaxCommand && keyStr === 'Ctrl-i') {
|
||||
if (keyBindingKey === KeyBindingKey.SelectParentSyntaxKeyBindingKey && keyStr === 'Ctrl-i') {
|
||||
if (systemStore.isMacOS) {
|
||||
return ['⌘', 'I']; // macOS: Cmd+i
|
||||
} else {
|
||||
@@ -114,7 +114,7 @@ const parseKeyBinding = (keyStr: string, command?: string): string[] => {
|
||||
}
|
||||
}
|
||||
|
||||
if (command === KeyBindingCommand.IndentLessCommand && keyStr === 'Ctrl-[') {
|
||||
if (keyBindingKey === KeyBindingKey.IndentLessKeyBindingKey && keyStr === 'Ctrl-[') {
|
||||
if (systemStore.isMacOS) {
|
||||
return ['⌘', '[']; // macOS: Cmd+[
|
||||
} else {
|
||||
@@ -122,7 +122,7 @@ const parseKeyBinding = (keyStr: string, command?: string): string[] => {
|
||||
}
|
||||
}
|
||||
|
||||
if (command === KeyBindingCommand.IndentMoreCommand && keyStr === 'Ctrl-]') {
|
||||
if (keyBindingKey === KeyBindingKey.IndentMoreKeyBindingKey && keyStr === 'Ctrl-]') {
|
||||
if (systemStore.isMacOS) {
|
||||
return ['⌘', ']']; // macOS: Cmd+]
|
||||
} else {
|
||||
@@ -130,7 +130,7 @@ const parseKeyBinding = (keyStr: string, command?: string): string[] => {
|
||||
}
|
||||
}
|
||||
|
||||
if (command === KeyBindingCommand.IndentSelectionCommand && keyStr === 'Ctrl-Alt-\\') {
|
||||
if (keyBindingKey === KeyBindingKey.IndentSelectionKeyBindingKey && keyStr === 'Ctrl-Alt-\\') {
|
||||
if (systemStore.isMacOS) {
|
||||
return ['⌘', '⌥', '\\']; // macOS: Cmd+Alt+\
|
||||
} else {
|
||||
@@ -138,7 +138,7 @@ const parseKeyBinding = (keyStr: string, command?: string): string[] => {
|
||||
}
|
||||
}
|
||||
|
||||
if (command === KeyBindingCommand.CursorMatchingBracketCommand && keyStr === 'Shift-Ctrl-\\') {
|
||||
if (keyBindingKey === KeyBindingKey.CursorMatchingBracketKeyBindingKey && keyStr === 'Shift-Ctrl-\\') {
|
||||
if (systemStore.isMacOS) {
|
||||
return ['⇧', '⌘', '\\']; // macOS: Shift+Cmd+\
|
||||
} else {
|
||||
@@ -146,7 +146,7 @@ const parseKeyBinding = (keyStr: string, command?: string): string[] => {
|
||||
}
|
||||
}
|
||||
|
||||
if (command === KeyBindingCommand.ToggleCommentCommand && keyStr === 'Ctrl-/') {
|
||||
if (keyBindingKey === KeyBindingKey.ToggleCommentKeyBindingKey && keyStr === 'Ctrl-/') {
|
||||
if (systemStore.isMacOS) {
|
||||
return ['⌘', '/']; // macOS: Cmd+/
|
||||
} else {
|
||||
@@ -155,7 +155,7 @@ const parseKeyBinding = (keyStr: string, command?: string): string[] => {
|
||||
}
|
||||
|
||||
// 特殊处理删除快捷键的操作系统差异
|
||||
if (command === KeyBindingCommand.DeleteGroupBackwardCommand && keyStr === 'Ctrl-Backspace') {
|
||||
if (keyBindingKey === KeyBindingKey.DeleteGroupBackwardKeyBindingKey && keyStr === 'Ctrl-Backspace') {
|
||||
if (systemStore.isMacOS) {
|
||||
return ['⌘', 'Backspace']; // macOS: Cmd+Backspace
|
||||
} else {
|
||||
@@ -163,7 +163,7 @@ const parseKeyBinding = (keyStr: string, command?: string): string[] => {
|
||||
}
|
||||
}
|
||||
|
||||
if (command === KeyBindingCommand.DeleteGroupForwardCommand && keyStr === 'Ctrl-Delete') {
|
||||
if (keyBindingKey === KeyBindingKey.DeleteGroupForwardKeyBindingKey && keyStr === 'Ctrl-Delete') {
|
||||
if (systemStore.isMacOS) {
|
||||
return ['⌘', 'Delete']; // macOS: Cmd+Delete
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user