🚧 Refactor basic services

This commit is contained in:
2025-12-14 02:19:50 +08:00
parent d16905c0a3
commit cc4c2189dc
126 changed files with 18164 additions and 4247 deletions

View File

@@ -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;
}

View File

@@ -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,

View File

@@ -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;

View File

@@ -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);
};

View File

@@ -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);
};
// 导出相关模块

View File

@@ -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);

View File

@@ -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) => {

View File

@@ -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();
// 注册所有扩展工厂

View File

@@ -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;

View File

@@ -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
}

View File

@@ -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(