🚨 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

@@ -50,7 +50,11 @@ export default defineConfig([
'.local', '.local',
'/bin', '/bin',
'Dockerfile', 'Dockerfile',
'**/bindings/' '**/bindings/',
'*.js',
'**/*.js',
'**/*.cjs',
'**/*.mjs',
], ],
} }
]); ]);

View File

@@ -78,7 +78,7 @@ _69: () => {
_70: () => { _70: () => {
return typeof process != "undefined" && return typeof process != "undefined" &&
Object.prototype.toString.call(process) == "[object process]" && Object.prototype.toString.call(process) == "[object process]" &&
process.platform == "win32" process.platform == "win32";
}, },
_85: s => JSON.stringify(s), _85: s => JSON.stringify(s),
_86: s => printToConsole(s), _86: s => printToConsole(s),
@@ -126,7 +126,7 @@ _157: Function.prototype.call.bind(DataView.prototype.setFloat32),
_158: Function.prototype.call.bind(DataView.prototype.getFloat64), _158: Function.prototype.call.bind(DataView.prototype.getFloat64),
_159: Function.prototype.call.bind(DataView.prototype.setFloat64), _159: Function.prototype.call.bind(DataView.prototype.setFloat64),
_165: x0 => format = x0, _165: x0 => format = x0,
_166: f => finalizeWrapper(f, function(x0,x1,x2) { return dartInstance.exports._166(f,arguments.length,x0,x1,x2) }), _166: f => finalizeWrapper(f, function(x0,x1,x2) { return dartInstance.exports._166(f,arguments.length,x0,x1,x2); }),
_184: (c) => _184: (c) =>
queueMicrotask(() => dartInstance.exports.$invokeCallback(c)), queueMicrotask(() => dartInstance.exports.$invokeCallback(c)),
_187: (s, m) => { _187: (s, m) => {
@@ -337,14 +337,14 @@ _272: (x0,x1) => x0.lastIndex = x1
}); });
return dartInstance; return dartInstance;
} };
// Call the main function for the instantiated module // Call the main function for the instantiated module
// `moduleInstance` is the instantiated dart2wasm module // `moduleInstance` is the instantiated dart2wasm module
// `args` are any arguments that should be passed into the main function. // `args` are any arguments that should be passed into the main function.
export const invoke = (moduleInstance, ...args) => { export const invoke = (moduleInstance, ...args) => {
moduleInstance.exports.$invokeMain(args); moduleInstance.exports.$invokeMain(args);
} };
export let format; export let format;

View File

@@ -1,5 +1,5 @@
import type { Plugin, SupportLanguage, Parser, Printer, SupportOption } from 'prettier' import type { Plugin, SupportLanguage, Parser, Printer, SupportOption } from 'prettier';
import dockerfileInit, { format } from './docker_fmt_vite.js' import dockerfileInit, { format } from './docker_fmt_vite.js';
// Language configuration for Dockerfile // Language configuration for Dockerfile
const languages: SupportLanguage[] = [ const languages: SupportLanguage[] = [
@@ -11,7 +11,7 @@ const languages: SupportLanguage[] = [
linguistLanguageId: 99, linguistLanguageId: 99,
vscodeLanguageIds: ['dockerfile'], vscodeLanguageIds: ['dockerfile'],
}, },
] ];
// Parser configuration // Parser configuration
const parsers: Record<string, Parser<any>> = { const parsers: Record<string, Parser<any>> = {
@@ -19,69 +19,60 @@ const parsers: Record<string, Parser<any>> = {
parse: (text: string) => { parse: (text: string) => {
// For Dockerfile, we don't need complex parsing, just return the text // For Dockerfile, we don't need complex parsing, just return the text
// The formatting will be handled by the print function // The formatting will be handled by the print function
return { type: 'dockerfile', value: text } return { type: 'dockerfile', value: text };
}, },
astFormat: 'dockerfile', astFormat: 'dockerfile',
locStart: () => 0, locStart: () => 0,
locEnd: () => 0, locEnd: () => 0,
}, },
} };
// Printer configuration // Printer configuration
const printers: Record<string, Printer<any>> = { const printers: Record<string, Printer<any>> = {
dockerfile: { dockerfile: {
// @ts-expect-error -- Support async printer like shell plugin // @ts-expect-error -- Support async printer like shell plugin
async print(path: any, options: any) { async print(path: any, options: any) {
await ensureInitialized() await ensureInitialized();
const text = path.getValue().value || path.getValue() const text = path.getValue().value || path.getValue();
try { try {
const formatted = format(text, { const formatted = format(text, {
indent: options.tabWidth || 2, indent: options.tabWidth || 2,
trailingNewline: true, trailingNewline: true,
spaceRedirects: options.spaceRedirects !== false, spaceRedirects: options.spaceRedirects !== false,
}) });
return formatted return formatted;
} catch (error) { } catch (error) {
console.warn('Dockerfile formatting error:', error) console.warn('Dockerfile formatting error:', error);
return text return text;
} }
}, },
}, },
} };
// WASM initialization // WASM initialization
let isInitialized = false let isInitialized = false;
let initPromise: Promise<void> | null = null let initPromise: Promise<void> | null = null;
async function ensureInitialized(): Promise<void> { async function ensureInitialized(): Promise<void> {
if (isInitialized) { if (isInitialized) {
return Promise.resolve() return Promise.resolve();
} }
if (!initPromise) { if (!initPromise) {
initPromise = (async () => { initPromise = (async () => {
try { try {
await dockerfileInit() await dockerfileInit();
isInitialized = true isInitialized = true;
} catch (error) { } catch (error) {
console.warn('Failed to initialize Dockerfile WASM module:', error) console.warn('Failed to initialize Dockerfile WASM module:', error);
initPromise = null initPromise = null;
throw error throw error;
} }
})() })();
} }
return initPromise return initPromise;
}
// Configuration mapping function
function mapOptionsToConfig(options: any) {
return {
indent: options.tabWidth || 2,
trailingNewline: options.insertFinalNewline !== false,
spaceRedirects: options.spaceRedirects !== false,
}
} }
// Plugin options // Plugin options
@@ -92,7 +83,7 @@ const options: Record<string, SupportOption> = {
default: true, default: true,
description: 'Add spaces around redirect operators', description: 'Add spaces around redirect operators',
}, },
} };
// Plugin definition // Plugin definition
const plugin: Plugin = { const plugin: Plugin = {
@@ -105,7 +96,7 @@ const plugin: Plugin = {
useTabs: false, useTabs: false,
spaceRedirects: true, spaceRedirects: true,
}, },
} };
export default plugin export default plugin;
export { languages, parsers, printers, options } export { languages, parsers, printers, options };

View File

