🚨 Format code
This commit is contained in:
@@ -50,7 +50,11 @@ export default defineConfig([
|
|||||||
'.local',
|
'.local',
|
||||||
'/bin',
|
'/bin',
|
||||||
'Dockerfile',
|
'Dockerfile',
|
||||||
'**/bindings/'
|
'**/bindings/',
|
||||||
|
'*.js',
|
||||||
|
'**/*.js',
|
||||||
|
'**/*.cjs',
|
||||||
|
'**/*.mjs',
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
@@ -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;
|
||||||
@@ -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 };
|
||||||
@@ -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();
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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';
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -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');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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';
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
};
|
||||||
})
|
});
|
||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|
||||||
|
|||||||
@@ -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,
|
||||||
}
|
};
|
||||||
})
|
});
|
||||||
@@ -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,
|
||||||
}
|
};
|
||||||
})
|
});
|
||||||
@@ -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;
|
||||||
|
|||||||
@@ -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';
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
};
|
||||||
})
|
});
|
||||||
@@ -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元素
|
||||||
|
|||||||
@@ -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];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 创建编辑菜单项
|
* 创建编辑菜单项
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
};
|
||||||
@@ -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);
|
||||||
}
|
};
|
||||||
@@ -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");
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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,表示命令已开始执行
|
||||||
}
|
};
|
||||||
@@ -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]),
|
||||||
|
|||||||
@@ -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";
|
||||||
|
|||||||
@@ -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 为空,不返回解析器
|
||||||
|
|||||||
@@ -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") {
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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: {
|
||||||
|
|||||||
@@ -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() : ''})`
|
||||||
|
|||||||
@@ -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};
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,4 +24,4 @@ function drawLineGutter(gutter: Record<Line, Color>, ctx: DrawContext, lineNumbe
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export { GUTTER_WIDTH, drawLineGutter }
|
export { GUTTER_WIDTH, drawLineGutter };
|
||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
};
|
||||||
@@ -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";
|
||||||
@@ -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
|
||||||
]
|
];
|
||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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[];
|
||||||
}
|
};
|
||||||
@@ -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';
|
||||||
@@ -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};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import {Command} from '@codemirror/view'
|
import {Command} from '@codemirror/view';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* CodeMirror快捷键绑定格式
|
* CodeMirror快捷键绑定格式
|
||||||
|
|||||||
@@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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[];
|
||||||
}
|
}
|
||||||
@@ -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';
|
||||||
@@ -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);
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ onMounted(async () => {
|
|||||||
});
|
});
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
backupStore.clearError();
|
backupStore.clearError();
|
||||||
})
|
});
|
||||||
|
|
||||||
// 认证方式选项
|
// 认证方式选项
|
||||||
const authMethodOptions = computed(() => [
|
const authMethodOptions = computed(() => [
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|
||||||
// 使用常量简化错误处理
|
// 使用常量简化错误处理
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|||||||
@@ -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';
|
||||||
|
|||||||
@@ -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 => {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user