🚨 Format code

This commit is contained in:
2025-09-24 21:44:42 +08:00
parent 1462d8a753
commit f5bfff80b7
76 changed files with 839 additions and 863 deletions

View File

@@ -19,7 +19,7 @@ declare global {
let menuElement: HTMLElement | null = null;
let clickOutsideHandler: ((e: MouseEvent) => void) | null = null;
// 子菜单缓存池
let submenuPool: Map<string, HTMLElement> = new Map();
const submenuPool: Map<string, HTMLElement> = new Map();
/**
* 获取或创建菜单DOM元素

View File

@@ -92,14 +92,6 @@ function formatKeyBinding(keyBinding: string): string {
.replace(/-/g, " + ");
}
/**
* 从命令注册表获取命令处理程序和翻译键
* @param command 命令ID
* @returns 命令处理程序和翻译键
*/
function getCommandInfo(command: KeyBindingCommand): { handler: (view: EditorView) => boolean, descriptionKey: string } | undefined {
return commandRegistry[command];
}
/**
* 创建编辑菜单项

View File

@@ -1,48 +1,48 @@
import { EditorView, Decoration } from "@codemirror/view"
import { WidgetType } from "@codemirror/view"
import { ViewUpdate, ViewPlugin, DecorationSet } from "@codemirror/view"
import { Extension, Compartment, StateEffect } from "@codemirror/state"
import { EditorView, Decoration } from "@codemirror/view";
import { WidgetType } from "@codemirror/view";
import { ViewUpdate, ViewPlugin, DecorationSet } from "@codemirror/view";
import { Extension, StateEffect } from "@codemirror/state";
// 创建字体变化效果
const fontChangeEffect = StateEffect.define<void>()
const fontChangeEffect = StateEffect.define<void>();
/**
* 复选框小部件类
*/
class CheckboxWidget extends WidgetType {
constructor(readonly checked: boolean) {
super()
super();
}
eq(other: CheckboxWidget) {
return other.checked == this.checked
return other.checked == this.checked;
}
toDOM() {
let wrap = document.createElement("span")
wrap.setAttribute("aria-hidden", "true")
wrap.className = "cm-checkbox-toggle"
const wrap = document.createElement("span");
wrap.setAttribute("aria-hidden", "true");
wrap.className = "cm-checkbox-toggle";
let box = document.createElement("input")
box.type = "checkbox"
box.checked = this.checked
box.tabIndex = -1
box.style.margin = "0"
box.style.padding = "0"
box.style.cursor = "pointer"
box.style.position = "relative"
box.style.top = "0.1em"
box.style.marginRight = "0.5em"
const box = document.createElement("input");
box.type = "checkbox";
box.checked = this.checked;
box.tabIndex = -1;
box.style.margin = "0";
box.style.padding = "0";
box.style.cursor = "pointer";
box.style.position = "relative";
box.style.top = "0.1em";
box.style.marginRight = "0.5em";
// 设置相对单位,让复选框跟随字体大小变化
box.style.width = "1em"
box.style.height = "1em"
box.style.width = "1em";
box.style.height = "1em";
wrap.appendChild(box)
return wrap
wrap.appendChild(box);
return wrap;
}
ignoreEvent() {
return false
return false;
}
}
@@ -50,82 +50,82 @@ class CheckboxWidget extends WidgetType {
* 查找并创建复选框装饰
*/
function findCheckboxes(view: EditorView) {
let widgets: any = []
const doc = view.state.doc
const widgets: any = [];
const doc = view.state.doc;
for (let { from, to } of view.visibleRanges) {
for (const { from, to } of view.visibleRanges) {
// 使用正则表达式查找 [x] 或 [ ] 模式
const text = doc.sliceString(from, to)
const checkboxRegex = /\[([ x])\]/gi
let match
const text = doc.sliceString(from, to);
const checkboxRegex = /\[([ x])\]/gi;
let match;
while ((match = checkboxRegex.exec(text)) !== null) {
const matchPos = from + match.index
const matchEnd = matchPos + match[0].length
const matchPos = from + match.index;
const matchEnd = matchPos + match[0].length;
// 检查前面是否有 "- " 模式
const beforeTwoChars = matchPos >= 2 ? doc.sliceString(matchPos - 2, matchPos) : ""
const afterChar = matchEnd < doc.length ? doc.sliceString(matchEnd, matchEnd + 1) : ""
const beforeTwoChars = matchPos >= 2 ? doc.sliceString(matchPos - 2, matchPos) : "";
const afterChar = matchEnd < doc.length ? doc.sliceString(matchEnd, matchEnd + 1) : "";
// 只有当前面是 "- " 且后面跟空格或行尾时才渲染
if (beforeTwoChars === "- " &&
(afterChar === "" || afterChar === " " || afterChar === "\t" || afterChar === "\n")) {
const isChecked = match[1].toLowerCase() === "x"
let deco = Decoration.replace({
const isChecked = match[1].toLowerCase() === "x";
const deco = Decoration.replace({
widget: new CheckboxWidget(isChecked),
inclusive: false,
})
});
// 替换整个 "- [ ]" 或 "- [x]" 模式,包括前面的 "- "
widgets.push(deco.range(matchPos - 2, matchEnd))
widgets.push(deco.range(matchPos - 2, matchEnd));
}
}
}
return Decoration.set(widgets)
return Decoration.set(widgets);
}
/**
* 切换复选框状态
*/
function toggleCheckbox(view: EditorView, pos: number) {
const doc = view.state.doc
const doc = view.state.doc;
// 查找当前位置附近的复选框模式(需要前面有 "- "
for (let offset = -5; offset <= 0; offset++) {
const checkPos = pos + offset
const checkPos = pos + offset;
if (checkPos >= 2 && checkPos + 3 <= doc.length) {
// 检查是否有 "- " 前缀
const prefix = doc.sliceString(checkPos - 2, checkPos)
const text = doc.sliceString(checkPos, checkPos + 3).toLowerCase()
const prefix = doc.sliceString(checkPos - 2, checkPos);
const text = doc.sliceString(checkPos, checkPos + 3).toLowerCase();
if (prefix === "- ") {
let change
let change;
if (text === "[x]") {
// 替换整个 "- [x]" 为 "- [ ]"
change = { from: checkPos - 2, to: checkPos + 3, insert: "- [ ]" }
change = { from: checkPos - 2, to: checkPos + 3, insert: "- [ ]" };
} else if (text === "[ ]") {
// 替换整个 "- [ ]" 为 "- [x]"
change = { from: checkPos - 2, to: checkPos + 3, insert: "- [x]" }
change = { from: checkPos - 2, to: checkPos + 3, insert: "- [x]" };
}
if (change) {
view.dispatch({ changes: change })
return true
view.dispatch({ changes: change });
return true;
}
}
}
}
return false
return false;
}
// 创建字体变化效果的便捷函数
export const triggerFontChange = (view: EditorView) => {
view.dispatch({
effects: fontChangeEffect.of(undefined)
})
}
});
};
/**
* 创建复选框扩展
@@ -134,10 +134,10 @@ export function createCheckboxExtension(): Extension {
return [
// 主要的复选框插件
ViewPlugin.fromClass(class {
decorations: DecorationSet
decorations: DecorationSet;
constructor(view: EditorView) {
this.decorations = findCheckboxes(view)
this.decorations = findCheckboxes(view);
}
update(update: ViewUpdate) {
@@ -145,10 +145,10 @@ export function createCheckboxExtension(): Extension {
const shouldUpdate = update.docChanged ||
update.viewportChanged ||
update.geometryChanged ||
update.transactions.some(tr => tr.effects.some(e => e.is(fontChangeEffect)))
update.transactions.some(tr => tr.effects.some(e => e.is(fontChangeEffect)));
if (shouldUpdate) {
this.decorations = findCheckboxes(update.view)
this.decorations = findCheckboxes(update.view);
}
}
}, {
@@ -156,10 +156,10 @@ export function createCheckboxExtension(): Extension {
eventHandlers: {
mousedown: (e, view) => {
let target = e.target as HTMLElement
const target = e.target as HTMLElement;
if (target.nodeName == "INPUT" && target.parentElement!.classList.contains("cm-checkbox-toggle")) {
const pos = view.posAtDOM(target)
return toggleCheckbox(view, pos)
const pos = view.posAtDOM(target);
return toggleCheckbox(view, pos);
}
}
}
@@ -180,15 +180,15 @@ export function createCheckboxExtension(): Extension {
fontSize: "inherit",
}
})
]
];
}
// 默认导出
export const checkboxExtension = createCheckboxExtension()
export const checkboxExtension = createCheckboxExtension();
// 导出类型和工具函数
export {
CheckboxWidget,
toggleCheckbox,
findCheckboxes
}
};

View File

@@ -193,8 +193,8 @@ function setSel(state: any, selection: EditorSelection) {
}
function extendSel(state: any, dispatch: any, how: (range: any) => any) {
let selection = updateSel(state.selection, range => {
let head = how(range);
const selection = updateSel(state.selection, range => {
const head = how(range);
return EditorSelection.range(range.anchor, head.head, head.goalColumn, head.bidiLevel || undefined);
});
if (selection.eq(state.selection)) return false;
@@ -203,7 +203,7 @@ function extendSel(state: any, dispatch: any, how: (range: any) => any) {
}
function moveSel(state: any, dispatch: any, how: (range: any) => any) {
let selection = updateSel(state.selection, how);
const selection = updateSel(state.selection, how);
if (selection.eq(state.selection)) return false;
dispatch(setSel(state, selection));
return true;
@@ -268,7 +268,7 @@ export function selectPreviousBlock({ state, dispatch }: any) {
/**
* 删除块
*/
export const deleteBlock = (options: EditorOptions): Command => ({ state, dispatch }) => {
export const deleteBlock = (_options: EditorOptions): Command => ({ state, dispatch }) => {
if (state.readOnly) return false;
const block = getActiveNoteBlock(state);
@@ -380,4 +380,4 @@ function moveCurrentBlock(state: any, dispatch: any, up: boolean) {
*/
export const formatCurrentBlock: Command = (view) => {
return formatBlockContent(view);
}
};

View File

@@ -18,10 +18,10 @@ const blockSeparatorRegex = new RegExp(`\\n∞∞∞(${languageTokensMatcher})(-
* 获取被复制的范围和内容
*/
function copiedRange(state: EditorState) {
let content: string[] = [];
let ranges: any[] = [];
const content: string[] = [];
const ranges: any[] = [];
for (let range of state.selection.ranges) {
for (const range of state.selection.ranges) {
if (!range.empty) {
content.push(state.sliceDoc(range.from, range.to));
ranges.push(range);
@@ -31,7 +31,7 @@ function copiedRange(state: EditorState) {
if (ranges.length === 0) {
// 如果所有范围都是空的,我们想要复制每个选择的整行(唯一的)
const copiedLines: number[] = [];
for (let range of state.selection.ranges) {
for (const range of state.selection.ranges) {
if (range.empty) {
const line = state.doc.lineAt(range.head);
const lineContent = state.sliceDoc(line.from, line.to);
@@ -55,7 +55,7 @@ function copiedRange(state: EditorState) {
*/
export const codeBlockCopyCut = EditorView.domEventHandlers({
copy(event, view) {
let { text, ranges } = copiedRange(view.state);
let { text } = copiedRange(view.state);
// 将块分隔符替换为双换行符
text = text.replaceAll(blockSeparatorRegex, "\n\n");

View File

@@ -19,7 +19,7 @@ class NoteBlockStart extends WidgetType {
}
toDOM() {
let wrap = document.createElement("div");
const wrap = document.createElement("div");
wrap.className = "code-block-start" + (this.isFirst ? " first" : "");
return wrap;
}
@@ -37,8 +37,8 @@ const noteBlockWidget = () => {
const builder = new RangeSetBuilder<Decoration>();
state.field(blockState).forEach((block: any) => {
let delimiter = block.delimiter;
let deco = Decoration.replace({
const delimiter = block.delimiter;
const deco = Decoration.replace({
widget: new NoteBlockStart(delimiter.from === 0),
inclusive: true,
block: true,
@@ -80,7 +80,7 @@ const noteBlockWidget = () => {
* 原子范围,防止在分隔符内编辑
*/
function atomicRanges(view: EditorView) {
let builder = new RangeSetBuilder();
const builder = new RangeSetBuilder();
view.state.field(blockState).forEach((block: any) => {
builder.add(
block.delimiter.from,
@@ -167,7 +167,7 @@ const blockLayer = layer({
return markers;
},
update(update: any, dom: any) {
update(update: any, _dom: any) {
return update.docChanged || update.viewportChanged;
},

View File

@@ -24,11 +24,11 @@ function updateSel(sel: EditorSelection, by: (range: SelectionRange) => Selectio
* 获取选中的行块
*/
function selectedLineBlocks(state: any): LineBlock[] {
let blocks: LineBlock[] = [];
const blocks: LineBlock[] = [];
let upto = -1;
for (let range of state.selection.ranges) {
let startLine = state.doc.lineAt(range.from);
for (const range of state.selection.ranges) {
const startLine = state.doc.lineAt(range.from);
let endLine = state.doc.lineAt(range.to);
if (!range.empty && range.to == endLine.from) {
@@ -36,7 +36,7 @@ function selectedLineBlocks(state: any): LineBlock[] {
}
if (upto >= startLine.number) {
let prev = blocks[blocks.length - 1];
const prev = blocks[blocks.length - 1];
prev.to = endLine.to;
prev.ranges.push(range);
} else {

View File

@@ -1,42 +1,42 @@
import { EditorSelection } from "@codemirror/state"
import { EditorSelection } from "@codemirror/state";
import * as prettier from "prettier/standalone"
import { getActiveNoteBlock } from "./state"
import { getLanguage } from "./lang-parser/languages"
import { SupportedLanguage } from "./types"
import * as prettier from "prettier/standalone";
import { getActiveNoteBlock } from "./state";
import { getLanguage } from "./lang-parser/languages";
import { SupportedLanguage } from "./types";
export const formatBlockContent = (view) => {
if (!view || view.state.readOnly)
return false
return false;
// 获取初始信息但不缓存state对象
const initialState = view.state
const block = getActiveNoteBlock(initialState)
const initialState = view.state;
const block = getActiveNoteBlock(initialState);
if (!block) {
return false
return false;
}
const blockFrom = block.content.from
const blockTo = block.content.to
const blockLanguageName = block.language.name as SupportedLanguage
const blockFrom = block.content.from;
const blockTo = block.content.to;
const blockLanguageName = block.language.name as SupportedLanguage;
const language = getLanguage(blockLanguageName)
const language = getLanguage(blockLanguageName);
if (!language || !language.prettier) {
return false
return false;
}
// 获取初始需要的信息
const cursorPos = initialState.selection.asSingle().ranges[0].head
const content = initialState.sliceDoc(blockFrom, blockTo)
const tabSize = initialState.tabSize
const cursorPos = initialState.selection.asSingle().ranges[0].head;
const content = initialState.sliceDoc(blockFrom, blockTo);
const tabSize = initialState.tabSize;
// 检查光标是否在块的开始或结束
const cursorAtEdge = cursorPos == blockFrom || cursorPos == blockTo
const cursorAtEdge = cursorPos == blockFrom || cursorPos == blockTo;
// 执行异步格式化
const performFormat = async () => {
let formattedContent
let formattedContent;
try {
// 构建格式化配置
const formatOptions = {
@@ -44,39 +44,39 @@ export const formatBlockContent = (view) => {
plugins: language.prettier!.plugins,
tabWidth: tabSize,
...language.prettier!.options,
}
};
// 格式化代码
const formatted = await prettier.format(content, formatOptions)
const formatted = await prettier.format(content, formatOptions);
// 计算新光标位置
const cursorOffset = cursorAtEdge
? (cursorPos == blockFrom ? 0 : formatted.length)
: Math.min(cursorPos - blockFrom, formatted.length)
: Math.min(cursorPos - blockFrom, formatted.length);
formattedContent = {
formatted,
cursorOffset
}
} catch (e) {
return false
};
} catch (_e) {
return false;
}
try {
// 格式化完成后再次获取最新状态
const currentState = view.state
const currentState = view.state;
// 重新获取当前块的位置
const currentBlock = getActiveNoteBlock(currentState)
const currentBlock = getActiveNoteBlock(currentState);
if (!currentBlock) {
console.warn('Block not found after formatting')
return false
console.warn('Block not found after formatting');
return false;
}
// 使用当前块的实际位置
const currentBlockFrom = currentBlock.content.from
const currentBlockTo = currentBlock.content.to
const currentBlockFrom = currentBlock.content.from;
const currentBlockTo = currentBlock.content.to;
// 基于最新状态创建更新
view.dispatch({
@@ -88,16 +88,16 @@ export const formatBlockContent = (view) => {
selection: EditorSelection.cursor(currentBlockFrom + Math.min(formattedContent.cursorOffset, formattedContent.formatted.length)),
scrollIntoView: true,
userEvent: "input"
})
});
return true;
} catch (error) {
console.error('Failed to apply formatting changes:', error);
return false;
}
}
};
// 执行异步格式化
performFormat()
return true // 立即返回 true表示命令已开始执行
}
performFormat();
return true; // 立即返回 true表示命令已开始执行
};

View File

@@ -49,8 +49,8 @@ export const CodeBlockLanguage = LRLanguage.define({
* 创建代码块语言支持
*/
export function codeBlockLang() {
let wrap = configureNesting();
let lang = CodeBlockLanguage.configure({ dialect: "", wrap: wrap });
const wrap = configureNesting();
const lang = CodeBlockLanguage.configure({ dialect: "", wrap: wrap });
return [
new LanguageSupport(lang, [json().support]),

View File

@@ -32,17 +32,17 @@ import {dockerFile} from "@codemirror/legacy-modes/mode/dockerfile";
import {lua} from "@codemirror/legacy-modes/mode/lua";
import {SupportedLanguage} from '../types';
import typescriptPlugin from "prettier/plugins/typescript"
import babelPrettierPlugin from "prettier/plugins/babel"
import htmlPrettierPlugin from "prettier/plugins/html"
import cssPrettierPlugin from "prettier/plugins/postcss"
import markdownPrettierPlugin from "prettier/plugins/markdown"
import yamlPrettierPlugin from "prettier/plugins/yaml"
import goPrettierPlugin from "@/common/prettier/plugins/go"
import sqlPrettierPlugin from "@/common/prettier/plugins/sql"
import phpPrettierPlugin from "@/common/prettier/plugins/php"
import javaPrettierPlugin from "@/common/prettier/plugins/java"
import xmlPrettierPlugin from "@prettier/plugin-xml"
import typescriptPlugin from "prettier/plugins/typescript";
import babelPrettierPlugin from "prettier/plugins/babel";
import htmlPrettierPlugin from "prettier/plugins/html";
import cssPrettierPlugin from "prettier/plugins/postcss";
import markdownPrettierPlugin from "prettier/plugins/markdown";
import yamlPrettierPlugin from "prettier/plugins/yaml";
import goPrettierPlugin from "@/common/prettier/plugins/go";
import sqlPrettierPlugin from "@/common/prettier/plugins/sql";
import phpPrettierPlugin from "@/common/prettier/plugins/php";
import javaPrettierPlugin from "@/common/prettier/plugins/java";
import xmlPrettierPlugin from "@prettier/plugin-xml";
import shellPrettierPlugin from "@/common/prettier/plugins/shell";
import dockerfilePrettierPlugin from "@/common/prettier/plugins/docker";
import rustPrettierPlugin from "@/common/prettier/plugins/rust";

View File

@@ -13,11 +13,11 @@ import { languageMapping } from "./languages";
*/
export function configureNesting() {
return parseMixed((node, input) => {
let id = node.type.id;
const id = node.type.id;
if (id === BlockContent) {
// 获取父节点中的语言标记
let blockLang = node.node.parent?.firstChild?.getChildren(BlockLanguage)[0];
const blockLang = node.node.parent?.firstChild?.getChildren(BlockLanguage)[0];
let langName = blockLang ? input.read(blockLang.from, blockLang.to) : null;
// 如果 BlockContent 为空,不返回解析器

View File

@@ -63,13 +63,13 @@ class MathResult extends WidgetType {
* 数学装饰函数
*/
function mathDeco(view: any): any {
let mathParsers = new WeakMap();
let builder = new RangeSetBuilder();
const mathParsers = new WeakMap();
const builder = new RangeSetBuilder();
for (let { from, to } of view.visibleRanges) {
for (const { from, to } of view.visibleRanges) {
for (let pos = from; pos <= to;) {
let line = view.state.doc.lineAt(pos);
var block = getNoteBlockFromPos(view.state, pos);
const line = view.state.doc.lineAt(pos);
const block = getNoteBlockFromPos(view.state, pos);
if (block && block.language.name === "math") {
// get math.js parser and cache it for this block
@@ -97,7 +97,7 @@ function mathDeco(view: any): any {
// if we got a result from math.js, add the result decoration
if (result !== undefined) {
let format = parser?.get("format");
const format = parser?.get("format");
let resultWidget: MathResult | undefined;
if (typeof(result) === "string") {

View File

@@ -21,11 +21,11 @@ const tokenRegEx = new RegExp(`^∞∞∞(${languageTokensMatcher})(-a)?$`, "g")
* 获取选中的行块
*/
function selectedLineBlocks(state: any): LineBlock[] {
let blocks: LineBlock[] = [];
const blocks: LineBlock[] = [];
let upto = -1;
for (let range of state.selection.ranges) {
let startLine = state.doc.lineAt(range.from);
for (const range of state.selection.ranges) {
const startLine = state.doc.lineAt(range.from);
let endLine = state.doc.lineAt(range.to);
if (!range.empty && range.to == endLine.from) {
@@ -33,7 +33,7 @@ function selectedLineBlocks(state: any): LineBlock[] {
}
if (upto >= startLine.number) {
let prev = blocks[blocks.length - 1];
const prev = blocks[blocks.length - 1];
prev.to = endLine.to;
prev.ranges.push(range);
} else {
@@ -57,15 +57,15 @@ function moveLine(state: any, dispatch: any, forward: boolean): boolean {
return false;
}
let changes: any[] = [];
let ranges: SelectionRange[] = [];
const changes: any[] = [];
const ranges: SelectionRange[] = [];
for (let block of selectedLineBlocks(state)) {
for (const block of selectedLineBlocks(state)) {
if (forward ? block.to == state.doc.length : block.from == 0) {
continue;
}
let nextLine = state.doc.lineAt(forward ? block.to + 1 : block.from - 1);
const nextLine = state.doc.lineAt(forward ? block.to + 1 : block.from - 1);
let previousLine;
if (!forward ? block.to == state.doc.length : block.from == 0) {
@@ -76,8 +76,8 @@ function moveLine(state: any, dispatch: any, forward: boolean): boolean {
// 如果整个选择是一个被分隔符包围的块,我们需要在分隔符之间添加额外的换行符
// 以避免创建两个只有单个换行符的分隔符,这会导致语法解析器无法解析
let nextLineIsSeparator = nextLine.text.match(tokenRegEx);
let blockSurroundedBySeparators = previousLine !== null &&
const nextLineIsSeparator = nextLine.text.match(tokenRegEx);
const blockSurroundedBySeparators = previousLine !== null &&
previousLine.text.match(tokenRegEx) && nextLineIsSeparator;
let size = nextLine.length + 1;
@@ -96,7 +96,7 @@ function moveLine(state: any, dispatch: any, forward: boolean): boolean {
);
}
for (let r of block.ranges) {
for (const r of block.ranges) {
ranges.push(EditorSelection.range(
Math.min(state.doc.length, r.anchor + size),
Math.min(state.doc.length, r.head + size)
@@ -108,7 +108,7 @@ function moveLine(state: any, dispatch: any, forward: boolean): boolean {
{ from: nextLine.from, to: block.from },
{ from: block.to, insert: state.lineBreak + nextLine.text + state.lineBreak }
);
for (let r of block.ranges) {
for (const r of block.ranges) {
ranges.push(EditorSelection.range(r.anchor - size, r.head - size));
}
} else {
@@ -116,7 +116,7 @@ function moveLine(state: any, dispatch: any, forward: boolean): boolean {
{ from: nextLine.from, to: block.from },
{ from: block.to, insert: state.lineBreak + nextLine.text }
);
for (let r of block.ranges) {
for (const r of block.ranges) {
ranges.push(EditorSelection.range(r.anchor - size, r.head - size));
}
}
@@ -143,7 +143,7 @@ function moveLine(state: any, dispatch: any, forward: boolean): boolean {
export const moveLineUp = ({ state, dispatch }: { state: any; dispatch: any }): boolean => {
// 防止移动行到第一个块分隔符之前
if (state.selection.ranges.some((range: SelectionRange) => {
let startLine = state.doc.lineAt(range.from);
const startLine = state.doc.lineAt(range.from);
return startLine.from <= state.field(blockState)[0].content.from;
})) {
return true;

View File

@@ -27,7 +27,7 @@ export const emptyBlockSelected = StateField.define<number | null>({
// 如果选择改变,重置状态
return null;
} else {
for (let e of tr.effects) {
for (const e of tr.effects) {
if (e.is(setEmptyBlockSelected)) {
// 切换状态为 true
return e.value;
@@ -164,8 +164,8 @@ export const blockAwareSelection = EditorState.transactionFilter.of((tr: any) =>
// 如果选择在一个块内,确保不超出块边界
if (fromBlock) {
let newFrom = Math.max(range.from, fromBlock.content.from);
let newTo = Math.min(range.to, fromBlock.content.to);
const newFrom = Math.max(range.from, fromBlock.content.from);
const newTo = Math.min(range.to, fromBlock.content.to);
if (newFrom !== range.from || newTo !== range.to) {
needsCorrection = true;

View File

@@ -14,7 +14,7 @@ export const transposeChars = ({ state, dispatch }: { state: any; dispatch: any
return false;
}
let changes = state.changeByRange((range: any) => {
const changes = state.changeByRange((range: any) => {
// 防止在代码块的开始或结束位置进行字符转置,因为这会破坏块语法
const block = getNoteBlockFromPos(state, range.from);
if (block && (range.from === block.content.from || range.from === block.content.to)) {
@@ -25,10 +25,10 @@ export const transposeChars = ({ state, dispatch }: { state: any; dispatch: any
return { range };
}
let pos = range.from;
let line = state.doc.lineAt(pos);
let from = pos == line.from ? pos - 1 : findClusterBreak(line.text, pos - line.from, false) + line.from;
let to = pos == line.to ? pos + 1 : findClusterBreak(line.text, pos - line.from, true) + line.from;
const pos = range.from;
const line = state.doc.lineAt(pos);
const from = pos == line.from ? pos - 1 : findClusterBreak(line.text, pos - line.from, false) + line.from;
const to = pos == line.to ? pos + 1 : findClusterBreak(line.text, pos - line.from, true) + line.from;
return {
changes: {

View File

@@ -240,7 +240,7 @@ export const colorView = (showPicker: boolean = true) =>
const comma = (target.dataset.colorraw || '').indexOf(',') > 4;
let converted = target.value;
if (data.colorType === ColorType.rgb) {
let funName = colorraw?.match(/^(rgba?)/) ? colorraw?.match(/^(rgba?)/)![0] : undefined;
const funName = colorraw?.match(/^(rgba?)/) ? colorraw?.match(/^(rgba?)/)![0] : undefined;
if (comma) {
converted = rgb
? `${funName}(${rgb.r}, ${rgb.g}, ${rgb.b}${data.alpha ? ', ' + data.alpha.trim() : ''})`

View File

@@ -1,37 +1,37 @@
import {foldService} from '@codemirror/language';
export const foldingOnIndent = foldService.of((state, from, to) => {
const line = state.doc.lineAt(from) // First line
const lines = state.doc.lines // Number of lines in the document
const indent = line.text.search(/\S|$/) // Indent level of the first line
let foldStart = from // Start of the fold
let foldEnd = to // End of the fold
const line = state.doc.lineAt(from); // First line
const lines = state.doc.lines; // Number of lines in the document
const indent = line.text.search(/\S|$/); // Indent level of the first line
let foldStart = from; // Start of the fold
let foldEnd = to; // End of the fold
// Check the next line if it is on a deeper indent level
// If it is, check the next line and so on
// If it is not, go on with the foldEnd
let nextLine = line
let nextLine = line;
while (nextLine.number < lines) {
nextLine = state.doc.line(nextLine.number + 1) // Next line
const nextIndent = nextLine.text.search(/\S|$/) // Indent level of the next line
nextLine = state.doc.line(nextLine.number + 1); // Next line
const nextIndent = nextLine.text.search(/\S|$/); // Indent level of the next line
// If the next line is on a deeper indent level, add it to the fold
if (nextIndent > indent) {
foldEnd = nextLine.to // Set the fold end to the end of the next line
foldEnd = nextLine.to; // Set the fold end to the end of the next line
} else {
break // If the next line is not on a deeper indent level, stop
break; // If the next line is not on a deeper indent level, stop
}
}
// If the fold is only one line, don't fold it
if (state.doc.lineAt(foldStart).number === state.doc.lineAt(foldEnd).number) {
return null
return null;
}
// Set the fold start to the end of the first line
// With this, the fold will not include the first line
foldStart = line.to
foldStart = line.to;
// Return a fold that covers the entire indent level
return {from: foldStart, to: foldEnd}
return {from: foldStart, to: foldEnd};
});

View File

@@ -8,7 +8,7 @@ import {
ViewUpdate,
} from '@codemirror/view';
import { Extension, Range } from '@codemirror/state';
import * as runtime from "@wailsio/runtime"
import * as runtime from "@wailsio/runtime";
const pathStr = `<svg viewBox="0 0 1024 1024" width="16" height="16" fill="currentColor"><path d="M607.934444 417.856853c-6.179746-6.1777-12.766768-11.746532-19.554358-16.910135l-0.01228 0.011256c-6.986111-6.719028-16.47216-10.857279-26.930349-10.857279-21.464871 0-38.864146 17.400299-38.864146 38.864146 0 9.497305 3.411703 18.196431 9.071609 24.947182l-0.001023 0c0.001023 0.001023 0.00307 0.00307 0.005117 0.004093 2.718925 3.242857 5.953595 6.03853 9.585309 8.251941 3.664459 3.021823 7.261381 5.997598 10.624988 9.361205l3.203972 3.204995c40.279379 40.229237 28.254507 109.539812-12.024871 149.820214L371.157763 796.383956c-40.278355 40.229237-105.761766 40.229237-146.042167 0l-3.229554-3.231601c-40.281425-40.278355-40.281425-105.809861 0-145.991002l75.93546-75.909877c9.742898-7.733125 15.997346-19.668968 15.997346-33.072233 0-23.312962-18.898419-42.211381-42.211381-42.211381-8.797363 0-16.963347 2.693342-23.725354 7.297197-0.021489-0.045025-0.044002-0.088004-0.066515-0.134053l-0.809435 0.757247c-2.989077 2.148943-5.691629 4.669346-8.025791 7.510044l-78.913281 73.841775c-74.178443 74.229608-74.178443 195.632609 0 269.758863l3.203972 3.202948c74.178443 74.127278 195.529255 74.127278 269.707698 0l171.829484-171.880649c74.076112-74.17435 80.357166-191.184297 6.282077-265.311575L607.934444 417.856853z"></path><path d="M855.61957 165.804257l-3.203972-3.203972c-74.17742-74.178443-195.528232-74.178443-269.706675 0L410.87944 334.479911c-74.178443 74.178443-78.263481 181.296089-4.085038 255.522628l3.152806 3.104711c3.368724 3.367701 6.865361 6.54302 10.434653 9.588379 2.583848 2.885723 5.618974 5.355985 8.992815 7.309476 0.025583 0.020466 0.052189 0.041956 0.077771 0.062422l0.011256-0.010233c5.377474 3.092431 11.608386 4.870938 18.257829 4.870938 20.263509 0 36.68962-16.428158 36.68962-36.68962 0-5.719258-1.309832-11.132548-3.645017-15.95846l0 0c-4.850471-10.891048-13.930267-17.521049-20.210297-23.802102l-3.15383-3.102664c-40.278355-40.278355-24.982998-98.79612 15.295358-139.074476l171.930791-171.830507c40.179095-40.280402 105.685018-40.280402 145.965419 0l3.206018 3.152806c40.279379 40.281425 40.279379 105.838513 0 146.06775l-75.686796 75.737962c-10.296507 7.628748-16.97358 19.865443-16.97358 33.662681 0 23.12365 18.745946 41.87062 41.87062 41.87062 8.048303 0 15.563464-2.275833 21.944801-6.211469 0.048095 0.081864 0.093121 0.157589 0.141216 0.240477l1.173732-1.083681c3.616364-2.421142 6.828522-5.393847 9.529027-8.792247l79.766718-73.603345C929.798013 361.334535 929.798013 239.981676 855.61957 165.804257z"></path></svg>`;
const defaultRegexp = /\b((?:https?|ftp):\/\/[^\s/$.?#].[^\s]*)\b/gi;
@@ -213,7 +213,7 @@ export const hyperLinkClickHandler = EditorView.domEventHandlers({
if (urlElement && urlElement.hasAttribute('data-url')) {
const url = urlElement.getAttribute('data-url');
if (url) {
runtime.Browser.OpenURL(url)
runtime.Browser.OpenURL(url);
event.preventDefault();
return true;
}

View File

@@ -46,7 +46,7 @@ export type Options = {
const Config = Facet.define<MinimapConfig | null, Required<Options>>({
combine: (c) => {
const configs: Array<Options> = [];
for (let config of c) {
for (const config of c) {
if (!config) {
continue;
}

View File

@@ -24,4 +24,4 @@ function drawLineGutter(gutter: Record<Line, Color>, ctx: DrawContext, lineNumbe
}
export { GUTTER_WIDTH, drawLineGutter }
export { GUTTER_WIDTH, drawLineGutter };

View File

@@ -218,7 +218,7 @@ const minimapClass = ViewPlugin.fromClass(
/* Small leading buffer */
drawContext.offsetX += 2;
for (let gutter of gutters) {
for (const gutter of gutters) {
drawLineGutter(gutter, drawContext, i + 1);
drawContext.offsetX += GUTTER_WIDTH;
}

View File

@@ -21,8 +21,8 @@ function computeLinesState(state: EditorState): Lines {
while (!lineCursor.done) {
const lineText = lineCursor.value;
let from = textOffset;
let to = from + lineText.length;
const from = textOffset;
const to = from + lineText.length;
// Iterate through folded ranges until we're at or past the current line
while (foldedRangeCursor.value && foldedRangeCursor.to < from) {
@@ -34,8 +34,8 @@ function computeLinesState(state: EditorState): Lines {
const lineEndsInFold = to > foldFrom && to <= foldTo;
if (lineStartInFold) {
let lastLine = lines.pop() ?? [];
let lastRange = lastLine.pop();
const lastLine = lines.pop() ?? [];
const lastRange = lastLine.pop();
// If the last range is folded, we extend the folded range
if (lastRange && lastRange.folded) {

View File

@@ -144,7 +144,7 @@ export class SelectionState extends LineBasedState<Array<Selection>> {
}
public drawLine(ctx: DrawContext, lineNumber: number) {
let {
const {
context,
lineHeight,
charWidth,

View File

@@ -33,7 +33,7 @@ const removeHighlight = StateEffect.define<{from: number, to: number}>({
// 配置facet
const highlightConfigFacet = Facet.define<TextHighlightConfig, Required<TextHighlightConfig>>({
combine: (configs) => {
let result = { ...DEFAULT_CONFIG };
const result = { ...DEFAULT_CONFIG };
for (const config of configs) {
if (config.backgroundColor !== undefined) {
result.backgroundColor = config.backgroundColor;

View File

@@ -1,4 +1,4 @@
import { findNext, findPrevious, getSearchQuery, RegExpCursor, replaceAll, replaceNext, SearchCursor, SearchQuery, setSearchQuery } from "@codemirror/search";
import { getSearchQuery, RegExpCursor, SearchCursor, SearchQuery, setSearchQuery } from "@codemirror/search";
import { CharCategory, EditorState, findClusterBreak, Text } from "@codemirror/state";
import { SearchVisibilityEffect } from "./state";
import { EditorView } from "@codemirror/view";
@@ -9,7 +9,7 @@ type Match = { from: number, to: number };
export class CustomSearchPanel {
dom!: HTMLElement;
searchField!: HTMLInputElement;
replaceField!: HTMLInputElement
replaceField!: HTMLInputElement;
matchCountField!: HTMLElement;
currentMatch!: number;
matches!: Match[];
@@ -34,7 +34,7 @@ export class CustomSearchPanel {
"close": '<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" fill="currentColor"><path fill-rule="evenodd" clip-rule="evenodd" d="M8 8.707l3.646 3.647.708-.707L8.707 8l3.647-3.646-.707-.708L8 7.293 4.354 3.646l-.707.708L7.293 8l-3.646 3.646.707.708L8 8.707z"/></svg>',
"replace": '<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" fill="currentColor"><path fill-rule="evenodd" clip-rule="evenodd" d="M3.221 3.739l2.261 2.269L7.7 3.784l-.7-.7-1.012 1.007-.008-1.6a.523.523 0 0 1 .5-.526H8V1H6.48A1.482 1.482 0 0 0 5 2.489V4.1L3.927 3.033l-.706.706zm6.67 1.794h.01c.183.311.451.467.806.467.393 0 .706-.168.94-.503.236-.335.353-.78.353-1.333 0-.511-.1-.913-.301-1.207-.201-.295-.488-.442-.86-.442-.405 0-.718.194-.938.581h-.01V1H9v4.919h.89v-.386zm-.015-1.061v-.34c0-.248.058-.448.175-.601a.54.54 0 0 1 .445-.23.49.49 0 0 1 .436.233c.104.154.155.368.155.643 0 .33-.056.587-.169.768a.524.524 0 0 1-.47.27.495.495 0 0 1-.411-.211.853.853 0 0 1-.16-.532zM9 12.769c-.256.154-.625.231-1.108.231-.563 0-1.02-.178-1.369-.533-.349-.355-.523-.813-.523-1.374 0-.648.186-1.158.56-1.53.374-.376.875-.563 1.5-.563.433 0 .746.06.94.179v.998a1.26 1.26 0 0 0-.792-.276c-.325 0-.583.1-.774.298-.19.196-.283.468-.283.816 0 .338.09.603.272.797.182.191.431.287.749.287.282 0 .558-.092.828-.276v.946zM4 7L3 8v6l1 1h7l1-1V8l-1-1H4zm0 1h7v6H4V8z"/></svg>',
"replaceAll": '<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" fill="currentColor"><path fill-rule="evenodd" clip-rule="evenodd" d="M11.6 2.677c.147-.31.356-.465.626-.465.248 0 .44.118.573.353.134.236.201.557.201.966 0 .443-.078.798-.235 1.067-.156.268-.365.402-.627.402-.237 0-.416-.125-.537-.374h-.008v.31H11V1h.593v1.677h.008zm-.016 1.1a.78.78 0 0 0 .107.426c.071.113.163.169.274.169.136 0 .24-.072.314-.216.075-.145.113-.35.113-.615 0-.22-.035-.39-.104-.514-.067-.124-.164-.187-.29-.187-.12 0-.219.062-.297.185a.886.886 0 0 0-.117.48v.272zM4.12 7.695L2 5.568l.662-.662 1.006 1v-1.51A1.39 1.39 0 0 1 5.055 3H7.4v.905H5.055a.49.49 0 0 0-.468.493l.007 1.5.949-.944.656.656-2.08 2.085zM9.356 4.93H10V3.22C10 2.408 9.685 2 9.056 2c-.135 0-.285.024-.45.073a1.444 1.444 0 0 0-.388.167v.665c.237-.203.487-.304.75-.304.261 0 .392.156.392.469l-.6.103c-.506.086-.76.406-.76.961 0 .263.061.473.183.631A.61.61 0 0 0 8.69 5c.29 0 .509-.16.657-.48h.009v.41zm.004-1.355v.193a.75.75 0 0 1-.12.436.368.368 0 0 1-.313.17.276.276 0 0 1-.22-.095.38.38 0 0 1-.08-.248c0-.222.11-.351.332-.389l.4-.067zM7 12.93h-.644v-.41h-.009c-.148.32-.367.48-.657.48a.61.61 0 0 1-.507-.235c-.122-.158-.183-.368-.183-.63 0-.556.254-.876.76-.962l.6-.103c0-.313-.13-.47-.392-.47-.263 0-.513.102-.75.305v-.665c.095-.063.224-.119.388-.167.165-.049.315-.073.45-.073.63 0 .944.407.944 1.22v1.71zm-.64-1.162v-.193l-.4.068c-.222.037-.333.166-.333.388 0 .1.027.183.08.248a.276.276 0 0 0 .22.095.368.368 0 0 0 .312-.17c.08-.116.12-.26.12-.436zM9.262 13c.321 0 .568-.058.738-.173v-.71a.9.9 0 0 1-.552.207.619.619 0 0 1-.5-.215c-.12-.145-.181-.345-.181-.598 0-.26.063-.464.189-.612a.644.644 0 0 1 .516-.223c.194 0 .37.069.528.207v-.749c-.129-.09-.338-.134-.626-.134-.417 0-.751.14-1.001.422-.249.28-.373.662-.373 1.148 0 .42.116.764.349 1.03.232.267.537.4.913.4zM2 9l1-1h9l1 1v5l-1 1H3l-1-1V9zm1 0v5h9V9H3zm3-2l1-1h7l1 1v5l-1 1V7H6z"/></svg>',
}
};
constructor(readonly view: EditorView) {
try {
@@ -63,7 +63,7 @@ export class CustomSearchPanel {
}
catch (err) {
console.warn(`ERROR: ${err}`)
console.warn(`ERROR: ${err}`);
}
}
@@ -84,24 +84,24 @@ export class CustomSearchPanel {
}
private charBefore(str: string, index: number) {
return str.slice(findClusterBreak(str, index, false), index)
return str.slice(findClusterBreak(str, index, false), index);
}
private charAfter(str: string, index: number) {
return str.slice(index, findClusterBreak(str, index))
return str.slice(index, findClusterBreak(str, index));
}
private stringWordTest(doc: Text, categorizer: (ch: string) => CharCategory) {
return (from: number, to: number, buf: string, bufPos: number) => {
if (bufPos > from || bufPos + buf.length < to) {
bufPos = Math.max(0, from - 2)
buf = doc.sliceString(bufPos, Math.min(doc.length, to + 2))
bufPos = Math.max(0, from - 2);
buf = doc.sliceString(bufPos, Math.min(doc.length, to + 2));
}
return (categorizer(this.charBefore(buf, from - bufPos)) != CharCategory.Word ||
categorizer(this.charAfter(buf, from - bufPos)) != CharCategory.Word) &&
(categorizer(this.charAfter(buf, to - bufPos)) != CharCategory.Word ||
categorizer(this.charBefore(buf, to - bufPos)) != CharCategory.Word)
}
categorizer(this.charBefore(buf, to - bufPos)) != CharCategory.Word);
};
}
private regexpWordTest(categorizer: (ch: string) => CharCategory) {
@@ -110,7 +110,7 @@ export class CustomSearchPanel {
(categorizer(this.charBefore(match.input, match.index)) != CharCategory.Word ||
categorizer(this.charAfter(match.input, match.index)) != CharCategory.Word) &&
(categorizer(this.charAfter(match.input, match.index + match[0].length)) != CharCategory.Word ||
categorizer(this.charBefore(match.input, match.index + match[0].length)) != CharCategory.Word)
categorizer(this.charBefore(match.input, match.index + match[0].length)) != CharCategory.Word);
}
@@ -123,11 +123,11 @@ export class CustomSearchPanel {
*/
findMatchesAndSelectClosest(state: EditorState): void {
const cursorPos = state.selection.main.head;
let query = getSearchQuery(state);
const query = getSearchQuery(state);
if (query.regexp) {
try {
this.regexCursor = new RegExpCursor(state.doc, query.search)
this.regexCursor = new RegExpCursor(state.doc, query.search);
this.searchCursor = undefined;
} catch (error) {
// 如果正则表达式无效,清空匹配结果并显示错误状态
@@ -143,10 +143,10 @@ export class CustomSearchPanel {
}
}
else {
let cursor = new SearchCursor(state.doc, query.search);
const cursor = new SearchCursor(state.doc, query.search);
if (cursor !== this.searchCursor) {
this.searchCursor = cursor;
this.regexCursor = undefined
this.regexCursor = undefined;
}
}
@@ -168,7 +168,7 @@ export class CustomSearchPanel {
}
else if (this.regexCursor) {
try {
const matchWord = this.regexpWordTest(state.charCategorizer(state.selection.main.head))
const matchWord = this.regexpWordTest(state.charCategorizer(state.selection.main.head));
while (!this.regexCursor.done) {
this.regexCursor.next();
@@ -228,13 +228,13 @@ export class CustomSearchPanel {
caseSensitive: this.matchCase,
regexp: this.useRegex,
wholeWord: this.matchWord,
})
});
let query = getSearchQuery(this.view.state)
const query = getSearchQuery(this.view.state);
if (!newQuery.eq(query)) {
this.view.dispatch({
effects: setSearchQuery.of(newQuery)
})
});
}
} catch (error) {
// 如果创建SearchQuery时出错通常是无效的正则表达式记录错误但不中断程序
@@ -243,7 +243,7 @@ export class CustomSearchPanel {
}
private svgIcon(name: keyof CustomSearchPanel['codicon']): HTMLDivElement {
let div = crelt("div", {},
const div = crelt("div", {},
) as HTMLDivElement;
div.innerHTML = this.codicon[name];
@@ -251,14 +251,14 @@ export class CustomSearchPanel {
}
public toggleReplace() {
this.replaceVisibile = !this.replaceVisibile
this.replaceVisibile = !this.replaceVisibile;
const replaceBar = this.dom.querySelector(".replace-bar") as HTMLElement;
const replaceButtons = this.dom.querySelector(".replace-buttons") as HTMLElement;
const toggleIcon = this.dom.querySelector(".toggle-replace") as HTMLElement;
if (replaceBar && toggleIcon && replaceButtons) {
replaceBar.style.display = this.replaceVisibile ? "flex" : "none";
replaceButtons.style.display = this.replaceVisibile ? "flex" : "none";
toggleIcon.innerHTML = this.svgIcon(this.replaceVisibile ? "downChevron" : "rightChevron").innerHTML
toggleIcon.innerHTML = this.svgIcon(this.replaceVisibile ? "downChevron" : "rightChevron").innerHTML;
}
}
@@ -273,7 +273,7 @@ export class CustomSearchPanel {
this.matchCase = !this.matchCase;
const toggleIcon = this.dom.querySelector(".case-sensitive-toggle") as HTMLElement;
if (toggleIcon) {
toggleIcon.classList.toggle("active")
toggleIcon.classList.toggle("active");
}
this.commit();
// 重新搜索以应用新的匹配规则
@@ -286,7 +286,7 @@ export class CustomSearchPanel {
this.matchWord = !this.matchWord;
const toggleIcon = this.dom.querySelector(".whole-word-toggle") as HTMLElement;
if (toggleIcon) {
toggleIcon.classList.toggle("active")
toggleIcon.classList.toggle("active");
}
this.commit();
// 重新搜索以应用新的匹配规则
@@ -299,7 +299,7 @@ export class CustomSearchPanel {
this.useRegex = !this.useRegex;
const toggleIcon = this.dom.querySelector(".regex-toggle") as HTMLElement;
if (toggleIcon) {
toggleIcon.classList.toggle("active")
toggleIcon.classList.toggle("active");
}
this.commit();
// 重新搜索以应用新的匹配规则
@@ -341,11 +341,11 @@ export class CustomSearchPanel {
}
public findReplaceMatch() {
let query = getSearchQuery(this.view.state)
const query = getSearchQuery(this.view.state);
if (query.replace) {
this.replace()
this.replace();
} else {
this.matchNext()
this.matchNext();
}
}
@@ -398,7 +398,7 @@ export class CustomSearchPanel {
private buildUI(): void {
let query = getSearchQuery(this.view.state)
const query = getSearchQuery(this.view.state);
this.searchField = crelt("input", {
value: query?.search ?? "",
@@ -420,14 +420,14 @@ export class CustomSearchPanel {
}) as HTMLInputElement;
let caseField = this.svgIcon("matchCase");
const caseField = this.svgIcon("matchCase");
caseField.className = "case-sensitive-toggle";
caseField.title = "Match Case (Alt+C)";
caseField.addEventListener("click", () => {
this.toggleCase();
});
let wordField = this.svgIcon("wholeWord");
const wordField = this.svgIcon("wholeWord");
wordField.className = "whole-word-toggle";
wordField.title = "Match Whole Word (Alt+W)";
wordField.addEventListener("click", () => {
@@ -435,50 +435,50 @@ export class CustomSearchPanel {
});
let reField = this.svgIcon("regex");
const reField = this.svgIcon("regex");
reField.className = "regex-toggle";
reField.title = "Use Regular Expression (Alt+R)";
reField.addEventListener("click", () => {
this.toggleRegex();
});
let toggleReplaceIcon = this.svgIcon(this.replaceVisibile ? "downChevron" : "rightChevron");
const toggleReplaceIcon = this.svgIcon(this.replaceVisibile ? "downChevron" : "rightChevron");
toggleReplaceIcon.className = "toggle-replace";
toggleReplaceIcon.addEventListener("click", () => {
this.toggleReplace();
});
this.matchCountField = crelt("span", { class: "match-count" }, "0 of 0")
this.matchCountField = crelt("span", { class: "match-count" }, "0 of 0");
let prevMatchButton = this.svgIcon("prevMatch");
const prevMatchButton = this.svgIcon("prevMatch");
prevMatchButton.className = "prev-match";
prevMatchButton.title = "Previous Match (Shift+Enter)";
prevMatchButton.addEventListener("click", () => {
this.matchPrevious();
});
let nextMatchButton = this.svgIcon("nextMatch");
const nextMatchButton = this.svgIcon("nextMatch");
nextMatchButton.className = "next-match";
nextMatchButton.title = "Next Match (Enter)";
nextMatchButton.addEventListener("click", () => {
this.matchNext();
});
let closeButton = this.svgIcon("close");
const closeButton = this.svgIcon("close");
closeButton.className = "close";
closeButton.title = "Close (Escape)"
closeButton.title = "Close (Escape)";
closeButton.addEventListener("click", () => {
this.close();
});
let replaceButton = this.svgIcon("replace");
const replaceButton = this.svgIcon("replace");
replaceButton.className = "replace-button";
replaceButton.title = "Replace (Enter)";
replaceButton.addEventListener("click", () => {
this.replace();
});
let replaceAllButton = this.svgIcon("replaceAll");
const replaceAllButton = this.svgIcon("replaceAll");
replaceAllButton.className = "replace-button";
replaceAllButton.title = "Replace All (Ctrl+Alt+Enter)";
replaceAllButton.addEventListener("click", () => {
@@ -534,7 +534,7 @@ export class CustomSearchPanel {
this.replaceField
);
replaceBar.style.display = this.replaceVisibile ? "flex" : "none"
replaceBar.style.display = this.replaceVisibile ? "flex" : "none";
const inputSection = crelt("div", { class: "input-section" },
searchBar,
@@ -545,7 +545,7 @@ export class CustomSearchPanel {
prevMatchButton,
nextMatchButton,
closeButton
)
);
const searchButtons = crelt("div", { class: "button-group" },
this.matchCountField,
@@ -557,9 +557,9 @@ export class CustomSearchPanel {
},
replaceButton,
replaceAllButton
)
);
replaceButtons.style.display = this.replaceVisibile ? "flex" : "none"
replaceButtons.style.display = this.replaceVisibile ? "flex" : "none";
const actionSection = crelt("div", { class: "actions-section" },
searchButtons,
@@ -599,14 +599,14 @@ export class CustomSearchPanel {
}
mount() {
this.searchField.select()
this.searchField.select();
}
destroy?(): void {
throw new Error("Method not implemented.");
}
get pos() { return 80 }
get pos() { return 80; }
}

View File

@@ -9,14 +9,14 @@ const isSearchActive = () : boolean => {
return document.activeElement.classList.contains('find-input');
}
return false;
}
};
const isReplaceActive = () : boolean => {
if (document.activeElement){
return document.activeElement.classList.contains('replace-input');
}
return false;
}
};
export const selectAllCommand: Command = (view) => {
if (isSearchActive() || isReplaceActive()) {
@@ -26,7 +26,7 @@ export const selectAllCommand: Command = (view) => {
else {
view.dispatch({
selection: { anchor: 0, head: view.state.doc.length }
})
});
return true;
}
};
@@ -37,7 +37,7 @@ export const deleteCharacterBackwards: Command = (view) => {
return true;
}
else {
deleteCharBackward(view)
deleteCharBackward(view);
return true;
}
};
@@ -48,7 +48,7 @@ export const deleteCharacterFowards: Command = (view) => {
return true;
}
else {
deleteCharForward(view)
deleteCharForward(view);
return true;
}
};
@@ -72,7 +72,7 @@ export const showSearchVisibilityCommand: Command = (view) => {
export const searchMoveCursorLeft: Command = (view) => {
if (isSearchActive() || isReplaceActive()) {
const input = document.activeElement as HTMLInputElement
const input = document.activeElement as HTMLInputElement;
const pos = input.selectionStart ?? 0;
if (pos > 0) {
input.selectionStart = input.selectionEnd = pos - 1;
@@ -80,14 +80,14 @@ export const searchMoveCursorLeft: Command = (view) => {
return true;
}
else {
cursorCharLeft(view)
cursorCharLeft(view);
return true;
}
}
};
export const searchMoveCursorRight: Command = (view) => {
if (isSearchActive() || isReplaceActive()) {
const input = document.activeElement as HTMLInputElement
const input = document.activeElement as HTMLInputElement;
const pos = input.selectionStart ?? 0;
if (pos < input.value.length) {
input.selectionStart = input.selectionEnd = pos + 1;
@@ -95,10 +95,10 @@ export const searchMoveCursorRight: Command = (view) => {
return true;
}
else {
cursorCharRight(view)
cursorCharRight(view);
return true;
}
}
};
export const hideSearchVisibilityCommand: Command = (view) => {
view.dispatch({
@@ -108,64 +108,64 @@ export const hideSearchVisibilityCommand: Command = (view) => {
};
export const searchToggleCase: Command = (view) => {
const plugin = view.plugin(VSCodeSearch)
const plugin = view.plugin(VSCodeSearch);
if (!plugin) return false;
plugin.toggleCaseInsensitive();
return true;
}
};
export const searchToggleWholeWord: Command = (view) => {
const plugin = view.plugin(VSCodeSearch)
const plugin = view.plugin(VSCodeSearch);
if (!plugin) return false;
plugin.toggleWholeWord();
return true;
}
};
export const searchToggleRegex: Command = (view) => {
const plugin = view.plugin(VSCodeSearch)
const plugin = view.plugin(VSCodeSearch);
if (!plugin) return false;
plugin.toggleRegex();
return true;
}
};
export const searchShowReplace: Command = (view) => {
const plugin = view.plugin(VSCodeSearch)
const plugin = view.plugin(VSCodeSearch);
if (!plugin) return false;
plugin.showReplace();
return true;
}
};
export const searchFindReplaceMatch: Command = (view) => {
const plugin = view.plugin(VSCodeSearch)
const plugin = view.plugin(VSCodeSearch);
if (!plugin) return false;
plugin.findReplaceMatch();
return true;
}
};
export const searchFindPrevious: Command = (view) => {
const plugin = view.plugin(VSCodeSearch)
const plugin = view.plugin(VSCodeSearch);
if (!plugin) return false;
plugin.findPrevious();
return true;
}
};
export const searchReplaceAll: Command = (view) => {
const plugin = view.plugin(VSCodeSearch)
const plugin = view.plugin(VSCodeSearch);
if (!plugin) return false;
plugin.replaceAll();
return true;
}
};

View File

@@ -1,4 +1,4 @@
export { VSCodeSearch, vscodeSearch} from "./plugin";
export { searchVisibilityField, SearchVisibilityEffect } from "./state";
export { searchBaseTheme } from "./theme"
export * from "./commands"
export { searchBaseTheme } from "./theme";
export * from "./commands";

View File

@@ -20,8 +20,8 @@ export class SearchPlugin {
}
this.prevQuery = currentQuery;
for (let tr of update.transactions) {
for (let e of tr.effects) {
for (const tr of update.transactions) {
for (const e of tr.effects) {
if (e.is(SearchVisibilityEffect)) {
this.searchControl.setVisibility(e.value);
}
@@ -55,19 +55,19 @@ export class SearchPlugin {
}
findNext() {
this.searchControl.matchNext()
this.searchControl.matchNext();
}
replace() {
this.searchControl.replace()
this.searchControl.replace();
}
replaceAll() {
this.searchControl.replaceAll()
this.searchControl.replaceAll();
}
findPrevious() {
this.searchControl.matchPrevious()
this.searchControl.matchPrevious();
}
}
@@ -77,4 +77,4 @@ export const vscodeSearch = [
search({}),
VSCodeSearch,
searchBaseTheme
]
];

View File

@@ -9,7 +9,7 @@ export const searchVisibilityField = StateField.define({
return false;
},
update(value, tr) {
for (let e of tr.effects) {
for (const e of tr.effects) {
if (e.is(SearchVisibilityEffect)) {
return e.value;
}

View File

@@ -160,7 +160,7 @@ const sharedTheme: Theme = {
alignItems: "center",
gap: "2px",
},
}
};
const lightTheme: Theme = {
".find-replace-container": {
@@ -254,7 +254,7 @@ const prependThemeSelector = (theme: Theme, selector: string): Theme => {
});
return updatedTheme;
}
};
export const searchBaseTheme = EditorView.baseTheme({
...sharedTheme,

View File

@@ -1,6 +1,6 @@
export function simulateBackspace(input: HTMLInputElement, direction: "backward" | "forward" = "backward") {
let start = input.selectionStart ?? 0;
let end = input.selectionEnd ?? 0;
const end = input.selectionEnd ?? 0;
// Do nothing if at boundaries
if (direction === "backward" && start === 0 && end === 0) return;

View File

@@ -1,4 +1,4 @@
import {KeyBindingCommand} from '@/../bindings/voidraft/internal/models/models'
import {KeyBindingCommand} from '@/../bindings/voidraft/internal/models/models';
import {
hideSearchVisibilityCommand,
searchReplaceAll,
@@ -7,7 +7,7 @@ import {
searchToggleRegex,
searchToggleWholeWord,
showSearchVisibilityCommand
} from '../extensions/vscodeSearch/commands'
} from '../extensions/vscodeSearch/commands';
import {
addNewBlockAfterCurrent,
addNewBlockAfterLast,
@@ -20,13 +20,13 @@ import {
moveCurrentBlockUp,
selectNextBlock,
selectPreviousBlock
} from '../extensions/codeblock/commands'
import {selectAll} from '../extensions/codeblock/selectAll'
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'
} from '../extensions/codeblock/commands';
import {selectAll} from '../extensions/codeblock/selectAll';
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,
@@ -52,15 +52,15 @@ import {
toggleComment,
undo,
undoSelection
} from '@codemirror/commands'
import {foldAll, foldCode, unfoldAll, unfoldCode} from '@codemirror/language'
import i18n from '@/i18n'
} from '@codemirror/commands';
import {foldAll, foldCode, unfoldAll, unfoldCode} from '@codemirror/language';
import i18n from '@/i18n';
// 默认编辑器选项
const defaultEditorOptions = {
defaultBlockToken: 'text',
defaultBlockAutoDetect: true,
}
};
/**
* 前端命令注册表
@@ -291,7 +291,7 @@ export const commandRegistry = {
handler: textHighlightToggleCommand,
descriptionKey: 'keybindings.commands.textHighlightToggle'
},
} as const
} as const;
/**
* 获取命令处理函数
@@ -299,8 +299,8 @@ export const commandRegistry = {
* @returns 对应的处理函数,如果不存在则返回 undefined
*/
export const getCommandHandler = (command: KeyBindingCommand) => {
return commandRegistry[command]?.handler
}
return commandRegistry[command]?.handler;
};
/**
* 获取命令描述
@@ -308,9 +308,9 @@ export const getCommandHandler = (command: KeyBindingCommand) => {
* @returns 对应的描述,如果不存在则返回 undefined
*/
export const getCommandDescription = (command: KeyBindingCommand) => {
const descriptionKey = commandRegistry[command]?.descriptionKey
return descriptionKey ? i18n.global.t(descriptionKey) : undefined
}
const descriptionKey = commandRegistry[command]?.descriptionKey;
return descriptionKey ? i18n.global.t(descriptionKey) : undefined;
};
/**
* 检查命令是否已注册
@@ -318,13 +318,13 @@ export const getCommandDescription = (command: KeyBindingCommand) => {
* @returns 是否已注册
*/
export const isCommandRegistered = (command: KeyBindingCommand): boolean => {
return command in commandRegistry
}
return command in commandRegistry;
};
/**
* 获取所有已注册的命令
* @returns 已注册的命令列表
*/
export const getRegisteredCommands = (): KeyBindingCommand[] => {
return Object.keys(commandRegistry) as KeyBindingCommand[]
}
return Object.keys(commandRegistry) as KeyBindingCommand[];
};

View File

@@ -1,46 +1,46 @@
import { Extension } from '@codemirror/state'
import { useKeybindingStore } from '@/stores/keybindingStore'
import { useExtensionStore } from '@/stores/extensionStore'
import { KeymapManager } from './keymapManager'
import { ExtensionID } from '@/../bindings/voidraft/internal/models/models'
import { Extension } from '@codemirror/state';
import { useKeybindingStore } from '@/stores/keybindingStore';
import { useExtensionStore } from '@/stores/extensionStore';
import { KeymapManager } from './keymapManager';
import { ExtensionID } from '@/../bindings/voidraft/internal/models/models';
/**
* 异步创建快捷键扩展
* 确保快捷键配置和扩展配置已加载
*/
export const createDynamicKeymapExtension = async (): Promise<Extension> => {
const keybindingStore = useKeybindingStore()
const extensionStore = useExtensionStore()
const keybindingStore = useKeybindingStore();
const extensionStore = useExtensionStore();
// 确保快捷键配置已加载
if (keybindingStore.keyBindings.length === 0) {
await keybindingStore.loadKeyBindings()
await keybindingStore.loadKeyBindings();
}
// 确保扩展配置已加载
if (extensionStore.extensions.length === 0) {
await extensionStore.loadExtensions()
await extensionStore.loadExtensions();
}
// 获取启用的扩展ID列表
const enabledExtensionIds = extensionStore.enabledExtensions.map(ext => ext.id)
const enabledExtensionIds = extensionStore.enabledExtensions.map(ext => ext.id);
return KeymapManager.createKeymapExtension(keybindingStore.keyBindings, enabledExtensionIds)
}
return KeymapManager.createKeymapExtension(keybindingStore.keyBindings, enabledExtensionIds);
};
/**
* 更新快捷键映射
* @param view 编辑器视图
*/
export const updateKeymapExtension = (view: any): void => {
const keybindingStore = useKeybindingStore()
const extensionStore = useExtensionStore()
const keybindingStore = useKeybindingStore();
const extensionStore = useExtensionStore();
// 获取启用的扩展ID列表
const enabledExtensionIds = extensionStore.enabledExtensions.map(ext => ext.id)
const enabledExtensionIds = extensionStore.enabledExtensions.map(ext => ext.id);
KeymapManager.updateKeymap(view, keybindingStore.keyBindings, enabledExtensionIds)
}
KeymapManager.updateKeymap(view, keybindingStore.keyBindings, enabledExtensionIds);
};
/**
* 获取指定扩展的快捷键
@@ -48,11 +48,11 @@ export const updateKeymapExtension = (view: any): void => {
* @returns 该扩展的快捷键列表
*/
export const getExtensionKeyBindings = (extensionId: ExtensionID) => {
const keybindingStore = useKeybindingStore()
return keybindingStore.getKeyBindingsByExtension(extensionId)
}
const keybindingStore = useKeybindingStore();
return keybindingStore.getKeyBindingsByExtension(extensionId);
};
// 导出相关模块
export { KeymapManager } from './keymapManager'
export { commandRegistry, getCommandHandler, getCommandDescription, isCommandRegistered, getRegisteredCommands } from './commandRegistry'
export type { KeyBinding, CommandHandler, CommandDefinition, KeymapResult } from './types'
export { KeymapManager } from './keymapManager';
export { commandRegistry, getCommandHandler, getCommandDescription, isCommandRegistered, getRegisteredCommands } from './commandRegistry';
export type { KeyBinding, CommandHandler, CommandDefinition, KeymapResult } from './types';

View File

@@ -1,15 +1,15 @@
import {keymap} from '@codemirror/view'
import {Extension, Compartment} from '@codemirror/state'
import {KeyBinding as KeyBindingConfig, ExtensionID} from '@/../bindings/voidraft/internal/models/models'
import {KeyBinding, KeymapResult} from './types'
import {getCommandHandler, isCommandRegistered} from './commandRegistry'
import {keymap} from '@codemirror/view';
import {Extension, Compartment} from '@codemirror/state';
import {KeyBinding as KeyBindingConfig, ExtensionID} from '@/../bindings/voidraft/internal/models/models';
import {KeyBinding, KeymapResult} from './types';
import {getCommandHandler, isCommandRegistered} from './commandRegistry';
/**
* 快捷键管理器
* 负责将后端配置转换为CodeMirror快捷键扩展
*/
export class KeymapManager {
private static compartment = new Compartment()
private static compartment = new Compartment();
/**
* 将后端快捷键配置转换为CodeMirror快捷键绑定
@@ -18,28 +18,28 @@ export class KeymapManager {
* @returns 转换结果
*/
static convertToKeyBindings(keyBindings: KeyBindingConfig[], enabledExtensions?: ExtensionID[]): KeymapResult {
const result: KeyBinding[] = []
const result: KeyBinding[] = [];
for (const binding of keyBindings) {
// 跳过禁用的快捷键
if (!binding.enabled) {
continue
continue;
}
// 如果提供了扩展列表,则只处理启用扩展的快捷键
if (enabledExtensions && !enabledExtensions.includes(binding.extension)) {
continue
continue;
}
// 检查命令是否已注册
if (!isCommandRegistered(binding.command)) {
continue
continue;
}
// 获取命令处理函数
const handler = getCommandHandler(binding.command)
const handler = getCommandHandler(binding.command);
if (!handler) {
continue
continue;
}
// 转换为CodeMirror快捷键格式
@@ -47,12 +47,12 @@ export class KeymapManager {
key: binding.key,
run: handler,
preventDefault: true
}
};
result.push(keyBinding)
result.push(keyBinding);
}
return {keyBindings: result}
return {keyBindings: result};
}
/**
@@ -63,9 +63,9 @@ export class KeymapManager {
*/
static createKeymapExtension(keyBindings: KeyBindingConfig[], enabledExtensions?: ExtensionID[]): Extension {
const {keyBindings: cmKeyBindings} =
this.convertToKeyBindings(keyBindings, enabledExtensions)
this.convertToKeyBindings(keyBindings, enabledExtensions);
return this.compartment.of(keymap.of(cmKeyBindings))
return this.compartment.of(keymap.of(cmKeyBindings));
}
/**
@@ -76,11 +76,11 @@ export class KeymapManager {
*/
static updateKeymap(view: any, keyBindings: KeyBindingConfig[], enabledExtensions: ExtensionID[]): void {
const {keyBindings: cmKeyBindings} =
this.convertToKeyBindings(keyBindings, enabledExtensions)
this.convertToKeyBindings(keyBindings, enabledExtensions);
view.dispatch({
effects: this.compartment.reconfigure(keymap.of(cmKeyBindings))
})
});
}
/**
@@ -89,16 +89,16 @@ export class KeymapManager {
* @returns 按扩展分组的快捷键映射
*/
static groupByExtension(keyBindings: KeyBindingConfig[]): Map<ExtensionID, KeyBindingConfig[]> {
const groups = new Map<ExtensionID, KeyBindingConfig[]>()
const groups = new Map<ExtensionID, KeyBindingConfig[]>();
for (const binding of keyBindings) {
if (!groups.has(binding.extension)) {
groups.set(binding.extension, [])
groups.set(binding.extension, []);
}
groups.get(binding.extension)!.push(binding)
groups.get(binding.extension)!.push(binding);
}
return groups
return groups;
}
/**
@@ -110,17 +110,17 @@ export class KeymapManager {
valid: KeyBindingConfig[]
invalid: KeyBindingConfig[]
} {
const valid: KeyBindingConfig[] = []
const invalid: KeyBindingConfig[] = []
const valid: KeyBindingConfig[] = [];
const invalid: KeyBindingConfig[] = [];
for (const binding of keyBindings) {
if (binding.enabled && binding.key && isCommandRegistered(binding.command)) {
valid.push(binding)
valid.push(binding);
} else {
invalid.push(binding)
invalid.push(binding);
}
}
return {valid, invalid}
return {valid, invalid};
}
}

View File

@@ -1,4 +1,4 @@
import {Command} from '@codemirror/view'
import {Command} from '@codemirror/view';
/**
* CodeMirror快捷键绑定格式

View File

@@ -1,6 +1,6 @@
import {Compartment, Extension, StateEffect} from '@codemirror/state'
import {EditorView} from '@codemirror/view'
import {Extension as ExtensionConfig, ExtensionID} from '@/../bindings/voidraft/internal/models/models'
import {Compartment, Extension, StateEffect} from '@codemirror/state';
import {EditorView} from '@codemirror/view';
import {Extension as ExtensionConfig, ExtensionID} from '@/../bindings/voidraft/internal/models/models';
/**
* 扩展工厂接口
@@ -56,18 +56,18 @@ interface EditorViewInfo {
*/
export class ExtensionManager {
// 统一的扩展状态存储
private extensionStates = new Map<ExtensionID, ExtensionState>()
private extensionStates = new Map<ExtensionID, ExtensionState>();
// 编辑器视图管理
private viewsMap = new Map<number, EditorViewInfo>()
private activeViewId: number | null = null
private viewsMap = new Map<number, EditorViewInfo>();
private activeViewId: number | null = null;
// 注册的扩展工厂
private extensionFactories = new Map<ExtensionID, ExtensionFactory>()
private extensionFactories = new Map<ExtensionID, ExtensionFactory>();
// 防抖处理
private debounceTimers = new Map<ExtensionID, number>()
private debounceDelay = 300 // 默认防抖时间为300毫秒
private debounceTimers = new Map<ExtensionID, number>();
private debounceDelay = 300; // 默认防抖时间为300毫秒
/**
* 注册扩展工厂
@@ -75,12 +75,12 @@ export class ExtensionManager {
* @param factory 扩展工厂
*/
registerExtension(id: ExtensionID, factory: ExtensionFactory): void {
this.extensionFactories.set(id, factory)
this.extensionFactories.set(id, factory);
// 创建初始状态
if (!this.extensionStates.has(id)) {
const compartment = new Compartment()
const defaultConfig = factory.getDefaultConfig()
const compartment = new Compartment();
const defaultConfig = factory.getDefaultConfig();
this.extensionStates.set(id, {
id,
@@ -89,7 +89,7 @@ export class ExtensionManager {
enabled: false,
compartment,
extension: [] // 默认为空扩展(禁用状态)
})
});
}
}
@@ -97,7 +97,7 @@ export class ExtensionManager {
* 获取所有注册的扩展ID列表
*/
getRegisteredExtensions(): ExtensionID[] {
return Array.from(this.extensionFactories.keys())
return Array.from(this.extensionFactories.keys());
}
/**
@@ -105,7 +105,7 @@ export class ExtensionManager {
* @param id 扩展ID
*/
isExtensionRegistered(id: ExtensionID): boolean {
return this.extensionFactories.has(id)
return this.extensionFactories.has(id);
}
/**
@@ -114,26 +114,26 @@ export class ExtensionManager {
*/
initializeExtensionsFromConfig(extensionConfigs: ExtensionConfig[]): void {
for (const config of extensionConfigs) {
const factory = this.extensionFactories.get(config.id)
if (!factory) continue
const factory = this.extensionFactories.get(config.id);
if (!factory) continue;
// 验证配置
if (factory.validateConfig && !factory.validateConfig(config.config)) {
continue
continue;
}
try {
// 创建扩展实例
const extension = config.enabled ? factory.create(config.config) : []
const extension = config.enabled ? factory.create(config.config) : [];
// 如果状态已存在则更新,否则创建新状态
if (this.extensionStates.has(config.id)) {
const state = this.extensionStates.get(config.id)!
state.config = config.config
state.enabled = config.enabled
state.extension = extension
const state = this.extensionStates.get(config.id)!;
state.config = config.config;
state.enabled = config.enabled;
state.extension = extension;
} else {
const compartment = new Compartment()
const compartment = new Compartment();
this.extensionStates.set(config.id, {
id: config.id,
factory,
@@ -141,10 +141,10 @@ export class ExtensionManager {
enabled: config.enabled,
compartment,
extension
})
});
}
} catch (error) {
console.error(`Failed to initialize extension ${config.id}:`, error)
console.error(`Failed to initialize extension ${config.id}:`, error);
}
}
}
@@ -154,14 +154,14 @@ export class ExtensionManager {
* @returns CodeMirror扩展数组
*/
getInitialExtensions(): Extension[] {
const extensions: Extension[] = []
const extensions: Extension[] = [];
// 为每个注册的扩展添加compartment
for (const state of this.extensionStates.values()) {
extensions.push(state.compartment.of(state.extension))
extensions.push(state.compartment.of(state.extension));
}
return extensions
return extensions;
}
/**
@@ -175,19 +175,19 @@ export class ExtensionManager {
view,
documentId,
registered: true
})
});
// 设置当前活动视图
this.activeViewId = documentId
this.activeViewId = documentId;
}
/**
* 获取当前活动视图
*/
private getActiveView(): EditorView | null {
if (this.activeViewId === null) return null
const viewInfo = this.viewsMap.get(this.activeViewId)
return viewInfo ? viewInfo.view : null
if (this.activeViewId === null) return null;
const viewInfo = this.viewsMap.get(this.activeViewId);
return viewInfo ? viewInfo.view : null;
}
/**
@@ -199,16 +199,16 @@ export class ExtensionManager {
updateExtension(id: ExtensionID, enabled: boolean, config: any = {}): void {
// 清除之前的定时器
if (this.debounceTimers.has(id)) {
window.clearTimeout(this.debounceTimers.get(id))
window.clearTimeout(this.debounceTimers.get(id));
}
// 设置新的定时器
const timerId = window.setTimeout(() => {
this.updateExtensionImmediate(id, enabled, config)
this.debounceTimers.delete(id)
}, this.debounceDelay)
this.updateExtensionImmediate(id, enabled, config);
this.debounceTimers.delete(id);
}, this.debounceDelay);
this.debounceTimers.set(id, timerId)
this.debounceTimers.set(id, timerId);
}
/**
@@ -219,30 +219,30 @@ export class ExtensionManager {
*/
updateExtensionImmediate(id: ExtensionID, enabled: boolean, config: any = {}): void {
// 获取扩展状态
const state = this.extensionStates.get(id)
if (!state) return
const state = this.extensionStates.get(id);
if (!state) return;
// 获取工厂
const factory = state.factory
const factory = state.factory;
// 验证配置
if (factory.validateConfig && !factory.validateConfig(config)) {
return
return;
}
try {
// 创建新的扩展实例
const extension = enabled ? factory.create(config) : []
const extension = enabled ? factory.create(config) : [];
// 更新内部状态
state.config = config
state.enabled = enabled
state.extension = extension
state.config = config;
state.enabled = enabled;
state.extension = extension;
// 应用到所有视图
this.applyExtensionToAllViews(id)
this.applyExtensionToAllViews(id);
} catch (error) {
console.error(`Failed to update extension ${id}:`, error)
console.error(`Failed to update extension ${id}:`, error);
}
}
@@ -251,19 +251,19 @@ export class ExtensionManager {
* @param id 扩展ID
*/
private applyExtensionToAllViews(id: ExtensionID): void {
const state = this.extensionStates.get(id)
if (!state) return
const state = this.extensionStates.get(id);
if (!state) return;
// 遍历所有视图并应用更改
for (const viewInfo of this.viewsMap.values()) {
try {
if (!viewInfo.registered) continue
if (!viewInfo.registered) continue;
viewInfo.view.dispatch({
effects: state.compartment.reconfigure(state.extension)
})
});
} catch (error) {
console.error(`Failed to apply extension ${id} to document ${viewInfo.documentId}:`, error)
console.error(`Failed to apply extension ${id} to document ${viewInfo.documentId}:`, error);
}
}
}
@@ -280,56 +280,56 @@ export class ExtensionManager {
// 清除所有相关的防抖定时器
for (const update of updates) {
if (this.debounceTimers.has(update.id)) {
window.clearTimeout(this.debounceTimers.get(update.id))
this.debounceTimers.delete(update.id)
window.clearTimeout(this.debounceTimers.get(update.id));
this.debounceTimers.delete(update.id);
}
}
// 更新所有扩展状态
for (const update of updates) {
// 获取扩展状态
const state = this.extensionStates.get(update.id)
if (!state) continue
const state = this.extensionStates.get(update.id);
if (!state) continue;
// 获取工厂
const factory = state.factory
const factory = state.factory;
// 验证配置
if (factory.validateConfig && !factory.validateConfig(update.config)) {
continue
continue;
}
try {
// 创建新的扩展实例
const extension = update.enabled ? factory.create(update.config) : []
const extension = update.enabled ? factory.create(update.config) : [];
// 更新内部状态
state.config = update.config
state.enabled = update.enabled
state.extension = extension
state.config = update.config;
state.enabled = update.enabled;
state.extension = extension;
} catch (error) {
console.error(`Failed to update extension ${update.id}:`, error)
console.error(`Failed to update extension ${update.id}:`, error);
}
}
// 将更改应用到所有视图
for (const viewInfo of this.viewsMap.values()) {
if (!viewInfo.registered) continue
if (!viewInfo.registered) continue;
const effects: StateEffect<any>[] = []
const effects: StateEffect<any>[] = [];
for (const update of updates) {
const state = this.extensionStates.get(update.id)
if (!state) continue
const state = this.extensionStates.get(update.id);
if (!state) continue;
effects.push(state.compartment.reconfigure(state.extension))
effects.push(state.compartment.reconfigure(state.extension));
}
if (effects.length > 0) {
try {
viewInfo.view.dispatch({ effects })
viewInfo.view.dispatch({ effects });
} catch (error) {
console.error(`Failed to apply extensions to document ${viewInfo.documentId}:`, error)
console.error(`Failed to apply extensions to document ${viewInfo.documentId}:`, error);
}
}
}
@@ -343,13 +343,13 @@ export class ExtensionManager {
enabled: boolean
config: any
} | null {
const state = this.extensionStates.get(id)
if (!state) return null
const state = this.extensionStates.get(id);
if (!state) return null;
return {
enabled: state.enabled,
config: state.config
}
};
}
/**
@@ -357,11 +357,11 @@ export class ExtensionManager {
* @param id 扩展ID
*/
resetExtensionToDefault(id: ExtensionID): void {
const state = this.extensionStates.get(id)
if (!state) return
const state = this.extensionStates.get(id);
if (!state) return;
const defaultConfig = state.factory.getDefaultConfig()
this.updateExtension(id, true, defaultConfig)
const defaultConfig = state.factory.getDefaultConfig();
this.updateExtension(id, true, defaultConfig);
}
/**
@@ -370,10 +370,10 @@ export class ExtensionManager {
*/
removeView(documentId: number): void {
if (this.activeViewId === documentId) {
this.activeViewId = null
this.activeViewId = null;
}
this.viewsMap.delete(documentId)
this.viewsMap.delete(documentId);
}
/**
@@ -382,13 +382,13 @@ export class ExtensionManager {
destroy(): void {
// 清除所有防抖定时器
for (const timerId of this.debounceTimers.values()) {
window.clearTimeout(timerId)
window.clearTimeout(timerId);
}
this.debounceTimers.clear()
this.debounceTimers.clear();
this.viewsMap.clear()
this.activeViewId = null
this.extensionFactories.clear()
this.extensionStates.clear()
this.viewsMap.clear();
this.activeViewId = null;
this.extensionFactories.clear();
this.extensionStates.clear();
}
}

View File

@@ -1,34 +1,34 @@
import {ExtensionFactory, ExtensionManager} from './ExtensionManager'
import {ExtensionID} from '@/../bindings/voidraft/internal/models/models'
import i18n from '@/i18n'
import {ExtensionFactory, ExtensionManager} from './ExtensionManager';
import {ExtensionID} from '@/../bindings/voidraft/internal/models/models';
import i18n from '@/i18n';
// 导入现有扩展的创建函数
import rainbowBracketsExtension from '../extensions/rainbowBracket/rainbowBracketsExtension'
import {createTextHighlighter} from '../extensions/textHighlight/textHighlightExtension'
import rainbowBracketsExtension from '../extensions/rainbowBracket/rainbowBracketsExtension';
import {createTextHighlighter} from '../extensions/textHighlight/textHighlightExtension';
import {color} from '../extensions/colorSelector'
import {hyperLink} from '../extensions/hyperlink'
import {minimap} from '../extensions/minimap'
import {vscodeSearch} from '../extensions/vscodeSearch'
import {createCheckboxExtension} from '../extensions/checkbox'
import {createTranslatorExtension} from '../extensions/translator'
import {color} from '../extensions/colorSelector';
import {hyperLink} from '../extensions/hyperlink';
import {minimap} from '../extensions/minimap';
import {vscodeSearch} from '../extensions/vscodeSearch';
import {createCheckboxExtension} from '../extensions/checkbox';
import {createTranslatorExtension} from '../extensions/translator';
import {foldingOnIndent} from '../extensions/fold/foldExtension'
import {foldingOnIndent} from '../extensions/fold/foldExtension';
/**
* 彩虹括号扩展工厂
*/
export const rainbowBracketsFactory: ExtensionFactory = {
create(config: any) {
return rainbowBracketsExtension()
create(_config: any) {
return rainbowBracketsExtension();
},
getDefaultConfig() {
return {}
return {};
},
validateConfig(config: any) {
return typeof config === 'object'
return typeof config === 'object';
}
}
};
/**
* 文本高亮扩展工厂
@@ -38,20 +38,20 @@ export const textHighlightFactory: ExtensionFactory = {
return createTextHighlighter({
backgroundColor: config.backgroundColor || '#FFD700',
opacity: config.opacity || 0.3
})
});
},
getDefaultConfig() {
return {
backgroundColor: '#FFD700', // 金黄色
opacity: 0.3 // 透明度
}
};
},
validateConfig(config: any) {
return typeof config === 'object' &&
(!config.backgroundColor || typeof config.backgroundColor === 'string') &&
(!config.opacity || (typeof config.opacity === 'number' && config.opacity >= 0 && config.opacity <= 1))
(!config.opacity || (typeof config.opacity === 'number' && config.opacity >= 0 && config.opacity <= 1));
}
}
};
/**
* 小地图扩展工厂
@@ -62,95 +62,95 @@ export const minimapFactory: ExtensionFactory = {
displayText: config.displayText || 'characters',
showOverlay: config.showOverlay || 'always',
autohide: config.autohide || false
}
return minimap(options)
};
return minimap(options);
},
getDefaultConfig() {
return {
displayText: 'characters',
showOverlay: 'always',
autohide: false
}
};
},
validateConfig(config: any) {
return typeof config === 'object' &&
(!config.displayText || typeof config.displayText === 'string') &&
(!config.showOverlay || typeof config.showOverlay === 'string') &&
(!config.autohide || typeof config.autohide === 'boolean')
(!config.autohide || typeof config.autohide === 'boolean');
}
}
};
/**
* 超链接扩展工厂
*/
export const hyperlinkFactory: ExtensionFactory = {
create(config: any) {
return hyperLink
create(_config: any) {
return hyperLink;
},
getDefaultConfig() {
return {}
return {};
},
validateConfig(config: any) {
return typeof config === 'object'
return typeof config === 'object';
}
}
};
/**
* 颜色选择器扩展工厂
*/
export const colorSelectorFactory: ExtensionFactory = {
create(config: any) {
return color
create(_config: any) {
return color;
},
getDefaultConfig() {
return {}
return {};
},
validateConfig(config: any) {
return typeof config === 'object'
return typeof config === 'object';
}
}
};
/**
* 搜索扩展工厂
*/
export const searchFactory: ExtensionFactory = {
create(config: any) {
return vscodeSearch
create(_config: any) {
return vscodeSearch;
},
getDefaultConfig() {
return {}
return {};
},
validateConfig(config: any) {
return typeof config === 'object'
return typeof config === 'object';
}
}
};
export const foldFactory: ExtensionFactory = {
create(config: any) {
create(_config: any) {
return foldingOnIndent;
},
getDefaultConfig(): any {
return {}
return {};
},
validateConfig(config: any): boolean {
return typeof config === 'object'
return typeof config === 'object';
}
}
};
/**
* 选择框扩展工厂
*/
export const checkboxFactory: ExtensionFactory = {
create(config: any) {
return createCheckboxExtension()
create(_config: any) {
return createCheckboxExtension();
},
getDefaultConfig() {
return {}
return {};
},
validateConfig(config: any) {
return typeof config === 'object'
return typeof config === 'object';
}
}
};
/**
* 翻译扩展工厂
@@ -161,19 +161,19 @@ export const translatorFactory: ExtensionFactory = {
defaultTranslator: config.defaultTranslator || 'bing',
minSelectionLength: config.minSelectionLength || 2,
maxTranslationLength: config.maxTranslationLength || 5000,
})
});
},
getDefaultConfig() {
return {
defaultTranslator: 'bing',
minSelectionLength: 2,
maxTranslationLength: 5000,
}
};
},
validateConfig(config: any) {
return typeof config === 'object'
return typeof config === 'object';
}
}
};
/**
* 所有扩展的统一配置
@@ -232,7 +232,7 @@ const EXTENSION_CONFIGS = {
displayNameKey: 'extensions.checkbox.name',
descriptionKey: 'extensions.checkbox.description'
}
}
};
/**
* 注册所有扩展工厂到管理器
@@ -240,8 +240,8 @@ const EXTENSION_CONFIGS = {
*/
export function registerAllExtensions(manager: ExtensionManager): void {
Object.entries(EXTENSION_CONFIGS).forEach(([id, config]) => {
manager.registerExtension(id as ExtensionID, config.factory)
})
manager.registerExtension(id as ExtensionID, config.factory);
});
}
/**
@@ -250,8 +250,8 @@ export function registerAllExtensions(manager: ExtensionManager): void {
* @returns 显示名称
*/
export function getExtensionDisplayName(id: ExtensionID): string {
const config = EXTENSION_CONFIGS[id as ExtensionID]
return config?.displayNameKey ? i18n.global.t(config.displayNameKey) : id
const config = EXTENSION_CONFIGS[id as ExtensionID];
return config?.displayNameKey ? i18n.global.t(config.displayNameKey) : id;
}
/**
@@ -260,8 +260,8 @@ export function getExtensionDisplayName(id: ExtensionID): string {
* @returns 描述
*/
export function getExtensionDescription(id: ExtensionID): string {
const config = EXTENSION_CONFIGS[id as ExtensionID]
return config?.descriptionKey ? i18n.global.t(config.descriptionKey) : ''
const config = EXTENSION_CONFIGS[id as ExtensionID];
return config?.descriptionKey ? i18n.global.t(config.descriptionKey) : '';
}
/**
@@ -270,7 +270,7 @@ export function getExtensionDescription(id: ExtensionID): string {
* @returns 扩展工厂实例
*/
export function getExtensionFactory(id: ExtensionID): ExtensionFactory | undefined {
return EXTENSION_CONFIGS[id as ExtensionID]?.factory
return EXTENSION_CONFIGS[id as ExtensionID]?.factory;
}
/**
@@ -279,8 +279,8 @@ export function getExtensionFactory(id: ExtensionID): ExtensionFactory | undefin
* @returns 默认配置对象
*/
export function getExtensionDefaultConfig(id: ExtensionID): any {
const factory = getExtensionFactory(id)
return factory?.getDefaultConfig() || {}
const factory = getExtensionFactory(id);
return factory?.getDefaultConfig() || {};
}
/**
@@ -289,8 +289,8 @@ export function getExtensionDefaultConfig(id: ExtensionID): any {
* @returns 是否有配置项
*/
export function hasExtensionConfig(id: ExtensionID): boolean {
const defaultConfig = getExtensionDefaultConfig(id)
return Object.keys(defaultConfig).length > 0
const defaultConfig = getExtensionDefaultConfig(id);
return Object.keys(defaultConfig).length > 0;
}
/**
@@ -298,5 +298,5 @@ export function hasExtensionConfig(id: ExtensionID): boolean {
* @returns 扩展ID数组
*/
export function getAllExtensionIds(): ExtensionID[] {
return Object.keys(EXTENSION_CONFIGS) as ExtensionID[]
return Object.keys(EXTENSION_CONFIGS) as ExtensionID[];
}

View File

@@ -1,44 +1,44 @@
import {Extension} from '@codemirror/state'
import {EditorView} from '@codemirror/view'
import {useExtensionStore} from '@/stores/extensionStore'
import {ExtensionManager} from './ExtensionManager'
import {registerAllExtensions} from './factories'
import {Extension} from '@codemirror/state';
import {EditorView} from '@codemirror/view';
import {useExtensionStore} from '@/stores/extensionStore';
import {ExtensionManager} from './ExtensionManager';
import {registerAllExtensions} from './factories';
/**
* 全局扩展管理器实例
*/
const extensionManager = new ExtensionManager()
const extensionManager = new ExtensionManager();
/**
* 异步创建动态扩展
* 确保扩展配置已加载
* @param documentId 可选的文档ID用于提前初始化视图
* @param _documentId 可选的文档ID用于提前初始化视图
*/
export const createDynamicExtensions = async (documentId?: number): Promise<Extension[]> => {
const extensionStore = useExtensionStore()
export const createDynamicExtensions = async (_documentId?: number): Promise<Extension[]> => {
const extensionStore = useExtensionStore();
// 注册所有扩展工厂
registerAllExtensions(extensionManager)
registerAllExtensions(extensionManager);
// 确保扩展配置已加载
if (extensionStore.extensions.length === 0) {
await extensionStore.loadExtensions()
await extensionStore.loadExtensions();
}
// 初始化扩展管理器配置
extensionManager.initializeExtensionsFromConfig(extensionStore.extensions)
extensionManager.initializeExtensionsFromConfig(extensionStore.extensions);
// 获取初始扩展配置
return extensionManager.getInitialExtensions()
}
return extensionManager.getInitialExtensions();
};
/**
* 获取扩展管理器实例
* @returns 扩展管理器
*/
export const getExtensionManager = (): ExtensionManager => {
return extensionManager
}
return extensionManager;
};
/**
* 设置编辑器视图到扩展管理器
@@ -46,18 +46,18 @@ export const getExtensionManager = (): ExtensionManager => {
* @param documentId 文档ID
*/
export const setExtensionManagerView = (view: EditorView, documentId: number): void => {
extensionManager.setView(view, documentId)
}
extensionManager.setView(view, documentId);
};
/**
* 从扩展管理器移除编辑器视图
* @param documentId 文档ID
*/
export const removeExtensionManagerView = (documentId: number): void => {
extensionManager.removeView(documentId)
}
extensionManager.removeView(documentId);
};
// 导出相关模块
export {ExtensionManager} from './ExtensionManager'
export {registerAllExtensions, getExtensionDisplayName, getExtensionDescription} from './factories'
export type {ExtensionFactory} from './ExtensionManager'
export {ExtensionManager} from './ExtensionManager';
export {registerAllExtensions, getExtensionDisplayName, getExtensionDescription} from './factories';
export type {ExtensionFactory} from './ExtensionManager';

View File

@@ -288,11 +288,6 @@ const updateSystemTheme = async (event: Event) => {
// 控制颜色选择器显示状态
const showPickerMap = ref<Record<string, boolean>>({});
// 切换颜色选择器显示状态
const toggleColorPicker = (colorKey: string) => {
showPickerMap.value[colorKey] = !showPickerMap.value[colorKey];
};
// 颜色变更处理
const handleColorChange = (colorKey: string, value: string) => {
updateColor(colorKey, value);

View File

@@ -21,7 +21,7 @@ onMounted(async () => {
});
onUnmounted(() => {
backupStore.clearError();
})
});
// 认证方式选项
const authMethodOptions = computed(() => [

View File

@@ -1,33 +1,32 @@
<script setup lang="ts">
import {computed, ref} from 'vue'
import {useI18n} from 'vue-i18n'
import {useEditorStore} from '@/stores/editorStore'
import {useExtensionStore} from '@/stores/extensionStore'
import {ExtensionService} from '@/../bindings/voidraft/internal/services'
import {ExtensionID} from '@/../bindings/voidraft/internal/models/models'
import {getExtensionManager} from '@/views/editor/manager'
import {computed, ref} from 'vue';
import {useI18n} from 'vue-i18n';
import {useEditorStore} from '@/stores/editorStore';
import {useExtensionStore} from '@/stores/extensionStore';
import {ExtensionService} from '@/../bindings/voidraft/internal/services';
import {ExtensionID} from '@/../bindings/voidraft/internal/models/models';
import {
getAllExtensionIds,
getExtensionDefaultConfig,
getExtensionDescription,
getExtensionDisplayName,
hasExtensionConfig
} from '@/views/editor/manager/factories'
import SettingSection from '../components/SettingSection.vue'
import SettingItem from '../components/SettingItem.vue'
import ToggleSwitch from '../components/ToggleSwitch.vue'
} from '@/views/editor/manager/factories';
import SettingSection from '../components/SettingSection.vue';
import SettingItem from '../components/SettingItem.vue';
import ToggleSwitch from '../components/ToggleSwitch.vue';
const {t} = useI18n()
const editorStore = useEditorStore()
const extensionStore = useExtensionStore()
const {t} = useI18n();
const editorStore = useEditorStore();
const extensionStore = useExtensionStore();
// 展开状态管理
const expandedExtensions = ref<Set<ExtensionID>>(new Set())
const expandedExtensions = ref<Set<ExtensionID>>(new Set());
// 获取所有可用的扩展
const availableExtensions = computed(() => {
return getAllExtensionIds().map(id => {
const extension = extensionStore.extensions.find(ext => ext.id === id)
const extension = extensionStore.extensions.find(ext => ext.id === id);
return {
id,
displayName: getExtensionDisplayName(id),
@@ -37,68 +36,68 @@ const availableExtensions = computed(() => {
hasConfig: hasExtensionConfig(id),
config: extension?.config || {},
defaultConfig: getExtensionDefaultConfig(id)
}
})
})
};
});
});
// 切换展开状态
const toggleExpanded = (extensionId: ExtensionID) => {
if (expandedExtensions.value.has(extensionId)) {
expandedExtensions.value.delete(extensionId)
expandedExtensions.value.delete(extensionId);
} else {
expandedExtensions.value.add(extensionId)
expandedExtensions.value.add(extensionId);
}
}
};
// 更新扩展状态
const updateExtension = async (extensionId: ExtensionID, enabled: boolean) => {
try {
await editorStore.updateExtension(extensionId, enabled)
await editorStore.updateExtension(extensionId, enabled);
} catch (error) {
console.error('Failed to update extension:', error)
console.error('Failed to update extension:', error);
}
}
};
// 更新扩展配置
const updateExtensionConfig = async (extensionId: ExtensionID, configKey: string, value: any) => {
try {
// 获取当前扩展状态
const extension = extensionStore.extensions.find(ext => ext.id === extensionId)
if (!extension) return
const extension = extensionStore.extensions.find(ext => ext.id === extensionId);
if (!extension) return;
// 更新配置
const updatedConfig = {...extension.config, [configKey]: value}
const updatedConfig = {...extension.config, [configKey]: value};
console.log(`[ExtensionsPage] 更新扩展 ${extensionId} 配置, ${configKey}=${value}`)
console.log(`[ExtensionsPage] 更新扩展 ${extensionId} 配置, ${configKey}=${value}`);
// 使用editorStore的updateExtension方法更新确保应用到所有编辑器实例
await editorStore.updateExtension(extensionId, extension.enabled, updatedConfig)
await editorStore.updateExtension(extensionId, extension.enabled, updatedConfig);
} catch (error) {
console.error('Failed to update extension config:', error)
console.error('Failed to update extension config:', error);
}
}
};
// 重置扩展到默认配置
const resetExtension = async (extensionId: ExtensionID) => {
try {
// 重置到默认配置(后端)
await ExtensionService.ResetExtensionToDefault(extensionId)
await ExtensionService.ResetExtensionToDefault(extensionId);
// 重新加载扩展状态以获取最新配置
await extensionStore.loadExtensions()
await extensionStore.loadExtensions();
// 获取重置后的状态,立即应用到所有编辑器视图
const extension = extensionStore.extensions.find(ext => ext.id === extensionId)
const extension = extensionStore.extensions.find(ext => ext.id === extensionId);
if (extension) {
// 通过editorStore更新确保所有视图都能同步
await editorStore.updateExtension(extensionId, extension.enabled, extension.config)
console.log(`[ExtensionsPage] 重置扩展 ${extensionId} 配置,同步应用到所有编辑器实例`)
await editorStore.updateExtension(extensionId, extension.enabled, extension.config);
console.log(`[ExtensionsPage] 重置扩展 ${extensionId} 配置,同步应用到所有编辑器实例`);
}
} catch (error) {
console.error('Failed to reset extension:', error)
console.error('Failed to reset extension:', error);
}
}
};
// 配置项类型定义
type ConfigItemType = 'toggle' | 'number' | 'text' | 'select'
@@ -131,25 +130,25 @@ const extensionConfigMeta: Partial<Record<ExtensionID, Record<string, ConfigItem
]
}
}
}
};
// 获取配置项类型
const getConfigItemType = (extensionId: ExtensionID, configKey: string, defaultValue: any): string => {
const meta = extensionConfigMeta[extensionId]?.[configKey]
const meta = extensionConfigMeta[extensionId]?.[configKey];
if (meta?.type) {
return meta.type
return meta.type;
}
// 根据默认值类型自动推断
if (typeof defaultValue === 'boolean') return 'toggle'
if (typeof defaultValue === 'number') return 'number'
return 'text'
}
if (typeof defaultValue === 'boolean') return 'toggle';
if (typeof defaultValue === 'number') return 'number';
return 'text';
};
// 获取选择框的选项列表
const getSelectOptions = (extensionId: ExtensionID, configKey: string): SelectOption[] => {
return extensionConfigMeta[extensionId]?.[configKey]?.options || []
}
return extensionConfigMeta[extensionId]?.[configKey]?.options || [];
};
</script>
<template>

View File

@@ -62,7 +62,7 @@ const startPolling = () => {
const delay = status === MigrationStatus.MigrationStatusCompleted ? 3000 : 5000;
hideProgressTimer = setTimeout(hideProgress, delay);
}
} catch (error) {
} catch (_error) {
stopPolling();
// 使用常量简化错误处理

View File

@@ -87,52 +87,52 @@
</template>
<script setup lang="ts">
import { ref } from 'vue'
import * as TestService from '@/../bindings/voidraft/internal/services/testservice'
import SettingSection from '../components/SettingSection.vue'
import SettingItem from '../components/SettingItem.vue'
import { ref } from 'vue';
import * as TestService from '@/../bindings/voidraft/internal/services/testservice';
import SettingSection from '../components/SettingSection.vue';
import SettingItem from '../components/SettingItem.vue';
// Badge测试状态
const badgeText = ref('')
const badgeStatus = ref<{ type: string; message: string } | null>(null)
const badgeText = ref('');
const badgeStatus = ref<{ type: string; message: string } | null>(null);
// 通知测试状态
const notificationTitle = ref('')
const notificationSubtitle = ref('')
const notificationBody = ref('')
const notificationStatus = ref<{ type: string; message: string } | null>(null)
const notificationTitle = ref('');
const notificationSubtitle = ref('');
const notificationBody = ref('');
const notificationStatus = ref<{ type: string; message: string } | null>(null);
// 清除状态
const clearStatus = ref<{ type: string; message: string } | null>(null)
const clearStatus = ref<{ type: string; message: string } | null>(null);
// 显示状态消息的辅助函数
const showStatus = (statusRef: any, type: 'success' | 'error', message: string) => {
statusRef.value = { type, message }
statusRef.value = { type, message };
setTimeout(() => {
statusRef.value = null
}, 5000)
}
statusRef.value = null;
}, 5000);
};
// 测试Badge功能
const testBadge = async () => {
try {
await TestService.TestBadge(badgeText.value)
showStatus(badgeStatus, 'success', `Badge ${badgeText.value ? 'set to: ' + badgeText.value : 'cleared'} successfully`)
await TestService.TestBadge(badgeText.value);
showStatus(badgeStatus, 'success', `Badge ${badgeText.value ? 'set to: ' + badgeText.value : 'cleared'} successfully`);
} catch (error: any) {
showStatus(badgeStatus, 'error', `Failed to set badge: ${error.message || error}`)
showStatus(badgeStatus, 'error', `Failed to set badge: ${error.message || error}`);
}
}
};
// 清除Badge
const clearBadge = async () => {
try {
await TestService.TestBadge('')
badgeText.value = ''
showStatus(badgeStatus, 'success', 'Badge cleared successfully')
await TestService.TestBadge('');
badgeText.value = '';
showStatus(badgeStatus, 'success', 'Badge cleared successfully');
} catch (error: any) {
showStatus(badgeStatus, 'error', `Failed to clear badge: ${error.message || error}`)
showStatus(badgeStatus, 'error', `Failed to clear badge: ${error.message || error}`);
}
}
};
// 测试通知功能
const testNotification = async () => {
@@ -141,37 +141,37 @@ const testNotification = async () => {
notificationTitle.value,
notificationSubtitle.value,
notificationBody.value
)
showStatus(notificationStatus, 'success', 'Notification sent successfully')
);
showStatus(notificationStatus, 'success', 'Notification sent successfully');
} catch (error: any) {
showStatus(notificationStatus, 'error', `Failed to send notification: ${error.message || error}`)
showStatus(notificationStatus, 'error', `Failed to send notification: ${error.message || error}`);
}
}
};
// 测试更新通知
const testUpdateNotification = async () => {
try {
await TestService.TestUpdateNotification()
showStatus(notificationStatus, 'success', 'Update notification sent successfully (badge + notification)')
await TestService.TestUpdateNotification();
showStatus(notificationStatus, 'success', 'Update notification sent successfully (badge + notification)');
} catch (error: any) {
showStatus(notificationStatus, 'error', `Failed to send update notification: ${error.message || error}`)
showStatus(notificationStatus, 'error', `Failed to send update notification: ${error.message || error}`);
}
}
};
// 清除所有测试状态
const clearAll = async () => {
try {
await TestService.ClearAll()
await TestService.ClearAll();
// 清空表单
badgeText.value = ''
notificationTitle.value = ''
notificationSubtitle.value = ''
notificationBody.value = ''
showStatus(clearStatus, 'success', 'All test states cleared successfully')
badgeText.value = '';
notificationTitle.value = '';
notificationSubtitle.value = '';
notificationBody.value = '';
showStatus(clearStatus, 'success', 'All test states cleared successfully');
} catch (error: any) {
showStatus(clearStatus, 'error', `Failed to clear test states: ${error.message || error}`)
showStatus(clearStatus, 'error', `Failed to clear test states: ${error.message || error}`);
}
}
};
</script>
<style scoped lang="scss">

View File

@@ -1,6 +1,6 @@
<script setup lang="ts">
import {useI18n} from 'vue-i18n';
import {computed, onMounted, ref} from 'vue';
import {computed} from 'vue';
import {useConfigStore} from '@/stores/configStore';
import {useUpdateStore} from '@/stores/updateStore';
import SettingSection from '../components/SettingSection.vue';