@@ -60,7 +60,7 @@ function initGofmt(): Promise<void> {
// Printer configuration // Printer configuration
const goPrinter: Printer<string> = { const goPrinter: Printer<string> = {
// @ts-expect-error -- Support async printer like shell plugin // @ts-expect-error -- Support async printer like shell plugin
async print(path, options) { async print(path, _options) {
try { try {
// Wait for initialization to complete // Wait for initialization to complete
await initGofmt(); await initGofmt();

View File

@@ -39,7 +39,7 @@ const groovyPrinter: Printer<string> = {
return groovyBeautify(path.node, { return groovyBeautify(path.node, {
width: options.printWidth || 80, width: options.printWidth || 80,
}).trim(); }).trim();
} catch (error) { } catch (_error) {
return path.node; return path.node;
} }
}, },

View File

@@ -58,7 +58,7 @@ type ModifierNode = JavaNonTerminal & {
annotation?: AnnotationCstNode[]; annotation?: AnnotationCstNode[];
}; };
}; };
type IsTuple<T> = T extends [] ? true : T extends [infer First, ...infer Remain] ? IsTuple<Remain> : false; type IsTuple<T> = T extends [] ? true : T extends [infer _First, ...infer Remain] ? IsTuple<Remain> : false;
type IndexProperties<T extends { type IndexProperties<T extends {
length: number; length: number;
}> = IsTuple<T> extends true ? Exclude<Partial<T>["length"], T["length"]> : number; }> = IsTuple<T> extends true ? Exclude<Partial<T>["length"], T["length"]> : number;

View File

@@ -1,5 +1,5 @@
/* tslint:disable */ /* tslint:disable */
/* eslint-disable */
export function format(input: string, filename: string, config?: Config): string; export function format(input: string, filename: string, config?: Config): string;
interface LayoutConfig { interface LayoutConfig {

View File

@@ -1,5 +1,5 @@
/* tslint:disable */ /* tslint:disable */
/* eslint-disable */
export const memory: WebAssembly.Memory; export const memory: WebAssembly.Memory;
export const format: (a: number, b: number, c: number, d: number, e: number, f: number) => void; export const format: (a: number, b: number, c: number, d: number, e: number, f: number) => void;
export const __wbindgen_export_0: (a: number, b: number) => number; export const __wbindgen_export_0: (a: number, b: number) => number;

View File

@@ -1,5 +1,5 @@
/* tslint:disable */ /* tslint:disable */
/* eslint-disable */
export function format(input: string, path?: string, config?: Config): string; export function format(input: string, path?: string, config?: Config): string;
export interface Config { export interface Config {

View File

@@ -1,5 +1,5 @@
/* tslint:disable */ /* tslint:disable */
/* eslint-disable */
export const memory: WebAssembly.Memory; export const memory: WebAssembly.Memory;
export const format: (a: number, b: number, c: number, d: number, e: number, f: number) => void; export const format: (a: number, b: number, c: number, d: number, e: number, f: number) => void;
export const __wbindgen_export_0: (a: number, b: number) => number; export const __wbindgen_export_0: (a: number, b: number) => number;

View File

@@ -1,5 +1,5 @@
/* tslint:disable */ /* tslint:disable */
/* eslint-disable */
export function format(input: string, config?: Config | null): string; export function format(input: string, config?: Config | null): string;
export interface Config { export interface Config {

View File

@@ -1,5 +1,5 @@
/* tslint:disable */ /* tslint:disable */
/* eslint-disable */
export const memory: WebAssembly.Memory; export const memory: WebAssembly.Memory;
export const format: (a: number, b: number, c: number, d: number) => void; export const format: (a: number, b: number, c: number, d: number) => void;
export const __wbindgen_export_0: (a: number, b: number) => number; export const __wbindgen_export_0: (a: number, b: number) => number;

View File

@@ -1,7 +1,7 @@
import _fs from 'node:fs' import _fs from 'node:fs';
declare global { declare global {
namespace globalThis { namespace globalThis {
var fs: typeof _fs let fs: typeof _fs;
} }
} }

View File

@@ -1,5 +1,5 @@
/* tslint:disable */ /* tslint:disable */
/* eslint-disable */
export function format(input: string, filename: string, config?: Config): string; export function format(input: string, filename: string, config?: Config): string;
interface LayoutConfig { interface LayoutConfig {

View File

@@ -1,5 +1,5 @@
/* tslint:disable */ /* tslint:disable */
/* eslint-disable */
export const memory: WebAssembly.Memory; export const memory: WebAssembly.Memory;
export const format: (a: number, b: number, c: number, d: number, e: number, f: number) => void; export const format: (a: number, b: number, c: number, d: number, e: number, f: number) => void;
export const __wbindgen_export_0: (a: number, b: number) => number; export const __wbindgen_export_0: (a: number, b: number) => number;

View File

@@ -23,7 +23,6 @@ import type {
TomlDocument, TomlDocument,
TomlExpression, TomlExpression,
TomlKeyVal, TomlKeyVal,
TomlComment,
TomlContext TomlContext
} from './types'; } from './types';
@@ -221,11 +220,11 @@ class TomlBeautifierVisitor extends BaseTomlCstVisitor {
} else { } else {
return this.visit(actualValueNode); return this.visit(actualValueNode);
} }
} catch (error) { } catch (_error) {
// 如果getSingle失败尝试直接处理children // 如果getSingle失败尝试直接处理children
if (ctx.children) { if (ctx.children) {
// 处理不同类型的值 // 处理不同类型的值
for (const [childKey, childNodes] of Object.entries(ctx.children)) { for (const [_childKey, childNodes] of Object.entries(ctx.children)) {
if (Array.isArray(childNodes) && childNodes.length > 0) { if (Array.isArray(childNodes) && childNodes.length > 0) {
const firstChild = childNodes[0]; const firstChild = childNodes[0];
@@ -385,14 +384,14 @@ class TomlBeautifierVisitor extends BaseTomlCstVisitor {
/** /**
* Visit newline (should not be called) * Visit newline (should not be called)
*/ */
nl(ctx: any): never { nl(_ctx: any): never {
throw new Error('Should not get here!'); throw new Error('Should not get here!');
} }
/** /**
* Visit comment newline (no-op) * Visit comment newline (no-op)
*/ */
commentNewline(ctx: any): void { commentNewline(_ctx: any): void {
// No operation needed // No operation needed
} }
} }
@@ -407,7 +406,7 @@ const beautifierVisitor = new TomlBeautifierVisitor();
* @param print - Print function (unused in this implementation) * @param print - Print function (unused in this implementation)
* @returns Formatted document * @returns Formatted document
*/ */
export function print(path: AstPath<TomlCstNode>, options?: any, print?: any): Doc { export function print(path: AstPath<TomlCstNode>, _options?: any, _print?: any): Doc {
const cst = path.node as TomlDocument; const cst = path.node as TomlDocument;
return beautifierVisitor.visit(cst); return beautifierVisitor.visit(cst);
} }

View File

@@ -4,7 +4,7 @@
* This plugin provides support for formatting multiple web languages using the web_fmt WASM implementation. * This plugin provides support for formatting multiple web languages using the web_fmt WASM implementation.
* web_fmt is a comprehensive formatter for web development supporting HTML, CSS, JavaScript, and JSON. * web_fmt is a comprehensive formatter for web development supporting HTML, CSS, JavaScript, and JSON.
*/ */
import type { Plugin, Parser, Printer, ParserOptions } from 'prettier'; import type { Plugin, Parser, Printer } from 'prettier';
// Import the web_fmt WASM module // Import the web_fmt WASM module
import webInit, { format } from './web_fmt_vite.js'; import webInit, { format } from './web_fmt_vite.js';

View File

@@ -1,5 +1,5 @@
/* tslint:disable */ /* tslint:disable */
/* eslint-disable */
export function format_json(src: string, config?: JsonConfig): string; export function format_json(src: string, config?: JsonConfig): string;
export function format_markup(src: string, filename: string, config?: MarkupConfig): string; export function format_markup(src: string, filename: string, config?: MarkupConfig): string;
export function format_script(src: string, filename: string, config?: ScriptConfig): string; export function format_script(src: string, filename: string, config?: ScriptConfig): string;

View File

@@ -1,5 +1,5 @@
/* tslint:disable */ /* tslint:disable */
/* eslint-disable */
export const memory: WebAssembly.Memory; export const memory: WebAssembly.Memory;
export const format_json: (a: number, b: number, c: number, d: number) => void; export const format_json: (a: number, b: number, c: number, d: number) => void;
export const format_markup: (a: number, b: number, c: number, d: number, e: number, f: number) => void; export const format_markup: (a: number, b: number, c: number, d: number, e: number, f: number) => void;

View File

@@ -42,7 +42,7 @@ const withSilentErrorHandling = async <T>(
): Promise<T | undefined> => { ): Promise<T | undefined> => {
try { try {
return await operation(); return await operation();
} catch (error) { } catch (_error) {
// 静默处理错误,不输出到控制台 // 静默处理错误,不输出到控制台
return fallback; return fallback;
} }

View File

@@ -50,7 +50,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, onMounted, onUnmounted, computed } from 'vue'; import { ref, onMounted, computed } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import * as runtime from '@wailsio/runtime'; import * as runtime from '@wailsio/runtime';
import { useDocumentStore } from '@/stores/documentStore'; import { useDocumentStore } from '@/stores/documentStore';
@@ -64,7 +64,7 @@ const minimizeWindow = async () => {
try { try {
await runtime.Window.Minimise(); await runtime.Window.Minimise();
} catch (error) { } catch (error) {
console.error(error) console.error(error);
} }
}; };
@@ -73,7 +73,7 @@ const toggleMaximize = async () => {
await runtime.Window.ToggleMaximise(); await runtime.Window.ToggleMaximise();
await checkMaximizedState(); await checkMaximizedState();
} catch (error) { } catch (error) {
console.error(error) console.error(error);
} }
}; };
@@ -81,7 +81,7 @@ const closeWindow = async () => {
try { try {
await runtime.Window.Close(); await runtime.Window.Close();
} catch (error) { } catch (error) {
console.error(error) console.error(error);
} }
}; };
@@ -89,7 +89,7 @@ const checkMaximizedState = async () => {
try { try {
isMaximized.value = await runtime.Window.IsMaximised(); isMaximized.value = await runtime.Window.IsMaximised();
} catch (error) { } catch (error) {
console.error(error) console.error(error);
} }
}; };

View File

@@ -196,7 +196,7 @@ const saveEdit = async () => {
try { try {
await documentStore.updateDocumentMetadata(editingId.value, trimmedTitle); await documentStore.updateDocumentMetadata(editingId.value, trimmedTitle);
await documentStore.updateDocuments(); await documentStore.updateDocuments();
} catch (error) { } catch (_error) {
return; return;
} }
} }
@@ -292,7 +292,7 @@ const formatTime = (dateString: string | null) => {
minute: '2-digit', minute: '2-digit',
hour12: false hour12: false
}); });
} catch (error) { } catch (_error) {
return t('toolbar.timeError'); return t('toolbar.timeError');
} }
}; };

View File

@@ -1,4 +1,4 @@
import {createRouter, createWebHashHistory, createWebHistory, RouteRecordRaw} from 'vue-router'; import {createRouter, createWebHashHistory, RouteRecordRaw} from 'vue-router';
import Editor from '@/views/editor/Editor.vue'; import Editor from '@/views/editor/Editor.vue';
import Settings from '@/views/settings/Settings.vue'; import Settings from '@/views/settings/Settings.vue';
import GeneralPage from '@/views/settings/pages/GeneralPage.vue'; import GeneralPage from '@/views/settings/pages/GeneralPage.vue';

View File

