✨ Add extension management service
This commit is contained in:
@@ -1,98 +0,0 @@
|
||||
import { EditorView, ViewPlugin, ViewUpdate } from '@codemirror/view';
|
||||
import { DocumentService } from '../../../../bindings/voidraft/internal/services';
|
||||
import { useDebounceFn } from '@vueuse/core';
|
||||
|
||||
// 定义自动保存配置选项
|
||||
export interface AutoSaveOptions {
|
||||
// 保存回调
|
||||
onSave?: (success: boolean) => void;
|
||||
// 内容变更延迟传递(毫秒)- 输入时不会立即发送,有一个小延迟,避免频繁调用后端
|
||||
debounceDelay?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建自动保存插件
|
||||
*
|
||||
* @param options 配置选项
|
||||
* @returns EditorView.Plugin
|
||||
*/
|
||||
export function createAutoSavePlugin(options: AutoSaveOptions = {}) {
|
||||
const {
|
||||
onSave = () => {},
|
||||
debounceDelay = 2000
|
||||
} = options;
|
||||
|
||||
return ViewPlugin.fromClass(
|
||||
class {
|
||||
private isActive: boolean = true;
|
||||
private isSaving: boolean = false;
|
||||
private readonly contentUpdateFn: (view: EditorView) => void;
|
||||
|
||||
constructor(private view: EditorView) {
|
||||
// 创建内容更新函数,简单传递内容给后端
|
||||
this.contentUpdateFn = this.createDebouncedUpdateFn(debounceDelay);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建防抖的内容更新函数
|
||||
*/
|
||||
private createDebouncedUpdateFn(delay: number): (view: EditorView) => void {
|
||||
// 使用VueUse的防抖函数创建一个新函数
|
||||
return useDebounceFn(async (view: EditorView) => {
|
||||
// 如果插件已不活跃或正在保存中,不发送
|
||||
if (!this.isActive || this.isSaving) return;
|
||||
|
||||
this.isSaving = true;
|
||||
const content = view.state.doc.toString();
|
||||
|
||||
try {
|
||||
// 简单将内容传递给后端,让后端处理保存策略
|
||||
await DocumentService.UpdateActiveDocumentContent(content);
|
||||
onSave(true);
|
||||
} catch (err) {
|
||||
// 静默处理错误,不在控制台打印
|
||||
onSave(false);
|
||||
} finally {
|
||||
this.isSaving = false;
|
||||
}
|
||||
}, delay);
|
||||
}
|
||||
|
||||
update(update: ViewUpdate) {
|
||||
// 如果内容没有变化,直接返回
|
||||
if (!update.docChanged) return;
|
||||
|
||||
// 调用防抖函数
|
||||
this.contentUpdateFn(this.view);
|
||||
}
|
||||
|
||||
destroy() {
|
||||
// 标记插件不再活跃
|
||||
this.isActive = false;
|
||||
|
||||
// 静默发送最终内容,忽略错误
|
||||
const content = this.view.state.doc.toString();
|
||||
DocumentService.UpdateActiveDocumentContent(content).then();
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建处理保存快捷键的插件
|
||||
* @param onSave 保存回调
|
||||
* @returns EditorView.Plugin
|
||||
*/
|
||||
export function createSaveShortcutPlugin(onSave: () => void) {
|
||||
return EditorView.domEventHandlers({
|
||||
keydown: (event) => {
|
||||
// Ctrl+S / Cmd+S
|
||||
if ((event.ctrlKey || event.metaKey) && event.key === 's') {
|
||||
event.preventDefault();
|
||||
onSave();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -1,95 +0,0 @@
|
||||
import {Extension} from '@codemirror/state';
|
||||
import {
|
||||
crosshairCursor,
|
||||
drawSelection,
|
||||
dropCursor,
|
||||
EditorView,
|
||||
highlightActiveLine,
|
||||
highlightActiveLineGutter,
|
||||
highlightSpecialChars,
|
||||
keymap,
|
||||
lineNumbers,
|
||||
rectangularSelection,
|
||||
} from '@codemirror/view';
|
||||
import {
|
||||
bracketMatching,
|
||||
defaultHighlightStyle,
|
||||
foldGutter,
|
||||
indentOnInput,
|
||||
syntaxHighlighting,
|
||||
} from '@codemirror/language';
|
||||
import {history} from '@codemirror/commands';
|
||||
import {highlightSelectionMatches} from '@codemirror/search';
|
||||
import {autocompletion, closeBrackets, closeBracketsKeymap} from '@codemirror/autocomplete';
|
||||
import {searchVisibilityField, vscodeSearch} from './vscodeSearch';
|
||||
|
||||
import {hyperLink} from './hyperlink';
|
||||
import {color} from './colorSelector';
|
||||
import {createTextHighlighter} from './textHighlightExtension';
|
||||
import {minimap} from './minimap';
|
||||
import {createCodeBlockExtension} from './codeblock/index';
|
||||
import {foldingOnIndent} from './foldExtension'
|
||||
import rainbowBrackets from "./rainbowBrackets";
|
||||
import {createCodeBlastExtension} from './codeblast';
|
||||
// 基本编辑器设置
|
||||
export const createBasicSetup = (): Extension[] => {
|
||||
return [
|
||||
// 基础UI
|
||||
lineNumbers(),
|
||||
highlightActiveLineGutter(),
|
||||
highlightSpecialChars(),
|
||||
dropCursor(),
|
||||
EditorView.lineWrapping,
|
||||
|
||||
// 历史记录
|
||||
history(),
|
||||
|
||||
// 代码折叠
|
||||
foldGutter(),
|
||||
|
||||
// 选择与高亮
|
||||
drawSelection(),
|
||||
highlightActiveLine(),
|
||||
highlightSelectionMatches(),
|
||||
rectangularSelection(),
|
||||
crosshairCursor(),
|
||||
|
||||
// 缩进和编辑辅助
|
||||
indentOnInput(),
|
||||
syntaxHighlighting(defaultHighlightStyle, {fallback: true}),
|
||||
bracketMatching(),
|
||||
closeBrackets(),
|
||||
|
||||
// 自动完成
|
||||
autocompletion(),
|
||||
|
||||
vscodeSearch,
|
||||
searchVisibilityField,
|
||||
foldingOnIndent,
|
||||
rainbowBrackets(),
|
||||
createCodeBlastExtension({
|
||||
effect: 1,
|
||||
shake: true,
|
||||
maxParticles: 300,
|
||||
shakeIntensity: 3
|
||||
}),
|
||||
hyperLink,
|
||||
color,
|
||||
...createTextHighlighter('hl'),
|
||||
minimap({
|
||||
displayText: 'characters',
|
||||
showOverlay: 'always',
|
||||
autohide: false,
|
||||
}),
|
||||
|
||||
createCodeBlockExtension({
|
||||
showBackground: true,
|
||||
enableAutoDetection: true,
|
||||
}),
|
||||
|
||||
// 键盘映射
|
||||
keymap.of([
|
||||
...closeBracketsKeymap,
|
||||
]),
|
||||
];
|
||||
};
|
||||
@@ -1,111 +0,0 @@
|
||||
import { EditorView } from '@codemirror/view';
|
||||
import { Extension, Compartment } from '@codemirror/state';
|
||||
|
||||
// 字体配置接口
|
||||
export interface FontConfig {
|
||||
fontFamily: string;
|
||||
fontSize?: number;
|
||||
lineHeight?: number;
|
||||
fontWeight?: string;
|
||||
}
|
||||
|
||||
// 创建字体配置compartment
|
||||
export const fontCompartment = new Compartment();
|
||||
|
||||
// 默认字体配置
|
||||
export const DEFAULT_FONT_CONFIG: FontConfig = {
|
||||
fontFamily: '"HarmonyOS Sans SC", "HarmonyOS Sans", "Microsoft YaHei", "PingFang SC", "Helvetica Neue", Arial, sans-serif',
|
||||
fontSize: 14,
|
||||
lineHeight: 1.5,
|
||||
fontWeight: 'normal'
|
||||
};
|
||||
|
||||
// 从后端配置创建字体配置
|
||||
export function createFontConfigFromBackend(backendConfig: {
|
||||
fontFamily?: string;
|
||||
fontSize?: number;
|
||||
lineHeight?: number;
|
||||
fontWeight?: string;
|
||||
}): FontConfig {
|
||||
return {
|
||||
fontFamily: backendConfig.fontFamily || DEFAULT_FONT_CONFIG.fontFamily,
|
||||
fontSize: backendConfig.fontSize || DEFAULT_FONT_CONFIG.fontSize,
|
||||
lineHeight: backendConfig.lineHeight || DEFAULT_FONT_CONFIG.lineHeight,
|
||||
fontWeight: backendConfig.fontWeight || DEFAULT_FONT_CONFIG.fontWeight,
|
||||
};
|
||||
}
|
||||
|
||||
// 创建字体样式扩展
|
||||
export function createFontExtension(config: Partial<FontConfig> = {}): Extension {
|
||||
const fontConfig = { ...DEFAULT_FONT_CONFIG, ...config };
|
||||
|
||||
const styles: Record<string, any> = {
|
||||
'&': {
|
||||
fontFamily: fontConfig.fontFamily,
|
||||
...(fontConfig.fontSize && { fontSize: `${fontConfig.fontSize}px` }),
|
||||
...(fontConfig.lineHeight && { lineHeight: fontConfig.lineHeight.toString() }),
|
||||
...(fontConfig.fontWeight && { fontWeight: fontConfig.fontWeight }),
|
||||
},
|
||||
'.cm-content': {
|
||||
fontFamily: fontConfig.fontFamily,
|
||||
...(fontConfig.fontSize && { fontSize: `${fontConfig.fontSize}px` }),
|
||||
...(fontConfig.lineHeight && { lineHeight: fontConfig.lineHeight.toString() }),
|
||||
...(fontConfig.fontWeight && { fontWeight: fontConfig.fontWeight }),
|
||||
},
|
||||
'.cm-editor': {
|
||||
fontFamily: fontConfig.fontFamily,
|
||||
},
|
||||
'.cm-scroller': {
|
||||
fontFamily: fontConfig.fontFamily,
|
||||
},
|
||||
'.cm-gutters': {
|
||||
fontFamily: fontConfig.fontFamily,
|
||||
...(fontConfig.fontSize && { fontSize: `${fontConfig.fontSize}px` }),
|
||||
},
|
||||
'.cm-lineNumbers': {
|
||||
fontFamily: fontConfig.fontFamily,
|
||||
...(fontConfig.fontSize && { fontSize: `${Math.max(10, fontConfig.fontSize - 1)}px` }),
|
||||
},
|
||||
'.cm-tooltip': {
|
||||
fontFamily: fontConfig.fontFamily,
|
||||
...(fontConfig.fontSize && { fontSize: `${Math.max(12, fontConfig.fontSize - 1)}px` }),
|
||||
},
|
||||
'.cm-completionLabel': {
|
||||
fontFamily: fontConfig.fontFamily,
|
||||
},
|
||||
'.cm-completionDetail': {
|
||||
fontFamily: fontConfig.fontFamily,
|
||||
}
|
||||
};
|
||||
|
||||
return EditorView.theme(styles);
|
||||
}
|
||||
|
||||
// 创建响应式字体大小扩展
|
||||
export function createResponsiveFontExtension(baseFontSize: number = 14): Extension {
|
||||
return fontCompartment.of(createFontExtension({
|
||||
fontSize: baseFontSize,
|
||||
lineHeight: 1.5
|
||||
}));
|
||||
}
|
||||
|
||||
// 从后端配置创建字体扩展
|
||||
export function createFontExtensionFromBackend(backendConfig: {
|
||||
fontFamily?: string;
|
||||
fontSize?: number;
|
||||
lineHeight?: number;
|
||||
fontWeight?: string;
|
||||
}): Extension {
|
||||
const fontConfig = createFontConfigFromBackend(backendConfig);
|
||||
return fontCompartment.of(createFontExtension(fontConfig));
|
||||
}
|
||||
|
||||
// 动态更新字体配置
|
||||
export function updateFontConfig(view: EditorView, config: Partial<FontConfig>): void {
|
||||
const newFontExtension = createFontExtension(config);
|
||||
|
||||
// 使用compartment重新配置字体扩展
|
||||
view.dispatch({
|
||||
effects: fontCompartment.reconfigure(newFontExtension)
|
||||
});
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
// 统一导出所有扩展
|
||||
export * from './tabExtension';
|
||||
export * from './wheelZoomExtension';
|
||||
export * from './statsExtension';
|
||||
export * from './autoSaveExtension';
|
||||
export * from './fontExtension';
|
||||
export * from './themeExtension';
|
||||
export * from './codeblast';
|
||||
export * from './codeblock';
|
||||
export * from './keymap';
|
||||
@@ -1,298 +0,0 @@
|
||||
import {KeyBindingCommand} from '@/../bindings/voidraft/internal/models/models'
|
||||
import {
|
||||
hideSearchVisibilityCommand,
|
||||
searchReplaceAll,
|
||||
searchShowReplace,
|
||||
searchToggleCase,
|
||||
searchToggleRegex,
|
||||
searchToggleWholeWord,
|
||||
showSearchVisibilityCommand
|
||||
} from '../vscodeSearch/commands'
|
||||
import {
|
||||
addNewBlockAfterCurrent,
|
||||
addNewBlockAfterLast,
|
||||
addNewBlockBeforeCurrent,
|
||||
deleteBlock,
|
||||
formatCurrentBlock,
|
||||
gotoNextBlock,
|
||||
gotoPreviousBlock,
|
||||
moveCurrentBlockDown,
|
||||
moveCurrentBlockUp,
|
||||
selectNextBlock,
|
||||
selectPreviousBlock
|
||||
} from '../codeblock/commands'
|
||||
import { selectAll } from '../codeblock/selectAll'
|
||||
import { deleteLineCommand } from '../codeblock/deleteLine'
|
||||
import { moveLineUp, moveLineDown } from '../codeblock/moveLines'
|
||||
import { transposeChars } from '@/views/editor/extensions'
|
||||
import { copyCommand, cutCommand, pasteCommand } from '../codeblock/copyPaste'
|
||||
import { undo, redo, undoSelection, redoSelection, cursorSyntaxLeft, cursorSyntaxRight, selectSyntaxLeft, selectSyntaxRight, copyLineUp, copyLineDown, insertBlankLine, selectLine, selectParentSyntax, indentLess, indentMore, indentSelection, cursorMatchingBracket, toggleComment, toggleBlockComment, insertNewlineAndIndent, deleteCharBackward, deleteCharForward, deleteGroupBackward, deleteGroupForward } from '@codemirror/commands'
|
||||
import { foldCode, unfoldCode, foldAll, unfoldAll } from '@codemirror/language'
|
||||
import i18n from '@/i18n'
|
||||
|
||||
// 默认编辑器选项
|
||||
const defaultEditorOptions = {
|
||||
defaultBlockToken: 'text',
|
||||
defaultBlockAutoDetect: true,
|
||||
}
|
||||
|
||||
/**
|
||||
* 前端命令注册表
|
||||
* 将后端定义的command字段映射到具体的前端方法和翻译键
|
||||
*/
|
||||
export const commandRegistry = {
|
||||
[KeyBindingCommand.ShowSearchCommand]: {
|
||||
handler: showSearchVisibilityCommand,
|
||||
descriptionKey: 'keybindings.commands.showSearch'
|
||||
},
|
||||
[KeyBindingCommand.HideSearchCommand]: {
|
||||
handler: hideSearchVisibilityCommand,
|
||||
descriptionKey: 'keybindings.commands.hideSearch'
|
||||
},
|
||||
[KeyBindingCommand.SearchToggleCaseCommand]: {
|
||||
handler: searchToggleCase,
|
||||
descriptionKey: 'keybindings.commands.searchToggleCase'
|
||||
},
|
||||
[KeyBindingCommand.SearchToggleWordCommand]: {
|
||||
handler: searchToggleWholeWord,
|
||||
descriptionKey: 'keybindings.commands.searchToggleWord'
|
||||
},
|
||||
[KeyBindingCommand.SearchToggleRegexCommand]: {
|
||||
handler: searchToggleRegex,
|
||||
descriptionKey: 'keybindings.commands.searchToggleRegex'
|
||||
},
|
||||
[KeyBindingCommand.SearchShowReplaceCommand]: {
|
||||
handler: searchShowReplace,
|
||||
descriptionKey: 'keybindings.commands.searchShowReplace'
|
||||
},
|
||||
[KeyBindingCommand.SearchReplaceAllCommand]: {
|
||||
handler: searchReplaceAll,
|
||||
descriptionKey: 'keybindings.commands.searchReplaceAll'
|
||||
},
|
||||
|
||||
// 代码块操作命令
|
||||
[KeyBindingCommand.BlockSelectAllCommand]: {
|
||||
handler: selectAll,
|
||||
descriptionKey: 'keybindings.commands.blockSelectAll'
|
||||
},
|
||||
[KeyBindingCommand.BlockAddAfterCurrentCommand]: {
|
||||
handler: addNewBlockAfterCurrent(defaultEditorOptions),
|
||||
descriptionKey: 'keybindings.commands.blockAddAfterCurrent'
|
||||
},
|
||||
[KeyBindingCommand.BlockAddAfterLastCommand]: {
|
||||
handler: addNewBlockAfterLast(defaultEditorOptions),
|
||||
descriptionKey: 'keybindings.commands.blockAddAfterLast'
|
||||
},
|
||||
[KeyBindingCommand.BlockAddBeforeCurrentCommand]: {
|
||||
handler: addNewBlockBeforeCurrent(defaultEditorOptions),
|
||||
descriptionKey: 'keybindings.commands.blockAddBeforeCurrent'
|
||||
},
|
||||
[KeyBindingCommand.BlockGotoPreviousCommand]: {
|
||||
handler: gotoPreviousBlock,
|
||||
descriptionKey: 'keybindings.commands.blockGotoPrevious'
|
||||
},
|
||||
[KeyBindingCommand.BlockGotoNextCommand]: {
|
||||
handler: gotoNextBlock,
|
||||
descriptionKey: 'keybindings.commands.blockGotoNext'
|
||||
},
|
||||
[KeyBindingCommand.BlockSelectPreviousCommand]: {
|
||||
handler: selectPreviousBlock,
|
||||
descriptionKey: 'keybindings.commands.blockSelectPrevious'
|
||||
},
|
||||
[KeyBindingCommand.BlockSelectNextCommand]: {
|
||||
handler: selectNextBlock,
|
||||
descriptionKey: 'keybindings.commands.blockSelectNext'
|
||||
},
|
||||
[KeyBindingCommand.BlockDeleteCommand]: {
|
||||
handler: deleteBlock(defaultEditorOptions),
|
||||
descriptionKey: 'keybindings.commands.blockDelete'
|
||||
},
|
||||
[KeyBindingCommand.BlockMoveUpCommand]: {
|
||||
handler: moveCurrentBlockUp,
|
||||
descriptionKey: 'keybindings.commands.blockMoveUp'
|
||||
},
|
||||
[KeyBindingCommand.BlockMoveDownCommand]: {
|
||||
handler: moveCurrentBlockDown,
|
||||
descriptionKey: 'keybindings.commands.blockMoveDown'
|
||||
},
|
||||
[KeyBindingCommand.BlockDeleteLineCommand]: {
|
||||
handler: deleteLineCommand,
|
||||
descriptionKey: 'keybindings.commands.blockDeleteLine'
|
||||
},
|
||||
[KeyBindingCommand.BlockMoveLineUpCommand]: {
|
||||
handler: moveLineUp,
|
||||
descriptionKey: 'keybindings.commands.blockMoveLineUp'
|
||||
},
|
||||
[KeyBindingCommand.BlockMoveLineDownCommand]: {
|
||||
handler: moveLineDown,
|
||||
descriptionKey: 'keybindings.commands.blockMoveLineDown'
|
||||
},
|
||||
[KeyBindingCommand.BlockTransposeCharsCommand]: {
|
||||
handler: transposeChars,
|
||||
descriptionKey: 'keybindings.commands.blockTransposeChars'
|
||||
},
|
||||
[KeyBindingCommand.BlockFormatCommand]: {
|
||||
handler: formatCurrentBlock,
|
||||
descriptionKey: 'keybindings.commands.blockFormat'
|
||||
},
|
||||
[KeyBindingCommand.BlockCopyCommand]: {
|
||||
handler: copyCommand,
|
||||
descriptionKey: 'keybindings.commands.blockCopy'
|
||||
},
|
||||
[KeyBindingCommand.BlockCutCommand]: {
|
||||
handler: cutCommand,
|
||||
descriptionKey: 'keybindings.commands.blockCut'
|
||||
},
|
||||
[KeyBindingCommand.BlockPasteCommand]: {
|
||||
handler: pasteCommand,
|
||||
descriptionKey: 'keybindings.commands.blockPaste'
|
||||
},
|
||||
[KeyBindingCommand.HistoryUndoCommand]: {
|
||||
handler: undo,
|
||||
descriptionKey: 'keybindings.commands.historyUndo'
|
||||
},
|
||||
[KeyBindingCommand.HistoryRedoCommand]: {
|
||||
handler: redo,
|
||||
descriptionKey: 'keybindings.commands.historyRedo'
|
||||
},
|
||||
[KeyBindingCommand.HistoryUndoSelectionCommand]: {
|
||||
handler: undoSelection,
|
||||
descriptionKey: 'keybindings.commands.historyUndoSelection'
|
||||
},
|
||||
[KeyBindingCommand.HistoryRedoSelectionCommand]: {
|
||||
handler: redoSelection,
|
||||
descriptionKey: 'keybindings.commands.historyRedoSelection'
|
||||
},
|
||||
[KeyBindingCommand.FoldCodeCommand]: {
|
||||
handler: foldCode,
|
||||
descriptionKey: 'keybindings.commands.foldCode'
|
||||
},
|
||||
[KeyBindingCommand.UnfoldCodeCommand]: {
|
||||
handler: unfoldCode,
|
||||
descriptionKey: 'keybindings.commands.unfoldCode'
|
||||
},
|
||||
[KeyBindingCommand.FoldAllCommand]: {
|
||||
handler: foldAll,
|
||||
descriptionKey: 'keybindings.commands.foldAll'
|
||||
},
|
||||
[KeyBindingCommand.UnfoldAllCommand]: {
|
||||
handler: unfoldAll,
|
||||
descriptionKey: 'keybindings.commands.unfoldAll'
|
||||
},
|
||||
[KeyBindingCommand.CursorSyntaxLeftCommand]: {
|
||||
handler: cursorSyntaxLeft,
|
||||
descriptionKey: 'keybindings.commands.cursorSyntaxLeft'
|
||||
},
|
||||
[KeyBindingCommand.CursorSyntaxRightCommand]: {
|
||||
handler: cursorSyntaxRight,
|
||||
descriptionKey: 'keybindings.commands.cursorSyntaxRight'
|
||||
},
|
||||
[KeyBindingCommand.SelectSyntaxLeftCommand]: {
|
||||
handler: selectSyntaxLeft,
|
||||
descriptionKey: 'keybindings.commands.selectSyntaxLeft'
|
||||
},
|
||||
[KeyBindingCommand.SelectSyntaxRightCommand]: {
|
||||
handler: selectSyntaxRight,
|
||||
descriptionKey: 'keybindings.commands.selectSyntaxRight'
|
||||
},
|
||||
[KeyBindingCommand.CopyLineUpCommand]: {
|
||||
handler: copyLineUp,
|
||||
descriptionKey: 'keybindings.commands.copyLineUp'
|
||||
},
|
||||
[KeyBindingCommand.CopyLineDownCommand]: {
|
||||
handler: copyLineDown,
|
||||
descriptionKey: 'keybindings.commands.copyLineDown'
|
||||
},
|
||||
[KeyBindingCommand.InsertBlankLineCommand]: {
|
||||
handler: insertBlankLine,
|
||||
descriptionKey: 'keybindings.commands.insertBlankLine'
|
||||
},
|
||||
[KeyBindingCommand.SelectLineCommand]: {
|
||||
handler: selectLine,
|
||||
descriptionKey: 'keybindings.commands.selectLine'
|
||||
},
|
||||
[KeyBindingCommand.SelectParentSyntaxCommand]: {
|
||||
handler: selectParentSyntax,
|
||||
descriptionKey: 'keybindings.commands.selectParentSyntax'
|
||||
},
|
||||
[KeyBindingCommand.IndentLessCommand]: {
|
||||
handler: indentLess,
|
||||
descriptionKey: 'keybindings.commands.indentLess'
|
||||
},
|
||||
[KeyBindingCommand.IndentMoreCommand]: {
|
||||
handler: indentMore,
|
||||
descriptionKey: 'keybindings.commands.indentMore'
|
||||
},
|
||||
[KeyBindingCommand.IndentSelectionCommand]: {
|
||||
handler: indentSelection,
|
||||
descriptionKey: 'keybindings.commands.indentSelection'
|
||||
},
|
||||
[KeyBindingCommand.CursorMatchingBracketCommand]: {
|
||||
handler: cursorMatchingBracket,
|
||||
descriptionKey: 'keybindings.commands.cursorMatchingBracket'
|
||||
},
|
||||
[KeyBindingCommand.ToggleCommentCommand]: {
|
||||
handler: toggleComment,
|
||||
descriptionKey: 'keybindings.commands.toggleComment'
|
||||
},
|
||||
[KeyBindingCommand.ToggleBlockCommentCommand]: {
|
||||
handler: toggleBlockComment,
|
||||
descriptionKey: 'keybindings.commands.toggleBlockComment'
|
||||
},
|
||||
[KeyBindingCommand.InsertNewlineAndIndentCommand]: {
|
||||
handler: insertNewlineAndIndent,
|
||||
descriptionKey: 'keybindings.commands.insertNewlineAndIndent'
|
||||
},
|
||||
[KeyBindingCommand.DeleteCharBackwardCommand]: {
|
||||
handler: deleteCharBackward,
|
||||
descriptionKey: 'keybindings.commands.deleteCharBackward'
|
||||
},
|
||||
[KeyBindingCommand.DeleteCharForwardCommand]: {
|
||||
handler: deleteCharForward,
|
||||
descriptionKey: 'keybindings.commands.deleteCharForward'
|
||||
},
|
||||
[KeyBindingCommand.DeleteGroupBackwardCommand]: {
|
||||
handler: deleteGroupBackward,
|
||||
descriptionKey: 'keybindings.commands.deleteGroupBackward'
|
||||
},
|
||||
[KeyBindingCommand.DeleteGroupForwardCommand]: {
|
||||
handler: deleteGroupForward,
|
||||
descriptionKey: 'keybindings.commands.deleteGroupForward'
|
||||
},
|
||||
} as const
|
||||
|
||||
/**
|
||||
* 获取命令处理函数
|
||||
* @param command 命令名称
|
||||
* @returns 对应的处理函数,如果不存在则返回 undefined
|
||||
*/
|
||||
export const getCommandHandler = (command: KeyBindingCommand) => {
|
||||
return commandRegistry[command]?.handler
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取命令描述
|
||||
* @param command 命令名称
|
||||
* @returns 对应的描述,如果不存在则返回 undefined
|
||||
*/
|
||||
export const getCommandDescription = (command: KeyBindingCommand) => {
|
||||
const descriptionKey = commandRegistry[command]?.descriptionKey
|
||||
return descriptionKey ? i18n.global.t(descriptionKey) : undefined
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查命令是否已注册
|
||||
* @param command 命令名称
|
||||
* @returns 是否已注册
|
||||
*/
|
||||
export const isCommandRegistered = (command: KeyBindingCommand): boolean => {
|
||||
return command in commandRegistry
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有已注册的命令
|
||||
* @returns 已注册的命令列表
|
||||
*/
|
||||
export const getRegisteredCommands = (): KeyBindingCommand[] => {
|
||||
return Object.keys(commandRegistry) as KeyBindingCommand[]
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
import { Extension } from '@codemirror/state'
|
||||
import { useKeybindingStore } from '@/stores/keybindingStore'
|
||||
import { KeymapManager } from './keymapManager'
|
||||
|
||||
/**
|
||||
* 异步创建快捷键扩展
|
||||
* 确保快捷键配置已加载
|
||||
*/
|
||||
export const createDynamicKeymapExtension = async (): Promise<Extension> => {
|
||||
const keybindingStore = useKeybindingStore()
|
||||
|
||||
// 确保快捷键配置已加载
|
||||
if (keybindingStore.keyBindings.length === 0) {
|
||||
await keybindingStore.loadKeyBindings()
|
||||
}
|
||||
|
||||
return KeymapManager.createKeymapExtension(keybindingStore.enabledKeyBindings)
|
||||
}
|
||||
|
||||
// 导出相关模块
|
||||
export { KeymapManager } from './keymapManager'
|
||||
export { commandRegistry, getCommandHandler, getCommandDescription, isCommandRegistered, getRegisteredCommands } from './commandRegistry'
|
||||
export type { KeyBinding, CommandHandler, CommandDefinition, KeymapResult } from './types'
|
||||
@@ -1,84 +0,0 @@
|
||||
import {keymap} from '@codemirror/view'
|
||||
import {Extension} from '@codemirror/state'
|
||||
import {KeyBinding as KeyBindingConfig} from '@/../bindings/voidraft/internal/models/models'
|
||||
import {KeyBinding, KeymapResult} from './types'
|
||||
import {getCommandHandler, isCommandRegistered} from './commandRegistry'
|
||||
|
||||
/**
|
||||
* 快捷键管理器
|
||||
* 负责将后端配置转换为CodeMirror快捷键扩展
|
||||
*/
|
||||
export class KeymapManager {
|
||||
/**
|
||||
* 将后端快捷键配置转换为CodeMirror快捷键绑定
|
||||
* @param keyBindings 后端快捷键配置列表
|
||||
* @returns 转换结果
|
||||
*/
|
||||
static convertToKeyBindings(keyBindings: KeyBindingConfig[]): KeymapResult {
|
||||
const result: KeyBinding[] = []
|
||||
|
||||
for (const binding of keyBindings) {
|
||||
// 跳过禁用的快捷键
|
||||
if (!binding.enabled) {
|
||||
continue
|
||||
}
|
||||
|
||||
// 检查命令是否已注册
|
||||
if (!isCommandRegistered(binding.command)) {
|
||||
continue
|
||||
}
|
||||
|
||||
// 获取命令处理函数
|
||||
const handler = getCommandHandler(binding.command)
|
||||
if (!handler) {
|
||||
continue
|
||||
}
|
||||
|
||||
// 转换为CodeMirror快捷键格式
|
||||
const keyBinding: KeyBinding = {
|
||||
key: binding.key,
|
||||
run: handler,
|
||||
preventDefault: true
|
||||
}
|
||||
|
||||
result.push(keyBinding)
|
||||
}
|
||||
|
||||
return {keyBindings: result}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建CodeMirror快捷键扩展
|
||||
* @param keyBindings 后端快捷键配置列表
|
||||
* @returns CodeMirror扩展
|
||||
*/
|
||||
static createKeymapExtension(keyBindings: KeyBindingConfig[]): Extension {
|
||||
const {keyBindings: cmKeyBindings} =
|
||||
this.convertToKeyBindings(keyBindings)
|
||||
|
||||
return keymap.of(cmKeyBindings)
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证快捷键配置
|
||||
* @param keyBindings 快捷键配置列表
|
||||
* @returns 验证结果
|
||||
*/
|
||||
static validateKeyBindings(keyBindings: KeyBindingConfig[]): {
|
||||
valid: KeyBindingConfig[]
|
||||
invalid: KeyBindingConfig[]
|
||||
} {
|
||||
const valid: KeyBindingConfig[] = []
|
||||
const invalid: KeyBindingConfig[] = []
|
||||
|
||||
for (const binding of keyBindings) {
|
||||
if (binding.enabled && binding.key && isCommandRegistered(binding.command)) {
|
||||
valid.push(binding)
|
||||
} else {
|
||||
invalid.push(binding)
|
||||
}
|
||||
}
|
||||
|
||||
return {valid, invalid}
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
import {Command} from '@codemirror/view'
|
||||
|
||||
/**
|
||||
* CodeMirror快捷键绑定格式
|
||||
*/
|
||||
export interface KeyBinding {
|
||||
key: string
|
||||
run: Command
|
||||
preventDefault?: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* 命令处理函数类型
|
||||
*/
|
||||
export type CommandHandler = Command
|
||||
|
||||
/**
|
||||
* 命令定义接口
|
||||
*/
|
||||
export interface CommandDefinition {
|
||||
handler: CommandHandler
|
||||
descriptionKey: string // 翻译键
|
||||
}
|
||||
|
||||
/**
|
||||
* 快捷键转换结果
|
||||
*/
|
||||
export interface KeymapResult {
|
||||
keyBindings: KeyBinding[]
|
||||
}
|
||||
@@ -70,7 +70,7 @@ const rainbowBracketsPlugin = ViewPlugin.fromClass(RainbowBracketsView, {
|
||||
decorations: (v) => v.decorations,
|
||||
});
|
||||
|
||||
export default function rainbowBrackets() {
|
||||
export default function rainbowBracketsExtension() {
|
||||
return [
|
||||
rainbowBracketsPlugin,
|
||||
EditorView.baseTheme({
|
||||
@@ -1,65 +0,0 @@
|
||||
import {Extension} from '@codemirror/state';
|
||||
import {EditorView} from '@codemirror/view';
|
||||
import {DocumentStats} from '@/stores/editorStore';
|
||||
import {getActiveNoteBlock} from '@/views/editor/extensions/codeblock/state';
|
||||
|
||||
// 更新编辑器文档统计信息
|
||||
export const updateStats = (
|
||||
view: EditorView,
|
||||
updateDocumentStats: (stats: DocumentStats) => void
|
||||
) => {
|
||||
if (!view) return;
|
||||
|
||||
const state = view.state;
|
||||
|
||||
// 获取当前光标所在的代码块
|
||||
const activeBlock = getActiveNoteBlock(state as any);
|
||||
|
||||
if (!activeBlock) {
|
||||
// 如果没有活动块,显示空统计
|
||||
updateDocumentStats({
|
||||
lines: 0,
|
||||
characters: 0,
|
||||
selectedCharacters: 0
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取当前块的内容范围
|
||||
const blockContent = state.doc.sliceString(activeBlock.content.from, activeBlock.content.to);
|
||||
|
||||
// 计算块内容的行数
|
||||
const blockLines = blockContent.split('\n').length;
|
||||
|
||||
// 计算选中的字符数(只统计在当前块内的选中内容)
|
||||
let selectedChars = 0;
|
||||
const selections = state.selection;
|
||||
if (selections) {
|
||||
for (let i = 0; i < selections.ranges.length; i++) {
|
||||
const range = selections.ranges[i];
|
||||
// 计算选中范围与当前块内容范围的交集
|
||||
const selectionStart = Math.max(range.from, activeBlock.content.from);
|
||||
const selectionEnd = Math.min(range.to, activeBlock.content.to);
|
||||
if (selectionStart < selectionEnd) {
|
||||
selectedChars += selectionEnd - selectionStart;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
updateDocumentStats({
|
||||
lines: blockLines,
|
||||
characters: blockContent.length,
|
||||
selectedCharacters: selectedChars
|
||||
});
|
||||
};
|
||||
|
||||
// 创建统计信息更新监听器扩展
|
||||
export const createStatsUpdateExtension = (
|
||||
updateDocumentStats: (stats: DocumentStats) => void
|
||||
): Extension => {
|
||||
return EditorView.updateListener.of(update => {
|
||||
if (update.docChanged || update.selectionSet) {
|
||||
updateStats(update.view, updateDocumentStats);
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -1,78 +0,0 @@
|
||||
import {Compartment, Extension} from '@codemirror/state';
|
||||
import {EditorView, keymap} from '@codemirror/view';
|
||||
import {indentSelection} from '@codemirror/commands';
|
||||
import {indentUnit} from '@codemirror/language';
|
||||
import {TabType} from '@/../bindings/voidraft/internal/models/models';
|
||||
|
||||
// Tab设置相关的compartment
|
||||
export const tabSizeCompartment = new Compartment();
|
||||
export const tabKeyCompartment = new Compartment();
|
||||
|
||||
// 自定义Tab键处理函数
|
||||
export const tabHandler = (view: EditorView, tabSize: number, tabType: TabType): boolean => {
|
||||
// 如果有选中文本,使用indentSelection
|
||||
if (!view.state.selection.main.empty) {
|
||||
return indentSelection(view);
|
||||
}
|
||||
|
||||
// 根据tabType创建缩进字符
|
||||
const indent = tabType === 'spaces' ? ' '.repeat(tabSize) : '\t';
|
||||
|
||||
// 在光标位置插入缩进字符
|
||||
const {state, dispatch} = view;
|
||||
dispatch(state.update(state.replaceSelection(indent), {scrollIntoView: true}));
|
||||
return true;
|
||||
};
|
||||
|
||||
// 获取Tab相关的扩展
|
||||
export const getTabExtensions = (tabSize: number, enableTabIndent: boolean, tabType: TabType): Extension[] => {
|
||||
const extensions: Extension[] = [];
|
||||
|
||||
// 根据tabType设置缩进单位
|
||||
const indentStr = tabType === 'spaces' ? ' '.repeat(tabSize) : '\t';
|
||||
extensions.push(tabSizeCompartment.of(indentUnit.of(indentStr)));
|
||||
|
||||
// 如果启用了Tab缩进,添加自定义Tab键映射
|
||||
if (enableTabIndent) {
|
||||
extensions.push(
|
||||
tabKeyCompartment.of(
|
||||
keymap.of([{
|
||||
key: "Tab",
|
||||
run: (view) => tabHandler(view, tabSize, tabType)
|
||||
}])
|
||||
)
|
||||
);
|
||||
} else {
|
||||
extensions.push(tabKeyCompartment.of([]));
|
||||
}
|
||||
|
||||
return extensions;
|
||||
};
|
||||
|
||||
// 更新Tab配置
|
||||
export const updateTabConfig = (
|
||||
view: EditorView | null,
|
||||
tabSize: number,
|
||||
enableTabIndent: boolean,
|
||||
tabType: TabType
|
||||
) => {
|
||||
if (!view) return;
|
||||
|
||||
// 根据tabType更新indentUnit配置
|
||||
const indentStr = tabType === 'spaces' ? ' '.repeat(tabSize) : '\t';
|
||||
view.dispatch({
|
||||
effects: tabSizeCompartment.reconfigure(indentUnit.of(indentStr))
|
||||
});
|
||||
|
||||
// 更新Tab键映射
|
||||
const tabKeymap = enableTabIndent
|
||||
? keymap.of([{
|
||||
key: "Tab",
|
||||
run: (view) => tabHandler(view, tabSize, tabType)
|
||||
}])
|
||||
: [];
|
||||
|
||||
view.dispatch({
|
||||
effects: tabKeyCompartment.reconfigure(tabKeymap)
|
||||
});
|
||||
};
|
||||
@@ -1,53 +0,0 @@
|
||||
import { Extension, Compartment } from '@codemirror/state';
|
||||
import { EditorView } from '@codemirror/view';
|
||||
import { SystemThemeType } from '@/../bindings/voidraft/internal/models/models';
|
||||
import { dark } from '@/views/editor/theme/dark';
|
||||
import { light } from '@/views/editor/theme/light';
|
||||
|
||||
// 主题区间 - 用于动态切换主题
|
||||
export const themeCompartment = new Compartment();
|
||||
|
||||
/**
|
||||
* 根据主题类型获取主题扩展
|
||||
*/
|
||||
const getThemeExtension = (themeType: SystemThemeType): Extension => {
|
||||
// 处理 auto 主题类型
|
||||
let actualTheme: SystemThemeType = themeType;
|
||||
if (themeType === SystemThemeType.SystemThemeAuto) {
|
||||
actualTheme = window.matchMedia('(prefers-color-scheme: dark)').matches
|
||||
? SystemThemeType.SystemThemeDark
|
||||
: SystemThemeType.SystemThemeLight;
|
||||
}
|
||||
|
||||
// 直接返回对应的主题扩展
|
||||
switch (actualTheme) {
|
||||
case SystemThemeType.SystemThemeLight:
|
||||
return light;
|
||||
case SystemThemeType.SystemThemeDark:
|
||||
default:
|
||||
return dark;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 创建主题扩展(用于编辑器初始化)
|
||||
*/
|
||||
export const createThemeExtension = (themeType: SystemThemeType = SystemThemeType.SystemThemeDark): Extension => {
|
||||
const extension = getThemeExtension(themeType);
|
||||
return themeCompartment.of(extension);
|
||||
};
|
||||
|
||||
/**
|
||||
* 更新编辑器主题
|
||||
*/
|
||||
export const updateEditorTheme = (view: EditorView, themeType: SystemThemeType): void => {
|
||||
if (!view?.dispatch) {
|
||||
return;
|
||||
}
|
||||
|
||||
const extension = getThemeExtension(themeType);
|
||||
view.dispatch({
|
||||
effects: themeCompartment.reconfigure(extension)
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
// 处理滚轮缩放字体的事件处理函数
|
||||
export const createWheelZoomHandler = (
|
||||
increaseFontSize: () => void,
|
||||
decreaseFontSize: () => void
|
||||
) => {
|
||||
return (event: WheelEvent) => {
|
||||
// 检查是否按住了Ctrl键
|
||||
if (event.ctrlKey) {
|
||||
// 阻止默认行为(防止页面缩放)
|
||||
event.preventDefault();
|
||||
|
||||
// 根据滚轮方向增大或减小字体
|
||||
if (event.deltaY < 0) {
|
||||
// 向上滚动,增大字体
|
||||
increaseFontSize();
|
||||
} else {
|
||||
// 向下滚动,减小字体
|
||||
decreaseFontSize();
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user