🐛 Fixed the issue of text highlighting expansion
This commit is contained in:
@@ -433,11 +433,6 @@ export enum ExtensionID {
|
||||
*/
|
||||
ExtensionSearch = "search",
|
||||
|
||||
/**
|
||||
* 代码块
|
||||
*/
|
||||
ExtensionCodeBlock = "codeBlock",
|
||||
|
||||
/**
|
||||
* 核心扩展
|
||||
* 编辑器核心功能
|
||||
@@ -918,6 +913,12 @@ export enum KeyBindingCommand {
|
||||
* 重做选择
|
||||
*/
|
||||
HistoryRedoSelectionCommand = "historyRedoSelection",
|
||||
|
||||
/**
|
||||
* 文本高亮扩展相关
|
||||
* 切换文本高亮
|
||||
*/
|
||||
TextHighlightToggleCommand = "textHighlightToggle",
|
||||
};
|
||||
|
||||
/**
|
||||
|
@@ -89,6 +89,7 @@ export default {
|
||||
deleteCharForward: 'Delete character forward',
|
||||
deleteGroupBackward: 'Delete group backward',
|
||||
deleteGroupForward: 'Delete group forward',
|
||||
textHighlightToggle: 'Toggle text highlight',
|
||||
}
|
||||
},
|
||||
settings: {
|
||||
@@ -182,17 +183,16 @@ export default {
|
||||
name: 'Search',
|
||||
description: 'Text search and replace functionality'
|
||||
},
|
||||
codeBlock: {
|
||||
name: 'Code Block',
|
||||
description: 'Code block syntax highlighting and formatting'
|
||||
},
|
||||
|
||||
fold: {
|
||||
name: 'Code Folding',
|
||||
description: 'Collapse and expand code sections for better readability'
|
||||
},
|
||||
textHighlight: {
|
||||
name: 'Text Highlight',
|
||||
description: 'Highlight text selections and search matches'
|
||||
description: 'Highlight selected text content (Ctrl+Shift+H to toggle highlight)',
|
||||
backgroundColor: 'Background Color',
|
||||
opacity: 'Opacity'
|
||||
}
|
||||
}
|
||||
};
|
@@ -89,6 +89,7 @@ export default {
|
||||
deleteCharForward: '向前删除字符',
|
||||
deleteGroupBackward: '向后删除组',
|
||||
deleteGroupForward: '向前删除组',
|
||||
textHighlightToggle: '切换文本高亮',
|
||||
}
|
||||
},
|
||||
settings: {
|
||||
@@ -182,17 +183,16 @@ export default {
|
||||
name: '搜索功能',
|
||||
description: '文本搜索和替换功能'
|
||||
},
|
||||
codeBlock: {
|
||||
name: '代码块',
|
||||
description: '代码块语法高亮和格式化'
|
||||
},
|
||||
|
||||
fold: {
|
||||
name: '代码折叠',
|
||||
description: '折叠和展开代码段以提高代码可读性'
|
||||
},
|
||||
textHighlight: {
|
||||
name: '文本高亮',
|
||||
description: '高亮显示文本选择和搜索匹配项'
|
||||
description: '高亮选中的文本内容 (Ctrl+Shift+H 切换高亮)',
|
||||
backgroundColor: '背景颜色',
|
||||
opacity: '透明度'
|
||||
}
|
||||
}
|
||||
};
|
@@ -18,6 +18,7 @@ import {createAutoSavePlugin, createSaveShortcutPlugin} from '@/views/editor/bas
|
||||
import {createDynamicKeymapExtension, updateKeymapExtension} from '@/views/editor/keymap';
|
||||
import {createDynamicExtensions, getExtensionManager, setExtensionManagerView} from '@/views/editor/manager';
|
||||
import {useExtensionStore} from './extensionStore';
|
||||
import createCodeBlockExtension from "@/views/editor/extensions/codeblock";
|
||||
|
||||
export interface DocumentStats {
|
||||
lines: number;
|
||||
@@ -142,6 +143,11 @@ export const useEditorStore = defineStore('editor', () => {
|
||||
}
|
||||
}
|
||||
});
|
||||
// 代码块功能
|
||||
const codeBlockExtension = createCodeBlockExtension({
|
||||
showBackground: true,
|
||||
enableAutoDetection: true
|
||||
});
|
||||
|
||||
// 创建动态快捷键扩展
|
||||
const keymapExtension = await createDynamicKeymapExtension();
|
||||
@@ -159,6 +165,7 @@ export const useEditorStore = defineStore('editor', () => {
|
||||
statsExtension,
|
||||
saveShortcutExtension,
|
||||
autoSaveExtension,
|
||||
codeBlockExtension,
|
||||
...dynamicExtensions
|
||||
];
|
||||
|
||||
|
@@ -1,120 +1,108 @@
|
||||
import { EditorState, StateEffect, StateField, Transaction, Range } from "@codemirror/state";
|
||||
import { Decoration, DecorationSet, EditorView, ViewPlugin, ViewUpdate } from "@codemirror/view";
|
||||
import { keymap } from "@codemirror/view";
|
||||
import { EditorState, StateEffect, StateField, Facet } from "@codemirror/state";
|
||||
import { Decoration, DecorationSet, EditorView } from "@codemirror/view";
|
||||
|
||||
// 全局高亮存储 - 以文档ID为键,高亮范围数组为值
|
||||
interface HighlightInfo {
|
||||
from: number;
|
||||
to: number;
|
||||
// 高亮配置接口
|
||||
export interface TextHighlightConfig {
|
||||
backgroundColor?: string;
|
||||
opacity?: number;
|
||||
}
|
||||
|
||||
class GlobalHighlightStore {
|
||||
private static instance: GlobalHighlightStore;
|
||||
private highlightMap: Map<string, HighlightInfo[]> = new Map();
|
||||
|
||||
private constructor() {}
|
||||
|
||||
public static getInstance(): GlobalHighlightStore {
|
||||
if (!GlobalHighlightStore.instance) {
|
||||
GlobalHighlightStore.instance = new GlobalHighlightStore();
|
||||
}
|
||||
return GlobalHighlightStore.instance;
|
||||
}
|
||||
|
||||
// 保存文档的高亮
|
||||
saveHighlights(documentId: string, highlights: HighlightInfo[]): void {
|
||||
this.highlightMap.set(documentId, [...highlights]);
|
||||
}
|
||||
|
||||
// 获取文档的高亮
|
||||
getHighlights(documentId: string): HighlightInfo[] {
|
||||
return this.highlightMap.get(documentId) || [];
|
||||
}
|
||||
|
||||
// 添加高亮
|
||||
addHighlight(documentId: string, highlight: HighlightInfo): void {
|
||||
const highlights = this.getHighlights(documentId);
|
||||
highlights.push(highlight);
|
||||
this.saveHighlights(documentId, highlights);
|
||||
}
|
||||
|
||||
// 移除高亮
|
||||
removeHighlights(documentId: string, from: number, to: number): void {
|
||||
const highlights = this.getHighlights(documentId);
|
||||
const filtered = highlights.filter(h => !(h.from < to && h.to > from));
|
||||
this.saveHighlights(documentId, filtered);
|
||||
}
|
||||
|
||||
// 清除文档的所有高亮
|
||||
clearHighlights(documentId: string): void {
|
||||
this.highlightMap.delete(documentId);
|
||||
}
|
||||
}
|
||||
|
||||
// 获取全局高亮存储实例
|
||||
const highlightStore = GlobalHighlightStore.getInstance();
|
||||
// 默认配置
|
||||
const DEFAULT_CONFIG: Required<TextHighlightConfig> = {
|
||||
backgroundColor: '#FFD700', // 金黄色
|
||||
opacity: 0.3
|
||||
};
|
||||
|
||||
// 定义添加和移除高亮的状态效果
|
||||
const addHighlight = StateEffect.define<{from: number, to: number, documentId: string}>({
|
||||
map: ({from, to, documentId}, change) => ({
|
||||
const addHighlight = StateEffect.define<{from: number, to: number}>({
|
||||
map: ({from, to}, change) => ({
|
||||
from: change.mapPos(from),
|
||||
to: change.mapPos(to),
|
||||
documentId
|
||||
to: change.mapPos(to)
|
||||
})
|
||||
});
|
||||
|
||||
const removeHighlight = StateEffect.define<{from: number, to: number, documentId: string}>({
|
||||
map: ({from, to, documentId}, change) => ({
|
||||
const removeHighlight = StateEffect.define<{from: number, to: number}>({
|
||||
map: ({from, to}, change) => ({
|
||||
from: change.mapPos(from),
|
||||
to: change.mapPos(to),
|
||||
documentId
|
||||
to: change.mapPos(to)
|
||||
})
|
||||
});
|
||||
|
||||
// 初始化高亮效果 - 用于页面加载时恢复高亮
|
||||
const initHighlights = StateEffect.define<{highlights: HighlightInfo[], documentId: string}>();
|
||||
|
||||
// 高亮样式
|
||||
const highlightMark = Decoration.mark({
|
||||
attributes: {style: `background-color: rgba(255, 215, 0, 0.3)`}
|
||||
|
||||
// 配置facet
|
||||
const highlightConfigFacet = Facet.define<TextHighlightConfig, Required<TextHighlightConfig>>({
|
||||
combine: (configs) => {
|
||||
let result = { ...DEFAULT_CONFIG };
|
||||
for (const config of configs) {
|
||||
if (config.backgroundColor !== undefined) {
|
||||
result.backgroundColor = config.backgroundColor;
|
||||
}
|
||||
if (config.opacity !== undefined) {
|
||||
result.opacity = config.opacity;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
});
|
||||
|
||||
// 存储高亮范围的状态字段
|
||||
// 创建高亮装饰
|
||||
function createHighlightMark(config: Required<TextHighlightConfig>): Decoration {
|
||||
const { backgroundColor, opacity } = config;
|
||||
const rgbaColor = hexToRgba(backgroundColor, opacity);
|
||||
|
||||
return Decoration.mark({
|
||||
attributes: {
|
||||
style: `background-color: ${rgbaColor}; border-radius: 2px;`
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 将十六进制颜色转换为RGBA
|
||||
function hexToRgba(hex: string, opacity: number): string {
|
||||
// 移除 # 符号
|
||||
hex = hex.replace('#', '');
|
||||
|
||||
// 处理短格式 (如 #FFF -> #FFFFFF)
|
||||
if (hex.length === 3) {
|
||||
hex = hex.split('').map(char => char + char).join('');
|
||||
}
|
||||
|
||||
const r = parseInt(hex.substr(0, 2), 16);
|
||||
const g = parseInt(hex.substr(2, 2), 16);
|
||||
const b = parseInt(hex.substr(4, 2), 16);
|
||||
|
||||
return `rgba(${r}, ${g}, ${b}, ${opacity})`;
|
||||
}
|
||||
|
||||
// 存储高亮范围的状态字段 - 支持撤销
|
||||
const highlightState = StateField.define<DecorationSet>({
|
||||
create() {
|
||||
return Decoration.none;
|
||||
},
|
||||
update(decorations, tr) {
|
||||
// 先映射现有的装饰,以适应文档变化
|
||||
// 映射现有装饰以适应文档变化
|
||||
decorations = decorations.map(tr.changes);
|
||||
|
||||
// 处理添加和移除高亮的效果
|
||||
// 处理效果
|
||||
for (const effect of tr.effects) {
|
||||
if (effect.is(addHighlight)) {
|
||||
const { from, to, documentId } = effect.value;
|
||||
const { from, to } = effect.value;
|
||||
const config = tr.state.facet(highlightConfigFacet);
|
||||
const highlightMark = createHighlightMark(config);
|
||||
|
||||
decorations = decorations.update({
|
||||
add: [highlightMark.range(from, to)]
|
||||
});
|
||||
// 同步到全局存储
|
||||
highlightStore.addHighlight(documentId, { from, to });
|
||||
}
|
||||
else if (effect.is(removeHighlight)) {
|
||||
const { from, to, documentId } = effect.value;
|
||||
const { from, to } = effect.value;
|
||||
decorations = decorations.update({
|
||||
filter: (rangeFrom, rangeTo) => {
|
||||
// 移除与指定范围重叠的装饰
|
||||
return !(rangeFrom < to && rangeTo > from);
|
||||
}
|
||||
});
|
||||
// 同步到全局存储
|
||||
highlightStore.removeHighlights(documentId, from, to);
|
||||
}
|
||||
else if (effect.is(initHighlights)) {
|
||||
const { highlights } = effect.value;
|
||||
const ranges = highlights.map(h => highlightMark.range(h.from, h.to));
|
||||
if (ranges.length > 0) {
|
||||
decorations = decorations.update({ add: ranges });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -123,51 +111,32 @@ const highlightState = StateField.define<DecorationSet>({
|
||||
provide: field => EditorView.decorations.from(field)
|
||||
});
|
||||
|
||||
// 定义高亮范围接口
|
||||
interface HighlightRange {
|
||||
from: number;
|
||||
to: number;
|
||||
decoration: Decoration;
|
||||
}
|
||||
|
||||
// 查找指定位置包含的高亮
|
||||
function findHighlightsAt(state: EditorState, pos: number): HighlightRange[] {
|
||||
const highlights: HighlightRange[] = [];
|
||||
|
||||
state.field(highlightState).between(pos, pos, (from, to, deco) => {
|
||||
highlights.push({ from, to, decoration: deco });
|
||||
});
|
||||
|
||||
return highlights;
|
||||
}
|
||||
|
||||
// 查找与给定范围重叠的所有高亮
|
||||
function findHighlightsInRange(state: EditorState, from: number, to: number): HighlightRange[] {
|
||||
const highlights: HighlightRange[] = [];
|
||||
function findHighlightsInRange(state: EditorState, from: number, to: number): Array<{from: number, to: number}> {
|
||||
const highlights: Array<{from: number, to: number}> = [];
|
||||
|
||||
state.field(highlightState).between(from, to, (rangeFrom, rangeTo, deco) => {
|
||||
// 只添加与指定范围有重叠的高亮
|
||||
state.field(highlightState).between(from, to, (rangeFrom, rangeTo) => {
|
||||
if (rangeFrom < to && rangeTo > from) {
|
||||
highlights.push({ from: rangeFrom, to: rangeTo, decoration: deco });
|
||||
highlights.push({ from: rangeFrom, to: rangeTo });
|
||||
}
|
||||
});
|
||||
|
||||
return highlights;
|
||||
}
|
||||
|
||||
// 收集当前所有高亮信息
|
||||
function collectAllHighlights(state: EditorState): HighlightInfo[] {
|
||||
const highlights: HighlightInfo[] = [];
|
||||
// 查找指定位置包含的高亮
|
||||
function findHighlightsAt(state: EditorState, pos: number): Array<{from: number, to: number}> {
|
||||
const highlights: Array<{from: number, to: number}> = [];
|
||||
|
||||
state.field(highlightState).between(0, state.doc.length, (from, to) => {
|
||||
state.field(highlightState).between(pos, pos, (from, to) => {
|
||||
highlights.push({ from, to });
|
||||
});
|
||||
|
||||
return highlights;
|
||||
}
|
||||
|
||||
// 添加高亮
|
||||
function addHighlightRange(view: EditorView, from: number, to: number, documentId: string): boolean {
|
||||
// 添加高亮范围
|
||||
function addHighlightRange(view: EditorView, from: number, to: number): boolean {
|
||||
if (from === to) return false; // 不高亮空选择
|
||||
|
||||
// 检查是否已经完全高亮
|
||||
@@ -179,27 +148,27 @@ function addHighlightRange(view: EditorView, from: number, to: number, documentI
|
||||
if (isFullyHighlighted) return false;
|
||||
|
||||
view.dispatch({
|
||||
effects: addHighlight.of({from, to, documentId})
|
||||
effects: addHighlight.of({from, to})
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// 移除高亮
|
||||
function removeHighlightRange(view: EditorView, from: number, to: number, documentId: string): boolean {
|
||||
// 移除高亮范围
|
||||
function removeHighlightRange(view: EditorView, from: number, to: number): boolean {
|
||||
const highlights = findHighlightsInRange(view.state, from, to);
|
||||
|
||||
if (highlights.length === 0) return false;
|
||||
|
||||
view.dispatch({
|
||||
effects: removeHighlight.of({from, to, documentId})
|
||||
effects: removeHighlight.of({from, to})
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// 切换高亮状态
|
||||
function toggleHighlight(view: EditorView, documentId: string): boolean {
|
||||
function toggleHighlight(view: EditorView): boolean {
|
||||
const selection = view.state.selection.main;
|
||||
|
||||
// 如果有选择文本
|
||||
@@ -211,10 +180,10 @@ function toggleHighlight(view: EditorView, documentId: string): boolean {
|
||||
|
||||
if (highlights.length > 0) {
|
||||
// 如果已有高亮,则移除
|
||||
return removeHighlightRange(view, from, to, documentId);
|
||||
return removeHighlightRange(view, from, to);
|
||||
} else {
|
||||
// 如果没有高亮,则添加
|
||||
return addHighlightRange(view, from, to, documentId);
|
||||
return addHighlightRange(view, from, to);
|
||||
}
|
||||
}
|
||||
// 如果是光标
|
||||
@@ -225,135 +194,20 @@ function toggleHighlight(view: EditorView, documentId: string): boolean {
|
||||
if (highlightsAtCursor.length > 0) {
|
||||
// 移除光标位置的高亮
|
||||
const highlight = highlightsAtCursor[0];
|
||||
return removeHighlightRange(view, highlight.from, highlight.to, documentId);
|
||||
return removeHighlightRange(view, highlight.from, highlight.to);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// 创建高亮快捷键,需要文档ID
|
||||
function createHighlightKeymap(documentId: string) {
|
||||
return keymap.of([
|
||||
{key: "Mod-h", run: (view) => toggleHighlight(view, documentId)}
|
||||
]);
|
||||
}
|
||||
|
||||
// 高亮刷新管理器类
|
||||
class HighlightRefreshManager {
|
||||
private view: EditorView;
|
||||
private refreshPending = false;
|
||||
private initialSetupDone = false;
|
||||
private rafId: number | null = null;
|
||||
private documentId: string;
|
||||
|
||||
constructor(view: EditorView, documentId: string) {
|
||||
this.view = view;
|
||||
this.documentId = documentId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用requestAnimationFrame安排视图更新
|
||||
*/
|
||||
scheduleRefresh(): void {
|
||||
if (this.refreshPending) return;
|
||||
|
||||
this.refreshPending = true;
|
||||
|
||||
this.rafId = requestAnimationFrame(() => {
|
||||
this.executeRefresh();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行视图更新
|
||||
*/
|
||||
private executeRefresh(): void {
|
||||
this.refreshPending = false;
|
||||
this.rafId = null;
|
||||
|
||||
if (!this.view.state) return;
|
||||
|
||||
try {
|
||||
// 触发一个空的更新,确保视图刷新
|
||||
this.view.dispatch({});
|
||||
} catch (e) {
|
||||
console.debug("highlight refresh error:", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化高亮 - 应用保存的高亮
|
||||
*/
|
||||
initHighlights(): void {
|
||||
const savedHighlights = highlightStore.getHighlights(this.documentId);
|
||||
if (savedHighlights.length > 0) {
|
||||
this.view.dispatch({
|
||||
effects: initHighlights.of({
|
||||
highlights: savedHighlights,
|
||||
documentId: this.documentId
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行初始化设置
|
||||
*/
|
||||
performInitialSetup(): void {
|
||||
if (this.initialSetupDone) return;
|
||||
|
||||
Promise.resolve().then(() => {
|
||||
this.initHighlights();
|
||||
this.scheduleRefresh();
|
||||
});
|
||||
|
||||
this.initialSetupDone = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理资源
|
||||
*/
|
||||
dispose(): void {
|
||||
if (this.rafId !== null) {
|
||||
cancelAnimationFrame(this.rafId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 创建高亮扩展
|
||||
export function createTextHighlighter(documentId: string) {
|
||||
// 视图插件
|
||||
const highlightSetupPlugin = ViewPlugin.define((view) => {
|
||||
// 创建刷新管理器实例
|
||||
const refreshManager = new HighlightRefreshManager(view, documentId);
|
||||
|
||||
// 执行初始化设置
|
||||
refreshManager.performInitialSetup();
|
||||
|
||||
return {
|
||||
update(update: ViewUpdate) {
|
||||
// 页面有内容变化时,保存最新的高亮状态
|
||||
if (update.docChanged || update.transactions.some(tr =>
|
||||
tr.effects.some(e => e.is(addHighlight) || e.is(removeHighlight))
|
||||
)) {
|
||||
// 延迟收集高亮信息,确保所有效果都已应用
|
||||
setTimeout(() => {
|
||||
const allHighlights = collectAllHighlights(view.state);
|
||||
highlightStore.saveHighlights(documentId, allHighlights);
|
||||
}, 0);
|
||||
}
|
||||
},
|
||||
destroy() {
|
||||
// 清理资源
|
||||
refreshManager.dispose();
|
||||
}
|
||||
};
|
||||
});
|
||||
// 导出文本高亮切换命令,供快捷键系统使用
|
||||
export const textHighlightToggleCommand = toggleHighlight;
|
||||
|
||||
// 创建文本高亮扩展
|
||||
export function createTextHighlighter(config: TextHighlightConfig = {}) {
|
||||
return [
|
||||
highlightState,
|
||||
createHighlightKeymap(documentId),
|
||||
highlightSetupPlugin
|
||||
highlightConfigFacet.of(config),
|
||||
highlightState
|
||||
];
|
||||
}
|
@@ -26,6 +26,7 @@ import {deleteLineCommand} from '../extensions/codeblock/deleteLine'
|
||||
import {moveLineDown, moveLineUp} from '../extensions/codeblock/moveLines'
|
||||
import {transposeChars} from '../extensions/codeblock'
|
||||
import {copyCommand, cutCommand, pasteCommand} from '../extensions/codeblock/copyPaste'
|
||||
import {textHighlightToggleCommand} from '../extensions/textHighlight/textHighlightExtension'
|
||||
import {
|
||||
copyLineDown,
|
||||
copyLineUp,
|
||||
@@ -284,6 +285,12 @@ export const commandRegistry = {
|
||||
handler: deleteGroupForward,
|
||||
descriptionKey: 'keybindings.commands.deleteGroupForward'
|
||||
},
|
||||
|
||||
// 文本高亮扩展命令
|
||||
[KeyBindingCommand.TextHighlightToggleCommand]: {
|
||||
handler: textHighlightToggleCommand,
|
||||
descriptionKey: 'keybindings.commands.textHighlightToggle'
|
||||
},
|
||||
} as const
|
||||
|
||||
/**
|
||||
|
@@ -10,7 +10,7 @@ import {color} from '../extensions/colorSelector'
|
||||
import {hyperLink} from '../extensions/hyperlink'
|
||||
import {minimap} from '../extensions/minimap'
|
||||
import {vscodeSearch} from '../extensions/vscodeSearch'
|
||||
import {createCodeBlockExtension} from '../extensions/codeblock'
|
||||
|
||||
import {foldingOnIndent} from '../extensions/fold/foldExtension'
|
||||
|
||||
/**
|
||||
@@ -33,16 +33,21 @@ export const rainbowBracketsFactory: ExtensionFactory = {
|
||||
*/
|
||||
export const textHighlightFactory: ExtensionFactory = {
|
||||
create(config: any) {
|
||||
return createTextHighlighter('default')
|
||||
return createTextHighlighter({
|
||||
backgroundColor: config.backgroundColor || '#FFD700',
|
||||
opacity: config.opacity || 0.3
|
||||
})
|
||||
},
|
||||
getDefaultConfig() {
|
||||
return {
|
||||
highlightClass: 'hl'
|
||||
backgroundColor: '#FFD700', // 金黄色
|
||||
opacity: 0.3 // 透明度
|
||||
}
|
||||
},
|
||||
validateConfig(config: any) {
|
||||
return typeof config === 'object' &&
|
||||
(!config.highlightClass || typeof config.highlightClass === 'string')
|
||||
(!config.backgroundColor || typeof config.backgroundColor === 'string') &&
|
||||
(!config.opacity || (typeof config.opacity === 'number' && config.opacity >= 0 && config.opacity <= 1))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -120,29 +125,7 @@ export const searchFactory: ExtensionFactory = {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 代码块扩展工厂
|
||||
*/
|
||||
export const codeBlockFactory: ExtensionFactory = {
|
||||
create(config: any) {
|
||||
const options = {
|
||||
showBackground: config.showBackground !== false,
|
||||
enableAutoDetection: config.enableAutoDetection !== false
|
||||
}
|
||||
return createCodeBlockExtension(options)
|
||||
},
|
||||
getDefaultConfig() {
|
||||
return {
|
||||
showBackground: true,
|
||||
enableAutoDetection: true
|
||||
}
|
||||
},
|
||||
validateConfig(config: any) {
|
||||
return typeof config === 'object' &&
|
||||
(!config.showBackground || typeof config.showBackground === 'boolean') &&
|
||||
(!config.enableAutoDetection || typeof config.enableAutoDetection === 'boolean')
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export const foldFactory: ExtensionFactory = {
|
||||
create(config: any) {
|
||||
@@ -193,11 +176,7 @@ const EXTENSION_CONFIGS = {
|
||||
displayNameKey: 'extensions.search.name',
|
||||
descriptionKey: 'extensions.search.description'
|
||||
},
|
||||
[ExtensionID.ExtensionCodeBlock]: {
|
||||
factory: codeBlockFactory,
|
||||
displayNameKey: 'extensions.codeBlock.name',
|
||||
descriptionKey: 'extensions.codeBlock.description'
|
||||
},
|
||||
|
||||
[ExtensionID.ExtensionFold]: {
|
||||
factory: foldFactory,
|
||||
displayNameKey: 'extensions.fold.name',
|
||||
|
@@ -8,10 +8,10 @@ import {ExtensionID} from '@/../bindings/voidraft/internal/models/models'
|
||||
import {getExtensionManager} from '@/views/editor/manager'
|
||||
import {
|
||||
getAllExtensionIds,
|
||||
getExtensionDefaultConfig,
|
||||
getExtensionDescription,
|
||||
getExtensionDisplayName,
|
||||
hasExtensionConfig,
|
||||
getExtensionDefaultConfig
|
||||
hasExtensionConfig
|
||||
} from '@/views/editor/manager/factories'
|
||||
import SettingSection from '../components/SettingSection.vue'
|
||||
import SettingItem from '../components/SettingItem.vue'
|
||||
@@ -67,7 +67,7 @@ const updateExtensionConfig = async (extensionId: ExtensionID, configKey: string
|
||||
if (!extension) return
|
||||
|
||||
// 更新配置
|
||||
const updatedConfig = { ...extension.config, [configKey]: value }
|
||||
const updatedConfig = {...extension.config, [configKey]: value}
|
||||
|
||||
await editorStore.updateExtension(extensionId, extension.enabled, updatedConfig)
|
||||
|
||||
@@ -114,25 +114,23 @@ const extensionConfigMeta: Partial<Record<ExtensionID, Record<string, ConfigItem
|
||||
displayText: {
|
||||
type: 'select',
|
||||
options: [
|
||||
{ value: 'characters', label: 'Characters' },
|
||||
{ value: 'blocks', label: 'Blocks' }
|
||||
{value: 'characters', label: 'Characters'},
|
||||
{value: 'blocks', label: 'Blocks'}
|
||||
]
|
||||
},
|
||||
showOverlay: {
|
||||
type: 'select',
|
||||
options: [
|
||||
{ value: 'always', label: 'Always' },
|
||||
{ value: 'mouse-over', label: 'Mouse Over' }
|
||||
{value: 'always', label: 'Always'},
|
||||
{value: 'mouse-over', label: 'Mouse Over'}
|
||||
]
|
||||
},
|
||||
autohide: { type: 'toggle' }
|
||||
},
|
||||
[ExtensionID.ExtensionCodeBlock]: {
|
||||
showBackground: { type: 'toggle' },
|
||||
enableAutoDetection: { type: 'toggle' }
|
||||
autohide: {type: 'toggle'}
|
||||
},
|
||||
|
||||
[ExtensionID.ExtensionTextHighlight]: {
|
||||
highlightClass: { type: 'text' }
|
||||
backgroundColor: {type: 'text'},
|
||||
opacity: {type: 'number'}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -180,8 +178,10 @@ const getSelectOptions = (extensionId: ExtensionID, configKey: string) => {
|
||||
:class="{ expanded: expandedExtensions.has(extension.id) }"
|
||||
:title="t('settings.extensionsPage.configuration')"
|
||||
>
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z"/>
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
|
||||
stroke-linecap="round" stroke-linejoin="round">
|
||||
<path
|
||||
d="M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z"/>
|
||||
<circle cx="12" cy="12" r="3"/>
|
||||
</svg>
|
||||
</button>
|
||||
@@ -231,7 +231,10 @@ const getSelectOptions = (extensionId: ExtensionID, configKey: string) => {
|
||||
type="number"
|
||||
class="config-input"
|
||||
:value="extension.config[configKey] ?? configValue"
|
||||
@input="updateExtensionConfig(extension.id, configKey, parseInt(($event.target as HTMLInputElement).value))"
|
||||
:min="configKey === 'opacity' ? 0 : undefined"
|
||||
:max="configKey === 'opacity' ? 1 : undefined"
|
||||
:step="configKey === 'opacity' ? 0.1 : 1"
|
||||
@input="updateExtensionConfig(extension.id, configKey, parseFloat(($event.target as HTMLInputElement).value))"
|
||||
/>
|
||||
|
||||
<!-- 选择框 -->
|
||||
|
@@ -26,7 +26,6 @@ const (
|
||||
|
||||
// 工具扩展
|
||||
ExtensionSearch ExtensionID = "search" // 搜索功能
|
||||
ExtensionCodeBlock ExtensionID = "codeBlock" // 代码块
|
||||
|
||||
// 核心扩展
|
||||
ExtensionEditor ExtensionID = "editor" // 编辑器核心功能
|
||||
@@ -90,7 +89,10 @@ func NewDefaultExtensions() []Extension {
|
||||
ID: ExtensionTextHighlight,
|
||||
Enabled: true,
|
||||
IsDefault: true,
|
||||
Config: ExtensionConfig{},
|
||||
Config: ExtensionConfig{
|
||||
"backgroundColor": "#FFD700",
|
||||
"opacity": 0.3,
|
||||
},
|
||||
},
|
||||
|
||||
// UI增强扩展
|
||||
@@ -112,15 +114,6 @@ func NewDefaultExtensions() []Extension {
|
||||
IsDefault: true,
|
||||
Config: ExtensionConfig{},
|
||||
},
|
||||
{
|
||||
ID: ExtensionCodeBlock,
|
||||
Enabled: true,
|
||||
IsDefault: true,
|
||||
Config: ExtensionConfig{
|
||||
"showBackground": true,
|
||||
"enableAutoDetection": true,
|
||||
},
|
||||
},
|
||||
|
||||
// 核心扩展
|
||||
{
|
||||
|
@@ -78,6 +78,9 @@ const (
|
||||
HistoryRedoCommand KeyBindingCommand = "historyRedo" // 重做
|
||||
HistoryUndoSelectionCommand KeyBindingCommand = "historyUndoSelection" // 撤销选择
|
||||
HistoryRedoSelectionCommand KeyBindingCommand = "historyRedoSelection" // 重做选择
|
||||
|
||||
// 文本高亮扩展相关
|
||||
TextHighlightToggleCommand KeyBindingCommand = "textHighlightToggle" // 切换文本高亮
|
||||
)
|
||||
|
||||
// KeyBindingMetadata 快捷键配置元数据
|
||||
@@ -157,115 +160,115 @@ func NewDefaultKeyBindings() []KeyBinding {
|
||||
IsDefault: true,
|
||||
},
|
||||
|
||||
// 代码块扩展快捷键
|
||||
// 代码块核心功能快捷键
|
||||
{
|
||||
Command: BlockSelectAllCommand,
|
||||
Extension: ExtensionCodeBlock,
|
||||
Extension: ExtensionEditor,
|
||||
Key: "Mod-a",
|
||||
Enabled: true,
|
||||
IsDefault: true,
|
||||
},
|
||||
{
|
||||
Command: BlockAddAfterCurrentCommand,
|
||||
Extension: ExtensionCodeBlock,
|
||||
Extension: ExtensionEditor,
|
||||
Key: "Mod-Enter",
|
||||
Enabled: true,
|
||||
IsDefault: true,
|
||||
},
|
||||
{
|
||||
Command: BlockAddAfterLastCommand,
|
||||
Extension: ExtensionCodeBlock,
|
||||
Extension: ExtensionEditor,
|
||||
Key: "Mod-Shift-Enter",
|
||||
Enabled: true,
|
||||
IsDefault: true,
|
||||
},
|
||||
{
|
||||
Command: BlockAddBeforeCurrentCommand,
|
||||
Extension: ExtensionCodeBlock,
|
||||
Extension: ExtensionEditor,
|
||||
Key: "Alt-Enter",
|
||||
Enabled: true,
|
||||
IsDefault: true,
|
||||
},
|
||||
{
|
||||
Command: BlockGotoPreviousCommand,
|
||||
Extension: ExtensionCodeBlock,
|
||||
Extension: ExtensionEditor,
|
||||
Key: "Mod-ArrowUp",
|
||||
Enabled: true,
|
||||
IsDefault: true,
|
||||
},
|
||||
{
|
||||
Command: BlockGotoNextCommand,
|
||||
Extension: ExtensionCodeBlock,
|
||||
Extension: ExtensionEditor,
|
||||
Key: "Mod-ArrowDown",
|
||||
Enabled: true,
|
||||
IsDefault: true,
|
||||
},
|
||||
{
|
||||
Command: BlockSelectPreviousCommand,
|
||||
Extension: ExtensionCodeBlock,
|
||||
Extension: ExtensionEditor,
|
||||
Key: "Mod-Shift-ArrowUp",
|
||||
Enabled: true,
|
||||
IsDefault: true,
|
||||
},
|
||||
{
|
||||
Command: BlockSelectNextCommand,
|
||||
Extension: ExtensionCodeBlock,
|
||||
Extension: ExtensionEditor,
|
||||
Key: "Mod-Shift-ArrowDown",
|
||||
Enabled: true,
|
||||
IsDefault: true,
|
||||
},
|
||||
{
|
||||
Command: BlockDeleteCommand,
|
||||
Extension: ExtensionCodeBlock,
|
||||
Extension: ExtensionEditor,
|
||||
Key: "Mod-Shift-d",
|
||||
Enabled: true,
|
||||
IsDefault: true,
|
||||
},
|
||||
{
|
||||
Command: BlockMoveUpCommand,
|
||||
Extension: ExtensionCodeBlock,
|
||||
Extension: ExtensionEditor,
|
||||
Key: "Alt-Mod-ArrowUp",
|
||||
Enabled: true,
|
||||
IsDefault: true,
|
||||
},
|
||||
{
|
||||
Command: BlockMoveDownCommand,
|
||||
Extension: ExtensionCodeBlock,
|
||||
Extension: ExtensionEditor,
|
||||
Key: "Alt-Mod-ArrowDown",
|
||||
Enabled: true,
|
||||
IsDefault: true,
|
||||
},
|
||||
{
|
||||
Command: BlockDeleteLineCommand,
|
||||
Extension: ExtensionCodeBlock,
|
||||
Extension: ExtensionEditor,
|
||||
Key: "Mod-Shift-k",
|
||||
Enabled: true,
|
||||
IsDefault: true,
|
||||
},
|
||||
{
|
||||
Command: BlockMoveLineUpCommand,
|
||||
Extension: ExtensionCodeBlock,
|
||||
Extension: ExtensionEditor,
|
||||
Key: "Alt-ArrowUp",
|
||||
Enabled: true,
|
||||
IsDefault: true,
|
||||
},
|
||||
{
|
||||
Command: BlockMoveLineDownCommand,
|
||||
Extension: ExtensionCodeBlock,
|
||||
Extension: ExtensionEditor,
|
||||
Key: "Alt-ArrowDown",
|
||||
Enabled: true,
|
||||
IsDefault: true,
|
||||
},
|
||||
{
|
||||
Command: BlockTransposeCharsCommand,
|
||||
Extension: ExtensionCodeBlock,
|
||||
Extension: ExtensionEditor,
|
||||
Key: "Ctrl-t",
|
||||
Enabled: true,
|
||||
IsDefault: true,
|
||||
},
|
||||
{
|
||||
Command: BlockFormatCommand,
|
||||
Extension: ExtensionCodeBlock,
|
||||
Extension: ExtensionEditor,
|
||||
Key: "Mod-Shift-f",
|
||||
Enabled: true,
|
||||
IsDefault: true,
|
||||
@@ -493,6 +496,15 @@ func NewDefaultKeyBindings() []KeyBinding {
|
||||
Enabled: true,
|
||||
IsDefault: true,
|
||||
},
|
||||
|
||||
// 文本高亮扩展快捷键
|
||||
{
|
||||
Command: TextHighlightToggleCommand,
|
||||
Extension: ExtensionTextHighlight,
|
||||
Key: "Mod-Shift-h",
|
||||
Enabled: true,
|
||||
IsDefault: true,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user