@@ -1,105 +1,105 @@
import {defineStore} from 'pinia' import {defineStore} from 'pinia';
import {computed, readonly, ref} from 'vue' import {computed, readonly, ref} from 'vue';
import type {GitBackupConfig} from '@/../bindings/voidraft/internal/models' import type {GitBackupConfig} from '@/../bindings/voidraft/internal/models';
import {BackupService} from '@/../bindings/voidraft/internal/services' import {BackupService} from '@/../bindings/voidraft/internal/services';
import {useConfigStore} from '@/stores/configStore' import {useConfigStore} from '@/stores/configStore';
/** /**
* Minimalist Backup Store * Minimalist Backup Store
*/ */
export const useBackupStore = defineStore('backup', () => { export const useBackupStore = defineStore('backup', () => {
// Core state // Core state
const config = ref<GitBackupConfig | null>(null) const config = ref<GitBackupConfig | null>(null);
const isPushing = ref(false) const isPushing = ref(false);
const error = ref<string | null>(null) const error = ref<string | null>(null);
const isInitialized = ref(false) const isInitialized = ref(false);
// Backup result states // Backup result states
const pushSuccess = ref(false) const pushSuccess = ref(false);
const pushError = ref(false) const pushError = ref(false);
// Timers for auto-hiding status icons and error messages // Timers for auto-hiding status icons and error messages
let pushStatusTimer: number | null = null let pushStatusTimer: number | null = null;
let errorTimer: number | null = null let errorTimer: number | null = null;
// 获取configStore // 获取configStore
const configStore = useConfigStore() const configStore = useConfigStore();
// Computed properties // Computed properties
const isEnabled = computed(() => configStore.config.backup.enabled) const isEnabled = computed(() => configStore.config.backup.enabled);
const isConfigured = computed(() => configStore.config.backup.repo_url) const isConfigured = computed(() => configStore.config.backup.repo_url);
// 清除状态显示 // 清除状态显示
const clearPushStatus = () => { const clearPushStatus = () => {
if (pushStatusTimer !== null) { if (pushStatusTimer !== null) {
window.clearTimeout(pushStatusTimer) window.clearTimeout(pushStatusTimer);
pushStatusTimer = null pushStatusTimer = null;
}
pushSuccess.value = false
pushError.value = false
} }
pushSuccess.value = false;
pushError.value = false;
};
// 清除错误信息和错误图标 // 清除错误信息和错误图标
const clearError = () => { const clearError = () => {
if (errorTimer !== null) { if (errorTimer !== null) {
window.clearTimeout(errorTimer) window.clearTimeout(errorTimer);
errorTimer = null errorTimer = null;
}
error.value = null
pushError.value = false
} }
error.value = null;
pushError.value = false;
};
// 设置错误信息和错误图标并自动清除 // 设置错误信息和错误图标并自动清除
const setErrorWithAutoHide = (errorMessage: string, hideAfter: number = 3000) => { const setErrorWithAutoHide = (errorMessage: string, hideAfter: number = 3000) => {
clearError() clearError();
clearPushStatus() clearPushStatus();
error.value = errorMessage error.value = errorMessage;
pushError.value = true pushError.value = true;
errorTimer = window.setTimeout(() => { errorTimer = window.setTimeout(() => {
error.value = null error.value = null;
pushError.value = false pushError.value = false;
errorTimer = null errorTimer = null;
}, hideAfter) }, hideAfter);
} };
// Push to remote repository // Push to remote repository
const pushToRemote = async () => { const pushToRemote = async () => {
if (isPushing.value || !isConfigured.value) return if (isPushing.value || !isConfigured.value) return;
isPushing.value = true isPushing.value = true;
clearError() // 清除之前的错误信息 clearError(); // 清除之前的错误信息
clearPushStatus() clearPushStatus();
try { try {
await BackupService.PushToRemote() await BackupService.PushToRemote();
// 显示成功状态并设置3秒后自动消失 // 显示成功状态并设置3秒后自动消失
pushSuccess.value = true pushSuccess.value = true;
pushStatusTimer = window.setTimeout(() => { pushStatusTimer = window.setTimeout(() => {
pushSuccess.value = false pushSuccess.value = false;
pushStatusTimer = null pushStatusTimer = null;
}, 3000) }, 3000);
} catch (err: any) { } catch (err: any) {
setErrorWithAutoHide(err?.message || 'Backup operation failed') setErrorWithAutoHide(err?.message || 'Backup operation failed');
} finally { } finally {
isPushing.value = false isPushing.value = false;
}
} }
};
// 初始化备份服务 // 初始化备份服务
const initialize = async () => { const initialize = async () => {
if (!isEnabled.value) return if (!isEnabled.value) return;
// 避免重复初始化 // 避免重复初始化
if (isInitialized.value) return if (isInitialized.value) return;
clearError() // 清除之前的错误信息 clearError(); // 清除之前的错误信息
try { try {
await BackupService.Initialize() await BackupService.Initialize();
isInitialized.value = true isInitialized.value = true;
} catch (err: any) { } catch (err: any) {
setErrorWithAutoHide(err?.message || 'Failed to initialize backup service') setErrorWithAutoHide(err?.message || 'Failed to initialize backup service');
}
} }
};
return { return {
@@ -119,5 +119,5 @@ export const useBackupStore = defineStore('backup', () => {
pushToRemote, pushToRemote,
initialize, initialize,
clearError clearError
} };
}) });

View File

@@ -43,7 +43,7 @@ const getBrowserLanguage = (): SupportedLocaleType => {
}; };
export const useConfigStore = defineStore('config', () => { export const useConfigStore = defineStore('config', () => {
const {locale, t} = useI18n(); const {locale} = useI18n();
// 响应式状态 // 响应式状态
const state = reactive({ const state = reactive({
@@ -193,7 +193,7 @@ export const useConfigStore = defineStore('config', () => {
state.isLoading = true; state.isLoading = true;
try { try {
await ConfigService.ResetConfig() await ConfigService.ResetConfig();
const appConfig = await ConfigService.GetConfig(); const appConfig = await ConfigService.GetConfig();
if (appConfig) { if (appConfig) {
state.config = JSON.parse(JSON.stringify(appConfig)) as AppConfig; state.config = JSON.parse(JSON.stringify(appConfig)) as AppConfig;
@@ -230,7 +230,7 @@ export const useConfigStore = defineStore('config', () => {
// 同步前端语言设置 // 同步前端语言设置
const frontendLocale = ConfigUtils.backendLanguageToFrontend(state.config.appearance.language); const frontendLocale = ConfigUtils.backendLanguageToFrontend(state.config.appearance.language);
locale.value = frontendLocale as any; locale.value = frontendLocale as any;
} catch (error) { } catch (_error) {
const browserLang = getBrowserLanguage(); const browserLang = getBrowserLanguage();
locale.value = browserLang as any; locale.value = browserLang as any;
} }

View File

@@ -133,7 +133,7 @@ export const useDocumentStore = defineStore('document', () => {
}; };
// 更新文档元数据 // 更新文档元数据
const updateDocumentMetadata = async (docId: number, title: string, newPath?: string): Promise<boolean> => { const updateDocumentMetadata = async (docId: number, title: string): Promise<boolean> => {
try { try {
await DocumentService.UpdateDocumentTitle(docId, title); await DocumentService.UpdateDocumentTitle(docId, title);

View File

@@ -1,40 +1,40 @@
import { defineStore } from 'pinia' import { defineStore } from 'pinia';
import { computed, ref } from 'vue' import { computed, ref } from 'vue';
import { Extension, ExtensionID } from '@/../bindings/voidraft/internal/models/models' import { Extension, ExtensionID } from '@/../bindings/voidraft/internal/models/models';
import { ExtensionService } from '@/../bindings/voidraft/internal/services' import { ExtensionService } from '@/../bindings/voidraft/internal/services';
export const useExtensionStore = defineStore('extension', () => { export const useExtensionStore = defineStore('extension', () => {
// 扩展配置数据 // 扩展配置数据
const extensions = ref<Extension[]>([]) const extensions = ref<Extension[]>([]);
// 获取启用的扩展 // 获取启用的扩展
const enabledExtensions = computed(() => const enabledExtensions = computed(() =>
extensions.value.filter(ext => ext.enabled) extensions.value.filter(ext => ext.enabled)
) );
// 获取启用的扩展ID列表 // 获取启用的扩展ID列表
const enabledExtensionIds = computed(() => const enabledExtensionIds = computed(() =>
enabledExtensions.value.map(ext => ext.id) enabledExtensions.value.map(ext => ext.id)
) );
/** /**
* 从后端加载扩展配置 * 从后端加载扩展配置
*/ */
const loadExtensions = async (): Promise<void> => { const loadExtensions = async (): Promise<void> => {
try { try {
extensions.value = await ExtensionService.GetAllExtensions() extensions.value = await ExtensionService.GetAllExtensions();
} catch (err) { } catch (err) {
console.error('[ExtensionStore] Failed to load extensions:', err) console.error('[ExtensionStore] Failed to load extensions:', err);
}
} }
};
/** /**
* 获取扩展配置 * 获取扩展配置
*/ */
const getExtensionConfig = (id: ExtensionID): any => { const getExtensionConfig = (id: ExtensionID): any => {
const extension = extensions.value.find(ext => ext.id === id) const extension = extensions.value.find(ext => ext.id === id);
return extension?.config ?? {} return extension?.config ?? {};
} };
return { return {
// 状态 // 状态
@@ -45,5 +45,5 @@ export const useExtensionStore = defineStore('extension', () => {
// 方法 // 方法
loadExtensions, loadExtensions,
getExtensionConfig, getExtensionConfig,
} };
}) });

View File

@@ -1,72 +1,68 @@
import {defineStore} from 'pinia' import {defineStore} from 'pinia';
import {computed, ref} from 'vue' import {computed, ref} from 'vue';
import {ExtensionID, KeyBinding, KeyBindingCommand} from '@/../bindings/voidraft/internal/models/models' import {ExtensionID, KeyBinding, KeyBindingCommand} from '@/../bindings/voidraft/internal/models/models';
import {GetAllKeyBindings} from '@/../bindings/voidraft/internal/services/keybindingservice' import {GetAllKeyBindings} from '@/../bindings/voidraft/internal/services/keybindingservice';
export const useKeybindingStore = defineStore('keybinding', () => { export const useKeybindingStore = defineStore('keybinding', () => {
// 快捷键配置数据 // 快捷键配置数据
const keyBindings = ref<KeyBinding[]>([]) const keyBindings = ref<KeyBinding[]>([]);
// 获取启用的快捷键 // 获取启用的快捷键
const enabledKeyBindings = computed(() => const enabledKeyBindings = computed(() =>
keyBindings.value.filter(kb => kb.enabled) keyBindings.value.filter(kb => kb.enabled)
) );
// 按扩展分组的快捷键 // 按扩展分组的快捷键
const keyBindingsByExtension = computed(() => { const keyBindingsByExtension = computed(() => {
const groups = new Map<ExtensionID, KeyBinding[]>() const groups = new Map<ExtensionID, KeyBinding[]>();
for (const binding of keyBindings.value) { for (const binding of keyBindings.value) {
if (!groups.has(binding.extension)) { 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;
}) });
// 获取指定扩展的快捷键 // 获取指定扩展的快捷键
const getKeyBindingsByExtension = computed(() => const getKeyBindingsByExtension = computed(() =>
(extension: ExtensionID) => (extension: ExtensionID) =>
keyBindings.value.filter(kb => kb.extension === extension) keyBindings.value.filter(kb => kb.extension === extension)
) );
// 按命令获取快捷键 // 按命令获取快捷键
const getKeyBindingByCommand = computed(() => const getKeyBindingByCommand = computed(() =>
(command: KeyBindingCommand) => (command: KeyBindingCommand) =>
keyBindings.value.find(kb => kb.command === command) keyBindings.value.find(kb => kb.command === command)
) );
/** /**
* 从后端加载快捷键配置 * 从后端加载快捷键配置
*/ */
const loadKeyBindings = async (): Promise<void> => { const loadKeyBindings = async (): Promise<void> => {
try { keyBindings.value = await GetAllKeyBindings();
keyBindings.value = await GetAllKeyBindings() };
} catch (err) {
throw err
}
}
/** /**
* 检查是否存在指定命令的快捷键 * 检查是否存在指定命令的快捷键
*/ */
const hasCommand = (command: KeyBindingCommand): boolean => { const hasCommand = (command: KeyBindingCommand): boolean => {
return keyBindings.value.some(kb => kb.command === command && kb.enabled) return keyBindings.value.some(kb => kb.command === command && kb.enabled);
} };
/** /**
* 获取扩展相关的所有扩展ID * 获取扩展相关的所有扩展ID
*/ */
const getAllExtensionIds = computed(() => { const getAllExtensionIds = computed(() => {
const extensionIds = new Set<ExtensionID>() const extensionIds = new Set<ExtensionID>();
for (const binding of keyBindings.value) { for (const binding of keyBindings.value) {
extensionIds.add(binding.extension) extensionIds.add(binding.extension);
} }
return Array.from(extensionIds) return Array.from(extensionIds);
}) });
return { return {
// 状态 // 状态
@@ -82,5 +78,5 @@ export const useKeybindingStore = defineStore('keybinding', () => {
// 方法 // 方法
loadKeyBindings, loadKeyBindings,
hasCommand, hasCommand,
} };
}) });

View File

@@ -43,7 +43,7 @@ export const useSystemStore = defineStore('system', () => {
try { try {
environment.value = await runtime.System.Environment(); environment.value = await runtime.System.Environment();
} catch (err) { } catch (_err) {
environment.value = null; environment.value = null;
} finally { } finally {
isLoading.value = false; isLoading.value = false;

View File

@@ -153,7 +153,7 @@ export const useTranslationStore = defineStore('translation', () => {
defaultTargetLang.value = validatedLang; defaultTargetLang.value = validatedLang;
} }
} }
} catch (err) { } catch (_err) {
error.value = 'no available translators'; error.value = 'no available translators';
} }
}; };

View File

@@ -1,116 +1,116 @@
import {defineStore} from 'pinia' import {defineStore} from 'pinia';
import {computed, ref} from 'vue' import {computed, ref} from 'vue';
import {CheckForUpdates, ApplyUpdate, RestartApplication} from '@/../bindings/voidraft/internal/services/selfupdateservice' import {CheckForUpdates, ApplyUpdate, RestartApplication} from '@/../bindings/voidraft/internal/services/selfupdateservice';
import {SelfUpdateResult} from '@/../bindings/voidraft/internal/services/models' import {SelfUpdateResult} from '@/../bindings/voidraft/internal/services/models';
import {useConfigStore} from './configStore' import {useConfigStore} from './configStore';
import * as runtime from "@wailsio/runtime" import * as runtime from "@wailsio/runtime";
export const useUpdateStore = defineStore('update', () => { export const useUpdateStore = defineStore('update', () => {
// 状态 // 状态
const isChecking = ref(false) const isChecking = ref(false);
const isUpdating = ref(false) const isUpdating = ref(false);
const updateResult = ref<SelfUpdateResult | null>(null) const updateResult = ref<SelfUpdateResult | null>(null);
const hasCheckedOnStartup = ref(false) const hasCheckedOnStartup = ref(false);
const updateSuccess = ref(false) const updateSuccess = ref(false);
const errorMessage = ref('') const errorMessage = ref('');
// 计算属性 // 计算属性
const hasUpdate = computed(() => updateResult.value?.hasUpdate || false) const hasUpdate = computed(() => updateResult.value?.hasUpdate || false);
// 检查更新 // 检查更新
const checkForUpdates = async (): Promise<boolean> => { const checkForUpdates = async (): Promise<boolean> => {
if (isChecking.value) return false if (isChecking.value) return false;
// 重置错误信息 // 重置错误信息
errorMessage.value = '' errorMessage.value = '';
isChecking.value = true isChecking.value = true;
try { try {
const result = await CheckForUpdates() const result = await CheckForUpdates();
if (result) { if (result) {
updateResult.value = result updateResult.value = result;
if (result.error) { if (result.error) {
errorMessage.value = result.error errorMessage.value = result.error;
return false return false;
} }
return true return true;
} }
return false return false;
} catch (error) { } catch (error) {
errorMessage.value = error instanceof Error ? error.message : 'Network error' errorMessage.value = error instanceof Error ? error.message : 'Network error';
return false return false;
} finally { } finally {
isChecking.value = false isChecking.value = false;
}
} }
};
// 应用更新 // 应用更新
const applyUpdate = async (): Promise<boolean> => { const applyUpdate = async (): Promise<boolean> => {
if (isUpdating.value) return false if (isUpdating.value) return false;
// 重置错误信息 // 重置错误信息
errorMessage.value = '' errorMessage.value = '';
isUpdating.value = true isUpdating.value = true;
try { try {
const result = await ApplyUpdate() const result = await ApplyUpdate();
if (result) { if (result) {
updateResult.value = result updateResult.value = result;
if (result.error) { if (result.error) {
errorMessage.value = result.error errorMessage.value = result.error;
return false return false;
} }
if (result.updateApplied) { if (result.updateApplied) {
updateSuccess.value = true updateSuccess.value = true;
return true return true;
} }
} }
return false return false;
} catch (error) { } catch (error) {
errorMessage.value = error instanceof Error ? error.message : 'Update failed' errorMessage.value = error instanceof Error ? error.message : 'Update failed';
return false return false;
} finally { } finally {
isUpdating.value = false isUpdating.value = false;
}
} }
};
// 重启应用 // 重启应用
const restartApplication = async (): Promise<boolean> => { const restartApplication = async (): Promise<boolean> => {
try { try {
await RestartApplication() await RestartApplication();
return true return true;
} catch (error) { } catch (error) {
errorMessage.value = error instanceof Error ? error.message : 'Restart failed' errorMessage.value = error instanceof Error ? error.message : 'Restart failed';
return false return false;
}
} }
};
// 启动时检查更新 // 启动时检查更新
const checkOnStartup = async () => { const checkOnStartup = async () => {
if (hasCheckedOnStartup.value) return if (hasCheckedOnStartup.value) return;
const configStore = useConfigStore() const configStore = useConfigStore();
if (configStore.config.updates.autoUpdate) { if (configStore.config.updates.autoUpdate) {
await checkForUpdates() await checkForUpdates();
}
hasCheckedOnStartup.value = true
} }
hasCheckedOnStartup.value = true;
};
// 打开发布页面 // 打开发布页面
const openReleaseURL = async () => { const openReleaseURL = async () => {
if (updateResult.value?.assetURL) { if (updateResult.value?.assetURL) {
await runtime.Browser.OpenURL(updateResult.value.assetURL) await runtime.Browser.OpenURL(updateResult.value.assetURL);
}
} }
};
// 重置状态 // 重置状态
const reset = () => { const reset = () => {
updateResult.value = null updateResult.value = null;
isChecking.value = false isChecking.value = false;
isUpdating.value = false isUpdating.value = false;
updateSuccess.value = false updateSuccess.value = false;
errorMessage.value = '' errorMessage.value = '';
} };
return { return {
// 状态 // 状态
@@ -131,5 +131,5 @@ export const useUpdateStore = defineStore('update', () => {
checkOnStartup, checkOnStartup,
openReleaseURL, openReleaseURL,
reset reset
} };
}) });

View File

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

View File

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

View File

@@ -193,8 +193,8 @@ function setSel(state: any, selection: EditorSelection) {
} }
function extendSel(state: any, dispatch: any, how: (range: any) => any) { function extendSel(state: any, dispatch: any, how: (range: any) => any) {
let selection = updateSel(state.selection, range => { const selection = updateSel(state.selection, range => {
let head = how(range); const head = how(range);
return EditorSelection.range(range.anchor, head.head, head.goalColumn, head.bidiLevel || undefined); return EditorSelection.range(range.anchor, head.head, head.goalColumn, head.bidiLevel || undefined);
}); });
if (selection.eq(state.selection)) return false; 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) { 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; if (selection.eq(state.selection)) return false;
dispatch(setSel(state, selection)); dispatch(setSel(state, selection));
return true; 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; if (state.readOnly) return false;
const block = getActiveNoteBlock(state); const block = getActiveNoteBlock(state);
@@ -380,4 +380,4 @@ function moveCurrentBlock(state: any, dispatch: any, up: boolean) {
*/ */
export const formatCurrentBlock: Command = (view) => { export const formatCurrentBlock: Command = (view) => {
return formatBlockContent(view); return formatBlockContent(view);
} };

View File

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

View File

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

View File

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

View File

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

View File

@@ -49,8 +49,8 @@ export const CodeBlockLanguage = LRLanguage.define({
* 创建代码块语言支持 * 创建代码块语言支持
*/ */
export function codeBlockLang() { export function codeBlockLang() {
let wrap = configureNesting(); const wrap = configureNesting();
let lang = CodeBlockLanguage.configure({ dialect: "", wrap: wrap }); const lang = CodeBlockLanguage.configure({ dialect: "", wrap: wrap });
return [ return [
new LanguageSupport(lang, [json().support]), 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 {lua} from "@codemirror/legacy-modes/mode/lua";
import {SupportedLanguage} from '../types'; import {SupportedLanguage} from '../types';
import typescriptPlugin from "prettier/plugins/typescript" import typescriptPlugin from "prettier/plugins/typescript";
import babelPrettierPlugin from "prettier/plugins/babel" import babelPrettierPlugin from "prettier/plugins/babel";
import htmlPrettierPlugin from "prettier/plugins/html" import htmlPrettierPlugin from "prettier/plugins/html";
import cssPrettierPlugin from "prettier/plugins/postcss" import cssPrettierPlugin from "prettier/plugins/postcss";
import markdownPrettierPlugin from "prettier/plugins/markdown" import markdownPrettierPlugin from "prettier/plugins/markdown";
import yamlPrettierPlugin from "prettier/plugins/yaml" import yamlPrettierPlugin from "prettier/plugins/yaml";
import goPrettierPlugin from "@/common/prettier/plugins/go" import goPrettierPlugin from "@/common/prettier/plugins/go";
import sqlPrettierPlugin from "@/common/prettier/plugins/sql" import sqlPrettierPlugin from "@/common/prettier/plugins/sql";
import phpPrettierPlugin from "@/common/prettier/plugins/php" import phpPrettierPlugin from "@/common/prettier/plugins/php";
import javaPrettierPlugin from "@/common/prettier/plugins/java" import javaPrettierPlugin from "@/common/prettier/plugins/java";
import xmlPrettierPlugin from "@prettier/plugin-xml" import xmlPrettierPlugin from "@prettier/plugin-xml";
import shellPrettierPlugin from "@/common/prettier/plugins/shell"; import shellPrettierPlugin from "@/common/prettier/plugins/shell";
import dockerfilePrettierPlugin from "@/common/prettier/plugins/docker"; import dockerfilePrettierPlugin from "@/common/prettier/plugins/docker";
import rustPrettierPlugin from "@/common/prettier/plugins/rust"; import rustPrettierPlugin from "@/common/prettier/plugins/rust";

View File

@@ -13,11 +13,11 @@ import { languageMapping } from "./languages";
*/ */
export function configureNesting() { export function configureNesting() {
return parseMixed((node, input) => { return parseMixed((node, input) => {
let id = node.type.id; const id = node.type.id;
if (id === BlockContent) { 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; let langName = blockLang ? input.read(blockLang.from, blockLang.to) : null;
// 如果 BlockContent 为空,不返回解析器 // 如果 BlockContent 为空,不返回解析器

View File

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

View File

@@ -21,11 +21,11 @@ const tokenRegEx = new RegExp(`^∞∞∞(${languageTokensMatcher})(-a)?$`, "g")
* 获取选中的行块 * 获取选中的行块
*/ */
function selectedLineBlocks(state: any): LineBlock[] { function selectedLineBlocks(state: any): LineBlock[] {
let blocks: LineBlock[] = []; const blocks: LineBlock[] = [];
let upto = -1; let upto = -1;
for (let range of state.selection.ranges) { for (const range of state.selection.ranges) {
let startLine = state.doc.lineAt(range.from); const startLine = state.doc.lineAt(range.from);
let endLine = state.doc.lineAt(range.to); let endLine = state.doc.lineAt(range.to);
if (!range.empty && range.to == endLine.from) { if (!range.empty && range.to == endLine.from) {
@@ -33,7 +33,7 @@ function selectedLineBlocks(state: any): LineBlock[] {
} }
if (upto >= startLine.number) { if (upto >= startLine.number) {
let prev = blocks[blocks.length - 1]; const prev = blocks[blocks.length - 1];
prev.to = endLine.to; prev.to = endLine.to;
prev.ranges.push(range); prev.ranges.push(range);
} else { } else {
@@ -57,15 +57,15 @@ function moveLine(state: any, dispatch: any, forward: boolean): boolean {
return false; return false;
} }
let changes: any[] = []; const changes: any[] = [];
let ranges: SelectionRange[] = []; 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) { if (forward ? block.to == state.doc.length : block.from == 0) {
continue; 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; let previousLine;
if (!forward ? block.to == state.doc.length : block.from == 0) { 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); const nextLineIsSeparator = nextLine.text.match(tokenRegEx);
let blockSurroundedBySeparators = previousLine !== null && const blockSurroundedBySeparators = previousLine !== null &&
previousLine.text.match(tokenRegEx) && nextLineIsSeparator; previousLine.text.match(tokenRegEx) && nextLineIsSeparator;
let size = nextLine.length + 1; 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( ranges.push(EditorSelection.range(
Math.min(state.doc.length, r.anchor + size), Math.min(state.doc.length, r.anchor + size),
Math.min(state.doc.length, r.head + 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: nextLine.from, to: block.from },
{ from: block.to, insert: state.lineBreak + nextLine.text + state.lineBreak } { 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)); ranges.push(EditorSelection.range(r.anchor - size, r.head - size));
} }
} else { } else {
@@ -116,7 +116,7 @@ function moveLine(state: any, dispatch: any, forward: boolean): boolean {
{ from: nextLine.from, to: block.from }, { from: nextLine.from, to: block.from },
{ from: block.to, insert: state.lineBreak + nextLine.text } { 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)); 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 => { export const moveLineUp = ({ state, dispatch }: { state: any; dispatch: any }): boolean => {
// 防止移动行到第一个块分隔符之前 // 防止移动行到第一个块分隔符之前
if (state.selection.ranges.some((range: SelectionRange) => { 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 startLine.from <= state.field(blockState)[0].content.from;
})) { })) {
return true; return true;

View File

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

View File

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

View File

@@ -240,7 +240,7 @@ export const colorView = (showPicker: boolean = true) =>
const comma = (target.dataset.colorraw || '').indexOf(',') > 4; const comma = (target.dataset.colorraw || '').indexOf(',') > 4;
let converted = target.value; let converted = target.value;
if (data.colorType === ColorType.rgb) { 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) { if (comma) {
converted = rgb converted = rgb
? `${funName}(${rgb.r}, ${rgb.g}, ${rgb.b}${data.alpha ? ', ' + data.alpha.trim() : ''})` ? `${funName}(${rgb.r}, ${rgb.g}, ${rgb.b}${data.alpha ? ', ' + data.alpha.trim() : ''})`

View File

@@ -1,37 +1,37 @@
import {foldService} from '@codemirror/language'; import {foldService} from '@codemirror/language';
export const foldingOnIndent = foldService.of((state, from, to) => { export const foldingOnIndent = foldService.of((state, from, to) => {
const line = state.doc.lineAt(from) // First line const line = state.doc.lineAt(from); // First line
const lines = state.doc.lines // Number of lines in the document const lines = state.doc.lines; // Number of lines in the document
const indent = line.text.search(/\S|$/) // Indent level of the first line const indent = line.text.search(/\S|$/); // Indent level of the first line
let foldStart = from // Start of the fold let foldStart = from; // Start of the fold
let foldEnd = to // End of the fold let foldEnd = to; // End of the fold
// Check the next line if it is on a deeper indent level // 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, check the next line and so on
// If it is not, go on with the foldEnd // If it is not, go on with the foldEnd
let nextLine = line let nextLine = line;
while (nextLine.number < lines) { while (nextLine.number < lines) {
nextLine = state.doc.line(nextLine.number + 1) // Next line nextLine = state.doc.line(nextLine.number + 1); // Next line
const nextIndent = nextLine.text.search(/\S|$/) // Indent level of the 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 the next line is on a deeper indent level, add it to the fold
if (nextIndent > indent) { 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 { } 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 the fold is only one line, don't fold it
if (state.doc.lineAt(foldStart).number === state.doc.lineAt(foldEnd).number) { 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 // Set the fold start to the end of the first line
// With this, the fold will not include 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 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, ViewUpdate,
} from '@codemirror/view'; } from '@codemirror/view';
import { Extension, Range } from '@codemirror/state'; 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 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; const defaultRegexp = /\b((?:https?|ftp):\/\/[^\s/$.?#].[^\s]*)\b/gi;
@@ -213,7 +213,7 @@ export const hyperLinkClickHandler = EditorView.domEventHandlers({
if (urlElement && urlElement.hasAttribute('data-url')) { if (urlElement && urlElement.hasAttribute('data-url')) {
const url = urlElement.getAttribute('data-url'); const url = urlElement.getAttribute('data-url');
if (url) { if (url) {
runtime.Browser.OpenURL(url) runtime.Browser.OpenURL(url);
event.preventDefault(); event.preventDefault();
return true; return true;
} }

View File

@@ -46,7 +46,7 @@ export type Options = {
const Config = Facet.define<MinimapConfig | null, Required<Options>>({ const Config = Facet.define<MinimapConfig | null, Required<Options>>({
combine: (c) => { combine: (c) => {
const configs: Array<Options> = []; const configs: Array<Options> = [];
for (let config of c) { for (const config of c) {
if (!config) { if (!config) {
continue; 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 */ /* Small leading buffer */
drawContext.offsetX += 2; drawContext.offsetX += 2;
for (let gutter of gutters) { for (const gutter of gutters) {
drawLineGutter(gutter, drawContext, i + 1); drawLineGutter(gutter, drawContext, i + 1);
drawContext.offsetX += GUTTER_WIDTH; drawContext.offsetX += GUTTER_WIDTH;
} }

View File

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

View File

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

View File

@@ -33,7 +33,7 @@ const removeHighlight = StateEffect.define<{from: number, to: number}>({
// 配置facet // 配置facet
const highlightConfigFacet = Facet.define<TextHighlightConfig, Required<TextHighlightConfig>>({ const highlightConfigFacet = Facet.define<TextHighlightConfig, Required<TextHighlightConfig>>({
combine: (configs) => { combine: (configs) => {
let result = { ...DEFAULT_CONFIG }; const result = { ...DEFAULT_CONFIG };
for (const config of configs) { for (const config of configs) {
if (config.backgroundColor !== undefined) { if (config.backgroundColor !== undefined) {
result.backgroundColor = config.backgroundColor; 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 { CharCategory, EditorState, findClusterBreak, Text } from "@codemirror/state";
import { SearchVisibilityEffect } from "./state"; import { SearchVisibilityEffect } from "./state";
import { EditorView } from "@codemirror/view"; import { EditorView } from "@codemirror/view";
@@ -9,7 +9,7 @@ type Match = { from: number, to: number };
export class CustomSearchPanel { export class CustomSearchPanel {
dom!: HTMLElement; dom!: HTMLElement;
searchField!: HTMLInputElement; searchField!: HTMLInputElement;
replaceField!: HTMLInputElement replaceField!: HTMLInputElement;
matchCountField!: HTMLElement; matchCountField!: HTMLElement;
currentMatch!: number; currentMatch!: number;
matches!: Match[]; 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>', "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>', "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>', "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) { constructor(readonly view: EditorView) {
try { try {
@@ -63,7 +63,7 @@ export class CustomSearchPanel {
} }
catch (err) { catch (err) {
console.warn(`ERROR: ${err}`) console.warn(`ERROR: ${err}`);
} }
} }
@@ -84,24 +84,24 @@ export class CustomSearchPanel {
} }
private charBefore(str: string, index: number) { 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) { 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) { private stringWordTest(doc: Text, categorizer: (ch: string) => CharCategory) {
return (from: number, to: number, buf: string, bufPos: number) => { return (from: number, to: number, buf: string, bufPos: number) => {
if (bufPos > from || bufPos + buf.length < to) { if (bufPos > from || bufPos + buf.length < to) {
bufPos = Math.max(0, from - 2) bufPos = Math.max(0, from - 2);
buf = doc.sliceString(bufPos, Math.min(doc.length, to + 2)) buf = doc.sliceString(bufPos, Math.min(doc.length, to + 2));
} }
return (categorizer(this.charBefore(buf, from - bufPos)) != CharCategory.Word || return (categorizer(this.charBefore(buf, from - bufPos)) != CharCategory.Word ||
categorizer(this.charAfter(buf, from - bufPos)) != CharCategory.Word) && categorizer(this.charAfter(buf, from - bufPos)) != CharCategory.Word) &&
(categorizer(this.charAfter(buf, to - 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) { private regexpWordTest(categorizer: (ch: string) => CharCategory) {
@@ -110,7 +110,7 @@ export class CustomSearchPanel {
(categorizer(this.charBefore(match.input, match.index)) != CharCategory.Word || (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)) != CharCategory.Word) &&
(categorizer(this.charAfter(match.input, match.index + match[0].length)) != 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 { findMatchesAndSelectClosest(state: EditorState): void {
const cursorPos = state.selection.main.head; const cursorPos = state.selection.main.head;
let query = getSearchQuery(state); const query = getSearchQuery(state);
if (query.regexp) { if (query.regexp) {
try { try {
this.regexCursor = new RegExpCursor(state.doc, query.search) this.regexCursor = new RegExpCursor(state.doc, query.search);
this.searchCursor = undefined; this.searchCursor = undefined;
} catch (error) { } catch (error) {
// 如果正则表达式无效,清空匹配结果并显示错误状态 // 如果正则表达式无效,清空匹配结果并显示错误状态
@@ -143,10 +143,10 @@ export class CustomSearchPanel {
} }
} }
else { else {
let cursor = new SearchCursor(state.doc, query.search); const cursor = new SearchCursor(state.doc, query.search);
if (cursor !== this.searchCursor) { if (cursor !== this.searchCursor) {
this.searchCursor = cursor; this.searchCursor = cursor;
this.regexCursor = undefined this.regexCursor = undefined;
} }
} }
@@ -168,7 +168,7 @@ export class CustomSearchPanel {
} }
else if (this.regexCursor) { else if (this.regexCursor) {
try { 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) { while (!this.regexCursor.done) {
this.regexCursor.next(); this.regexCursor.next();
@@ -228,13 +228,13 @@ export class CustomSearchPanel {
caseSensitive: this.matchCase, caseSensitive: this.matchCase,
regexp: this.useRegex, regexp: this.useRegex,
wholeWord: this.matchWord, wholeWord: this.matchWord,
}) });
let query = getSearchQuery(this.view.state) const query = getSearchQuery(this.view.state);
if (!newQuery.eq(query)) { if (!newQuery.eq(query)) {
this.view.dispatch({ this.view.dispatch({
effects: setSearchQuery.of(newQuery) effects: setSearchQuery.of(newQuery)
}) });
} }
} catch (error) { } catch (error) {
// 如果创建SearchQuery时出错通常是无效的正则表达式记录错误但不中断程序 // 如果创建SearchQuery时出错通常是无效的正则表达式记录错误但不中断程序
@@ -243,7 +243,7 @@ export class CustomSearchPanel {
} }
private svgIcon(name: keyof CustomSearchPanel['codicon']): HTMLDivElement { private svgIcon(name: keyof CustomSearchPanel['codicon']): HTMLDivElement {
let div = crelt("div", {}, const div = crelt("div", {},
) as HTMLDivElement; ) as HTMLDivElement;
div.innerHTML = this.codicon[name]; div.innerHTML = this.codicon[name];
@@ -251,14 +251,14 @@ export class CustomSearchPanel {
} }
public toggleReplace() { public toggleReplace() {
this.replaceVisibile = !this.replaceVisibile this.replaceVisibile = !this.replaceVisibile;
const replaceBar = this.dom.querySelector(".replace-bar") as HTMLElement; const replaceBar = this.dom.querySelector(".replace-bar") as HTMLElement;
const replaceButtons = this.dom.querySelector(".replace-buttons") as HTMLElement; const replaceButtons = this.dom.querySelector(".replace-buttons") as HTMLElement;
const toggleIcon = this.dom.querySelector(".toggle-replace") as HTMLElement; const toggleIcon = this.dom.querySelector(".toggle-replace") as HTMLElement;
if (replaceBar && toggleIcon && replaceButtons) { if (replaceBar && toggleIcon && replaceButtons) {
replaceBar.style.display = this.replaceVisibile ? "flex" : "none"; replaceBar.style.display = this.replaceVisibile ? "flex" : "none";
replaceButtons.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; this.matchCase = !this.matchCase;
const toggleIcon = this.dom.querySelector(".case-sensitive-toggle") as HTMLElement; const toggleIcon = this.dom.querySelector(".case-sensitive-toggle") as HTMLElement;
if (toggleIcon) { if (toggleIcon) {
toggleIcon.classList.toggle("active") toggleIcon.classList.toggle("active");
} }
this.commit(); this.commit();
// 重新搜索以应用新的匹配规则 // 重新搜索以应用新的匹配规则
@@ -286,7 +286,7 @@ export class CustomSearchPanel {
this.matchWord = !this.matchWord; this.matchWord = !this.matchWord;
const toggleIcon = this.dom.querySelector(".whole-word-toggle") as HTMLElement; const toggleIcon = this.dom.querySelector(".whole-word-toggle") as HTMLElement;
if (toggleIcon) { if (toggleIcon) {
toggleIcon.classList.toggle("active") toggleIcon.classList.toggle("active");
} }
this.commit(); this.commit();
// 重新搜索以应用新的匹配规则 // 重新搜索以应用新的匹配规则
@@ -299,7 +299,7 @@ export class CustomSearchPanel {
this.useRegex = !this.useRegex; this.useRegex = !this.useRegex;
const toggleIcon = this.dom.querySelector(".regex-toggle") as HTMLElement; const toggleIcon = this.dom.querySelector(".regex-toggle") as HTMLElement;
if (toggleIcon) { if (toggleIcon) {
toggleIcon.classList.toggle("active") toggleIcon.classList.toggle("active");
} }
this.commit(); this.commit();
// 重新搜索以应用新的匹配规则 // 重新搜索以应用新的匹配规则
@@ -341,11 +341,11 @@ export class CustomSearchPanel {
} }
public findReplaceMatch() { public findReplaceMatch() {
let query = getSearchQuery(this.view.state) const query = getSearchQuery(this.view.state);
if (query.replace) { if (query.replace) {
this.replace() this.replace();
} else { } else {
this.matchNext() this.matchNext();
} }
} }
@@ -398,7 +398,7 @@ export class CustomSearchPanel {
private buildUI(): void { private buildUI(): void {
let query = getSearchQuery(this.view.state) const query = getSearchQuery(this.view.state);
this.searchField = crelt("input", { this.searchField = crelt("input", {
value: query?.search ?? "", value: query?.search ?? "",
@@ -420,14 +420,14 @@ export class CustomSearchPanel {
}) as HTMLInputElement; }) as HTMLInputElement;
let caseField = this.svgIcon("matchCase"); const caseField = this.svgIcon("matchCase");
caseField.className = "case-sensitive-toggle"; caseField.className = "case-sensitive-toggle";
caseField.title = "Match Case (Alt+C)"; caseField.title = "Match Case (Alt+C)";
caseField.addEventListener("click", () => { caseField.addEventListener("click", () => {
this.toggleCase(); this.toggleCase();
}); });
let wordField = this.svgIcon("wholeWord"); const wordField = this.svgIcon("wholeWord");
wordField.className = "whole-word-toggle"; wordField.className = "whole-word-toggle";
wordField.title = "Match Whole Word (Alt+W)"; wordField.title = "Match Whole Word (Alt+W)";
wordField.addEventListener("click", () => { 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.className = "regex-toggle";
reField.title = "Use Regular Expression (Alt+R)"; reField.title = "Use Regular Expression (Alt+R)";
reField.addEventListener("click", () => { reField.addEventListener("click", () => {
this.toggleRegex(); this.toggleRegex();
}); });
let toggleReplaceIcon = this.svgIcon(this.replaceVisibile ? "downChevron" : "rightChevron"); const toggleReplaceIcon = this.svgIcon(this.replaceVisibile ? "downChevron" : "rightChevron");
toggleReplaceIcon.className = "toggle-replace"; toggleReplaceIcon.className = "toggle-replace";
toggleReplaceIcon.addEventListener("click", () => { toggleReplaceIcon.addEventListener("click", () => {
this.toggleReplace(); 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.className = "prev-match";
prevMatchButton.title = "Previous Match (Shift+Enter)"; prevMatchButton.title = "Previous Match (Shift+Enter)";
prevMatchButton.addEventListener("click", () => { prevMatchButton.addEventListener("click", () => {
this.matchPrevious(); this.matchPrevious();
}); });
let nextMatchButton = this.svgIcon("nextMatch"); const nextMatchButton = this.svgIcon("nextMatch");
nextMatchButton.className = "next-match"; nextMatchButton.className = "next-match";
nextMatchButton.title = "Next Match (Enter)"; nextMatchButton.title = "Next Match (Enter)";
nextMatchButton.addEventListener("click", () => { nextMatchButton.addEventListener("click", () => {
this.matchNext(); this.matchNext();
}); });
let closeButton = this.svgIcon("close"); const closeButton = this.svgIcon("close");
closeButton.className = "close"; closeButton.className = "close";
closeButton.title = "Close (Escape)" closeButton.title = "Close (Escape)";
closeButton.addEventListener("click", () => { closeButton.addEventListener("click", () => {
this.close(); this.close();
}); });
let replaceButton = this.svgIcon("replace"); const replaceButton = this.svgIcon("replace");
replaceButton.className = "replace-button"; replaceButton.className = "replace-button";
replaceButton.title = "Replace (Enter)"; replaceButton.title = "Replace (Enter)";
replaceButton.addEventListener("click", () => { replaceButton.addEventListener("click", () => {
this.replace(); this.replace();
}); });
let replaceAllButton = this.svgIcon("replaceAll"); const replaceAllButton = this.svgIcon("replaceAll");
replaceAllButton.className = "replace-button"; replaceAllButton.className = "replace-button";
replaceAllButton.title = "Replace All (Ctrl+Alt+Enter)"; replaceAllButton.title = "Replace All (Ctrl+Alt+Enter)";
replaceAllButton.addEventListener("click", () => { replaceAllButton.addEventListener("click", () => {
@@ -534,7 +534,7 @@ export class CustomSearchPanel {
this.replaceField this.replaceField
); );
replaceBar.style.display = this.replaceVisibile ? "flex" : "none" replaceBar.style.display = this.replaceVisibile ? "flex" : "none";
const inputSection = crelt("div", { class: "input-section" }, const inputSection = crelt("div", { class: "input-section" },
searchBar, searchBar,
@@ -545,7 +545,7 @@ export class CustomSearchPanel {
prevMatchButton, prevMatchButton,
nextMatchButton, nextMatchButton,
closeButton closeButton
) );
const searchButtons = crelt("div", { class: "button-group" }, const searchButtons = crelt("div", { class: "button-group" },
this.matchCountField, this.matchCountField,
@@ -557,9 +557,9 @@ export class CustomSearchPanel {
}, },
replaceButton, replaceButton,
replaceAllButton replaceAllButton
) );
replaceButtons.style.display = this.replaceVisibile ? "flex" : "none" replaceButtons.style.display = this.replaceVisibile ? "flex" : "none";
const actionSection = crelt("div", { class: "actions-section" }, const actionSection = crelt("div", { class: "actions-section" },
searchButtons, searchButtons,
@@ -599,14 +599,14 @@ export class CustomSearchPanel {
} }
mount() { mount() {
this.searchField.select() this.searchField.select();
} }
destroy?(): void { destroy?(): void {
throw new Error("Method not implemented."); 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 document.activeElement.classList.contains('find-input');
} }
return false; return false;
} };
const isReplaceActive = () : boolean => { const isReplaceActive = () : boolean => {
if (document.activeElement){ if (document.activeElement){
return document.activeElement.classList.contains('replace-input'); return document.activeElement.classList.contains('replace-input');
} }
return false; return false;
} };
export const selectAllCommand: Command = (view) => { export const selectAllCommand: Command = (view) => {
if (isSearchActive() || isReplaceActive()) { if (isSearchActive() || isReplaceActive()) {
@@ -26,7 +26,7 @@ export const selectAllCommand: Command = (view) => {
else { else {
view.dispatch({ view.dispatch({
selection: { anchor: 0, head: view.state.doc.length } selection: { anchor: 0, head: view.state.doc.length }
}) });
return true; return true;
} }
}; };
@@ -37,7 +37,7 @@ export const deleteCharacterBackwards: Command = (view) => {
return true; return true;
} }
else { else {
deleteCharBackward(view) deleteCharBackward(view);
return true; return true;
} }
}; };
@@ -48,7 +48,7 @@ export const deleteCharacterFowards: Command = (view) => {
return true; return true;
} }
else { else {
deleteCharForward(view) deleteCharForward(view);
return true; return true;
} }
}; };
@@ -72,7 +72,7 @@ export const showSearchVisibilityCommand: Command = (view) => {
export const searchMoveCursorLeft: Command = (view) => { export const searchMoveCursorLeft: Command = (view) => {
if (isSearchActive() || isReplaceActive()) { if (isSearchActive() || isReplaceActive()) {
const input = document.activeElement as HTMLInputElement const input = document.activeElement as HTMLInputElement;
const pos = input.selectionStart ?? 0; const pos = input.selectionStart ?? 0;
if (pos > 0) { if (pos > 0) {
input.selectionStart = input.selectionEnd = pos - 1; input.selectionStart = input.selectionEnd = pos - 1;
@@ -80,14 +80,14 @@ export const searchMoveCursorLeft: Command = (view) => {
return true; return true;
} }
else { else {
cursorCharLeft(view) cursorCharLeft(view);
return true; return true;
} }
} };
export const searchMoveCursorRight: Command = (view) => { export const searchMoveCursorRight: Command = (view) => {
if (isSearchActive() || isReplaceActive()) { if (isSearchActive() || isReplaceActive()) {
const input = document.activeElement as HTMLInputElement const input = document.activeElement as HTMLInputElement;
const pos = input.selectionStart ?? 0; const pos = input.selectionStart ?? 0;
if (pos < input.value.length) { if (pos < input.value.length) {
input.selectionStart = input.selectionEnd = pos + 1; input.selectionStart = input.selectionEnd = pos + 1;
@@ -95,10 +95,10 @@ export const searchMoveCursorRight: Command = (view) => {
return true; return true;
} }
else { else {
cursorCharRight(view) cursorCharRight(view);
return true; return true;
} }
} };
export const hideSearchVisibilityCommand: Command = (view) => { export const hideSearchVisibilityCommand: Command = (view) => {
view.dispatch({ view.dispatch({
@@ -108,64 +108,64 @@ export const hideSearchVisibilityCommand: Command = (view) => {
}; };
export const searchToggleCase: Command = (view) => { export const searchToggleCase: Command = (view) => {
const plugin = view.plugin(VSCodeSearch) const plugin = view.plugin(VSCodeSearch);
if (!plugin) return false; if (!plugin) return false;
plugin.toggleCaseInsensitive(); plugin.toggleCaseInsensitive();
return true; return true;
} };
export const searchToggleWholeWord: Command = (view) => { export const searchToggleWholeWord: Command = (view) => {
const plugin = view.plugin(VSCodeSearch) const plugin = view.plugin(VSCodeSearch);
if (!plugin) return false; if (!plugin) return false;
plugin.toggleWholeWord(); plugin.toggleWholeWord();
return true; return true;
} };
export const searchToggleRegex: Command = (view) => { export const searchToggleRegex: Command = (view) => {
const plugin = view.plugin(VSCodeSearch) const plugin = view.plugin(VSCodeSearch);
if (!plugin) return false; if (!plugin) return false;
plugin.toggleRegex(); plugin.toggleRegex();
return true; return true;
} };
export const searchShowReplace: Command = (view) => { export const searchShowReplace: Command = (view) => {
const plugin = view.plugin(VSCodeSearch) const plugin = view.plugin(VSCodeSearch);
if (!plugin) return false; if (!plugin) return false;
plugin.showReplace(); plugin.showReplace();
return true; return true;
} };
export const searchFindReplaceMatch: Command = (view) => { export const searchFindReplaceMatch: Command = (view) => {
const plugin = view.plugin(VSCodeSearch) const plugin = view.plugin(VSCodeSearch);
if (!plugin) return false; if (!plugin) return false;
plugin.findReplaceMatch(); plugin.findReplaceMatch();
return true; return true;
} };
export const searchFindPrevious: Command = (view) => { export const searchFindPrevious: Command = (view) => {
const plugin = view.plugin(VSCodeSearch) const plugin = view.plugin(VSCodeSearch);
if (!plugin) return false; if (!plugin) return false;
plugin.findPrevious(); plugin.findPrevious();
return true; return true;
} };
export const searchReplaceAll: Command = (view) => { export const searchReplaceAll: Command = (view) => {
const plugin = view.plugin(VSCodeSearch) const plugin = view.plugin(VSCodeSearch);
if (!plugin) return false; if (!plugin) return false;
plugin.replaceAll(); plugin.replaceAll();
return true; return true;
} };

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,6 +1,6 @@
export function simulateBackspace(input: HTMLInputElement, direction: "backward" | "forward" = "backward") { export function simulateBackspace(input: HTMLInputElement, direction: "backward" | "forward" = "backward") {
let start = input.selectionStart ?? 0; let start = input.selectionStart ?? 0;
let end = input.selectionEnd ?? 0; const end = input.selectionEnd ?? 0;
// Do nothing if at boundaries // Do nothing if at boundaries
if (direction === "backward" && start === 0 && end === 0) return; 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 { import {
hideSearchVisibilityCommand, hideSearchVisibilityCommand,
searchReplaceAll, searchReplaceAll,
@@ -7,7 +7,7 @@ import {
searchToggleRegex, searchToggleRegex,
searchToggleWholeWord, searchToggleWholeWord,
showSearchVisibilityCommand showSearchVisibilityCommand
} from '../extensions/vscodeSearch/commands' } from '../extensions/vscodeSearch/commands';
import { import {
addNewBlockAfterCurrent, addNewBlockAfterCurrent,
addNewBlockAfterLast, addNewBlockAfterLast,
@@ -20,13 +20,13 @@ import {
moveCurrentBlockUp, moveCurrentBlockUp,
selectNextBlock, selectNextBlock,
selectPreviousBlock selectPreviousBlock
} from '../extensions/codeblock/commands' } from '../extensions/codeblock/commands';
import {selectAll} from '../extensions/codeblock/selectAll' import {selectAll} from '../extensions/codeblock/selectAll';
import {deleteLineCommand} from '../extensions/codeblock/deleteLine' import {deleteLineCommand} from '../extensions/codeblock/deleteLine';
import {moveLineDown, moveLineUp} from '../extensions/codeblock/moveLines' import {moveLineDown, moveLineUp} from '../extensions/codeblock/moveLines';
import {transposeChars} from '../extensions/codeblock' import {transposeChars} from '../extensions/codeblock';
import {copyCommand, cutCommand, pasteCommand} from '../extensions/codeblock/copyPaste' import {copyCommand, cutCommand, pasteCommand} from '../extensions/codeblock/copyPaste';
import {textHighlightToggleCommand} from '../extensions/textHighlight/textHighlightExtension' import {textHighlightToggleCommand} from '../extensions/textHighlight/textHighlightExtension';
import { import {
copyLineDown, copyLineDown,
copyLineUp, copyLineUp,
@@ -52,15 +52,15 @@ import {
toggleComment, toggleComment,
undo, undo,
undoSelection undoSelection
} from '@codemirror/commands' } from '@codemirror/commands';
import {foldAll, foldCode, unfoldAll, unfoldCode} from '@codemirror/language' import {foldAll, foldCode, unfoldAll, unfoldCode} from '@codemirror/language';
import i18n from '@/i18n' import i18n from '@/i18n';
// 默认编辑器选项 // 默认编辑器选项
const defaultEditorOptions = { const defaultEditorOptions = {
defaultBlockToken: 'text', defaultBlockToken: 'text',
defaultBlockAutoDetect: true, defaultBlockAutoDetect: true,
} };
/** /**
* 前端命令注册表 * 前端命令注册表
@@ -291,7 +291,7 @@ export const commandRegistry = {
handler: textHighlightToggleCommand, handler: textHighlightToggleCommand,
descriptionKey: 'keybindings.commands.textHighlightToggle' descriptionKey: 'keybindings.commands.textHighlightToggle'
}, },
} as const } as const;
/** /**
* 获取命令处理函数 * 获取命令处理函数
@@ -299,8 +299,8 @@ export const commandRegistry = {
* @returns 对应的处理函数,如果不存在则返回 undefined * @returns 对应的处理函数,如果不存在则返回 undefined
*/ */
export const getCommandHandler = (command: KeyBindingCommand) => { export const getCommandHandler = (command: KeyBindingCommand) => {
return commandRegistry[command]?.handler return commandRegistry[command]?.handler;
} };
/** /**
* 获取命令描述 * 获取命令描述
@@ -308,9 +308,9 @@ export const getCommandHandler = (command: KeyBindingCommand) => {
* @returns 对应的描述,如果不存在则返回 undefined * @returns 对应的描述,如果不存在则返回 undefined
*/ */
export const getCommandDescription = (command: KeyBindingCommand) => { export const getCommandDescription = (command: KeyBindingCommand) => {
const descriptionKey = commandRegistry[command]?.descriptionKey const descriptionKey = commandRegistry[command]?.descriptionKey;
return descriptionKey ? i18n.global.t(descriptionKey) : undefined return descriptionKey ? i18n.global.t(descriptionKey) : undefined;
} };
/** /**
* 检查命令是否已注册 * 检查命令是否已注册
@@ -318,13 +318,13 @@ export const getCommandDescription = (command: KeyBindingCommand) => {
* @returns 是否已注册 * @returns 是否已注册
*/ */
export const isCommandRegistered = (command: KeyBindingCommand): boolean => { export const isCommandRegistered = (command: KeyBindingCommand): boolean => {
return command in commandRegistry return command in commandRegistry;
} };
/** /**
* 获取所有已注册的命令 * 获取所有已注册的命令
* @returns 已注册的命令列表 * @returns 已注册的命令列表
*/ */
export const getRegisteredCommands = (): KeyBindingCommand[] => { 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 { Extension } from '@codemirror/state';
import { useKeybindingStore } from '@/stores/keybindingStore' import { useKeybindingStore } from '@/stores/keybindingStore';
import { useExtensionStore } from '@/stores/extensionStore' import { useExtensionStore } from '@/stores/extensionStore';
import { KeymapManager } from './keymapManager' import { KeymapManager } from './keymapManager';
import { ExtensionID } from '@/../bindings/voidraft/internal/models/models' import { ExtensionID } from '@/../bindings/voidraft/internal/models/models';
/** /**
* 异步创建快捷键扩展 * 异步创建快捷键扩展
* 确保快捷键配置和扩展配置已加载 * 确保快捷键配置和扩展配置已加载
*/ */
export const createDynamicKeymapExtension = async (): Promise<Extension> => { export const createDynamicKeymapExtension = async (): Promise<Extension> => {
const keybindingStore = useKeybindingStore() const keybindingStore = useKeybindingStore();
const extensionStore = useExtensionStore() const extensionStore = useExtensionStore();
// 确保快捷键配置已加载 // 确保快捷键配置已加载
if (keybindingStore.keyBindings.length === 0) { if (keybindingStore.keyBindings.length === 0) {
await keybindingStore.loadKeyBindings() await keybindingStore.loadKeyBindings();
} }
// 确保扩展配置已加载 // 确保扩展配置已加载
if (extensionStore.extensions.length === 0) { if (extensionStore.extensions.length === 0) {
await extensionStore.loadExtensions() await extensionStore.loadExtensions();
} }
// 获取启用的扩展ID列表 // 获取启用的扩展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 编辑器视图 * @param view 编辑器视图
*/ */
export const updateKeymapExtension = (view: any): void => { export const updateKeymapExtension = (view: any): void => {
const keybindingStore = useKeybindingStore() const keybindingStore = useKeybindingStore();
const extensionStore = useExtensionStore() const extensionStore = useExtensionStore();
// 获取启用的扩展ID列表 // 获取启用的扩展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 该扩展的快捷键列表 * @returns 该扩展的快捷键列表
*/ */
export const getExtensionKeyBindings = (extensionId: ExtensionID) => { export const getExtensionKeyBindings = (extensionId: ExtensionID) => {
const keybindingStore = useKeybindingStore() const keybindingStore = useKeybindingStore();
return keybindingStore.getKeyBindingsByExtension(extensionId) return keybindingStore.getKeyBindingsByExtension(extensionId);
} };
// 导出相关模块 // 导出相关模块
export { KeymapManager } from './keymapManager' export { KeymapManager } from './keymapManager';
export { commandRegistry, getCommandHandler, getCommandDescription, isCommandRegistered, getRegisteredCommands } from './commandRegistry' export { commandRegistry, getCommandHandler, getCommandDescription, isCommandRegistered, getRegisteredCommands } from './commandRegistry';
export type { KeyBinding, CommandHandler, CommandDefinition, KeymapResult } from './types' export type { KeyBinding, CommandHandler, CommandDefinition, KeymapResult } from './types';

View File

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

View File

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

View File

@@ -1,34 +1,34 @@
import {ExtensionFactory, ExtensionManager} from './ExtensionManager' import {ExtensionFactory, ExtensionManager} from './ExtensionManager';
import {ExtensionID} from '@/../bindings/voidraft/internal/models/models' import {ExtensionID} from '@/../bindings/voidraft/internal/models/models';
import i18n from '@/i18n' import i18n from '@/i18n';
// 导入现有扩展的创建函数 // 导入现有扩展的创建函数
import rainbowBracketsExtension from '../extensions/rainbowBracket/rainbowBracketsExtension' import rainbowBracketsExtension from '../extensions/rainbowBracket/rainbowBracketsExtension';
import {createTextHighlighter} from '../extensions/textHighlight/textHighlightExtension' import {createTextHighlighter} from '../extensions/textHighlight/textHighlightExtension';
import {color} from '../extensions/colorSelector' import {color} from '../extensions/colorSelector';
import {hyperLink} from '../extensions/hyperlink' import {hyperLink} from '../extensions/hyperlink';
import {minimap} from '../extensions/minimap' import {minimap} from '../extensions/minimap';
import {vscodeSearch} from '../extensions/vscodeSearch' import {vscodeSearch} from '../extensions/vscodeSearch';
import {createCheckboxExtension} from '../extensions/checkbox' import {createCheckboxExtension} from '../extensions/checkbox';
import {createTranslatorExtension} from '../extensions/translator' import {createTranslatorExtension} from '../extensions/translator';
import {foldingOnIndent} from '../extensions/fold/foldExtension' import {foldingOnIndent} from '../extensions/fold/foldExtension';
/** /**
* 彩虹括号扩展工厂 * 彩虹括号扩展工厂
*/ */
export const rainbowBracketsFactory: ExtensionFactory = { export const rainbowBracketsFactory: ExtensionFactory = {
create(config: any) { create(_config: any) {
return rainbowBracketsExtension() return rainbowBracketsExtension();
}, },
getDefaultConfig() { getDefaultConfig() {
return {} return {};
}, },
validateConfig(config: any) { validateConfig(config: any) {
return typeof config === 'object' return typeof config === 'object';
}
} }
};
/** /**
* 文本高亮扩展工厂 * 文本高亮扩展工厂
@@ -38,20 +38,20 @@ export const textHighlightFactory: ExtensionFactory = {
return createTextHighlighter({ return createTextHighlighter({
backgroundColor: config.backgroundColor || '#FFD700', backgroundColor: config.backgroundColor || '#FFD700',
opacity: config.opacity || 0.3 opacity: config.opacity || 0.3
}) });
}, },
getDefaultConfig() { getDefaultConfig() {
return { return {
backgroundColor: '#FFD700', // 金黄色 backgroundColor: '#FFD700', // 金黄色
opacity: 0.3 // 透明度 opacity: 0.3 // 透明度
} };
}, },
validateConfig(config: any) { validateConfig(config: any) {
return typeof config === 'object' && return typeof config === 'object' &&
(!config.backgroundColor || typeof config.backgroundColor === 'string') && (!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', displayText: config.displayText || 'characters',
showOverlay: config.showOverlay || 'always', showOverlay: config.showOverlay || 'always',
autohide: config.autohide || false autohide: config.autohide || false
} };
return minimap(options) return minimap(options);
}, },
getDefaultConfig() { getDefaultConfig() {
return { return {
displayText: 'characters', displayText: 'characters',
showOverlay: 'always', showOverlay: 'always',
autohide: false autohide: false
} };
}, },
validateConfig(config: any) { validateConfig(config: any) {
return typeof config === 'object' && return typeof config === 'object' &&
(!config.displayText || typeof config.displayText === 'string') && (!config.displayText || typeof config.displayText === 'string') &&
(!config.showOverlay || typeof config.showOverlay === 'string') && (!config.showOverlay || typeof config.showOverlay === 'string') &&
(!config.autohide || typeof config.autohide === 'boolean') (!config.autohide || typeof config.autohide === 'boolean');
}
} }
};
/** /**
* 超链接扩展工厂 * 超链接扩展工厂
*/ */
export const hyperlinkFactory: ExtensionFactory = { export const hyperlinkFactory: ExtensionFactory = {
create(config: any) { create(_config: any) {
return hyperLink return hyperLink;
}, },
getDefaultConfig() { getDefaultConfig() {
return {} return {};
}, },
validateConfig(config: any) { validateConfig(config: any) {
return typeof config === 'object' return typeof config === 'object';
}
} }
};
/** /**
* 颜色选择器扩展工厂 * 颜色选择器扩展工厂
*/ */
export const colorSelectorFactory: ExtensionFactory = { export const colorSelectorFactory: ExtensionFactory = {
create(config: any) { create(_config: any) {
return color return color;
}, },
getDefaultConfig() { getDefaultConfig() {
return {} return {};
}, },
validateConfig(config: any) { validateConfig(config: any) {
return typeof config === 'object' return typeof config === 'object';
}
} }
};
/** /**
* 搜索扩展工厂 * 搜索扩展工厂
*/ */
export const searchFactory: ExtensionFactory = { export const searchFactory: ExtensionFactory = {
create(config: any) { create(_config: any) {
return vscodeSearch return vscodeSearch;
}, },
getDefaultConfig() { getDefaultConfig() {
return {} return {};
}, },
validateConfig(config: any) { validateConfig(config: any) {
return typeof config === 'object' return typeof config === 'object';
}
} }
};
export const foldFactory: ExtensionFactory = { export const foldFactory: ExtensionFactory = {
create(config: any) { create(_config: any) {
return foldingOnIndent; return foldingOnIndent;
}, },
getDefaultConfig(): any { getDefaultConfig(): any {
return {} return {};
}, },
validateConfig(config: any): boolean { validateConfig(config: any): boolean {
return typeof config === 'object' return typeof config === 'object';
}
} }
};
/** /**
* 选择框扩展工厂 * 选择框扩展工厂
*/ */
export const checkboxFactory: ExtensionFactory = { export const checkboxFactory: ExtensionFactory = {
create(config: any) { create(_config: any) {
return createCheckboxExtension() return createCheckboxExtension();
}, },
getDefaultConfig() { getDefaultConfig() {
return {} return {};
}, },
validateConfig(config: any) { validateConfig(config: any) {
return typeof config === 'object' return typeof config === 'object';
}
} }
};
/** /**
* 翻译扩展工厂 * 翻译扩展工厂
@@ -161,19 +161,19 @@ export const translatorFactory: ExtensionFactory = {
defaultTranslator: config.defaultTranslator || 'bing', defaultTranslator: config.defaultTranslator || 'bing',
minSelectionLength: config.minSelectionLength || 2, minSelectionLength: config.minSelectionLength || 2,
maxTranslationLength: config.maxTranslationLength || 5000, maxTranslationLength: config.maxTranslationLength || 5000,
}) });
}, },
getDefaultConfig() { getDefaultConfig() {
return { return {
defaultTranslator: 'bing', defaultTranslator: 'bing',
minSelectionLength: 2, minSelectionLength: 2,
maxTranslationLength: 5000, maxTranslationLength: 5000,
} };
}, },
validateConfig(config: any) { validateConfig(config: any) {
return typeof config === 'object' return typeof config === 'object';
}
} }
};
/** /**
* 所有扩展的统一配置 * 所有扩展的统一配置
@@ -232,7 +232,7 @@ const EXTENSION_CONFIGS = {
displayNameKey: 'extensions.checkbox.name', displayNameKey: 'extensions.checkbox.name',
descriptionKey: 'extensions.checkbox.description' descriptionKey: 'extensions.checkbox.description'
} }
} };
/** /**
* 注册所有扩展工厂到管理器 * 注册所有扩展工厂到管理器
@@ -240,8 +240,8 @@ const EXTENSION_CONFIGS = {
*/ */
export function registerAllExtensions(manager: ExtensionManager): void { export function registerAllExtensions(manager: ExtensionManager): void {
Object.entries(EXTENSION_CONFIGS).forEach(([id, config]) => { 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 显示名称 * @returns 显示名称
*/ */
export function getExtensionDisplayName(id: ExtensionID): string { export function getExtensionDisplayName(id: ExtensionID): string {
const config = EXTENSION_CONFIGS[id as ExtensionID] const config = EXTENSION_CONFIGS[id as ExtensionID];
return config?.displayNameKey ? i18n.global.t(config.displayNameKey) : id return config?.displayNameKey ? i18n.global.t(config.displayNameKey) : id;
} }
/** /**
@@ -260,8 +260,8 @@ export function getExtensionDisplayName(id: ExtensionID): string {
* @returns 描述 * @returns 描述
*/ */
export function getExtensionDescription(id: ExtensionID): string { export function getExtensionDescription(id: ExtensionID): string {
const config = EXTENSION_CONFIGS[id as ExtensionID] const config = EXTENSION_CONFIGS[id as ExtensionID];
return config?.descriptionKey ? i18n.global.t(config.descriptionKey) : '' return config?.descriptionKey ? i18n.global.t(config.descriptionKey) : '';
} }
/** /**
@@ -270,7 +270,7 @@ export function getExtensionDescription(id: ExtensionID): string {
* @returns 扩展工厂实例 * @returns 扩展工厂实例
*/ */
export function getExtensionFactory(id: ExtensionID): ExtensionFactory | undefined { 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 默认配置对象 * @returns 默认配置对象
*/ */
export function getExtensionDefaultConfig(id: ExtensionID): any { export function getExtensionDefaultConfig(id: ExtensionID): any {
const factory = getExtensionFactory(id) const factory = getExtensionFactory(id);
return factory?.getDefaultConfig() || {} return factory?.getDefaultConfig() || {};
} }
/** /**
@@ -289,8 +289,8 @@ export function getExtensionDefaultConfig(id: ExtensionID): any {
* @returns 是否有配置项 * @returns 是否有配置项
*/ */
export function hasExtensionConfig(id: ExtensionID): boolean { export function hasExtensionConfig(id: ExtensionID): boolean {
const defaultConfig = getExtensionDefaultConfig(id) const defaultConfig = getExtensionDefaultConfig(id);
return Object.keys(defaultConfig).length > 0 return Object.keys(defaultConfig).length > 0;
} }
/** /**
@@ -298,5 +298,5 @@ export function hasExtensionConfig(id: ExtensionID): boolean {
* @returns 扩展ID数组 * @returns 扩展ID数组
*/ */
export function getAllExtensionIds(): ExtensionID[] { 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 {Extension} from '@codemirror/state';
import {EditorView} from '@codemirror/view' import {EditorView} from '@codemirror/view';
import {useExtensionStore} from '@/stores/extensionStore' import {useExtensionStore} from '@/stores/extensionStore';
import {ExtensionManager} from './ExtensionManager' import {ExtensionManager} from './ExtensionManager';
import {registerAllExtensions} from './factories' 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[]> => { export const createDynamicExtensions = async (_documentId?: number): Promise<Extension[]> => {
const extensionStore = useExtensionStore() const extensionStore = useExtensionStore();
// 注册所有扩展工厂 // 注册所有扩展工厂
registerAllExtensions(extensionManager) registerAllExtensions(extensionManager);
// 确保扩展配置已加载 // 确保扩展配置已加载
if (extensionStore.extensions.length === 0) { 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 扩展管理器 * @returns 扩展管理器
*/ */
export const getExtensionManager = (): ExtensionManager => { export const getExtensionManager = (): ExtensionManager => {
return extensionManager return extensionManager;
} };
/** /**
* 设置编辑器视图到扩展管理器 * 设置编辑器视图到扩展管理器
@@ -46,18 +46,18 @@ export const getExtensionManager = (): ExtensionManager => {
* @param documentId 文档ID * @param documentId 文档ID
*/ */
export const setExtensionManagerView = (view: EditorView, documentId: number): void => { export const setExtensionManagerView = (view: EditorView, documentId: number): void => {
extensionManager.setView(view, documentId) extensionManager.setView(view, documentId);
} };
/** /**
* 从扩展管理器移除编辑器视图 * 从扩展管理器移除编辑器视图
* @param documentId 文档ID * @param documentId 文档ID
*/ */
export const removeExtensionManagerView = (documentId: number): void => { export const removeExtensionManagerView = (documentId: number): void => {
extensionManager.removeView(documentId) extensionManager.removeView(documentId);
} };
// 导出相关模块 // 导出相关模块
export {ExtensionManager} from './ExtensionManager' export {ExtensionManager} from './ExtensionManager';
export {registerAllExtensions, getExtensionDisplayName, getExtensionDescription} from './factories' export {registerAllExtensions, getExtensionDisplayName, getExtensionDescription} from './factories';
export type {ExtensionFactory} from './ExtensionManager' export type {ExtensionFactory} from './ExtensionManager';

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -2,7 +2,7 @@ import {defineConfig, loadEnv} from 'vite';
import vue from '@vitejs/plugin-vue'; import vue from '@vitejs/plugin-vue';
import Components from 'unplugin-vue-components/vite'; import Components from 'unplugin-vue-components/vite';
import * as path from 'path'; import * as path from 'path';
import {nodePolyfills} from 'vite-plugin-node-polyfills' import {nodePolyfills} from 'vite-plugin-node-polyfills';
export default defineConfig(({mode}: { mode: string }): object => { export default defineConfig(({mode}: { mode: string }): object => {
const env: Record<string, string> = loadEnv(mode, process.cwd()); const env: Record<string, string> = loadEnv(mode, process.cwd());
@@ -58,5 +58,5 @@ export default defineConfig(({mode}: { mode: string }): object => {
}, },
} }
} }
} };
}); });