Compare commits
5 Commits
Author | SHA1 | Date | |
---|---|---|---|
a720a4cfb8 | |||
b5510d605c | |||
4d62da912a | |||
b52e067d50 | |||
8dce06c30e |
10
README.md
10
README.md
@@ -14,6 +14,8 @@ VoidRaft is a modern developer-focused text editor that allows you to record, or
|
||||
- Smart language detection - Automatically recognizes code block language types
|
||||
- Code formatting - Built-in Prettier support for one-click code beautification
|
||||
- Block editing mode - Split content into independent code blocks, each with different language settings
|
||||
- Multi-window support - edit multiple documents at the same time
|
||||
- Support for custom themes - Custom editor themes
|
||||
|
||||
### Modern Interface
|
||||
|
||||
@@ -53,6 +55,7 @@ cd voidraft
|
||||
# Install frontend dependencies
|
||||
cd frontend
|
||||
npm install
|
||||
npm run build
|
||||
cd ..
|
||||
|
||||
# Start development server
|
||||
@@ -116,12 +119,9 @@ Voidraft/
|
||||
| Linux | Planned | Future versions |
|
||||
|
||||
### Planned Features
|
||||
- [ ] Custom themes - Customize editor themes
|
||||
- [ ] Multi-window support - Support editing multiple documents simultaneously
|
||||
- ✅ Custom themes - Customize editor themes
|
||||
- ✅ Multi-window support - Support editing multiple documents simultaneously
|
||||
- [ ] Enhanced clipboard - Monitor and manage clipboard history
|
||||
- Automatic text content saving
|
||||
- Image content support
|
||||
- History management
|
||||
- [ ] Data synchronization - Cloud backup for configurations and documents
|
||||
- [ ] Extension system - Support for custom plugins
|
||||
|
||||
|
10
README_ZH.md
10
README_ZH.md
@@ -14,6 +14,8 @@ Voidraft 是一个现代化的开发者专用文本编辑器,让你能够随
|
||||
- 智能语言检测 - 自动识别代码块语言类型
|
||||
- 代码格式化 - 内置 Prettier 支持,一键美化代码
|
||||
- 块状编辑模式 - 将内容分割为独立的代码块,每个块可设置不同语言
|
||||
- 支持多窗口 - 同时编辑多个文档
|
||||
- 支持自定义主题 - 自定义编辑器主题
|
||||
|
||||
### 现代化界面
|
||||
|
||||
@@ -54,6 +56,7 @@ cd voidraft
|
||||
# 安装前端依赖
|
||||
cd frontend
|
||||
npm install
|
||||
npm run build
|
||||
cd ..
|
||||
|
||||
# 启动开发服务器
|
||||
@@ -117,12 +120,9 @@ Voidraft/
|
||||
| Linux | 计划中 | 后续版本 |
|
||||
|
||||
### 计划添加的功能
|
||||
- [ ] 自定义主题 - 自定义编辑器主题
|
||||
- [ ] 多窗口支持 - 支持同时编辑多个文档
|
||||
- ✅ 自定义主题 - 自定义编辑器主题
|
||||
- ✅ 多窗口支持 - 支持同时编辑多个文档
|
||||
- [ ] 剪切板增强 - 监听和管理剪切板历史
|
||||
- 文本内容自动保存
|
||||
- 图片内容支持
|
||||
- 历史记录管理
|
||||
- [ ] 数据同步 - 配置和文档云端备份
|
||||
- [ ] 扩展系统 - 支持自定义插件
|
||||
|
||||
|
@@ -1,37 +0,0 @@
|
||||
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||
// This file is automatically generated. DO NOT EDIT
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore: Unused imports
|
||||
import {Create as $Create} from "@wailsio/runtime";
|
||||
|
||||
/**
|
||||
* DB is a database handle representing a pool of zero or more
|
||||
* underlying connections. It's safe for concurrent use by multiple
|
||||
* goroutines.
|
||||
*
|
||||
* The sql package creates and frees connections automatically; it
|
||||
* also maintains a free pool of idle connections. If the database has
|
||||
* a concept of per-connection state, such state can be reliably observed
|
||||
* within a transaction ([Tx]) or connection ([Conn]). Once [DB.Begin] is called, the
|
||||
* returned [Tx] is bound to a single connection. Once [Tx.Commit] or
|
||||
* [Tx.Rollback] is called on the transaction, that transaction's
|
||||
* connection is returned to [DB]'s idle connection pool. The pool size
|
||||
* can be controlled with [DB.SetMaxIdleConns].
|
||||
*/
|
||||
export class DB {
|
||||
|
||||
/** Creates a new DB instance. */
|
||||
constructor($$source: Partial<DB> = {}) {
|
||||
|
||||
Object.assign(this, $$source);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new DB instance from a string or object.
|
||||
*/
|
||||
static createFrom($$source: any = {}): DB {
|
||||
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
|
||||
return new DB($$parsedSource as Partial<DB>);
|
||||
}
|
||||
}
|
@@ -11,15 +11,55 @@ import * as slog$0 from "../../../../../../log/slog/models.js";
|
||||
|
||||
export class App {
|
||||
/**
|
||||
* The main application menu
|
||||
* Manager pattern for organized API
|
||||
*/
|
||||
"ApplicationMenu": Menu | null;
|
||||
"Window": WindowManager | null;
|
||||
"ContextMenu": ContextMenuManager | null;
|
||||
"KeyBinding": KeyBindingManager | null;
|
||||
"Browser": BrowserManager | null;
|
||||
"Env": EnvironmentManager | null;
|
||||
"Dialog": DialogManager | null;
|
||||
"Event": EventManager | null;
|
||||
"Menu": MenuManager | null;
|
||||
"Screen": ScreenManager | null;
|
||||
"Clipboard": ClipboardManager | null;
|
||||
"SystemTray": SystemTrayManager | null;
|
||||
"Logger": slog$0.Logger | null;
|
||||
|
||||
/** Creates a new App instance. */
|
||||
constructor($$source: Partial<App> = {}) {
|
||||
if (!("ApplicationMenu" in $$source)) {
|
||||
this["ApplicationMenu"] = null;
|
||||
if (!("Window" in $$source)) {
|
||||
this["Window"] = null;
|
||||
}
|
||||
if (!("ContextMenu" in $$source)) {
|
||||
this["ContextMenu"] = null;
|
||||
}
|
||||
if (!("KeyBinding" in $$source)) {
|
||||
this["KeyBinding"] = null;
|
||||
}
|
||||
if (!("Browser" in $$source)) {
|
||||
this["Browser"] = null;
|
||||
}
|
||||
if (!("Env" in $$source)) {
|
||||
this["Env"] = null;
|
||||
}
|
||||
if (!("Dialog" in $$source)) {
|
||||
this["Dialog"] = null;
|
||||
}
|
||||
if (!("Event" in $$source)) {
|
||||
this["Event"] = null;
|
||||
}
|
||||
if (!("Menu" in $$source)) {
|
||||
this["Menu"] = null;
|
||||
}
|
||||
if (!("Screen" in $$source)) {
|
||||
this["Screen"] = null;
|
||||
}
|
||||
if (!("Clipboard" in $$source)) {
|
||||
this["Clipboard"] = null;
|
||||
}
|
||||
if (!("SystemTray" in $$source)) {
|
||||
this["SystemTray"] = null;
|
||||
}
|
||||
if (!("Logger" in $$source)) {
|
||||
this["Logger"] = null;
|
||||
@@ -34,31 +74,308 @@ export class App {
|
||||
static createFrom($$source: any = {}): App {
|
||||
const $$createField0_0 = $$createType1;
|
||||
const $$createField1_0 = $$createType3;
|
||||
const $$createField2_0 = $$createType5;
|
||||
const $$createField3_0 = $$createType7;
|
||||
const $$createField4_0 = $$createType9;
|
||||
const $$createField5_0 = $$createType11;
|
||||
const $$createField6_0 = $$createType13;
|
||||
const $$createField7_0 = $$createType15;
|
||||
const $$createField8_0 = $$createType17;
|
||||
const $$createField9_0 = $$createType19;
|
||||
const $$createField10_0 = $$createType21;
|
||||
const $$createField11_0 = $$createType23;
|
||||
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
|
||||
if ("ApplicationMenu" in $$parsedSource) {
|
||||
$$parsedSource["ApplicationMenu"] = $$createField0_0($$parsedSource["ApplicationMenu"]);
|
||||
if ("Window" in $$parsedSource) {
|
||||
$$parsedSource["Window"] = $$createField0_0($$parsedSource["Window"]);
|
||||
}
|
||||
if ("ContextMenu" in $$parsedSource) {
|
||||
$$parsedSource["ContextMenu"] = $$createField1_0($$parsedSource["ContextMenu"]);
|
||||
}
|
||||
if ("KeyBinding" in $$parsedSource) {
|
||||
$$parsedSource["KeyBinding"] = $$createField2_0($$parsedSource["KeyBinding"]);
|
||||
}
|
||||
if ("Browser" in $$parsedSource) {
|
||||
$$parsedSource["Browser"] = $$createField3_0($$parsedSource["Browser"]);
|
||||
}
|
||||
if ("Env" in $$parsedSource) {
|
||||
$$parsedSource["Env"] = $$createField4_0($$parsedSource["Env"]);
|
||||
}
|
||||
if ("Dialog" in $$parsedSource) {
|
||||
$$parsedSource["Dialog"] = $$createField5_0($$parsedSource["Dialog"]);
|
||||
}
|
||||
if ("Event" in $$parsedSource) {
|
||||
$$parsedSource["Event"] = $$createField6_0($$parsedSource["Event"]);
|
||||
}
|
||||
if ("Menu" in $$parsedSource) {
|
||||
$$parsedSource["Menu"] = $$createField7_0($$parsedSource["Menu"]);
|
||||
}
|
||||
if ("Screen" in $$parsedSource) {
|
||||
$$parsedSource["Screen"] = $$createField8_0($$parsedSource["Screen"]);
|
||||
}
|
||||
if ("Clipboard" in $$parsedSource) {
|
||||
$$parsedSource["Clipboard"] = $$createField9_0($$parsedSource["Clipboard"]);
|
||||
}
|
||||
if ("SystemTray" in $$parsedSource) {
|
||||
$$parsedSource["SystemTray"] = $$createField10_0($$parsedSource["SystemTray"]);
|
||||
}
|
||||
if ("Logger" in $$parsedSource) {
|
||||
$$parsedSource["Logger"] = $$createField1_0($$parsedSource["Logger"]);
|
||||
$$parsedSource["Logger"] = $$createField11_0($$parsedSource["Logger"]);
|
||||
}
|
||||
return new App($$parsedSource as Partial<App>);
|
||||
}
|
||||
}
|
||||
|
||||
export class Menu {
|
||||
/**
|
||||
* BrowserManager manages browser-related operations
|
||||
*/
|
||||
export class BrowserManager {
|
||||
|
||||
/** Creates a new Menu instance. */
|
||||
constructor($$source: Partial<Menu> = {}) {
|
||||
/** Creates a new BrowserManager instance. */
|
||||
constructor($$source: Partial<BrowserManager> = {}) {
|
||||
|
||||
Object.assign(this, $$source);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new Menu instance from a string or object.
|
||||
* Creates a new BrowserManager instance from a string or object.
|
||||
*/
|
||||
static createFrom($$source: any = {}): Menu {
|
||||
static createFrom($$source: any = {}): BrowserManager {
|
||||
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
|
||||
return new Menu($$parsedSource as Partial<Menu>);
|
||||
return new BrowserManager($$parsedSource as Partial<BrowserManager>);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ClipboardManager manages clipboard operations
|
||||
*/
|
||||
export class ClipboardManager {
|
||||
|
||||
/** Creates a new ClipboardManager instance. */
|
||||
constructor($$source: Partial<ClipboardManager> = {}) {
|
||||
|
||||
Object.assign(this, $$source);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new ClipboardManager instance from a string or object.
|
||||
*/
|
||||
static createFrom($$source: any = {}): ClipboardManager {
|
||||
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
|
||||
return new ClipboardManager($$parsedSource as Partial<ClipboardManager>);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ContextMenuManager manages all context menu operations
|
||||
*/
|
||||
export class ContextMenuManager {
|
||||
|
||||
/** Creates a new ContextMenuManager instance. */
|
||||
constructor($$source: Partial<ContextMenuManager> = {}) {
|
||||
|
||||
Object.assign(this, $$source);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new ContextMenuManager instance from a string or object.
|
||||
*/
|
||||
static createFrom($$source: any = {}): ContextMenuManager {
|
||||
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
|
||||
return new ContextMenuManager($$parsedSource as Partial<ContextMenuManager>);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* DialogManager manages dialog-related operations
|
||||
*/
|
||||
export class DialogManager {
|
||||
|
||||
/** Creates a new DialogManager instance. */
|
||||
constructor($$source: Partial<DialogManager> = {}) {
|
||||
|
||||
Object.assign(this, $$source);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new DialogManager instance from a string or object.
|
||||
*/
|
||||
static createFrom($$source: any = {}): DialogManager {
|
||||
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
|
||||
return new DialogManager($$parsedSource as Partial<DialogManager>);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* EnvironmentManager manages environment-related operations
|
||||
*/
|
||||
export class EnvironmentManager {
|
||||
|
||||
/** Creates a new EnvironmentManager instance. */
|
||||
constructor($$source: Partial<EnvironmentManager> = {}) {
|
||||
|
||||
Object.assign(this, $$source);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new EnvironmentManager instance from a string or object.
|
||||
*/
|
||||
static createFrom($$source: any = {}): EnvironmentManager {
|
||||
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
|
||||
return new EnvironmentManager($$parsedSource as Partial<EnvironmentManager>);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* EventManager manages event-related operations
|
||||
*/
|
||||
export class EventManager {
|
||||
|
||||
/** Creates a new EventManager instance. */
|
||||
constructor($$source: Partial<EventManager> = {}) {
|
||||
|
||||
Object.assign(this, $$source);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new EventManager instance from a string or object.
|
||||
*/
|
||||
static createFrom($$source: any = {}): EventManager {
|
||||
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
|
||||
return new EventManager($$parsedSource as Partial<EventManager>);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* KeyBindingManager manages all key binding operations
|
||||
*/
|
||||
export class KeyBindingManager {
|
||||
|
||||
/** Creates a new KeyBindingManager instance. */
|
||||
constructor($$source: Partial<KeyBindingManager> = {}) {
|
||||
|
||||
Object.assign(this, $$source);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new KeyBindingManager instance from a string or object.
|
||||
*/
|
||||
static createFrom($$source: any = {}): KeyBindingManager {
|
||||
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
|
||||
return new KeyBindingManager($$parsedSource as Partial<KeyBindingManager>);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* MenuManager manages menu-related operations
|
||||
*/
|
||||
export class MenuManager {
|
||||
|
||||
/** Creates a new MenuManager instance. */
|
||||
constructor($$source: Partial<MenuManager> = {}) {
|
||||
|
||||
Object.assign(this, $$source);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new MenuManager instance from a string or object.
|
||||
*/
|
||||
static createFrom($$source: any = {}): MenuManager {
|
||||
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
|
||||
return new MenuManager($$parsedSource as Partial<MenuManager>);
|
||||
}
|
||||
}
|
||||
|
||||
export class ScreenManager {
|
||||
|
||||
/** Creates a new ScreenManager instance. */
|
||||
constructor($$source: Partial<ScreenManager> = {}) {
|
||||
|
||||
Object.assign(this, $$source);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new ScreenManager instance from a string or object.
|
||||
*/
|
||||
static createFrom($$source: any = {}): ScreenManager {
|
||||
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
|
||||
return new ScreenManager($$parsedSource as Partial<ScreenManager>);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ServiceOptions provides optional parameters for calls to [NewService].
|
||||
*/
|
||||
export class ServiceOptions {
|
||||
/**
|
||||
* Name can be set to override the name of the service
|
||||
* for logging and debugging purposes.
|
||||
*
|
||||
* If empty, it will default
|
||||
* either to the value obtained through the [ServiceName] interface,
|
||||
* or to the type name.
|
||||
*/
|
||||
"Name": string;
|
||||
|
||||
/**
|
||||
* If the service instance implements [http.Handler],
|
||||
* it will be mounted on the internal asset server
|
||||
* at the prefix specified by Route.
|
||||
*/
|
||||
"Route": string;
|
||||
|
||||
/**
|
||||
* MarshalError will be called if non-nil
|
||||
* to marshal to JSON the error values returned by this service's methods.
|
||||
*
|
||||
* MarshalError is not allowed to fail,
|
||||
* but it may return a nil slice to fall back
|
||||
* to the globally configured error handler.
|
||||
*
|
||||
* If the returned slice is not nil, it must contain valid JSON.
|
||||
*/
|
||||
"MarshalError": any;
|
||||
|
||||
/** Creates a new ServiceOptions instance. */
|
||||
constructor($$source: Partial<ServiceOptions> = {}) {
|
||||
if (!("Name" in $$source)) {
|
||||
this["Name"] = "";
|
||||
}
|
||||
if (!("Route" in $$source)) {
|
||||
this["Route"] = "";
|
||||
}
|
||||
if (!("MarshalError" in $$source)) {
|
||||
this["MarshalError"] = null;
|
||||
}
|
||||
|
||||
Object.assign(this, $$source);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new ServiceOptions instance from a string or object.
|
||||
*/
|
||||
static createFrom($$source: any = {}): ServiceOptions {
|
||||
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
|
||||
return new ServiceOptions($$parsedSource as Partial<ServiceOptions>);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* SystemTrayManager manages system tray-related operations
|
||||
*/
|
||||
export class SystemTrayManager {
|
||||
|
||||
/** Creates a new SystemTrayManager instance. */
|
||||
constructor($$source: Partial<SystemTrayManager> = {}) {
|
||||
|
||||
Object.assign(this, $$source);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new SystemTrayManager instance from a string or object.
|
||||
*/
|
||||
static createFrom($$source: any = {}): SystemTrayManager {
|
||||
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
|
||||
return new SystemTrayManager($$parsedSource as Partial<SystemTrayManager>);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,8 +396,48 @@ export class WebviewWindow {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* WindowManager manages all window-related operations
|
||||
*/
|
||||
export class WindowManager {
|
||||
|
||||
/** Creates a new WindowManager instance. */
|
||||
constructor($$source: Partial<WindowManager> = {}) {
|
||||
|
||||
Object.assign(this, $$source);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new WindowManager instance from a string or object.
|
||||
*/
|
||||
static createFrom($$source: any = {}): WindowManager {
|
||||
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
|
||||
return new WindowManager($$parsedSource as Partial<WindowManager>);
|
||||
}
|
||||
}
|
||||
|
||||
// Private type creation functions
|
||||
const $$createType0 = Menu.createFrom;
|
||||
const $$createType0 = WindowManager.createFrom;
|
||||
const $$createType1 = $Create.Nullable($$createType0);
|
||||
const $$createType2 = slog$0.Logger.createFrom;
|
||||
const $$createType2 = ContextMenuManager.createFrom;
|
||||
const $$createType3 = $Create.Nullable($$createType2);
|
||||
const $$createType4 = KeyBindingManager.createFrom;
|
||||
const $$createType5 = $Create.Nullable($$createType4);
|
||||
const $$createType6 = BrowserManager.createFrom;
|
||||
const $$createType7 = $Create.Nullable($$createType6);
|
||||
const $$createType8 = EnvironmentManager.createFrom;
|
||||
const $$createType9 = $Create.Nullable($$createType8);
|
||||
const $$createType10 = DialogManager.createFrom;
|
||||
const $$createType11 = $Create.Nullable($$createType10);
|
||||
const $$createType12 = EventManager.createFrom;
|
||||
const $$createType13 = $Create.Nullable($$createType12);
|
||||
const $$createType14 = MenuManager.createFrom;
|
||||
const $$createType15 = $Create.Nullable($$createType14);
|
||||
const $$createType16 = ScreenManager.createFrom;
|
||||
const $$createType17 = $Create.Nullable($$createType16);
|
||||
const $$createType18 = ClipboardManager.createFrom;
|
||||
const $$createType19 = $Create.Nullable($$createType18);
|
||||
const $$createType20 = SystemTrayManager.createFrom;
|
||||
const $$createType21 = $Create.Nullable($$createType20);
|
||||
const $$createType22 = slog$0.Logger.createFrom;
|
||||
const $$createType23 = $Create.Nullable($$createType22);
|
||||
|
@@ -1,4 +1,9 @@
|
||||
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||
// This file is automatically generated. DO NOT EDIT
|
||||
|
||||
import * as Service from "./service.js";
|
||||
export {
|
||||
Service
|
||||
};
|
||||
|
||||
export * from "./models.js";
|
@@ -0,0 +1,51 @@
|
||||
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||
// This file is automatically generated. DO NOT EDIT
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore: Unused imports
|
||||
import {Create as $Create} from "@wailsio/runtime";
|
||||
|
||||
export class Config {
|
||||
/**
|
||||
* DBSource is the database URI to use.
|
||||
* The string ":memory:" can be used to create an in-memory database.
|
||||
* The sqlite driver can be configured through query parameters.
|
||||
* For more details see https://pkg.go.dev/modernc.org/sqlite#Driver.Open
|
||||
*/
|
||||
"DBSource": string;
|
||||
|
||||
/** Creates a new Config instance. */
|
||||
constructor($$source: Partial<Config> = {}) {
|
||||
if (!("DBSource" in $$source)) {
|
||||
this["DBSource"] = "";
|
||||
}
|
||||
|
||||
Object.assign(this, $$source);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new Config instance from a string or object.
|
||||
*/
|
||||
static createFrom($$source: any = {}): Config {
|
||||
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
|
||||
return new Config($$parsedSource as Partial<Config>);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Row holds a single row in the result of a query.
|
||||
* It is a key-value map where keys are column names.
|
||||
*/
|
||||
export type Row = { [_: string]: any };
|
||||
|
||||
/**
|
||||
* Rows holds the result of a query
|
||||
* as an array of key-value maps where keys are column names.
|
||||
*/
|
||||
export type Rows = Row[];
|
||||
|
||||
/**
|
||||
* Stmt wraps a prepared sql statement pointer.
|
||||
* It provides the same methods as the [sql.Stmt] type.
|
||||
*/
|
||||
export type Stmt = string;
|
@@ -0,0 +1,223 @@
|
||||
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||
// This file is automatically generated. DO NOT EDIT
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore: Unused imports
|
||||
import {Call as $Call, Create as $Create} from "@wailsio/runtime";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore: Unused imports
|
||||
import * as application$0 from "../../application/models.js";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore: Unused imports
|
||||
import * as $models from "./models.js";
|
||||
|
||||
export {
|
||||
ExecContext as Execute,
|
||||
QueryContext as Query
|
||||
};
|
||||
|
||||
import { Stmt } from "./stmt.js";
|
||||
|
||||
/**
|
||||
* Prepare creates a prepared statement for later queries or executions.
|
||||
* Multiple queries or executions may be run concurrently from the returned statement.
|
||||
*
|
||||
* The caller must call the statement's Close method when it is no longer needed.
|
||||
* Statements are closed automatically
|
||||
* when the connection they are associated with is closed.
|
||||
*
|
||||
* Prepare supports early cancellation.
|
||||
*/
|
||||
export function Prepare(query: string): Promise<Stmt | null> & { cancel(): void } {
|
||||
const promise = PrepareContext(query);
|
||||
const wrapper: any = (promise.then(function (id) {
|
||||
return id == null ? null : new Stmt(
|
||||
ClosePrepared.bind(null, id),
|
||||
ExecPrepared.bind(null, id),
|
||||
QueryPrepared.bind(null, id));
|
||||
}));
|
||||
wrapper.cancel = promise.cancel;
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Close closes the current database connection if one is open, otherwise has no effect.
|
||||
* Additionally, Close closes all open prepared statements associated to the connection.
|
||||
*
|
||||
* Even when a non-nil error is returned,
|
||||
* the database service is left in a consistent state,
|
||||
* ready for a call to [Service.Open].
|
||||
*/
|
||||
export function Close(): Promise<void> & { cancel(): void } {
|
||||
let $resultPromise = $Call.ByID(1888105376) as any;
|
||||
return $resultPromise;
|
||||
}
|
||||
|
||||
/**
|
||||
* ClosePrepared closes a prepared statement
|
||||
* obtained with [Service.Prepare] or [Service.PrepareContext].
|
||||
* ClosePrepared is idempotent:
|
||||
* it has no effect on prepared statements that are already closed.
|
||||
*/
|
||||
function ClosePrepared(stmt: $models.Stmt | null): Promise<void> & { cancel(): void } {
|
||||
let $resultPromise = $Call.ByID(2526200629, stmt) as any;
|
||||
return $resultPromise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure changes the database service configuration.
|
||||
* The connection state at call time is preserved.
|
||||
* Consumers will need to call [Service.Open] manually after Configure
|
||||
* in order to reconnect with the new configuration.
|
||||
*
|
||||
* See [NewWithConfig] for details on configuration.
|
||||
*/
|
||||
export function Configure(config: $models.Config | null): Promise<void> & { cancel(): void } {
|
||||
let $resultPromise = $Call.ByID(1939578712, config) as any;
|
||||
return $resultPromise;
|
||||
}
|
||||
|
||||
/**
|
||||
* ExecContext executes a query without returning any rows.
|
||||
* It supports early cancellation.
|
||||
*/
|
||||
function ExecContext(query: string, ...args: any[]): Promise<void> & { cancel(): void } {
|
||||
let $resultPromise = $Call.ByID(674944556, query, args) as any;
|
||||
return $resultPromise;
|
||||
}
|
||||
|
||||
/**
|
||||
* ExecPrepared executes a prepared statement
|
||||
* obtained with [Service.Prepare] or [Service.PrepareContext]
|
||||
* without returning any rows.
|
||||
* It supports early cancellation.
|
||||
*/
|
||||
function ExecPrepared(stmt: $models.Stmt | null, ...args: any[]): Promise<void> & { cancel(): void } {
|
||||
let $resultPromise = $Call.ByID(2086877656, stmt, args) as any;
|
||||
return $resultPromise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute executes a query without returning any rows.
|
||||
*/
|
||||
export function Execute(query: string, ...args: any[]): Promise<void> & { cancel(): void } {
|
||||
let $resultPromise = $Call.ByID(3811930203, query, args) as any;
|
||||
return $resultPromise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Open validates the current configuration,
|
||||
* closes the current connection if one is present,
|
||||
* then opens and validates a new connection.
|
||||
*
|
||||
* Even when a non-nil error is returned,
|
||||
* the database service is left in a consistent state,
|
||||
* ready for a new call to Open.
|
||||
*/
|
||||
export function Open(): Promise<void> & { cancel(): void } {
|
||||
let $resultPromise = $Call.ByID(2012175612) as any;
|
||||
return $resultPromise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare creates a prepared statement for later queries or executions.
|
||||
* Multiple queries or executions may be run concurrently from the returned statement.
|
||||
*
|
||||
* The caller should call the statement's Close method when it is no longer needed.
|
||||
* Statements are closed automatically
|
||||
* when the connection they are associated with is closed.
|
||||
*/
|
||||
export function Prepare(query: string): Promise<$models.Stmt | null> & { cancel(): void } {
|
||||
let $resultPromise = $Call.ByID(1801965143, query) as any;
|
||||
return $resultPromise;
|
||||
}
|
||||
|
||||
/**
|
||||
* PrepareContext creates a prepared statement for later queries or executions.
|
||||
* Multiple queries or executions may be run concurrently from the returned statement.
|
||||
*
|
||||
* The caller must call the statement's Close method when it is no longer needed.
|
||||
* Statements are closed automatically
|
||||
* when the connection they are associated with is closed.
|
||||
*
|
||||
* PrepareContext supports early cancellation.
|
||||
*/
|
||||
function PrepareContext(query: string): Promise<$models.Stmt | null> & { cancel(): void } {
|
||||
let $resultPromise = $Call.ByID(570941694, query) as any;
|
||||
return $resultPromise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Query executes a query and returns a slice of key-value records,
|
||||
* one per row, with column names as keys.
|
||||
*/
|
||||
export function Query(query: string, ...args: any[]): Promise<$models.Rows> & { cancel(): void } {
|
||||
let $resultPromise = $Call.ByID(860757720, query, args) as any;
|
||||
let $typingPromise = $resultPromise.then(($result: any) => {
|
||||
return $$createType1($result);
|
||||
}) as any;
|
||||
$typingPromise.cancel = $resultPromise.cancel.bind($resultPromise);
|
||||
return $typingPromise;
|
||||
}
|
||||
|
||||
/**
|
||||
* QueryContext executes a query and returns a slice of key-value records,
|
||||
* one per row, with column names as keys.
|
||||
* It supports early cancellation, returning the slice of results fetched so far.
|
||||
*/
|
||||
function QueryContext(query: string, ...args: any[]): Promise<$models.Rows> & { cancel(): void } {
|
||||
let $resultPromise = $Call.ByID(4115542347, query, args) as any;
|
||||
let $typingPromise = $resultPromise.then(($result: any) => {
|
||||
return $$createType1($result);
|
||||
}) as any;
|
||||
$typingPromise.cancel = $resultPromise.cancel.bind($resultPromise);
|
||||
return $typingPromise;
|
||||
}
|
||||
|
||||
/**
|
||||
* QueryPrepared executes a prepared statement
|
||||
* obtained with [Service.Prepare] or [Service.PrepareContext]
|
||||
* and returns a slice of key-value records, one per row, with column names as keys.
|
||||
* It supports early cancellation, returning the slice of results fetched so far.
|
||||
*/
|
||||
function QueryPrepared(stmt: $models.Stmt | null, ...args: any[]): Promise<$models.Rows> & { cancel(): void } {
|
||||
let $resultPromise = $Call.ByID(3885083725, stmt, args) as any;
|
||||
let $typingPromise = $resultPromise.then(($result: any) => {
|
||||
return $$createType1($result);
|
||||
}) as any;
|
||||
$typingPromise.cancel = $resultPromise.cancel.bind($resultPromise);
|
||||
return $typingPromise;
|
||||
}
|
||||
|
||||
/**
|
||||
* ServiceName returns the name of the plugin.
|
||||
* You should use the go module format e.g. github.com/myuser/myplugin
|
||||
*/
|
||||
export function ServiceName(): Promise<string> & { cancel(): void } {
|
||||
let $resultPromise = $Call.ByID(1637123084) as any;
|
||||
return $resultPromise;
|
||||
}
|
||||
|
||||
/**
|
||||
* ServiceShutdown closes the database connection.
|
||||
* It returns a non-nil error in case of failures.
|
||||
*/
|
||||
export function ServiceShutdown(): Promise<void> & { cancel(): void } {
|
||||
let $resultPromise = $Call.ByID(3650435925) as any;
|
||||
return $resultPromise;
|
||||
}
|
||||
|
||||
/**
|
||||
* ServiceStartup opens the database connection.
|
||||
* It returns a non-nil error in case of failures.
|
||||
*/
|
||||
export function ServiceStartup(options: application$0.ServiceOptions): Promise<void> & { cancel(): void } {
|
||||
let $resultPromise = $Call.ByID(1113159936, options) as any;
|
||||
return $resultPromise;
|
||||
}
|
||||
|
||||
// Private type creation functions
|
||||
const $$createType0 = $Create.Map($Create.Any, $Create.Any);
|
||||
const $$createType1 = $Create.Array($$createType0);
|
@@ -0,0 +1,79 @@
|
||||
//@ts-check
|
||||
|
||||
//@ts-ignore: Unused imports
|
||||
import * as $models from "./models.js";
|
||||
|
||||
const execSymbol = Symbol("exec"),
|
||||
querySymbol = Symbol("query"),
|
||||
closeSymbol = Symbol("close");
|
||||
|
||||
/**
|
||||
* Stmt represents a prepared statement for later queries or executions.
|
||||
* Multiple queries or executions may be run concurrently on the same statement.
|
||||
*
|
||||
* The caller must call the statement's Close method when it is no longer needed.
|
||||
* Statements are closed automatically
|
||||
* when the connection they are associated with is closed.
|
||||
*/
|
||||
export class Stmt {
|
||||
/**
|
||||
* Constructs a new prepared statement instance.
|
||||
* @param {(...args: any[]) => Promise<void>} close
|
||||
* @param {(...args: any[]) => Promise<void> & { cancel(): void }} exec
|
||||
* @param {(...args: any[]) => Promise<$models.Rows> & { cancel(): void }} query
|
||||
*/
|
||||
constructor(close, exec, query) {
|
||||
/**
|
||||
* @member
|
||||
* @private
|
||||
* @type {typeof close}
|
||||
*/
|
||||
this[closeSymbol] = close;
|
||||
|
||||
/**
|
||||
* @member
|
||||
* @private
|
||||
* @type {typeof exec}
|
||||
*/
|
||||
this[execSymbol] = exec;
|
||||
|
||||
/**
|
||||
* @member
|
||||
* @private
|
||||
* @type {typeof query}
|
||||
*/
|
||||
this[querySymbol] = query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the prepared statement.
|
||||
* It has no effect when the statement is already closed.
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
Close() {
|
||||
return this[closeSymbol]();
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the prepared statement without returning any rows.
|
||||
* It supports early cancellation.
|
||||
*
|
||||
* @param {any[]} args
|
||||
* @returns {Promise<void> & { cancel(): void }}
|
||||
*/
|
||||
Exec(...args) {
|
||||
return this[execSymbol](...args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the prepared statement
|
||||
* and returns a slice of key-value records, one per row, with column names as keys.
|
||||
* It supports early cancellation, returning the array of results fetched so far.
|
||||
*
|
||||
* @param {any[]} args
|
||||
* @returns {Promise<$models.Rows> & { cancel(): void }}
|
||||
*/
|
||||
Query(...args) {
|
||||
return this[querySymbol](...args);
|
||||
}
|
||||
}
|
@@ -102,6 +102,11 @@ export class AppearanceConfig {
|
||||
*/
|
||||
"systemTheme": SystemThemeType;
|
||||
|
||||
/**
|
||||
* 自定义主题配置
|
||||
*/
|
||||
"customTheme": CustomThemeConfig;
|
||||
|
||||
/** Creates a new AppearanceConfig instance. */
|
||||
constructor($$source: Partial<AppearanceConfig> = {}) {
|
||||
if (!("language" in $$source)) {
|
||||
@@ -110,6 +115,9 @@ export class AppearanceConfig {
|
||||
if (!("systemTheme" in $$source)) {
|
||||
this["systemTheme"] = ("" as SystemThemeType);
|
||||
}
|
||||
if (!("customTheme" in $$source)) {
|
||||
this["customTheme"] = (new CustomThemeConfig());
|
||||
}
|
||||
|
||||
Object.assign(this, $$source);
|
||||
}
|
||||
@@ -118,7 +126,11 @@ export class AppearanceConfig {
|
||||
* Creates a new AppearanceConfig instance from a string or object.
|
||||
*/
|
||||
static createFrom($$source: any = {}): AppearanceConfig {
|
||||
const $$createField2_0 = $$createType5;
|
||||
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
|
||||
if ("customTheme" in $$parsedSource) {
|
||||
$$parsedSource["customTheme"] = $$createField2_0($$parsedSource["customTheme"]);
|
||||
}
|
||||
return new AppearanceConfig($$parsedSource as Partial<AppearanceConfig>);
|
||||
}
|
||||
}
|
||||
@@ -158,6 +170,49 @@ export class ConfigMetadata {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* CustomThemeConfig 自定义主题配置
|
||||
*/
|
||||
export class CustomThemeConfig {
|
||||
/**
|
||||
* 深色主题配置
|
||||
*/
|
||||
"darkTheme": ThemeColorConfig;
|
||||
|
||||
/**
|
||||
* 浅色主题配置
|
||||
*/
|
||||
"lightTheme": ThemeColorConfig;
|
||||
|
||||
/** Creates a new CustomThemeConfig instance. */
|
||||
constructor($$source: Partial<CustomThemeConfig> = {}) {
|
||||
if (!("darkTheme" in $$source)) {
|
||||
this["darkTheme"] = (new ThemeColorConfig());
|
||||
}
|
||||
if (!("lightTheme" in $$source)) {
|
||||
this["lightTheme"] = (new ThemeColorConfig());
|
||||
}
|
||||
|
||||
Object.assign(this, $$source);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new CustomThemeConfig instance from a string or object.
|
||||
*/
|
||||
static createFrom($$source: any = {}): CustomThemeConfig {
|
||||
const $$createField0_0 = $$createType6;
|
||||
const $$createField1_0 = $$createType6;
|
||||
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
|
||||
if ("darkTheme" in $$parsedSource) {
|
||||
$$parsedSource["darkTheme"] = $$createField0_0($$parsedSource["darkTheme"]);
|
||||
}
|
||||
if ("lightTheme" in $$parsedSource) {
|
||||
$$parsedSource["lightTheme"] = $$createField1_0($$parsedSource["lightTheme"]);
|
||||
}
|
||||
return new CustomThemeConfig($$parsedSource as Partial<CustomThemeConfig>);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Document represents a document in the system
|
||||
*/
|
||||
@@ -334,7 +389,7 @@ export class Extension {
|
||||
* Creates a new Extension instance from a string or object.
|
||||
*/
|
||||
static createFrom($$source: any = {}): Extension {
|
||||
const $$createField3_0 = $$createType5;
|
||||
const $$createField3_0 = $$createType7;
|
||||
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
|
||||
if ("config" in $$parsedSource) {
|
||||
$$parsedSource["config"] = $$createField3_0($$parsedSource["config"]);
|
||||
@@ -467,7 +522,7 @@ export class GeneralConfig {
|
||||
* Creates a new GeneralConfig instance from a string or object.
|
||||
*/
|
||||
static createFrom($$source: any = {}): GeneralConfig {
|
||||
const $$createField5_0 = $$createType7;
|
||||
const $$createField5_0 = $$createType9;
|
||||
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
|
||||
if ("globalHotkey" in $$parsedSource) {
|
||||
$$parsedSource["globalHotkey"] = $$createField5_0($$parsedSource["globalHotkey"]);
|
||||
@@ -963,84 +1018,6 @@ export enum KeyBindingCommand {
|
||||
TextHighlightToggleCommand = "textHighlightToggle",
|
||||
};
|
||||
|
||||
/**
|
||||
* KeyBindingConfig 快捷键配置
|
||||
*/
|
||||
export class KeyBindingConfig {
|
||||
/**
|
||||
* 快捷键列表
|
||||
*/
|
||||
"keyBindings": KeyBinding[];
|
||||
|
||||
/**
|
||||
* 配置元数据
|
||||
*/
|
||||
"metadata": KeyBindingMetadata;
|
||||
|
||||
/** Creates a new KeyBindingConfig instance. */
|
||||
constructor($$source: Partial<KeyBindingConfig> = {}) {
|
||||
if (!("keyBindings" in $$source)) {
|
||||
this["keyBindings"] = [];
|
||||
}
|
||||
if (!("metadata" in $$source)) {
|
||||
this["metadata"] = (new KeyBindingMetadata());
|
||||
}
|
||||
|
||||
Object.assign(this, $$source);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new KeyBindingConfig instance from a string or object.
|
||||
*/
|
||||
static createFrom($$source: any = {}): KeyBindingConfig {
|
||||
const $$createField0_0 = $$createType9;
|
||||
const $$createField1_0 = $$createType10;
|
||||
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
|
||||
if ("keyBindings" in $$parsedSource) {
|
||||
$$parsedSource["keyBindings"] = $$createField0_0($$parsedSource["keyBindings"]);
|
||||
}
|
||||
if ("metadata" in $$parsedSource) {
|
||||
$$parsedSource["metadata"] = $$createField1_0($$parsedSource["metadata"]);
|
||||
}
|
||||
return new KeyBindingConfig($$parsedSource as Partial<KeyBindingConfig>);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* KeyBindingMetadata 快捷键配置元数据
|
||||
*/
|
||||
export class KeyBindingMetadata {
|
||||
/**
|
||||
* 配置版本
|
||||
*/
|
||||
"version": string;
|
||||
|
||||
/**
|
||||
* 最后更新时间
|
||||
*/
|
||||
"lastUpdated": string;
|
||||
|
||||
/** Creates a new KeyBindingMetadata instance. */
|
||||
constructor($$source: Partial<KeyBindingMetadata> = {}) {
|
||||
if (!("version" in $$source)) {
|
||||
this["version"] = "";
|
||||
}
|
||||
if (!("lastUpdated" in $$source)) {
|
||||
this["lastUpdated"] = "";
|
||||
}
|
||||
|
||||
Object.assign(this, $$source);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new KeyBindingMetadata instance from a string or object.
|
||||
*/
|
||||
static createFrom($$source: any = {}): KeyBindingMetadata {
|
||||
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
|
||||
return new KeyBindingMetadata($$parsedSource as Partial<KeyBindingMetadata>);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* LanguageType 语言类型定义
|
||||
*/
|
||||
@@ -1106,6 +1083,214 @@ export enum TabType {
|
||||
TabTypeTab = "tab",
|
||||
};
|
||||
|
||||
/**
|
||||
* ThemeColorConfig 主题颜色配置
|
||||
*/
|
||||
export class ThemeColorConfig {
|
||||
/**
|
||||
* 基础色调
|
||||
* 主背景色
|
||||
*/
|
||||
"background": string;
|
||||
|
||||
/**
|
||||
* 次要背景色
|
||||
*/
|
||||
"backgroundSecondary": string;
|
||||
|
||||
/**
|
||||
* 面板背景
|
||||
*/
|
||||
"surface": string;
|
||||
|
||||
/**
|
||||
* 主文本色
|
||||
*/
|
||||
"foreground": string;
|
||||
|
||||
/**
|
||||
* 次要文本色
|
||||
*/
|
||||
"foregroundSecondary": string;
|
||||
|
||||
/**
|
||||
* 语法高亮
|
||||
* 注释色
|
||||
*/
|
||||
"comment": string;
|
||||
|
||||
/**
|
||||
* 关键字
|
||||
*/
|
||||
"keyword": string;
|
||||
|
||||
/**
|
||||
* 字符串
|
||||
*/
|
||||
"string": string;
|
||||
|
||||
/**
|
||||
* 函数名
|
||||
*/
|
||||
"function": string;
|
||||
|
||||
/**
|
||||
* 数字
|
||||
*/
|
||||
"number": string;
|
||||
|
||||
/**
|
||||
* 操作符
|
||||
*/
|
||||
"operator": string;
|
||||
|
||||
/**
|
||||
* 变量
|
||||
*/
|
||||
"variable": string;
|
||||
|
||||
/**
|
||||
* 类型
|
||||
*/
|
||||
"type": string;
|
||||
|
||||
/**
|
||||
* 界面元素
|
||||
* 光标
|
||||
*/
|
||||
"cursor": string;
|
||||
|
||||
/**
|
||||
* 选中背景
|
||||
*/
|
||||
"selection": string;
|
||||
|
||||
/**
|
||||
* 失焦选中背景
|
||||
*/
|
||||
"selectionBlur": string;
|
||||
|
||||
/**
|
||||
* 当前行高亮
|
||||
*/
|
||||
"activeLine": string;
|
||||
|
||||
/**
|
||||
* 行号
|
||||
*/
|
||||
"lineNumber": string;
|
||||
|
||||
/**
|
||||
* 活动行号
|
||||
*/
|
||||
"activeLineNumber": string;
|
||||
|
||||
/**
|
||||
* 边框分割线
|
||||
* 边框色
|
||||
*/
|
||||
"borderColor": string;
|
||||
|
||||
/**
|
||||
* 浅色边框
|
||||
*/
|
||||
"borderLight": string;
|
||||
|
||||
/**
|
||||
* 搜索匹配
|
||||
* 搜索匹配
|
||||
*/
|
||||
"searchMatch": string;
|
||||
|
||||
/**
|
||||
* 匹配括号
|
||||
*/
|
||||
"matchingBracket": string;
|
||||
|
||||
/** Creates a new ThemeColorConfig instance. */
|
||||
constructor($$source: Partial<ThemeColorConfig> = {}) {
|
||||
if (!("background" in $$source)) {
|
||||
this["background"] = "";
|
||||
}
|
||||
if (!("backgroundSecondary" in $$source)) {
|
||||
this["backgroundSecondary"] = "";
|
||||
}
|
||||
if (!("surface" in $$source)) {
|
||||
this["surface"] = "";
|
||||
}
|
||||
if (!("foreground" in $$source)) {
|
||||
this["foreground"] = "";
|
||||
}
|
||||
if (!("foregroundSecondary" in $$source)) {
|
||||
this["foregroundSecondary"] = "";
|
||||
}
|
||||
if (!("comment" in $$source)) {
|
||||
this["comment"] = "";
|
||||
}
|
||||
if (!("keyword" in $$source)) {
|
||||
this["keyword"] = "";
|
||||
}
|
||||
if (!("string" in $$source)) {
|
||||
this["string"] = "";
|
||||
}
|
||||
if (!("function" in $$source)) {
|
||||
this["function"] = "";
|
||||
}
|
||||
if (!("number" in $$source)) {
|
||||
this["number"] = "";
|
||||
}
|
||||
if (!("operator" in $$source)) {
|
||||
this["operator"] = "";
|
||||
}
|
||||
if (!("variable" in $$source)) {
|
||||
this["variable"] = "";
|
||||
}
|
||||
if (!("type" in $$source)) {
|
||||
this["type"] = "";
|
||||
}
|
||||
if (!("cursor" in $$source)) {
|
||||
this["cursor"] = "";
|
||||
}
|
||||
if (!("selection" in $$source)) {
|
||||
this["selection"] = "";
|
||||
}
|
||||
if (!("selectionBlur" in $$source)) {
|
||||
this["selectionBlur"] = "";
|
||||
}
|
||||
if (!("activeLine" in $$source)) {
|
||||
this["activeLine"] = "";
|
||||
}
|
||||
if (!("lineNumber" in $$source)) {
|
||||
this["lineNumber"] = "";
|
||||
}
|
||||
if (!("activeLineNumber" in $$source)) {
|
||||
this["activeLineNumber"] = "";
|
||||
}
|
||||
if (!("borderColor" in $$source)) {
|
||||
this["borderColor"] = "";
|
||||
}
|
||||
if (!("borderLight" in $$source)) {
|
||||
this["borderLight"] = "";
|
||||
}
|
||||
if (!("searchMatch" in $$source)) {
|
||||
this["searchMatch"] = "";
|
||||
}
|
||||
if (!("matchingBracket" in $$source)) {
|
||||
this["matchingBracket"] = "";
|
||||
}
|
||||
|
||||
Object.assign(this, $$source);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new ThemeColorConfig instance from a string or object.
|
||||
*/
|
||||
static createFrom($$source: any = {}): ThemeColorConfig {
|
||||
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
|
||||
return new ThemeColorConfig($$parsedSource as Partial<ThemeColorConfig>);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* UpdateSourceType 更新源类型
|
||||
*/
|
||||
@@ -1204,8 +1389,8 @@ export class UpdatesConfig {
|
||||
* Creates a new UpdatesConfig instance from a string or object.
|
||||
*/
|
||||
static createFrom($$source: any = {}): UpdatesConfig {
|
||||
const $$createField6_0 = $$createType11;
|
||||
const $$createField7_0 = $$createType12;
|
||||
const $$createField6_0 = $$createType10;
|
||||
const $$createField7_0 = $$createType11;
|
||||
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
|
||||
if ("github" in $$parsedSource) {
|
||||
$$parsedSource["github"] = $$createField6_0($$parsedSource["github"]);
|
||||
@@ -1223,16 +1408,15 @@ const $$createType1 = EditingConfig.createFrom;
|
||||
const $$createType2 = AppearanceConfig.createFrom;
|
||||
const $$createType3 = UpdatesConfig.createFrom;
|
||||
const $$createType4 = ConfigMetadata.createFrom;
|
||||
var $$createType5 = (function $$initCreateType5(...args): any {
|
||||
if ($$createType5 === $$initCreateType5) {
|
||||
$$createType5 = $$createType6;
|
||||
const $$createType5 = CustomThemeConfig.createFrom;
|
||||
const $$createType6 = ThemeColorConfig.createFrom;
|
||||
var $$createType7 = (function $$initCreateType7(...args): any {
|
||||
if ($$createType7 === $$initCreateType7) {
|
||||
$$createType7 = $$createType8;
|
||||
}
|
||||
return $$createType5(...args);
|
||||
return $$createType7(...args);
|
||||
});
|
||||
const $$createType6 = $Create.Map($Create.Any, $Create.Any);
|
||||
const $$createType7 = HotkeyCombo.createFrom;
|
||||
const $$createType8 = KeyBinding.createFrom;
|
||||
const $$createType9 = $Create.Array($$createType8);
|
||||
const $$createType10 = KeyBindingMetadata.createFrom;
|
||||
const $$createType11 = GithubConfig.createFrom;
|
||||
const $$createType12 = GiteaConfig.createFrom;
|
||||
const $$createType8 = $Create.Map($Create.Any, $Create.Any);
|
||||
const $$createType9 = HotkeyCombo.createFrom;
|
||||
const $$createType10 = GithubConfig.createFrom;
|
||||
const $$createType11 = GiteaConfig.createFrom;
|
||||
|
@@ -42,6 +42,14 @@ export function ResetConfig(): Promise<void> & { cancel(): void } {
|
||||
return $resultPromise;
|
||||
}
|
||||
|
||||
/**
|
||||
* ServiceShutdown 关闭服务
|
||||
*/
|
||||
export function ServiceShutdown(): Promise<void> & { cancel(): void } {
|
||||
let $resultPromise = $Call.ByID(3963562361) as any;
|
||||
return $resultPromise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set 设置配置项
|
||||
*/
|
||||
|
@@ -12,19 +12,7 @@ import {Call as $Call, Create as $Create} from "@wailsio/runtime";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore: Unused imports
|
||||
import * as sql$0 from "../../../database/sql/models.js";
|
||||
|
||||
/**
|
||||
* GetDB returns the database connection
|
||||
*/
|
||||
export function GetDB(): Promise<sql$0.DB | null> & { cancel(): void } {
|
||||
let $resultPromise = $Call.ByID(228760371) as any;
|
||||
let $typingPromise = $resultPromise.then(($result: any) => {
|
||||
return $$createType1($result);
|
||||
}) as any;
|
||||
$typingPromise.cancel = $resultPromise.cancel.bind($resultPromise);
|
||||
return $typingPromise;
|
||||
}
|
||||
import * as application$0 from "../../../github.com/wailsapp/wails/v3/pkg/application/models.js";
|
||||
|
||||
/**
|
||||
* OnDataPathChanged handles data path changes
|
||||
@@ -34,6 +22,18 @@ export function OnDataPathChanged(): Promise<void> & { cancel(): void } {
|
||||
return $resultPromise;
|
||||
}
|
||||
|
||||
// Private type creation functions
|
||||
const $$createType0 = sql$0.DB.createFrom;
|
||||
const $$createType1 = $Create.Nullable($$createType0);
|
||||
/**
|
||||
* ServiceShutdown shuts down the service when the application closes
|
||||
*/
|
||||
export function ServiceShutdown(): Promise<void> & { cancel(): void } {
|
||||
let $resultPromise = $Call.ByID(3907893632) as any;
|
||||
return $resultPromise;
|
||||
}
|
||||
|
||||
/**
|
||||
* ServiceStartup initializes the service when the application starts
|
||||
*/
|
||||
export function ServiceStartup(options: application$0.ServiceOptions): Promise<void> & { cancel(): void } {
|
||||
let $resultPromise = $Call.ByID(2067840771, options) as any;
|
||||
return $resultPromise;
|
||||
}
|
||||
|
@@ -10,6 +10,9 @@
|
||||
// @ts-ignore: Unused imports
|
||||
import {Call as $Call, Create as $Create} from "@wailsio/runtime";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore: Unused imports
|
||||
import * as application$0 from "../../../github.com/wailsapp/wails/v3/pkg/application/models.js";
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore: Unused imports
|
||||
import * as models$0 from "../models/models.js";
|
||||
@@ -86,6 +89,14 @@ export function RestoreDocument(id: number): Promise<void> & { cancel(): void }
|
||||
return $resultPromise;
|
||||
}
|
||||
|
||||
/**
|
||||
* ServiceStartup initializes the service when the application starts
|
||||
*/
|
||||
export function ServiceStartup(options: application$0.ServiceOptions): Promise<void> & { cancel(): void } {
|
||||
let $resultPromise = $Call.ByID(1474135487, options) as any;
|
||||
return $resultPromise;
|
||||
}
|
||||
|
||||
/**
|
||||
* UpdateDocumentContent updates the content of a document
|
||||
*/
|
||||
|
@@ -10,6 +10,9 @@
|
||||
// @ts-ignore: Unused imports
|
||||
import {Call as $Call, Create as $Create} from "@wailsio/runtime";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore: Unused imports
|
||||
import * as application$0 from "../../../github.com/wailsapp/wails/v3/pkg/application/models.js";
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore: Unused imports
|
||||
import * as models$0 from "../models/models.js";
|
||||
@@ -42,6 +45,14 @@ export function ResetExtensionToDefault(id: models$0.ExtensionID): Promise<void>
|
||||
return $resultPromise;
|
||||
}
|
||||
|
||||
/**
|
||||
* ServiceStartup 启动时调用
|
||||
*/
|
||||
export function ServiceStartup(options: application$0.ServiceOptions): Promise<void> & { cancel(): void } {
|
||||
let $resultPromise = $Call.ByID(40324057, options) as any;
|
||||
return $resultPromise;
|
||||
}
|
||||
|
||||
/**
|
||||
* UpdateExtensionEnabled 更新扩展启用状态
|
||||
*/
|
||||
|
@@ -54,7 +54,7 @@ export function RegisterHotkey(hotkey: models$0.HotkeyCombo | null): Promise<voi
|
||||
}
|
||||
|
||||
/**
|
||||
* OnShutdown 关闭服务
|
||||
* ServiceShutdown 关闭服务
|
||||
*/
|
||||
export function ServiceShutdown(): Promise<void> & { cancel(): void } {
|
||||
let $resultPromise = $Call.ByID(157291181) as any;
|
||||
|
@@ -14,6 +14,7 @@ import * as StartupService from "./startupservice.js";
|
||||
import * as SystemService from "./systemservice.js";
|
||||
import * as TranslationService from "./translationservice.js";
|
||||
import * as TrayService from "./trayservice.js";
|
||||
import * as WindowService from "./windowservice.js";
|
||||
export {
|
||||
ConfigService,
|
||||
DatabaseService,
|
||||
@@ -27,7 +28,8 @@ export {
|
||||
StartupService,
|
||||
SystemService,
|
||||
TranslationService,
|
||||
TrayService
|
||||
TrayService,
|
||||
WindowService
|
||||
};
|
||||
|
||||
export * from "./models.js";
|
||||
|
@@ -10,6 +10,9 @@
|
||||
// @ts-ignore: Unused imports
|
||||
import {Call as $Call, Create as $Create} from "@wailsio/runtime";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore: Unused imports
|
||||
import * as application$0 from "../../../github.com/wailsapp/wails/v3/pkg/application/models.js";
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore: Unused imports
|
||||
import * as models$0 from "../models/models.js";
|
||||
@@ -27,19 +30,13 @@ export function GetAllKeyBindings(): Promise<models$0.KeyBinding[]> & { cancel()
|
||||
}
|
||||
|
||||
/**
|
||||
* GetKeyBindingConfig 获取完整快捷键配置
|
||||
* ServiceStartup 启动时调用
|
||||
*/
|
||||
export function GetKeyBindingConfig(): Promise<models$0.KeyBindingConfig | null> & { cancel(): void } {
|
||||
let $resultPromise = $Call.ByID(3804318356) as any;
|
||||
let $typingPromise = $resultPromise.then(($result: any) => {
|
||||
return $$createType3($result);
|
||||
}) as any;
|
||||
$typingPromise.cancel = $resultPromise.cancel.bind($resultPromise);
|
||||
return $typingPromise;
|
||||
export function ServiceStartup(options: application$0.ServiceOptions): Promise<void> & { cancel(): void } {
|
||||
let $resultPromise = $Call.ByID(2057121990, options) as any;
|
||||
return $resultPromise;
|
||||
}
|
||||
|
||||
// Private type creation functions
|
||||
const $$createType0 = models$0.KeyBinding.createFrom;
|
||||
const $$createType1 = $Create.Array($$createType0);
|
||||
const $$createType2 = models$0.KeyBindingConfig.createFrom;
|
||||
const $$createType3 = $Create.Nullable($$createType2);
|
||||
|
@@ -42,5 +42,13 @@ export function MigrateDirectory(srcPath: string, dstPath: string): Promise<void
|
||||
return $resultPromise;
|
||||
}
|
||||
|
||||
/**
|
||||
* ServiceShutdown 服务关闭
|
||||
*/
|
||||
export function ServiceShutdown(): Promise<void> & { cancel(): void } {
|
||||
let $resultPromise = $Call.ByID(3472042605) as any;
|
||||
return $resultPromise;
|
||||
}
|
||||
|
||||
// Private type creation functions
|
||||
const $$createType0 = $models.MigrationProgress.createFrom;
|
||||
|
@@ -5,6 +5,10 @@
|
||||
// @ts-ignore: Unused imports
|
||||
import {Create as $Create} from "@wailsio/runtime";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore: Unused imports
|
||||
import * as application$0 from "../../../github.com/wailsapp/wails/v3/pkg/application/models.js";
|
||||
|
||||
/**
|
||||
* MemoryStats 内存统计信息
|
||||
*/
|
||||
@@ -197,3 +201,43 @@ export class SelfUpdateResult {
|
||||
return new SelfUpdateResult($$parsedSource as Partial<SelfUpdateResult>);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* WindowInfo 窗口信息
|
||||
*/
|
||||
export class WindowInfo {
|
||||
"Window": application$0.WebviewWindow | null;
|
||||
"DocumentID": number;
|
||||
"Title": string;
|
||||
|
||||
/** Creates a new WindowInfo instance. */
|
||||
constructor($$source: Partial<WindowInfo> = {}) {
|
||||
if (!("Window" in $$source)) {
|
||||
this["Window"] = null;
|
||||
}
|
||||
if (!("DocumentID" in $$source)) {
|
||||
this["DocumentID"] = 0;
|
||||
}
|
||||
if (!("Title" in $$source)) {
|
||||
this["Title"] = "";
|
||||
}
|
||||
|
||||
Object.assign(this, $$source);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new WindowInfo instance from a string or object.
|
||||
*/
|
||||
static createFrom($$source: any = {}): WindowInfo {
|
||||
const $$createField0_0 = $$createType1;
|
||||
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
|
||||
if ("Window" in $$parsedSource) {
|
||||
$$parsedSource["Window"] = $$createField0_0($$parsedSource["Window"]);
|
||||
}
|
||||
return new WindowInfo($$parsedSource as Partial<WindowInfo>);
|
||||
}
|
||||
}
|
||||
|
||||
// Private type creation functions
|
||||
const $$createType0 = application$0.WebviewWindow.createFrom;
|
||||
const $$createType1 = $Create.Nullable($$createType0);
|
||||
|
@@ -0,0 +1,59 @@
|
||||
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||
// This file is automatically generated. DO NOT EDIT
|
||||
|
||||
/**
|
||||
* WindowService 窗口管理服务
|
||||
* @module
|
||||
*/
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore: Unused imports
|
||||
import {Call as $Call, Create as $Create} from "@wailsio/runtime";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore: Unused imports
|
||||
import * as application$0 from "../../../github.com/wailsapp/wails/v3/pkg/application/models.js";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore: Unused imports
|
||||
import * as $models from "./models.js";
|
||||
|
||||
/**
|
||||
* GetOpenWindows 获取所有打开的窗口信息
|
||||
*/
|
||||
export function GetOpenWindows(): Promise<$models.WindowInfo[]> & { cancel(): void } {
|
||||
let $resultPromise = $Call.ByID(1464997251) as any;
|
||||
let $typingPromise = $resultPromise.then(($result: any) => {
|
||||
return $$createType1($result);
|
||||
}) as any;
|
||||
$typingPromise.cancel = $resultPromise.cancel.bind($resultPromise);
|
||||
return $typingPromise;
|
||||
}
|
||||
|
||||
/**
|
||||
* IsDocumentWindowOpen 检查指定文档的窗口是否已打开
|
||||
*/
|
||||
export function IsDocumentWindowOpen(documentID: number): Promise<boolean> & { cancel(): void } {
|
||||
let $resultPromise = $Call.ByID(1735611839, documentID) as any;
|
||||
return $resultPromise;
|
||||
}
|
||||
|
||||
/**
|
||||
* OpenDocumentWindow 为指定文档ID打开新窗口
|
||||
*/
|
||||
export function OpenDocumentWindow(documentID: number): Promise<void> & { cancel(): void } {
|
||||
let $resultPromise = $Call.ByID(494716471, documentID) as any;
|
||||
return $resultPromise;
|
||||
}
|
||||
|
||||
/**
|
||||
* SetAppReferences 设置应用和主窗口引用
|
||||
*/
|
||||
export function SetAppReferences(app: application$0.App | null, mainWindow: application$0.WebviewWindow | null): Promise<void> & { cancel(): void } {
|
||||
let $resultPromise = $Call.ByID(1120840759, app, mainWindow) as any;
|
||||
return $resultPromise;
|
||||
}
|
||||
|
||||
// Private type creation functions
|
||||
const $$createType0 = $models.WindowInfo.createFrom;
|
||||
const $$createType1 = $Create.Array($$createType0);
|
1
frontend/components.d.ts
vendored
1
frontend/components.d.ts
vendored
@@ -11,6 +11,7 @@ declare module 'vue' {
|
||||
BlockLanguageSelector: typeof import('./src/components/toolbar/BlockLanguageSelector.vue')['default']
|
||||
DocumentSelector: typeof import('./src/components/toolbar/DocumentSelector.vue')['default']
|
||||
LinuxTitleBar: typeof import('./src/components/titlebar/LinuxTitleBar.vue')['default']
|
||||
LoadingScreen: typeof import('./src/components/loading/LoadingScreen.vue')['default']
|
||||
MacOSTitleBar: typeof import('./src/components/titlebar/MacOSTitleBar.vue')['default']
|
||||
MemoryMonitor: typeof import('./src/components/monitor/MemoryMonitor.vue')['default']
|
||||
RouterLink: typeof import('vue-router')['RouterLink']
|
||||
|
160
frontend/package-lock.json
generated
160
frontend/package-lock.json
generated
@@ -54,13 +54,14 @@
|
||||
"sass": "^1.89.2",
|
||||
"vue": "^3.5.17",
|
||||
"vue-i18n": "^11.1.9",
|
||||
"vue-pick-colors": "^1.8.0",
|
||||
"vue-router": "^4.5.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.30.1",
|
||||
"@lezer/generator": "^1.8.0",
|
||||
"@types/lodash": "^4.17.20",
|
||||
"@types/node": "^24.0.10",
|
||||
"@types/node": "^24.0.12",
|
||||
"@types/remarkable": "^2.0.8",
|
||||
"@vitejs/plugin-vue": "^6.0.0",
|
||||
"@wailsio/runtime": "latest",
|
||||
@@ -68,9 +69,9 @@
|
||||
"eslint-plugin-vue": "^10.3.0",
|
||||
"globals": "^16.3.0",
|
||||
"typescript": "^5.8.3",
|
||||
"typescript-eslint": "^8.35.1",
|
||||
"typescript-eslint": "^8.36.0",
|
||||
"unplugin-vue-components": "^28.8.0",
|
||||
"vite": "^7.0.2",
|
||||
"vite": "^7.0.3",
|
||||
"vue-eslint-parser": "^10.2.0",
|
||||
"vue-tsc": "^3.0.1"
|
||||
}
|
||||
@@ -1822,6 +1823,16 @@
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/@popperjs/core": {
|
||||
"version": "2.11.8",
|
||||
"resolved": "https://registry.npmmirror.com/@popperjs/core/-/core-2.11.8.tgz",
|
||||
"integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/popperjs"
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/pluginutils": {
|
||||
"version": "1.0.0-beta.19",
|
||||
"resolved": "https://registry.npmmirror.com/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.19.tgz",
|
||||
@@ -2131,9 +2142,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "24.0.10",
|
||||
"resolved": "https://registry.npmmirror.com/@types/node/-/node-24.0.10.tgz",
|
||||
"integrity": "sha512-ENHwaH+JIRTDIEEbDK6QSQntAYGtbvdDXnMXnZaZ6k13Du1dPMmprkEHIL7ok2Wl2aZevetwTAb5S+7yIF+enA==",
|
||||
"version": "24.0.12",
|
||||
"resolved": "https://registry.npmmirror.com/@types/node/-/node-24.0.12.tgz",
|
||||
"integrity": "sha512-LtOrbvDf5ndC9Xi+4QZjVL0woFymF/xSTKZKPgrrl7H7XoeDvnD+E2IclKVDyaK9UM756W/3BXqSU+JEHopA9g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -2148,17 +2159,17 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@typescript-eslint/eslint-plugin": {
|
||||
"version": "8.35.1",
|
||||
"resolved": "https://registry.npmmirror.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.35.1.tgz",
|
||||
"integrity": "sha512-9XNTlo7P7RJxbVeICaIIIEipqxLKguyh+3UbXuT2XQuFp6d8VOeDEGuz5IiX0dgZo8CiI6aOFLg4e8cF71SFVg==",
|
||||
"version": "8.36.0",
|
||||
"resolved": "https://registry.npmmirror.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.36.0.tgz",
|
||||
"integrity": "sha512-lZNihHUVB6ZZiPBNgOQGSxUASI7UJWhT8nHyUGCnaQ28XFCw98IfrMCG3rUl1uwUWoAvodJQby2KTs79UTcrAg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@eslint-community/regexpp": "^4.10.0",
|
||||
"@typescript-eslint/scope-manager": "8.35.1",
|
||||
"@typescript-eslint/type-utils": "8.35.1",
|
||||
"@typescript-eslint/utils": "8.35.1",
|
||||
"@typescript-eslint/visitor-keys": "8.35.1",
|
||||
"@typescript-eslint/scope-manager": "8.36.0",
|
||||
"@typescript-eslint/type-utils": "8.36.0",
|
||||
"@typescript-eslint/utils": "8.36.0",
|
||||
"@typescript-eslint/visitor-keys": "8.36.0",
|
||||
"graphemer": "^1.4.0",
|
||||
"ignore": "^7.0.0",
|
||||
"natural-compare": "^1.4.0",
|
||||
@@ -2172,7 +2183,7 @@
|
||||
"url": "https://opencollective.com/typescript-eslint"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@typescript-eslint/parser": "^8.35.1",
|
||||
"@typescript-eslint/parser": "^8.36.0",
|
||||
"eslint": "^8.57.0 || ^9.0.0",
|
||||
"typescript": ">=4.8.4 <5.9.0"
|
||||
}
|
||||
@@ -2188,16 +2199,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/parser": {
|
||||
"version": "8.35.1",
|
||||
"resolved": "https://registry.npmmirror.com/@typescript-eslint/parser/-/parser-8.35.1.tgz",
|
||||
"integrity": "sha512-3MyiDfrfLeK06bi/g9DqJxP5pV74LNv4rFTyvGDmT3x2p1yp1lOd+qYZfiRPIOf/oON+WRZR5wxxuF85qOar+w==",
|
||||
"version": "8.36.0",
|
||||
"resolved": "https://registry.npmmirror.com/@typescript-eslint/parser/-/parser-8.36.0.tgz",
|
||||
"integrity": "sha512-FuYgkHwZLuPbZjQHzJXrtXreJdFMKl16BFYyRrLxDhWr6Qr7Kbcu2s1Yhu8tsiMXw1S0W1pjfFfYEt+R604s+Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/scope-manager": "8.35.1",
|
||||
"@typescript-eslint/types": "8.35.1",
|
||||
"@typescript-eslint/typescript-estree": "8.35.1",
|
||||
"@typescript-eslint/visitor-keys": "8.35.1",
|
||||
"@typescript-eslint/scope-manager": "8.36.0",
|
||||
"@typescript-eslint/types": "8.36.0",
|
||||
"@typescript-eslint/typescript-estree": "8.36.0",
|
||||
"@typescript-eslint/visitor-keys": "8.36.0",
|
||||
"debug": "^4.3.4"
|
||||
},
|
||||
"engines": {
|
||||
@@ -2213,14 +2224,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/project-service": {
|
||||
"version": "8.35.1",
|
||||
"resolved": "https://registry.npmmirror.com/@typescript-eslint/project-service/-/project-service-8.35.1.tgz",
|
||||
"integrity": "sha512-VYxn/5LOpVxADAuP3NrnxxHYfzVtQzLKeldIhDhzC8UHaiQvYlXvKuVho1qLduFbJjjy5U5bkGwa3rUGUb1Q6Q==",
|
||||
"version": "8.36.0",
|
||||
"resolved": "https://registry.npmmirror.com/@typescript-eslint/project-service/-/project-service-8.36.0.tgz",
|
||||
"integrity": "sha512-JAhQFIABkWccQYeLMrHadu/fhpzmSQ1F1KXkpzqiVxA/iYI6UnRt2trqXHt1sYEcw1mxLnB9rKMsOxXPxowN/g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/tsconfig-utils": "^8.35.1",
|
||||
"@typescript-eslint/types": "^8.35.1",
|
||||
"@typescript-eslint/tsconfig-utils": "^8.36.0",
|
||||
"@typescript-eslint/types": "^8.36.0",
|
||||
"debug": "^4.3.4"
|
||||
},
|
||||
"engines": {
|
||||
@@ -2235,14 +2246,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/scope-manager": {
|
||||
"version": "8.35.1",
|
||||
"resolved": "https://registry.npmmirror.com/@typescript-eslint/scope-manager/-/scope-manager-8.35.1.tgz",
|
||||
"integrity": "sha512-s/Bpd4i7ht2934nG+UoSPlYXd08KYz3bmjLEb7Ye1UVob0d1ENiT3lY8bsCmik4RqfSbPw9xJJHbugpPpP5JUg==",
|
||||
"version": "8.36.0",
|
||||
"resolved": "https://registry.npmmirror.com/@typescript-eslint/scope-manager/-/scope-manager-8.36.0.tgz",
|
||||
"integrity": "sha512-wCnapIKnDkN62fYtTGv2+RY8FlnBYA3tNm0fm91kc2BjPhV2vIjwwozJ7LToaLAyb1ca8BxrS7vT+Pvvf7RvqA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.35.1",
|
||||
"@typescript-eslint/visitor-keys": "8.35.1"
|
||||
"@typescript-eslint/types": "8.36.0",
|
||||
"@typescript-eslint/visitor-keys": "8.36.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
@@ -2253,9 +2264,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/tsconfig-utils": {
|
||||
"version": "8.35.1",
|
||||
"resolved": "https://registry.npmmirror.com/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.35.1.tgz",
|
||||
"integrity": "sha512-K5/U9VmT9dTHoNowWZpz+/TObS3xqC5h0xAIjXPw+MNcKV9qg6eSatEnmeAwkjHijhACH0/N7bkhKvbt1+DXWQ==",
|
||||
"version": "8.36.0",
|
||||
"resolved": "https://registry.npmmirror.com/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.36.0.tgz",
|
||||
"integrity": "sha512-Nhh3TIEgN18mNbdXpd5Q8mSCBnrZQeY9V7Ca3dqYvNDStNIGRmJA6dmrIPMJ0kow3C7gcQbpsG2rPzy1Ks/AnA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -2270,14 +2281,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/type-utils": {
|
||||
"version": "8.35.1",
|
||||
"resolved": "https://registry.npmmirror.com/@typescript-eslint/type-utils/-/type-utils-8.35.1.tgz",
|
||||
"integrity": "sha512-HOrUBlfVRz5W2LIKpXzZoy6VTZzMu2n8q9C2V/cFngIC5U1nStJgv0tMV4sZPzdf4wQm9/ToWUFPMN9Vq9VJQQ==",
|
||||
"version": "8.36.0",
|
||||
"resolved": "https://registry.npmmirror.com/@typescript-eslint/type-utils/-/type-utils-8.36.0.tgz",
|
||||
"integrity": "sha512-5aaGYG8cVDd6cxfk/ynpYzxBRZJk7w/ymto6uiyUFtdCozQIsQWh7M28/6r57Fwkbweng8qAzoMCPwSJfWlmsg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/typescript-estree": "8.35.1",
|
||||
"@typescript-eslint/utils": "8.35.1",
|
||||
"@typescript-eslint/typescript-estree": "8.36.0",
|
||||
"@typescript-eslint/utils": "8.36.0",
|
||||
"debug": "^4.3.4",
|
||||
"ts-api-utils": "^2.1.0"
|
||||
},
|
||||
@@ -2294,9 +2305,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/types": {
|
||||
"version": "8.35.1",
|
||||
"resolved": "https://registry.npmmirror.com/@typescript-eslint/types/-/types-8.35.1.tgz",
|
||||
"integrity": "sha512-q/O04vVnKHfrrhNAscndAn1tuQhIkwqnaW+eu5waD5IPts2eX1dgJxgqcPx5BX109/qAz7IG6VrEPTOYKCNfRQ==",
|
||||
"version": "8.36.0",
|
||||
"resolved": "https://registry.npmmirror.com/@typescript-eslint/types/-/types-8.36.0.tgz",
|
||||
"integrity": "sha512-xGms6l5cTJKQPZOKM75Dl9yBfNdGeLRsIyufewnxT4vZTrjC0ImQT4fj8QmtJK84F58uSh5HVBSANwcfiXxABQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -2308,16 +2319,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/typescript-estree": {
|
||||
"version": "8.35.1",
|
||||
"resolved": "https://registry.npmmirror.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.35.1.tgz",
|
||||
"integrity": "sha512-Vvpuvj4tBxIka7cPs6Y1uvM7gJgdF5Uu9F+mBJBPY4MhvjrjWGK4H0lVgLJd/8PWZ23FTqsaJaLEkBCFUk8Y9g==",
|
||||
"version": "8.36.0",
|
||||
"resolved": "https://registry.npmmirror.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.36.0.tgz",
|
||||
"integrity": "sha512-JaS8bDVrfVJX4av0jLpe4ye0BpAaUW7+tnS4Y4ETa3q7NoZgzYbN9zDQTJ8kPb5fQ4n0hliAt9tA4Pfs2zA2Hg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/project-service": "8.35.1",
|
||||
"@typescript-eslint/tsconfig-utils": "8.35.1",
|
||||
"@typescript-eslint/types": "8.35.1",
|
||||
"@typescript-eslint/visitor-keys": "8.35.1",
|
||||
"@typescript-eslint/project-service": "8.36.0",
|
||||
"@typescript-eslint/tsconfig-utils": "8.36.0",
|
||||
"@typescript-eslint/types": "8.36.0",
|
||||
"@typescript-eslint/visitor-keys": "8.36.0",
|
||||
"debug": "^4.3.4",
|
||||
"fast-glob": "^3.3.2",
|
||||
"is-glob": "^4.0.3",
|
||||
@@ -2363,16 +2374,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/utils": {
|
||||
"version": "8.35.1",
|
||||
"resolved": "https://registry.npmmirror.com/@typescript-eslint/utils/-/utils-8.35.1.tgz",
|
||||
"integrity": "sha512-lhnwatFmOFcazAsUm3ZnZFpXSxiwoa1Lj50HphnDe1Et01NF4+hrdXONSUHIcbVu2eFb1bAf+5yjXkGVkXBKAQ==",
|
||||
"version": "8.36.0",
|
||||
"resolved": "https://registry.npmmirror.com/@typescript-eslint/utils/-/utils-8.36.0.tgz",
|
||||
"integrity": "sha512-VOqmHu42aEMT+P2qYjylw6zP/3E/HvptRwdn/PZxyV27KhZg2IOszXod4NcXisWzPAGSS4trE/g4moNj6XmH2g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@eslint-community/eslint-utils": "^4.7.0",
|
||||
"@typescript-eslint/scope-manager": "8.35.1",
|
||||
"@typescript-eslint/types": "8.35.1",
|
||||
"@typescript-eslint/typescript-estree": "8.35.1"
|
||||
"@typescript-eslint/scope-manager": "8.36.0",
|
||||
"@typescript-eslint/types": "8.36.0",
|
||||
"@typescript-eslint/typescript-estree": "8.36.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
@@ -2387,13 +2398,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/visitor-keys": {
|
||||
"version": "8.35.1",
|
||||
"resolved": "https://registry.npmmirror.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.35.1.tgz",
|
||||
"integrity": "sha512-VRwixir4zBWCSTP/ljEo091lbpypz57PoeAQ9imjG+vbeof9LplljsL1mos4ccG6H9IjfrVGM359RozUnuFhpw==",
|
||||
"version": "8.36.0",
|
||||
"resolved": "https://registry.npmmirror.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.36.0.tgz",
|
||||
"integrity": "sha512-vZrhV2lRPWDuGoxcmrzRZyxAggPL+qp3WzUrlZD+slFueDiYHxeBa34dUXPuC0RmGKzl4lS5kFJYvKCq9cnNDA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.35.1",
|
||||
"@typescript-eslint/types": "8.36.0",
|
||||
"eslint-visitor-keys": "^4.2.1"
|
||||
},
|
||||
"engines": {
|
||||
@@ -4816,15 +4827,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/typescript-eslint": {
|
||||
"version": "8.35.1",
|
||||
"resolved": "https://registry.npmmirror.com/typescript-eslint/-/typescript-eslint-8.35.1.tgz",
|
||||
"integrity": "sha512-xslJjFzhOmHYQzSB/QTeASAHbjmxOGEP6Coh93TXmUBFQoJ1VU35UHIDmG06Jd6taf3wqqC1ntBnCMeymy5Ovw==",
|
||||
"version": "8.36.0",
|
||||
"resolved": "https://registry.npmmirror.com/typescript-eslint/-/typescript-eslint-8.36.0.tgz",
|
||||
"integrity": "sha512-fTCqxthY+h9QbEgSIBfL9iV6CvKDFuoxg6bHPNpJ9HIUzS+jy2lCEyCmGyZRWEBSaykqcDPf1SJ+BfCI8DRopA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/eslint-plugin": "8.35.1",
|
||||
"@typescript-eslint/parser": "8.35.1",
|
||||
"@typescript-eslint/utils": "8.35.1"
|
||||
"@typescript-eslint/eslint-plugin": "8.36.0",
|
||||
"@typescript-eslint/parser": "8.36.0",
|
||||
"@typescript-eslint/utils": "8.36.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
@@ -5124,9 +5135,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/vite": {
|
||||
"version": "7.0.2",
|
||||
"resolved": "https://registry.npmmirror.com/vite/-/vite-7.0.2.tgz",
|
||||
"integrity": "sha512-hxdyZDY1CM6SNpKI4w4lcUc3Mtkd9ej4ECWVHSMrOdSinVc2zYOAppHeGc/hzmRo3pxM5blMzkuWHOJA/3NiFw==",
|
||||
"version": "7.0.3",
|
||||
"resolved": "https://registry.npmmirror.com/vite/-/vite-7.0.3.tgz",
|
||||
"integrity": "sha512-y2L5oJZF7bj4c0jgGYgBNSdIu+5HF+m68rn2cQXFbGoShdhV1phX9rbnxy9YXj82aS8MMsCLAAFkRxZeWdldrQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -5304,6 +5315,19 @@
|
||||
"integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/vue-pick-colors": {
|
||||
"version": "1.8.0",
|
||||
"resolved": "https://registry.npmmirror.com/vue-pick-colors/-/vue-pick-colors-1.8.0.tgz",
|
||||
"integrity": "sha512-lIP28A1BZEPp0v0Y6m9lNbsC6jNM2LP+Dc2tJbUXiNRvDgXqBMe/msX3svqjspV4B+SZdPAjx75JY2zem0hA2Q==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@popperjs/core": "^2.11.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@popperjs/core": "^2.11.2",
|
||||
"vue": "^3.2.26"
|
||||
}
|
||||
},
|
||||
"node_modules/vue-router": {
|
||||
"version": "4.5.1",
|
||||
"resolved": "https://registry.npmmirror.com/vue-router/-/vue-router-4.5.1.tgz",
|
||||
|
@@ -58,13 +58,14 @@
|
||||
"sass": "^1.89.2",
|
||||
"vue": "^3.5.17",
|
||||
"vue-i18n": "^11.1.9",
|
||||
"vue-pick-colors": "^1.8.0",
|
||||
"vue-router": "^4.5.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.30.1",
|
||||
"@lezer/generator": "^1.8.0",
|
||||
"@types/lodash": "^4.17.20",
|
||||
"@types/node": "^24.0.10",
|
||||
"@types/node": "^24.0.12",
|
||||
"@types/remarkable": "^2.0.8",
|
||||
"@vitejs/plugin-vue": "^6.0.0",
|
||||
"@wailsio/runtime": "latest",
|
||||
@@ -72,9 +73,9 @@
|
||||
"eslint-plugin-vue": "^10.3.0",
|
||||
"globals": "^16.3.0",
|
||||
"typescript": "^5.8.3",
|
||||
"typescript-eslint": "^8.35.1",
|
||||
"typescript-eslint": "^8.36.0",
|
||||
"unplugin-vue-components": "^28.8.0",
|
||||
"vite": "^7.0.2",
|
||||
"vite": "^7.0.3",
|
||||
"vue-eslint-parser": "^10.2.0",
|
||||
"vue-tsc": "^3.0.1"
|
||||
}
|
||||
|
@@ -28,6 +28,11 @@
|
||||
--dark-danger-color: #ff6b6b;
|
||||
--dark-bg-primary: #1a1a1a;
|
||||
--dark-bg-hover: #2a2a2a;
|
||||
--dark-loading-bg-gradient: radial-gradient(#222922, #000500);
|
||||
--dark-loading-color: #fff;
|
||||
--dark-loading-glow: 0 0 10px rgba(50, 255, 50, 0.5), 0 0 5px rgba(100, 255, 100, 0.5);
|
||||
--dark-loading-done-color: #6f6;
|
||||
--dark-loading-overlay: linear-gradient(transparent 0%, rgba(10, 16, 10, 0.5) 50%);
|
||||
|
||||
/* 浅色主题颜色变量 */
|
||||
--light-toolbar-bg: #f8f9fa;
|
||||
@@ -55,6 +60,11 @@
|
||||
--light-danger-color: #dc3545;
|
||||
--light-bg-primary: #ffffff;
|
||||
--light-bg-hover: #f1f3f4;
|
||||
--light-loading-bg-gradient: radial-gradient(#f0f6f0, #e5efe5);
|
||||
--light-loading-color: #1a3c1a;
|
||||
--light-loading-glow: 0 0 10px rgba(0, 160, 0, 0.3), 0 0 5px rgba(0, 120, 0, 0.2);
|
||||
--light-loading-done-color: #008800;
|
||||
--light-loading-overlay: linear-gradient(transparent 0%, rgba(220, 240, 220, 0.5) 50%);
|
||||
|
||||
/* 默认使用深色主题 */
|
||||
--toolbar-bg: var(--dark-toolbar-bg);
|
||||
@@ -83,6 +93,12 @@
|
||||
--text-danger: var(--dark-danger-color);
|
||||
--bg-primary: var(--dark-bg-primary);
|
||||
--bg-hover: var(--dark-bg-hover);
|
||||
--voidraft-bg-gradient: var(--dark-loading-bg-gradient);
|
||||
--voidraft-loading-color: var(--dark-loading-color);
|
||||
--voidraft-loading-glow: var(--dark-loading-glow);
|
||||
--voidraft-loading-done-color: var(--dark-loading-done-color);
|
||||
--voidraft-loading-overlay: var(--dark-loading-overlay);
|
||||
--voidraft-mono-font: "HarmonyOS Sans Mono", monospace;
|
||||
|
||||
color-scheme: light dark;
|
||||
}
|
||||
@@ -116,6 +132,11 @@
|
||||
--text-danger: var(--dark-danger-color);
|
||||
--bg-primary: var(--dark-bg-primary);
|
||||
--bg-hover: var(--dark-bg-hover);
|
||||
--voidraft-bg-gradient: var(--dark-loading-bg-gradient);
|
||||
--voidraft-loading-color: var(--dark-loading-color);
|
||||
--voidraft-loading-glow: var(--dark-loading-glow);
|
||||
--voidraft-loading-done-color: var(--dark-loading-done-color);
|
||||
--voidraft-loading-overlay: var(--dark-loading-overlay);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -148,6 +169,11 @@
|
||||
--text-danger: var(--light-danger-color);
|
||||
--bg-primary: var(--light-bg-primary);
|
||||
--bg-hover: var(--light-bg-hover);
|
||||
--voidraft-bg-gradient: var(--light-loading-bg-gradient);
|
||||
--voidraft-loading-color: var(--light-loading-color);
|
||||
--voidraft-loading-glow: var(--light-loading-glow);
|
||||
--voidraft-loading-done-color: var(--light-loading-done-color);
|
||||
--voidraft-loading-overlay: var(--light-loading-overlay);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -179,6 +205,11 @@
|
||||
--text-danger: var(--light-danger-color);
|
||||
--bg-primary: var(--light-bg-primary);
|
||||
--bg-hover: var(--light-bg-hover);
|
||||
--voidraft-bg-gradient: var(--light-loading-bg-gradient);
|
||||
--voidraft-loading-color: var(--light-loading-color);
|
||||
--voidraft-loading-glow: var(--light-loading-glow);
|
||||
--voidraft-loading-done-color: var(--light-loading-done-color);
|
||||
--voidraft-loading-overlay: var(--light-loading-overlay);
|
||||
}
|
||||
|
||||
/* 手动选择深色主题 */
|
||||
@@ -207,4 +238,11 @@
|
||||
--selection-bg: var(--dark-selection-bg);
|
||||
--selection-text: var(--dark-selection-text);
|
||||
--text-danger: var(--dark-danger-color);
|
||||
--bg-primary: var(--dark-bg-primary);
|
||||
--bg-hover: var(--dark-bg-hover);
|
||||
--voidraft-bg-gradient: var(--dark-loading-bg-gradient);
|
||||
--voidraft-loading-color: var(--dark-loading-color);
|
||||
--voidraft-loading-glow: var(--dark-loading-glow);
|
||||
--voidraft-loading-done-color: var(--dark-loading-done-color);
|
||||
--voidraft-loading-overlay: var(--dark-loading-overlay);
|
||||
}
|
177
frontend/src/components/loading/LoadingScreen.vue
Normal file
177
frontend/src/components/loading/LoadingScreen.vue
Normal file
@@ -0,0 +1,177 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, onBeforeUnmount } from 'vue';
|
||||
|
||||
const props = defineProps({
|
||||
text: {
|
||||
type: String,
|
||||
default: 'LOADING'
|
||||
}
|
||||
});
|
||||
|
||||
const characters = ref<HTMLSpanElement[]>([]);
|
||||
const isDone = ref(false);
|
||||
const cycleCount = 5;
|
||||
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890!@#$%^&*()-_=+{}|[]\\;\':"<>?,./`~'.split('');
|
||||
let animationFrameId: number | null = null;
|
||||
let resetTimeoutId: number | null = null;
|
||||
|
||||
// 将字符串拆分为单个字符的span
|
||||
function letterize() {
|
||||
const container = document.querySelector('.loading-word');
|
||||
if (!container) return;
|
||||
|
||||
// 清除现有内容
|
||||
container.innerHTML = '';
|
||||
|
||||
// 为每个字符创建span
|
||||
for (let i = 0; i < props.text.length; i++) {
|
||||
const span = document.createElement('span');
|
||||
span.setAttribute('data-orig', props.text[i]);
|
||||
span.textContent = '-';
|
||||
span.className = `char${i+1}`;
|
||||
container.appendChild(span);
|
||||
}
|
||||
|
||||
// 获取所有span元素
|
||||
characters.value = Array.from(container.querySelectorAll('span'));
|
||||
}
|
||||
|
||||
// 获取随机字符
|
||||
function getRandomChar() {
|
||||
return chars[Math.floor(Math.random() * chars.length)];
|
||||
}
|
||||
|
||||
// 动画循环
|
||||
function animationLoop() {
|
||||
let currentCycle = 0;
|
||||
let currentLetterIndex = 0;
|
||||
let isAnimationDone = false;
|
||||
|
||||
function loop() {
|
||||
// 为未完成的字符设置随机字符和不透明度
|
||||
for (let i = currentLetterIndex; i < characters.value.length; i++) {
|
||||
const char = characters.value[i];
|
||||
if (!char.classList.contains('done')) {
|
||||
char.textContent = getRandomChar();
|
||||
char.style.opacity = Math.random().toString();
|
||||
}
|
||||
}
|
||||
|
||||
if (currentCycle < cycleCount) {
|
||||
// 继续当前周期
|
||||
currentCycle++;
|
||||
} else if (currentLetterIndex < characters.value.length) {
|
||||
// 当前周期结束,显示下一个字符的原始值
|
||||
const currentChar = characters.value[currentLetterIndex];
|
||||
currentChar.textContent = currentChar.getAttribute('data-orig') || '';
|
||||
currentChar.style.opacity = '1';
|
||||
currentChar.classList.add('done');
|
||||
currentLetterIndex++;
|
||||
currentCycle = 0;
|
||||
} else {
|
||||
// 所有字符都已显示
|
||||
isAnimationDone = true;
|
||||
isDone.value = true;
|
||||
}
|
||||
|
||||
if (!isAnimationDone) {
|
||||
animationFrameId = requestAnimationFrame(loop);
|
||||
} else {
|
||||
// 等待一段时间后重置动画
|
||||
resetTimeoutId = window.setTimeout(() => {
|
||||
reset();
|
||||
}, 750);
|
||||
}
|
||||
}
|
||||
|
||||
loop();
|
||||
}
|
||||
|
||||
// 重置动画
|
||||
function reset() {
|
||||
isDone.value = false;
|
||||
|
||||
for (const char of characters.value) {
|
||||
char.textContent = char.getAttribute('data-orig') || '';
|
||||
char.classList.remove('done');
|
||||
}
|
||||
|
||||
animationLoop();
|
||||
}
|
||||
|
||||
// 清理所有定时器
|
||||
function cleanup() {
|
||||
if (animationFrameId !== null) {
|
||||
cancelAnimationFrame(animationFrameId);
|
||||
animationFrameId = null;
|
||||
}
|
||||
|
||||
if (resetTimeoutId !== null) {
|
||||
clearTimeout(resetTimeoutId);
|
||||
resetTimeoutId = null;
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
letterize();
|
||||
animationLoop();
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
cleanup();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="loading-screen">
|
||||
<div class="loading-word"></div>
|
||||
<div class="overlay"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.loading-screen {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: var(--voidraft-bg-gradient, radial-gradient(#222922, #000500));
|
||||
z-index: 1000;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-family: var(--voidraft-mono-font, monospace),serif;
|
||||
}
|
||||
|
||||
.loading-word {
|
||||
color: var(--voidraft-loading-color, #fff);
|
||||
font-size: 2.5em;
|
||||
height: 2.5em;
|
||||
line-height: 2.5em;
|
||||
text-align: center;
|
||||
text-shadow: var(--voidraft-loading-glow, 0 0 10px rgba(50, 255, 50, 0.5), 0 0 5px rgba(100, 255, 100, 0.5));
|
||||
}
|
||||
|
||||
.loading-word span {
|
||||
display: inline-block;
|
||||
transform: translateX(100%) scale(0.9);
|
||||
transition: transform 500ms;
|
||||
}
|
||||
|
||||
.loading-word .done {
|
||||
color: var(--voidraft-loading-done-color, #6f6);
|
||||
transform: translateX(0) scale(1);
|
||||
}
|
||||
|
||||
.overlay {
|
||||
background-image: var(--voidraft-loading-overlay, linear-gradient(transparent 0%, rgba(10, 16, 10, 0.5) 50%));
|
||||
background-size: 1000px 2px;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
</style>
|
@@ -1,10 +1,10 @@
|
||||
<template>
|
||||
<div class="linux-titlebar" style="--wails-draggable:drag" @contextmenu.prevent @mouseenter="checkMaximizedState" @mouseup="checkMaximizedState">
|
||||
<div class="linux-titlebar" style="--wails-draggable:drag" @contextmenu.prevent>
|
||||
<div class="titlebar-content" @dblclick="toggleMaximize" @contextmenu.prevent>
|
||||
<div class="titlebar-icon">
|
||||
<img src="/appicon.png" alt="voidraft" />
|
||||
</div>
|
||||
<div class="titlebar-title">voidraft</div>
|
||||
<div class="titlebar-title">{{ titleText }}</div>
|
||||
</div>
|
||||
|
||||
<div class="titlebar-controls" style="--wails-draggable:no-drag" @contextmenu.prevent>
|
||||
@@ -46,12 +46,15 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, onUnmounted } from 'vue';
|
||||
import { ref, onMounted, onUnmounted, computed } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import * as runtime from '@wailsio/runtime';
|
||||
import { useWindowStore } from '@/stores/windowStore';
|
||||
import { useDocumentStore } from '@/stores/documentStore';
|
||||
|
||||
const { t } = useI18n();
|
||||
const isMaximized = ref(false);
|
||||
const documentStore = useDocumentStore();
|
||||
|
||||
const minimizeWindow = async () => {
|
||||
try {
|
||||
@@ -96,6 +99,12 @@ const checkMaximizedState = async () => {
|
||||
}
|
||||
};
|
||||
|
||||
// 计算标题文本
|
||||
const titleText = computed(() => {
|
||||
const currentDoc = documentStore.currentDocument;
|
||||
return currentDoc ? `voidraft - ${currentDoc.title}` : 'voidraft';
|
||||
});
|
||||
|
||||
onMounted(async () => {
|
||||
await checkMaximizedState();
|
||||
|
||||
@@ -285,4 +294,4 @@ onUnmounted(() => {
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</style>
|
@@ -44,19 +44,22 @@
|
||||
</div>
|
||||
|
||||
<div class="titlebar-content" @dblclick="toggleMaximize" @contextmenu.prevent>
|
||||
<div class="titlebar-title">voidraft</div>
|
||||
<div class="titlebar-title">{{ titleText }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, onUnmounted } from 'vue';
|
||||
import { ref, onMounted, onUnmounted, computed } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import * as runtime from '@wailsio/runtime';
|
||||
import { useWindowStore } from '@/stores/windowStore';
|
||||
import { useDocumentStore } from '@/stores/documentStore';
|
||||
|
||||
const { t } = useI18n();
|
||||
const isMaximized = ref(false);
|
||||
const showControlIcons = ref(false);
|
||||
const documentStore = useDocumentStore();
|
||||
|
||||
const minimizeWindow = async () => {
|
||||
try {
|
||||
@@ -101,6 +104,12 @@ const checkMaximizedState = async () => {
|
||||
}
|
||||
};
|
||||
|
||||
// 计算标题文本
|
||||
const titleText = computed(() => {
|
||||
const currentDoc = documentStore.currentDocument;
|
||||
return currentDoc ? `voidraft - ${currentDoc.title}` : 'voidraft';
|
||||
});
|
||||
|
||||
onMounted(async () => {
|
||||
await checkMaximizedState();
|
||||
|
||||
@@ -259,4 +268,4 @@ onUnmounted(() => {
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</style>
|
@@ -1,11 +1,10 @@
|
||||
<template>
|
||||
<div class="windows-titlebar" style="--wails-draggable:drag" @contextmenu.prevent @mouseenter="checkMaximizedState"
|
||||
@mouseup="checkMaximizedState">
|
||||
<div class="windows-titlebar" style="--wails-draggable:drag" @contextmenu.prevent>
|
||||
<div class="titlebar-content" @dblclick="toggleMaximize" @contextmenu.prevent>
|
||||
<div class="titlebar-icon">
|
||||
<img src="/appicon.png" alt="voidraft"/>
|
||||
</div>
|
||||
<div class="titlebar-title">voidraft</div>
|
||||
<div class="titlebar-title">{{ titleText }}</div>
|
||||
</div>
|
||||
|
||||
<div class="titlebar-controls" style="--wails-draggable:no-drag" @contextmenu.prevent>
|
||||
@@ -40,13 +39,22 @@
|
||||
import {computed, onMounted, onUnmounted, ref} from 'vue';
|
||||
import {useI18n} from 'vue-i18n';
|
||||
import * as runtime from '@wailsio/runtime';
|
||||
import { useWindowStore } from '@/stores/windowStore';
|
||||
import { useDocumentStore } from '@/stores/documentStore';
|
||||
|
||||
const {t} = useI18n();
|
||||
const isMaximized = ref(false);
|
||||
const documentStore = useDocumentStore();
|
||||
|
||||
// 计算属性用于图标,减少重复渲染
|
||||
const maximizeIcon = computed(() => isMaximized.value ? '' : '');
|
||||
|
||||
// 计算标题文本
|
||||
const titleText = computed(() => {
|
||||
const currentDoc = documentStore.currentDocument;
|
||||
return currentDoc ? `voidraft - ${currentDoc.title}` : 'voidraft';
|
||||
});
|
||||
|
||||
const minimizeWindow = async () => {
|
||||
try {
|
||||
await runtime.Window.Minimise();
|
||||
@@ -239,4 +247,4 @@ onUnmounted(() => {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</style>
|
@@ -1,11 +1,13 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted, onUnmounted, nextTick } from 'vue';
|
||||
import { useDocumentStore } from '@/stores/documentStore';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import type { Document } from '@/../bindings/voidraft/internal/models/models';
|
||||
import {computed, nextTick, onMounted, onUnmounted, ref} from 'vue';
|
||||
import {useDocumentStore} from '@/stores/documentStore';
|
||||
import {useI18n} from 'vue-i18n';
|
||||
import type {Document} from '@/../bindings/voidraft/internal/models/models';
|
||||
import {useWindowStore} from "@/stores/windowStore";
|
||||
|
||||
const documentStore = useDocumentStore();
|
||||
const { t } = useI18n();
|
||||
const windowStore = useWindowStore();
|
||||
const {t} = useI18n();
|
||||
|
||||
// 组件状态
|
||||
const showMenu = ref(false);
|
||||
@@ -15,30 +17,33 @@ const editingId = ref<number | null>(null);
|
||||
const editingTitle = ref('');
|
||||
const editInputRef = ref<HTMLInputElement>();
|
||||
const deleteConfirmId = ref<number | null>(null);
|
||||
// 添加错误提示状态
|
||||
const alreadyOpenDocId = ref<number | null>(null);
|
||||
const errorMessageTimer = ref<number | null>(null);
|
||||
|
||||
// 过滤后的文档列表 + 创建选项
|
||||
const filteredItems = computed(() => {
|
||||
const docs = documentStore.documentList;
|
||||
const query = inputValue.value.trim();
|
||||
|
||||
|
||||
if (!query) {
|
||||
return docs;
|
||||
}
|
||||
|
||||
|
||||
// 过滤匹配的文档
|
||||
const filtered = docs.filter(doc =>
|
||||
doc.title.toLowerCase().includes(query.toLowerCase())
|
||||
const filtered = docs.filter(doc =>
|
||||
doc.title.toLowerCase().includes(query.toLowerCase())
|
||||
);
|
||||
|
||||
|
||||
// 如果输入的不是已存在文档的完整标题,添加创建选项
|
||||
const exactMatch = docs.some(doc => doc.title.toLowerCase() === query.toLowerCase());
|
||||
if (!exactMatch && query.length > 0) {
|
||||
return [
|
||||
{ id: -1, title: t('toolbar.createDocument') + ` "${query}"`, isCreateOption: true } as any,
|
||||
{id: -1, title: t('toolbar.createDocument') + ` "${query}"`, isCreateOption: true} as any,
|
||||
...filtered
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
return filtered;
|
||||
});
|
||||
|
||||
@@ -65,6 +70,18 @@ const closeMenu = () => {
|
||||
editingId.value = null;
|
||||
editingTitle.value = '';
|
||||
deleteConfirmId.value = null;
|
||||
|
||||
// 清除错误状态和定时器
|
||||
clearErrorMessage();
|
||||
};
|
||||
|
||||
// 清除错误提示和定时器
|
||||
const clearErrorMessage = () => {
|
||||
if (errorMessageTimer.value) {
|
||||
clearTimeout(errorMessageTimer.value);
|
||||
errorMessageTimer.value = null;
|
||||
}
|
||||
alreadyOpenDocId.value = null;
|
||||
};
|
||||
|
||||
// 切换菜单
|
||||
@@ -90,6 +107,23 @@ const selectItem = async (item: any) => {
|
||||
// 选择文档
|
||||
const selectDoc = async (doc: Document) => {
|
||||
try {
|
||||
const hasOpen = await windowStore.isDocumentWindowOpen(doc.id);
|
||||
if (hasOpen) {
|
||||
// 设置错误状态并启动定时器
|
||||
alreadyOpenDocId.value = doc.id;
|
||||
|
||||
// 清除之前的定时器(如果存在)
|
||||
if (errorMessageTimer.value) {
|
||||
clearTimeout(errorMessageTimer.value);
|
||||
}
|
||||
|
||||
// 设置新的定时器,3秒后清除错误信息
|
||||
errorMessageTimer.value = window.setTimeout(() => {
|
||||
alreadyOpenDocId.value = null;
|
||||
errorMessageTimer.value = null;
|
||||
}, 3000);
|
||||
return;
|
||||
}
|
||||
const success = await documentStore.openDocument(doc.id);
|
||||
if (success) {
|
||||
closeMenu();
|
||||
@@ -108,7 +142,7 @@ const validateTitle = (title: string): string | null => {
|
||||
return t('toolbar.documentNameRequired');
|
||||
}
|
||||
if (title.trim().length > MAX_TITLE_LENGTH) {
|
||||
return t('toolbar.documentNameTooLong', { max: MAX_TITLE_LENGTH });
|
||||
return t('toolbar.documentNameTooLong', {max: MAX_TITLE_LENGTH});
|
||||
}
|
||||
return null;
|
||||
};
|
||||
@@ -118,7 +152,6 @@ const createDoc = async (title: string) => {
|
||||
const trimmedTitle = title.trim();
|
||||
const error = validateTitle(trimmedTitle);
|
||||
if (error) {
|
||||
console.error('创建文档失败:', error);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -165,16 +198,26 @@ const saveEdit = async () => {
|
||||
editingTitle.value = '';
|
||||
};
|
||||
|
||||
// 在新窗口打开文档
|
||||
const openInNewWindow = async (doc: Document, event: Event) => {
|
||||
event.stopPropagation();
|
||||
try {
|
||||
await documentStore.openDocumentInNewWindow(doc.id);
|
||||
} catch (error) {
|
||||
console.error('Failed to open document in new window:', error);
|
||||
}
|
||||
};
|
||||
|
||||
// 处理删除 - 简化确认机制
|
||||
const handleDelete = async (doc: Document, event: Event) => {
|
||||
event.stopPropagation();
|
||||
|
||||
|
||||
if (deleteConfirmId.value === doc.id) {
|
||||
// 确认删除
|
||||
try {
|
||||
await documentStore.deleteDocument(doc.id);
|
||||
await documentStore.updateDocuments();
|
||||
|
||||
|
||||
// 如果删除的是当前文档,切换到第一个文档
|
||||
if (documentStore.currentDocument?.id === doc.id && documentStore.documentList.length > 0) {
|
||||
const firstDoc = documentStore.documentList[0];
|
||||
@@ -190,7 +233,7 @@ const handleDelete = async (doc: Document, event: Event) => {
|
||||
// 进入确认状态
|
||||
deleteConfirmId.value = doc.id;
|
||||
editingId.value = null; // 清除编辑状态
|
||||
|
||||
|
||||
// 3秒后自动取消确认状态
|
||||
setTimeout(() => {
|
||||
if (deleteConfirmId.value === doc.id) {
|
||||
@@ -203,11 +246,11 @@ const handleDelete = async (doc: Document, event: Event) => {
|
||||
// 格式化时间
|
||||
const formatTime = (dateString: string | null) => {
|
||||
if (!dateString) return t('toolbar.unknownTime');
|
||||
|
||||
|
||||
try {
|
||||
const date = new Date(dateString);
|
||||
if (isNaN(date.getTime())) return t('toolbar.invalidDate');
|
||||
|
||||
|
||||
// 根据当前语言显示时间格式
|
||||
const locale = t('locale') === 'zh-CN' ? 'zh-CN' : 'en-US';
|
||||
return date.toLocaleString(locale, {
|
||||
@@ -285,6 +328,10 @@ onMounted(() => {
|
||||
onUnmounted(() => {
|
||||
document.removeEventListener('click', handleClickOutside);
|
||||
document.removeEventListener('keydown', handleKeydown);
|
||||
// 清理定时器
|
||||
if (errorMessageTimer.value) {
|
||||
clearTimeout(errorMessageTimer.value);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -300,79 +347,103 @@ onUnmounted(() => {
|
||||
<div v-if="showMenu" class="doc-menu">
|
||||
<!-- 输入框 -->
|
||||
<div class="input-box">
|
||||
<input
|
||||
ref="inputRef"
|
||||
v-model="inputValue"
|
||||
type="text"
|
||||
class="main-input"
|
||||
:placeholder="t('toolbar.searchOrCreateDocument')"
|
||||
:maxlength="MAX_TITLE_LENGTH"
|
||||
@keydown="handleInputKeydown"
|
||||
<input
|
||||
ref="inputRef"
|
||||
v-model="inputValue"
|
||||
type="text"
|
||||
class="main-input"
|
||||
:placeholder="t('toolbar.searchOrCreateDocument')"
|
||||
:maxlength="MAX_TITLE_LENGTH"
|
||||
@keydown="handleInputKeydown"
|
||||
/>
|
||||
<svg class="input-icon" xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<svg class="input-icon" xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24"
|
||||
fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<circle cx="11" cy="11" r="8"></circle>
|
||||
<path d="m21 21-4.35-4.35"></path>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- 项目列表 -->
|
||||
<div class="item-list">
|
||||
<div
|
||||
v-for="item in filteredItems"
|
||||
:key="item.id"
|
||||
class="list-item"
|
||||
:class="{
|
||||
<div
|
||||
v-for="item in filteredItems"
|
||||
:key="item.id"
|
||||
class="list-item"
|
||||
:class="{
|
||||
'active': !item.isCreateOption && documentStore.currentDocument?.id === item.id,
|
||||
'create-item': item.isCreateOption
|
||||
}"
|
||||
@click="selectItem(item)"
|
||||
@click="selectItem(item)"
|
||||
>
|
||||
<!-- 创建选项 -->
|
||||
<div v-if="item.isCreateOption" class="create-option">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24" fill="none"
|
||||
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M5 12h14"></path>
|
||||
<path d="M12 5v14"></path>
|
||||
</svg>
|
||||
<span>{{ item.title }}</span>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- 文档项 -->
|
||||
<div v-else class="doc-item-content">
|
||||
<!-- 普通显示 -->
|
||||
<div v-if="editingId !== item.id" class="doc-info">
|
||||
<div class="doc-title">{{ item.title }}</div>
|
||||
<div class="doc-date">{{ formatTime(item.updatedAt) }}</div>
|
||||
<!-- 根据状态显示错误信息或时间 -->
|
||||
<div v-if="alreadyOpenDocId === item.id" class="doc-error">
|
||||
{{ t('toolbar.alreadyOpenInNewWindow') }}
|
||||
</div>
|
||||
<div v-else class="doc-date">{{ formatTime(item.updatedAt) }}</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- 编辑状态 -->
|
||||
<div v-else class="doc-edit">
|
||||
<input
|
||||
:ref="el => editInputRef = el as HTMLInputElement"
|
||||
v-model="editingTitle"
|
||||
type="text"
|
||||
class="edit-input"
|
||||
:maxlength="MAX_TITLE_LENGTH"
|
||||
@keydown="handleEditKeydown"
|
||||
@blur="saveEdit"
|
||||
@click.stop
|
||||
:ref="el => editInputRef = el as HTMLInputElement"
|
||||
v-model="editingTitle"
|
||||
type="text"
|
||||
class="edit-input"
|
||||
:maxlength="MAX_TITLE_LENGTH"
|
||||
@keydown="handleEditKeydown"
|
||||
@blur="saveEdit"
|
||||
@click.stop
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
<div v-if="editingId !== item.id" class="doc-actions">
|
||||
<!-- 只有非当前文档才显示在新窗口打开按钮 -->
|
||||
<button
|
||||
v-if="documentStore.currentDocument?.id !== item.id"
|
||||
class="action-btn"
|
||||
@click="openInNewWindow(item, $event)"
|
||||
:title="t('toolbar.openInNewWindow')"
|
||||
>
|
||||
<svg width="12" height="12" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"
|
||||
fill="currentColor">
|
||||
<path
|
||||
d="M172.8 1017.6c-89.6 0-166.4-70.4-166.4-166.4V441.6c0-89.6 70.4-166.4 166.4-166.4h416c89.6 0 166.4 70.4 166.4 166.4v416c0 89.6-70.4 166.4-166.4 166.4l-416-6.4z m0-659.2c-51.2 0-89.6 38.4-89.6 89.6v416c0 51.2 38.4 89.6 89.6 89.6h416c51.2 0 89.6-38.4 89.6-89.6V441.6c0-51.2-38.4-89.6-89.6-89.6H172.8z"></path>
|
||||
<path
|
||||
d="M851.2 19.2H435.2C339.2 19.2 268.8 96 268.8 185.6v25.6h70.4v-25.6c0-51.2 38.4-89.6 89.6-89.6h409.6c51.2 0 89.6 38.4 89.6 89.6v409.6c0 51.2-38.4 89.6-89.6 89.6h-38.4V768h51.2c96 0 166.4-76.8 166.4-166.4V185.6c0-96-76.8-166.4-166.4-166.4z"></path>
|
||||
</svg>
|
||||
</button>
|
||||
<button class="action-btn" @click="startRename(item, $event)" :title="t('toolbar.rename')">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24" fill="none"
|
||||
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M17 3a2.85 2.83 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5Z"></path>
|
||||
</svg>
|
||||
</button>
|
||||
<button
|
||||
v-if="documentStore.documentList.length > 1 && item.id !== 1"
|
||||
class="action-btn delete-btn"
|
||||
:class="{ 'delete-confirm': deleteConfirmId === item.id }"
|
||||
@click="handleDelete(item, $event)"
|
||||
:title="deleteConfirmId === item.id ? t('toolbar.confirmDelete') : t('toolbar.delete')"
|
||||
<button
|
||||
v-if="documentStore.documentList.length > 1 && item.id !== 1"
|
||||
class="action-btn delete-btn"
|
||||
:class="{ 'delete-confirm': deleteConfirmId === item.id }"
|
||||
@click="handleDelete(item, $event)"
|
||||
:title="deleteConfirmId === item.id ? t('toolbar.confirmDelete') : t('toolbar.delete')"
|
||||
>
|
||||
<svg v-if="deleteConfirmId !== item.id" xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<svg v-if="deleteConfirmId !== item.id" xmlns="http://www.w3.org/2000/svg" width="12" height="12"
|
||||
viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
|
||||
stroke-linejoin="round">
|
||||
<polyline points="3,6 5,6 21,6"></polyline>
|
||||
<path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path>
|
||||
</svg>
|
||||
@@ -381,12 +452,12 @@ onUnmounted(() => {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- 空状态 -->
|
||||
<div v-if="filteredItems.length === 0" class="empty">
|
||||
{{ t('toolbar.noDocumentFound') }}
|
||||
</div>
|
||||
|
||||
|
||||
<!-- 加载状态 -->
|
||||
<div v-if="documentStore.isLoading" class="loading">
|
||||
{{ t('toolbar.loading') }}
|
||||
@@ -399,7 +470,7 @@ onUnmounted(() => {
|
||||
<style scoped lang="scss">
|
||||
.document-selector {
|
||||
position: relative;
|
||||
|
||||
|
||||
.doc-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
@@ -411,30 +482,30 @@ onUnmounted(() => {
|
||||
gap: 3px;
|
||||
padding: 2px 4px;
|
||||
border-radius: 3px;
|
||||
|
||||
|
||||
&:hover {
|
||||
background-color: var(--border-color);
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
|
||||
.doc-name {
|
||||
max-width: 80px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
|
||||
.arrow {
|
||||
font-size: 8px;
|
||||
margin-left: 2px;
|
||||
transition: transform 0.2s ease;
|
||||
|
||||
|
||||
&.open {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.doc-menu {
|
||||
position: absolute;
|
||||
bottom: 100%;
|
||||
@@ -448,12 +519,12 @@ onUnmounted(() => {
|
||||
z-index: 1000;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
|
||||
overflow: hidden;
|
||||
|
||||
|
||||
.input-box {
|
||||
position: relative;
|
||||
padding: 8px;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
|
||||
|
||||
.main-input {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
@@ -464,16 +535,16 @@ onUnmounted(() => {
|
||||
font-size: 11px;
|
||||
color: var(--text-primary);
|
||||
outline: none;
|
||||
|
||||
|
||||
&:focus {
|
||||
border-color: var(--text-muted);
|
||||
}
|
||||
|
||||
|
||||
&::placeholder {
|
||||
color: var(--text-muted);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.input-icon {
|
||||
position: absolute;
|
||||
left: 14px;
|
||||
@@ -483,34 +554,34 @@ onUnmounted(() => {
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.item-list {
|
||||
max-height: 240px;
|
||||
overflow-y: auto;
|
||||
|
||||
.list-item {
|
||||
cursor: pointer;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
|
||||
&:hover {
|
||||
background-color: var(--bg-hover);
|
||||
}
|
||||
|
||||
|
||||
.list-item {
|
||||
cursor: pointer;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
|
||||
&:hover {
|
||||
background-color: var(--bg-hover);
|
||||
}
|
||||
|
||||
&.active {
|
||||
background-color: var(--selection-bg);
|
||||
|
||||
|
||||
.doc-item-content .doc-info {
|
||||
.doc-title {
|
||||
color: var(--selection-text);
|
||||
}
|
||||
|
||||
.doc-date {
|
||||
|
||||
.doc-date, .doc-error {
|
||||
color: var(--selection-text);
|
||||
opacity: 0.7;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
&.create-item {
|
||||
.create-option {
|
||||
display: flex;
|
||||
@@ -519,24 +590,24 @@ onUnmounted(() => {
|
||||
padding: 8px 8px;
|
||||
font-size: 11px;
|
||||
font-weight: normal;
|
||||
|
||||
|
||||
svg {
|
||||
flex-shrink: 0;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.doc-item-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 8px 8px;
|
||||
|
||||
|
||||
.doc-info {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
|
||||
|
||||
.doc-title {
|
||||
font-size: 12px;
|
||||
margin-bottom: 2px;
|
||||
@@ -545,17 +616,24 @@ onUnmounted(() => {
|
||||
white-space: nowrap;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
|
||||
.doc-date {
|
||||
font-size: 10px;
|
||||
color: var(--text-muted);
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.doc-error {
|
||||
font-size: 10px;
|
||||
color: var(--text-danger);
|
||||
font-weight: 500;
|
||||
animation: fadeInOut 3s forwards;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.doc-edit {
|
||||
flex: 1;
|
||||
|
||||
|
||||
.edit-input {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
@@ -566,19 +644,19 @@ onUnmounted(() => {
|
||||
font-size: 11px;
|
||||
color: var(--text-primary);
|
||||
outline: none;
|
||||
|
||||
|
||||
&:focus {
|
||||
border-color: var(--text-muted);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.doc-actions {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s ease;
|
||||
|
||||
|
||||
.action-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
@@ -591,31 +669,31 @@ onUnmounted(() => {
|
||||
justify-content: center;
|
||||
min-width: 20px;
|
||||
min-height: 20px;
|
||||
|
||||
|
||||
svg {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
}
|
||||
|
||||
|
||||
&:hover {
|
||||
background-color: var(--border-color);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
|
||||
&.delete-btn:hover {
|
||||
color: var(--text-danger);
|
||||
}
|
||||
|
||||
|
||||
&.delete-confirm {
|
||||
background-color: var(--text-danger);
|
||||
color: white;
|
||||
|
||||
|
||||
.confirm-text {
|
||||
font-size: 10px;
|
||||
padding: 0 4px;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
|
||||
&:hover {
|
||||
background-color: var(--text-danger);
|
||||
color: white !important; // 确保确认状态下文字始终为白色
|
||||
@@ -625,12 +703,12 @@ onUnmounted(() => {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
&:hover .doc-actions {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.empty, .loading {
|
||||
padding: 12px 8px;
|
||||
text-align: center;
|
||||
@@ -639,25 +717,37 @@ onUnmounted(() => {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 自定义滚动条
|
||||
.item-list {
|
||||
&::-webkit-scrollbar {
|
||||
width: 4px;
|
||||
}
|
||||
|
||||
|
||||
&::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background-color: var(--border-color);
|
||||
border-radius: 2px;
|
||||
|
||||
|
||||
&:hover {
|
||||
background-color: var(--text-muted);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@keyframes fadeInOut {
|
||||
0% {
|
||||
opacity: 1;
|
||||
}
|
||||
70% {
|
||||
opacity: 1;
|
||||
}
|
||||
100% {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
@@ -4,6 +4,7 @@ import {onMounted, onUnmounted, ref, watch, computed} from 'vue';
|
||||
import {useConfigStore} from '@/stores/configStore';
|
||||
import {useEditorStore} from '@/stores/editorStore';
|
||||
import {useUpdateStore} from '@/stores/updateStore';
|
||||
import {useWindowStore} from '@/stores/windowStore';
|
||||
import * as runtime from '@wailsio/runtime';
|
||||
import {useRouter} from 'vue-router';
|
||||
import BlockLanguageSelector from './BlockLanguageSelector.vue';
|
||||
@@ -15,20 +16,23 @@ import {formatBlockContent} from '@/views/editor/extensions/codeblock/formatCode
|
||||
const editorStore = useEditorStore();
|
||||
const configStore = useConfigStore();
|
||||
const updateStore = useUpdateStore();
|
||||
const windowStore = useWindowStore();
|
||||
const {t} = useI18n();
|
||||
const router = useRouter();
|
||||
|
||||
// 当前块是否支持格式化的响应式状态
|
||||
const canFormatCurrentBlock = ref(false);
|
||||
|
||||
// 窗口置顶状态管理
|
||||
// 窗口置顶状态管理(仅当前窗口,不同步到配置文件)
|
||||
const isCurrentWindowOnTop = ref(false);
|
||||
|
||||
const setWindowAlwaysOnTop = async (isTop: boolean) => {
|
||||
await runtime.Window.SetAlwaysOnTop(isTop);
|
||||
};
|
||||
|
||||
const toggleAlwaysOnTop = async () => {
|
||||
await configStore.toggleAlwaysOnTop();
|
||||
await runtime.Window.SetAlwaysOnTop(configStore.config.general.alwaysOnTop);
|
||||
isCurrentWindowOnTop.value = !isCurrentWindowOnTop.value;
|
||||
await runtime.Window.SetAlwaysOnTop(isCurrentWindowOnTop.value);
|
||||
};
|
||||
|
||||
// 跳转到设置页面
|
||||
@@ -136,20 +140,12 @@ onUnmounted(() => {
|
||||
cleanupListeners = [];
|
||||
});
|
||||
|
||||
// 监听置顶设置变化
|
||||
watch(
|
||||
() => configStore.config.general.alwaysOnTop,
|
||||
async (newValue) => {
|
||||
if (isLoaded.value) {
|
||||
await runtime.Window.SetAlwaysOnTop(newValue);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// 组件加载后应用置顶设置
|
||||
// 组件加载后初始化置顶状态
|
||||
watch(isLoaded, async (loaded) => {
|
||||
if (loaded && configStore.config.general.alwaysOnTop) {
|
||||
await setWindowAlwaysOnTop(true);
|
||||
if (loaded) {
|
||||
// 初始化时从配置文件读取置顶状态
|
||||
isCurrentWindowOnTop.value = configStore.config.general.alwaysOnTop;
|
||||
await setWindowAlwaysOnTop(isCurrentWindowOnTop.value);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -197,7 +193,7 @@ const updateButtonTitle = computed(() => {
|
||||
</span>
|
||||
|
||||
<!-- 文档选择器 -->
|
||||
<DocumentSelector/>
|
||||
<DocumentSelector v-if="windowStore.isMainWindow"/>
|
||||
|
||||
<!-- 块语言选择器 -->
|
||||
<BlockLanguageSelector/>
|
||||
@@ -265,7 +261,7 @@ const updateButtonTitle = computed(() => {
|
||||
<!-- 窗口置顶图标按钮 -->
|
||||
<div
|
||||
class="pin-button"
|
||||
:class="{ 'active': configStore.config.general.alwaysOnTop }"
|
||||
:class="{ 'active': isCurrentWindowOnTop }"
|
||||
:title="t('toolbar.alwaysOnTop')"
|
||||
@click="toggleAlwaysOnTop"
|
||||
>
|
||||
@@ -276,7 +272,7 @@ const updateButtonTitle = computed(() => {
|
||||
</div>
|
||||
|
||||
|
||||
<button class="settings-btn" :title="t('toolbar.settings')" @click="goToSettings">
|
||||
<button v-if="windowStore.isMainWindow" class="settings-btn" :title="t('toolbar.settings')" @click="goToSettings">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none"
|
||||
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<circle cx="12" cy="12" r="3"></circle>
|
||||
|
@@ -29,6 +29,8 @@ export default {
|
||||
delete: 'Delete',
|
||||
confirm: 'Confirm',
|
||||
confirmDelete: 'Click again to confirm delete',
|
||||
openInNewWindow: 'Open in New Window',
|
||||
alreadyOpenInNewWindow: 'Already open in another window',
|
||||
documentNameTooLong: 'Document name cannot exceed {max} characters',
|
||||
documentNameRequired: 'Document name cannot be empty',
|
||||
cannotDeleteLastDocument: 'Cannot delete the last document',
|
||||
@@ -120,9 +122,11 @@ export default {
|
||||
keyBindings: 'Key Bindings',
|
||||
updates: 'Updates',
|
||||
reset: 'Reset',
|
||||
apply: 'Apply',
|
||||
cancel: 'Cancel',
|
||||
dangerZone: 'Danger Zone',
|
||||
resetAllSettings: 'Reset All Settings',
|
||||
confirmReset: 'Click again to confirm reset',
|
||||
confirmReset: 'Confirm the reset?',
|
||||
globalHotkey: 'Global Keyboard Shortcuts',
|
||||
enableGlobalHotkey: 'Enable Global Hotkeys',
|
||||
window: 'Window/Application',
|
||||
@@ -143,6 +147,60 @@ export default {
|
||||
fontFamilyDescription: 'Choose editor font family',
|
||||
fontWeight: 'Font Weight',
|
||||
fontWeightDescription: 'Set the thickness of the font',
|
||||
fontWeights: {
|
||||
'100': 'Thin (100)',
|
||||
'200': 'Extra Light (200)',
|
||||
'300': 'Light (300)',
|
||||
'normal': 'Regular (400)',
|
||||
'500': 'Medium (500)',
|
||||
'600': 'Semi Bold (600)',
|
||||
'bold': 'Bold (700)',
|
||||
'800': 'Extra Bold (800)',
|
||||
'900': 'Black (900)'
|
||||
},
|
||||
customThemeColors: 'Custom Theme Colors',
|
||||
resetToDefault: 'Reset to Default',
|
||||
colorValue: 'Color Value',
|
||||
themeColors: {
|
||||
basic: 'Basic Colors',
|
||||
text: 'Text Colors',
|
||||
syntax: 'Syntax Highlighting',
|
||||
interface: 'Interface Elements',
|
||||
border: 'Borders & Dividers',
|
||||
search: 'Search & Matching',
|
||||
background: 'Main Background',
|
||||
backgroundSecondary: 'Secondary Background',
|
||||
surface: 'Panel Background',
|
||||
foreground: 'Primary Text',
|
||||
foregroundSecondary: 'Secondary Text',
|
||||
comment: 'Comments',
|
||||
keyword: 'Keywords',
|
||||
string: 'Strings',
|
||||
function: 'Functions',
|
||||
number: 'Numbers',
|
||||
operator: 'Operators',
|
||||
variable: 'Variables',
|
||||
type: 'Types',
|
||||
cursor: 'Cursor',
|
||||
selection: 'Selection Background',
|
||||
selectionBlur: 'Unfocused Selection',
|
||||
activeLine: 'Active Line Highlight',
|
||||
lineNumber: 'Line Numbers',
|
||||
activeLineNumber: 'Active Line Number',
|
||||
borderColor: 'Border Color',
|
||||
borderLight: 'Light Border',
|
||||
searchMatch: 'Search Match',
|
||||
matchingBracket: 'Matching Bracket'
|
||||
},
|
||||
fontFamilies: {
|
||||
harmonyOS: 'HarmonyOS Sans',
|
||||
microsoftYahei: 'Microsoft YaHei',
|
||||
pingfang: 'PingFang SC',
|
||||
jetbrainsMono: 'JetBrains Mono',
|
||||
firaCode: 'Fira Code',
|
||||
sourceCodePro: 'Source Code Pro',
|
||||
cascadiaCode: 'Cascadia Code'
|
||||
},
|
||||
lineHeight: 'Line Height',
|
||||
lineHeightDescription: 'Set the spacing between text lines',
|
||||
tabSettings: 'Tab Settings',
|
||||
@@ -176,13 +234,14 @@ export default {
|
||||
categoryTools: 'Tools',
|
||||
configuration: 'Configuration',
|
||||
resetToDefault: 'Reset to Default Configuration',
|
||||
// Keep necessary extension interface translations, configuration items display in English directly
|
||||
},
|
||||
updateNow: 'Update Now',
|
||||
updating: 'Updating...',
|
||||
updateSuccess: 'Update Success',
|
||||
updateSuccessRestartRequired: 'Update has been successfully applied. Please restart the application.',
|
||||
restartNow: 'Restart Now',
|
||||
hotkeyPreview: 'Preview:',
|
||||
none: 'None',
|
||||
},
|
||||
extensions: {
|
||||
rainbowBrackets: {
|
||||
@@ -232,4 +291,4 @@ export default {
|
||||
memory: 'Memory',
|
||||
clickToClean: 'Click to clean memory'
|
||||
}
|
||||
};
|
||||
};
|
@@ -29,6 +29,8 @@ export default {
|
||||
delete: '删除',
|
||||
confirm: '确认',
|
||||
confirmDelete: '再次点击确认删除',
|
||||
openInNewWindow: '在新窗口中打开',
|
||||
alreadyOpenInNewWindow: '已在新窗口中打开',
|
||||
documentNameTooLong: '文档名称不能超过{max}个字符',
|
||||
documentNameRequired: '文档名称不能为空',
|
||||
cannotDeleteLastDocument: '无法删除最后一个文档',
|
||||
@@ -121,9 +123,11 @@ export default {
|
||||
keyBindings: '快捷键',
|
||||
updates: '更新',
|
||||
reset: '重置',
|
||||
apply: '应用',
|
||||
cancel: '取消',
|
||||
dangerZone: '危险操作',
|
||||
resetAllSettings: '重置所有设置',
|
||||
confirmReset: '再次点击确认重置',
|
||||
confirmReset: '确认重置?',
|
||||
globalHotkey: '全局键盘快捷键',
|
||||
enableGlobalHotkey: '启用全局热键',
|
||||
window: '窗口/应用程序',
|
||||
@@ -182,8 +186,63 @@ export default {
|
||||
categoryTools: '工具扩展',
|
||||
configuration: '配置',
|
||||
resetToDefault: '重置为默认配置',
|
||||
// 保留必要的扩展界面翻译,配置项直接显示英文
|
||||
}
|
||||
},
|
||||
fontWeights: {
|
||||
'100': '极细 (100)',
|
||||
'200': '超细 (200)',
|
||||
'300': '细 (300)',
|
||||
'normal': '正常 (400)',
|
||||
'500': '中等 (500)',
|
||||
'600': '半粗 (600)',
|
||||
'bold': '粗体 (700)',
|
||||
'800': '超粗 (800)',
|
||||
'900': '极粗 (900)'
|
||||
},
|
||||
customThemeColors: '自定义主题颜色',
|
||||
resetToDefault: '重置为默认',
|
||||
colorValue: '颜色值',
|
||||
themeColors: {
|
||||
basic: '基础色调',
|
||||
text: '文本颜色',
|
||||
syntax: '语法高亮',
|
||||
interface: '界面元素',
|
||||
border: '边框分割线',
|
||||
search: '搜索匹配',
|
||||
background: '主背景色',
|
||||
backgroundSecondary: '次要背景色',
|
||||
surface: '面板背景',
|
||||
foreground: '主文本色',
|
||||
foregroundSecondary: '次要文本色',
|
||||
comment: '注释色',
|
||||
keyword: '关键字',
|
||||
string: '字符串',
|
||||
function: '函数名',
|
||||
number: '数字',
|
||||
operator: '操作符',
|
||||
variable: '变量',
|
||||
type: '类型',
|
||||
cursor: '光标',
|
||||
selection: '选中背景',
|
||||
selectionBlur: '失焦选中背景',
|
||||
activeLine: '当前行高亮',
|
||||
lineNumber: '行号',
|
||||
activeLineNumber: '活动行号',
|
||||
borderColor: '边框色',
|
||||
borderLight: '浅色边框',
|
||||
searchMatch: '搜索匹配',
|
||||
matchingBracket: '匹配括号'
|
||||
},
|
||||
fontFamilies: {
|
||||
harmonyOS: '鸿蒙字体',
|
||||
microsoftYahei: '微软雅黑',
|
||||
pingfang: '苹方字体',
|
||||
jetbrainsMono: 'JetBrains Mono',
|
||||
firaCode: 'Fira Code',
|
||||
sourceCodePro: 'Source Code Pro',
|
||||
cascadiaCode: 'Cascadia Code'
|
||||
},
|
||||
hotkeyPreview: '预览:',
|
||||
none: '无',
|
||||
},
|
||||
extensions: {
|
||||
rainbowBrackets: {
|
||||
@@ -233,4 +292,4 @@ export default {
|
||||
memory: '内存',
|
||||
clickToClean: '点击清理内存'
|
||||
}
|
||||
};
|
||||
};
|
@@ -73,7 +73,8 @@ const EDITING_CONFIG_KEY_MAP: EditingConfigKeyMap = {
|
||||
|
||||
const APPEARANCE_CONFIG_KEY_MAP: AppearanceConfigKeyMap = {
|
||||
language: 'appearance.language',
|
||||
systemTheme: 'appearance.systemTheme'
|
||||
systemTheme: 'appearance.systemTheme',
|
||||
customTheme: 'appearance.customTheme'
|
||||
} as const;
|
||||
|
||||
const UPDATES_CONFIG_KEY_MAP: UpdatesConfigKeyMap = {
|
||||
@@ -95,26 +96,40 @@ const CONFIG_LIMITS = {
|
||||
tabType: {values: [TabType.TabTypeSpaces, TabType.TabTypeTab], default: TabType.TabTypeSpaces}
|
||||
} as const;
|
||||
|
||||
// 常用字体选项
|
||||
export const FONT_OPTIONS = [
|
||||
// 创建获取翻译的函数
|
||||
export const createFontOptions = (t: (key: string) => string) => [
|
||||
{
|
||||
label: '鸿蒙字体',
|
||||
label: t('settings.fontFamilies.harmonyOS'),
|
||||
value: '"HarmonyOS Sans SC", "HarmonyOS Sans", "Microsoft YaHei", "PingFang SC", "Helvetica Neue", Arial, sans-serif'
|
||||
},
|
||||
{label: '微软雅黑', value: '"Microsoft YaHei", "PingFang SC", "Helvetica Neue", Arial, sans-serif'},
|
||||
{label: '苹方字体', value: '"PingFang SC", "Microsoft YaHei", "Helvetica Neue", Arial, sans-serif'},
|
||||
{
|
||||
label: 'JetBrains Mono',
|
||||
label: t('settings.fontFamilies.microsoftYahei'),
|
||||
value: '"Microsoft YaHei", "PingFang SC", "Helvetica Neue", Arial, sans-serif'
|
||||
},
|
||||
{
|
||||
label: t('settings.fontFamilies.pingfang'),
|
||||
value: '"PingFang SC", "Microsoft YaHei", "Helvetica Neue", Arial, sans-serif'
|
||||
},
|
||||
{
|
||||
label: t('settings.fontFamilies.jetbrainsMono'),
|
||||
value: '"JetBrains Mono", "Fira Code", "SF Mono", Monaco, Consolas, "Ubuntu Mono", monospace'
|
||||
},
|
||||
{label: 'Fira Code', value: '"Fira Code", "JetBrains Mono", "SF Mono", Monaco, Consolas, "Ubuntu Mono", monospace'},
|
||||
{label: 'Source Code Pro', value: '"Source Code Pro", "SF Mono", Monaco, Consolas, "Ubuntu Mono", monospace'},
|
||||
{label: 'Cascadia Code', value: '"Cascadia Code", "SF Mono", Monaco, Consolas, "Ubuntu Mono", monospace'},
|
||||
{
|
||||
label: '系统等宽字体',
|
||||
value: '"SF Mono", Monaco, "Cascadia Code", "Roboto Mono", Consolas, "Courier New", monospace'
|
||||
label: t('settings.fontFamilies.firaCode'),
|
||||
value: '"Fira Code", "JetBrains Mono", "SF Mono", Monaco, Consolas, "Ubuntu Mono", monospace'
|
||||
},
|
||||
{
|
||||
label: t('settings.fontFamilies.sourceCodePro'),
|
||||
value: '"Source Code Pro", "SF Mono", Monaco, Consolas, "Ubuntu Mono", monospace'
|
||||
},
|
||||
{
|
||||
label: t('settings.fontFamilies.cascadiaCode'),
|
||||
value: '"Cascadia Code", "SF Mono", Monaco, Consolas, "Ubuntu Mono", monospace'
|
||||
}
|
||||
] as const;
|
||||
];
|
||||
|
||||
// 常用字体选项
|
||||
export const FONT_OPTIONS = createFontOptions((key) => key);
|
||||
|
||||
// 获取浏览器的默认语言
|
||||
const getBrowserLanguage = (): SupportedLocaleType => {
|
||||
@@ -157,7 +172,77 @@ const DEFAULT_CONFIG: AppConfig = {
|
||||
},
|
||||
appearance: {
|
||||
language: LanguageType.LangZhCN,
|
||||
systemTheme: SystemThemeType.SystemThemeAuto
|
||||
systemTheme: SystemThemeType.SystemThemeAuto,
|
||||
customTheme: {
|
||||
darkTheme: {
|
||||
// 基础色调
|
||||
background: '#252B37',
|
||||
backgroundSecondary: '#213644',
|
||||
surface: '#474747',
|
||||
foreground: '#9BB586',
|
||||
foregroundSecondary: '#9c9c9c',
|
||||
|
||||
// 语法高亮
|
||||
comment: '#6272a4',
|
||||
keyword: '#ff79c6',
|
||||
string: '#f1fa8c',
|
||||
function: '#50fa7b',
|
||||
number: '#bd93f9',
|
||||
operator: '#ff79c6',
|
||||
variable: '#8fbcbb',
|
||||
type: '#8be9fd',
|
||||
|
||||
// 界面元素
|
||||
cursor: '#fff',
|
||||
selection: '#0865a9aa',
|
||||
selectionBlur: '#225377aa',
|
||||
activeLine: 'rgba(255,255,255,0.04)',
|
||||
lineNumber: 'rgba(255,255,255, 0.15)',
|
||||
activeLineNumber: 'rgba(255,255,255, 0.6)',
|
||||
|
||||
// 边框分割线
|
||||
borderColor: '#1e222a',
|
||||
borderLight: 'rgba(255,255,255, 0.1)',
|
||||
|
||||
// 搜索匹配
|
||||
searchMatch: '#8fbcbb',
|
||||
matchingBracket: 'rgba(255,255,255,0.1)'
|
||||
},
|
||||
lightTheme: {
|
||||
// 基础色调
|
||||
background: '#ffffff',
|
||||
backgroundSecondary: '#f1faf1',
|
||||
surface: '#f5f5f5',
|
||||
foreground: '#444d56',
|
||||
foregroundSecondary: '#6a737d',
|
||||
|
||||
// 语法高亮
|
||||
comment: '#6a737d',
|
||||
keyword: '#d73a49',
|
||||
string: '#032f62',
|
||||
function: '#005cc5',
|
||||
number: '#005cc5',
|
||||
operator: '#d73a49',
|
||||
variable: '#24292e',
|
||||
type: '#6f42c1',
|
||||
|
||||
// 界面元素
|
||||
cursor: '#000',
|
||||
selection: '#77baff8c',
|
||||
selectionBlur: '#b2c2ca85',
|
||||
activeLine: '#000000',
|
||||
lineNumber: '#000000',
|
||||
activeLineNumber: '#000000',
|
||||
|
||||
// 边框分割线
|
||||
borderColor: '#dfdfdf',
|
||||
borderLight: '#0000000C',
|
||||
|
||||
// 搜索匹配
|
||||
searchMatch: '#005cc5',
|
||||
matchingBracket: 'rgba(0,0,0,0.1)'
|
||||
}
|
||||
}
|
||||
},
|
||||
updates: {
|
||||
version: "1.0.0",
|
||||
@@ -184,7 +269,7 @@ const DEFAULT_CONFIG: AppConfig = {
|
||||
|
||||
|
||||
export const useConfigStore = defineStore('config', () => {
|
||||
const {locale} = useI18n();
|
||||
const {locale, t} = useI18n();
|
||||
|
||||
// 响应式状态
|
||||
const state = reactive({
|
||||
@@ -192,6 +277,9 @@ export const useConfigStore = defineStore('config', () => {
|
||||
isLoading: false,
|
||||
configLoaded: false
|
||||
});
|
||||
|
||||
// 初始化FONT_OPTIONS国际化版本
|
||||
const localizedFontOptions = computed(() => createFontOptions(t));
|
||||
|
||||
// 计算属性 - 使用工厂函数简化
|
||||
const createLimitComputed = (key: NumberConfigKey) => computed(() => CONFIG_LIMITS[key]);
|
||||
@@ -346,6 +434,51 @@ export const useConfigStore = defineStore('config', () => {
|
||||
await updateAppearanceConfig('systemTheme', systemTheme);
|
||||
};
|
||||
|
||||
// 更新自定义主题方法
|
||||
const updateCustomTheme = async (themeType: 'darkTheme' | 'lightTheme', colorKey: string, colorValue: string): Promise<void> => {
|
||||
// 确保配置已加载
|
||||
if (!state.configLoaded && !state.isLoading) {
|
||||
await initConfig();
|
||||
}
|
||||
|
||||
try {
|
||||
// 深拷贝当前配置
|
||||
const customTheme = JSON.parse(JSON.stringify(state.config.appearance.customTheme));
|
||||
|
||||
// 更新对应主题的颜色值
|
||||
customTheme[themeType][colorKey] = colorValue;
|
||||
|
||||
// 更新整个自定义主题配置到后端
|
||||
await ConfigService.Set(APPEARANCE_CONFIG_KEY_MAP.customTheme, customTheme);
|
||||
|
||||
// 更新前端状态
|
||||
state.config.appearance.customTheme = customTheme;
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
// 设置整个自定义主题配置
|
||||
const setCustomTheme = async (customTheme: any): Promise<void> => {
|
||||
// 确保配置已加载
|
||||
if (!state.configLoaded && !state.isLoading) {
|
||||
await initConfig();
|
||||
}
|
||||
|
||||
try {
|
||||
// 更新整个自定义主题配置到后端
|
||||
await ConfigService.Set(APPEARANCE_CONFIG_KEY_MAP.customTheme, customTheme);
|
||||
|
||||
// 更新前端状态
|
||||
state.config.appearance.customTheme = customTheme;
|
||||
|
||||
// 确保Vue能检测到变化
|
||||
state.config.appearance = { ...state.config.appearance };
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
// 初始化语言设置
|
||||
const initializeLanguage = async (): Promise<void> => {
|
||||
try {
|
||||
@@ -394,7 +527,8 @@ export const useConfigStore = defineStore('config', () => {
|
||||
config: computed(() => state.config),
|
||||
configLoaded: computed(() => state.configLoaded),
|
||||
isLoading: computed(() => state.isLoading),
|
||||
|
||||
localizedFontOptions,
|
||||
|
||||
// 限制常量
|
||||
...limits,
|
||||
|
||||
@@ -408,6 +542,8 @@ export const useConfigStore = defineStore('config', () => {
|
||||
|
||||
// 主题相关方法
|
||||
setSystemTheme,
|
||||
updateCustomTheme,
|
||||
setCustomTheme,
|
||||
|
||||
// 字体大小操作
|
||||
...adjusters.fontSize,
|
||||
|
@@ -1,6 +1,7 @@
|
||||
import {defineStore} from 'pinia';
|
||||
import {computed, ref} from 'vue';
|
||||
import {DocumentService} from '@/../bindings/voidraft/internal/services';
|
||||
import {OpenDocumentWindow} from '@/../bindings/voidraft/internal/services/windowservice';
|
||||
import {Document} from '@/../bindings/voidraft/internal/models/models';
|
||||
|
||||
const SCRATCH_DOCUMENT_ID = 1; // 默认草稿文档ID
|
||||
@@ -50,6 +51,17 @@ export const useDocumentStore = defineStore('document', () => {
|
||||
|
||||
// === 公共API ===
|
||||
|
||||
// 在新窗口中打开文档
|
||||
const openDocumentInNewWindow = async (docId: number): Promise<boolean> => {
|
||||
try {
|
||||
await OpenDocumentWindow(docId);
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('Failed to open document in new window:', error);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
// 更新文档列表
|
||||
const updateDocuments = async () => {
|
||||
try {
|
||||
@@ -186,12 +198,15 @@ export const useDocumentStore = defineStore('document', () => {
|
||||
};
|
||||
|
||||
// === 初始化 ===
|
||||
const initialize = async (): Promise<void> => {
|
||||
const initialize = async (urlDocumentId?: number): Promise<void> => {
|
||||
try {
|
||||
await updateDocuments();
|
||||
|
||||
// 如果存在持久化的文档ID,尝试打开该文档
|
||||
if (currentDocumentId.value && documents.value[currentDocumentId.value]) {
|
||||
// 优先使用URL参数中的文档ID
|
||||
if (urlDocumentId && documents.value[urlDocumentId]) {
|
||||
await openDocument(urlDocumentId);
|
||||
} else if (currentDocumentId.value && documents.value[currentDocumentId.value]) {
|
||||
// 如果URL中没有指定文档ID,则使用持久化的文档ID
|
||||
await openDocument(currentDocumentId.value);
|
||||
} else {
|
||||
// 否则获取第一个文档ID并打开
|
||||
@@ -218,6 +233,7 @@ export const useDocumentStore = defineStore('document', () => {
|
||||
// 方法
|
||||
updateDocuments,
|
||||
openDocument,
|
||||
openDocumentInNewWindow,
|
||||
createNewDocument,
|
||||
saveNewDocument,
|
||||
updateDocumentMetadata,
|
||||
@@ -232,4 +248,4 @@ export const useDocumentStore = defineStore('document', () => {
|
||||
storage: localStorage,
|
||||
pick: ['currentDocumentId']
|
||||
}
|
||||
});
|
||||
});
|
@@ -65,6 +65,9 @@ export const useEditorStore = defineStore('editor', () => {
|
||||
characters: 0,
|
||||
selectedCharacters: 0
|
||||
});
|
||||
|
||||
// 编辑器加载状态
|
||||
const isLoading = ref(false);
|
||||
|
||||
// 异步操作竞态条件控制
|
||||
const operationSequence = ref(0);
|
||||
@@ -434,10 +437,12 @@ export const useEditorStore = defineStore('editor', () => {
|
||||
|
||||
// 加载编辑器
|
||||
const loadEditor = async (documentId: number, content: string) => {
|
||||
// 设置加载状态
|
||||
isLoading.value = true;
|
||||
// 生成新的操作ID
|
||||
const operationId = getNextOperationId();
|
||||
const abortController = new AbortController();
|
||||
|
||||
|
||||
try {
|
||||
// 验证参数
|
||||
if (!documentId) {
|
||||
@@ -500,15 +505,20 @@ export const useEditorStore = defineStore('editor', () => {
|
||||
} catch (error) {
|
||||
if (error instanceof Error && error.message === 'Operation cancelled') {
|
||||
console.log(`Editor loading cancelled for document ${documentId}`);
|
||||
return;
|
||||
} else {
|
||||
console.error('Failed to load editor:', error);
|
||||
}
|
||||
console.error('Failed to load editor:', error);
|
||||
} finally {
|
||||
// 清理操作记录
|
||||
pendingOperations.value.delete(operationId);
|
||||
if (currentLoadingDocumentId.value === documentId) {
|
||||
currentLoadingDocumentId.value = null;
|
||||
}
|
||||
|
||||
// 延迟一段时间后再取消加载状态
|
||||
setTimeout(() => {
|
||||
isLoading.value = false;
|
||||
}, 800);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -684,6 +694,7 @@ export const useEditorStore = defineStore('editor', () => {
|
||||
// 状态
|
||||
currentEditor,
|
||||
documentStats,
|
||||
isLoading,
|
||||
|
||||
// 方法
|
||||
setEditorContainer,
|
||||
|
@@ -1,19 +1,41 @@
|
||||
import { defineStore } from 'pinia';
|
||||
import { computed } from 'vue';
|
||||
import { SystemThemeType } from '@/../bindings/voidraft/internal/models/models';
|
||||
import { useConfigStore } from './configStore';
|
||||
import {defineStore} from 'pinia';
|
||||
import {computed, reactive} from 'vue';
|
||||
import {SystemThemeType} from '@/../bindings/voidraft/internal/models/models';
|
||||
import {useConfigStore} from './configStore';
|
||||
import {useEditorStore} from './editorStore';
|
||||
import {defaultDarkColors} from '@/views/editor/theme/dark';
|
||||
import {defaultLightColors} from '@/views/editor/theme/light';
|
||||
|
||||
/**
|
||||
* 主题管理 Store
|
||||
* 职责:
|
||||
* 职责:管理主题状态和颜色配置
|
||||
*/
|
||||
export const useThemeStore = defineStore('theme', () => {
|
||||
const configStore = useConfigStore();
|
||||
|
||||
// 响应式状态 - 存储当前使用的主题颜色
|
||||
const themeColors = reactive({
|
||||
darkTheme: { ...defaultDarkColors },
|
||||
lightTheme: { ...defaultLightColors }
|
||||
});
|
||||
|
||||
// 计算属性 - 当前选择的主题类型
|
||||
const currentTheme = computed(() =>
|
||||
configStore.config?.appearance?.systemTheme || SystemThemeType.SystemThemeAuto
|
||||
);
|
||||
|
||||
// 初始化主题颜色 - 从配置加载
|
||||
const initializeThemeColors = () => {
|
||||
const customTheme = configStore.config?.appearance?.customTheme;
|
||||
if (customTheme) {
|
||||
if (customTheme.darkTheme) {
|
||||
Object.assign(themeColors.darkTheme, customTheme.darkTheme);
|
||||
}
|
||||
if (customTheme.lightTheme) {
|
||||
Object.assign(themeColors.lightTheme, customTheme.lightTheme);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 应用主题到 DOM
|
||||
const applyThemeToDOM = (theme: SystemThemeType) => {
|
||||
@@ -27,18 +49,97 @@ export const useThemeStore = defineStore('theme', () => {
|
||||
const initializeTheme = () => {
|
||||
const theme = configStore.config?.appearance?.systemTheme || SystemThemeType.SystemThemeAuto;
|
||||
applyThemeToDOM(theme);
|
||||
initializeThemeColors();
|
||||
};
|
||||
|
||||
// 设置主题
|
||||
const setTheme = async (theme: SystemThemeType) => {
|
||||
await configStore.setSystemTheme(theme);
|
||||
applyThemeToDOM(theme);
|
||||
refreshEditorTheme();
|
||||
};
|
||||
|
||||
// 更新主题颜色
|
||||
const updateThemeColors = (darkColors: any = null, lightColors: any = null): boolean => {
|
||||
let hasChanges = false;
|
||||
|
||||
if (darkColors) {
|
||||
Object.entries(darkColors).forEach(([key, value]) => {
|
||||
if (value !== undefined && themeColors.darkTheme[key] !== value) {
|
||||
themeColors.darkTheme[key] = value;
|
||||
hasChanges = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (lightColors) {
|
||||
Object.entries(lightColors).forEach(([key, value]) => {
|
||||
if (value !== undefined && themeColors.lightTheme[key] !== value) {
|
||||
themeColors.lightTheme[key] = value;
|
||||
hasChanges = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return hasChanges;
|
||||
};
|
||||
|
||||
// 保存主题颜色到配置
|
||||
const saveThemeColors = async () => {
|
||||
const customTheme = {
|
||||
darkTheme: { ...themeColors.darkTheme },
|
||||
lightTheme: { ...themeColors.lightTheme }
|
||||
};
|
||||
|
||||
await configStore.setCustomTheme(customTheme);
|
||||
};
|
||||
|
||||
// 重置主题颜色
|
||||
const resetThemeColors = async (themeType: 'darkTheme' | 'lightTheme') => {
|
||||
try {
|
||||
// 1. 更新内存中的颜色状态
|
||||
if (themeType === 'darkTheme') {
|
||||
Object.assign(themeColors.darkTheme, defaultDarkColors);
|
||||
}
|
||||
|
||||
if (themeType === 'lightTheme') {
|
||||
Object.assign(themeColors.lightTheme, defaultLightColors);
|
||||
}
|
||||
|
||||
// 2. 保存到配置
|
||||
await saveThemeColors();
|
||||
|
||||
// 3. 刷新编辑器主题
|
||||
refreshEditorTheme();
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('Failed to reset theme colors:', error);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
// 刷新编辑器主题(在主题颜色更改后调用)
|
||||
const refreshEditorTheme = () => {
|
||||
// 使用当前主题重新应用DOM主题
|
||||
const theme = currentTheme.value;
|
||||
applyThemeToDOM(theme);
|
||||
|
||||
const editorStore = useEditorStore();
|
||||
if (editorStore) {
|
||||
editorStore.applyThemeSettings();
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
currentTheme,
|
||||
themeColors,
|
||||
setTheme,
|
||||
initializeTheme,
|
||||
applyThemeToDOM,
|
||||
updateThemeColors,
|
||||
saveThemeColors,
|
||||
resetThemeColors,
|
||||
refreshEditorTheme
|
||||
};
|
||||
});
|
||||
});
|
31
frontend/src/stores/windowStore.ts
Normal file
31
frontend/src/stores/windowStore.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import {computed} from 'vue';
|
||||
import {defineStore} from 'pinia';
|
||||
import {IsDocumentWindowOpen} from "@/../bindings/voidraft/internal/services/windowservice";
|
||||
|
||||
|
||||
export const useWindowStore = defineStore('window', () => {
|
||||
// 判断是否为主窗口
|
||||
const isMainWindow = computed(() => {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
return !urlParams.has('documentId');
|
||||
});
|
||||
|
||||
// 获取当前窗口的documentId
|
||||
const currentDocumentId = computed(() => {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
return urlParams.get('documentId');
|
||||
});
|
||||
/**
|
||||
* 判断文档窗口是否打开
|
||||
* @param documentId 文档ID
|
||||
*/
|
||||
async function isDocumentWindowOpen(documentId: number) {
|
||||
return IsDocumentWindowOpen(documentId);
|
||||
}
|
||||
|
||||
return {
|
||||
isMainWindow,
|
||||
currentDocumentId,
|
||||
isDocumentWindowOpen
|
||||
};
|
||||
});
|
@@ -1,14 +1,17 @@
|
||||
<script setup lang="ts">
|
||||
import {onBeforeUnmount, onMounted, ref, watch} from 'vue';
|
||||
import {onBeforeUnmount, onMounted, ref} from 'vue';
|
||||
import {useEditorStore} from '@/stores/editorStore';
|
||||
import {useDocumentStore} from '@/stores/documentStore';
|
||||
import {useConfigStore} from '@/stores/configStore';
|
||||
import {createWheelZoomHandler} from './basic/wheelZoomExtension';
|
||||
import Toolbar from '@/components/toolbar/Toolbar.vue';
|
||||
import {useWindowStore} from "@/stores/windowStore";
|
||||
import LoadingScreen from '@/components/loading/LoadingScreen.vue';
|
||||
|
||||
const editorStore = useEditorStore();
|
||||
const documentStore = useDocumentStore();
|
||||
const configStore = useConfigStore();
|
||||
const windowStore = useWindowStore();
|
||||
|
||||
const editorElement = ref<HTMLElement | null>(null);
|
||||
|
||||
@@ -21,8 +24,11 @@ const wheelHandler = createWheelZoomHandler(
|
||||
onMounted(async () => {
|
||||
if (!editorElement.value) return;
|
||||
|
||||
// 初始化文档存储,会自动使用持久化的文档ID
|
||||
await documentStore.initialize();
|
||||
// 从URL查询参数中获取documentId
|
||||
const urlDocumentId = windowStore.currentDocumentId ? parseInt(windowStore.currentDocumentId) : undefined;
|
||||
|
||||
// 初始化文档存储,优先使用URL参数中的文档ID
|
||||
await documentStore.initialize(urlDocumentId);
|
||||
|
||||
// 设置编辑器容器
|
||||
editorStore.setEditorContainer(editorElement.value);
|
||||
@@ -41,6 +47,7 @@ onBeforeUnmount(() => {
|
||||
|
||||
<template>
|
||||
<div class="editor-container">
|
||||
<LoadingScreen v-if="editorStore.isLoading" text="VOIDRAFT" />
|
||||
<div ref="editorElement" class="editor"></div>
|
||||
<Toolbar/>
|
||||
</div>
|
||||
@@ -53,6 +60,7 @@ onBeforeUnmount(() => {
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
|
||||
.editor {
|
||||
width: 100%;
|
||||
@@ -69,4 +77,4 @@ onBeforeUnmount(() => {
|
||||
:deep(.cm-scroller) {
|
||||
overflow: auto;
|
||||
}
|
||||
</style>
|
||||
</style>
|
@@ -1,8 +1,9 @@
|
||||
import { Extension, Compartment } from '@codemirror/state';
|
||||
import { EditorView } from '@codemirror/view';
|
||||
import { SystemThemeType } from '@/../bindings/voidraft/internal/models/models';
|
||||
import { dark } from '@/views/editor/theme/dark';
|
||||
import { light } from '@/views/editor/theme/light';
|
||||
import { createDarkTheme } from '@/views/editor/theme/dark';
|
||||
import { createLightTheme } from '@/views/editor/theme/light';
|
||||
import { useThemeStore } from '@/stores/themeStore';
|
||||
|
||||
// 主题区间 - 用于动态切换主题
|
||||
export const themeCompartment = new Compartment();
|
||||
@@ -11,6 +12,8 @@ export const themeCompartment = new Compartment();
|
||||
* 根据主题类型获取主题扩展
|
||||
*/
|
||||
const getThemeExtension = (themeType: SystemThemeType): Extension => {
|
||||
const themeStore = useThemeStore();
|
||||
|
||||
// 处理 auto 主题类型
|
||||
let actualTheme: SystemThemeType = themeType;
|
||||
if (themeType === SystemThemeType.SystemThemeAuto) {
|
||||
@@ -19,13 +22,11 @@ const getThemeExtension = (themeType: SystemThemeType): Extension => {
|
||||
: SystemThemeType.SystemThemeLight;
|
||||
}
|
||||
|
||||
// 直接返回对应的主题扩展
|
||||
switch (actualTheme) {
|
||||
case SystemThemeType.SystemThemeLight:
|
||||
return light;
|
||||
case SystemThemeType.SystemThemeDark:
|
||||
default:
|
||||
return dark;
|
||||
// 根据主题类型创建主题
|
||||
if (actualTheme === SystemThemeType.SystemThemeLight) {
|
||||
return createLightTheme(themeStore.themeColors.lightTheme);
|
||||
} else {
|
||||
return createDarkTheme(themeStore.themeColors.darkTheme);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -45,9 +46,13 @@ export const updateEditorTheme = (view: EditorView, themeType: SystemThemeType):
|
||||
return;
|
||||
}
|
||||
|
||||
const extension = getThemeExtension(themeType);
|
||||
view.dispatch({
|
||||
effects: themeCompartment.reconfigure(extension)
|
||||
});
|
||||
try {
|
||||
const extension = getThemeExtension(themeType);
|
||||
view.dispatch({
|
||||
effects: themeCompartment.reconfigure(extension)
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Failed to update editor theme:', error);
|
||||
}
|
||||
};
|
||||
|
||||
|
@@ -3,8 +3,7 @@ import { Range } from '@codemirror/state';
|
||||
|
||||
// 生成彩虹颜色数组
|
||||
function generateColors(): string[] {
|
||||
return [
|
||||
'red', 'orange', 'yellow', 'green', 'blue', 'indigo', 'violet',
|
||||
return ['red', 'orange', 'yellow', 'green', 'blue', 'indigo', 'violet'
|
||||
];
|
||||
}
|
||||
|
||||
@@ -75,13 +74,13 @@ export default function rainbowBracketsExtension() {
|
||||
rainbowBracketsPlugin,
|
||||
EditorView.baseTheme({
|
||||
// 为每种颜色定义CSS样式
|
||||
'.cm-rainbow-bracket-red': { color: 'red' },
|
||||
'.cm-rainbow-bracket-orange': { color: 'orange' },
|
||||
'.cm-rainbow-bracket-yellow': { color: 'yellow' },
|
||||
'.cm-rainbow-bracket-green': { color: 'green' },
|
||||
'.cm-rainbow-bracket-blue': { color: 'blue' },
|
||||
'.cm-rainbow-bracket-indigo': { color: 'indigo' },
|
||||
'.cm-rainbow-bracket-violet': { color: 'violet' },
|
||||
'.cm-rainbow-bracket-red': { color: '#FF6B6B' },
|
||||
'.cm-rainbow-bracket-orange': { color: '#FF9E6B' },
|
||||
'.cm-rainbow-bracket-yellow': { color: '#FFD166' },
|
||||
'.cm-rainbow-bracket-green': { color: '#06D6A0' },
|
||||
'.cm-rainbow-bracket-blue': { color: '#118AB2' },
|
||||
'.cm-rainbow-bracket-indigo': { color: '#6B5B95' },
|
||||
'.cm-rainbow-bracket-violet': { color: '#9B5DE5' },
|
||||
}),
|
||||
];
|
||||
}
|
@@ -65,6 +65,10 @@ export class ExtensionManager {
|
||||
// 注册的扩展工厂
|
||||
private extensionFactories = new Map<ExtensionID, ExtensionFactory>()
|
||||
|
||||
// 防抖处理
|
||||
private debounceTimers = new Map<ExtensionID, number>()
|
||||
private debounceDelay = 300 // 默认防抖时间为300毫秒
|
||||
|
||||
/**
|
||||
* 注册扩展工厂
|
||||
* @param id 扩展ID
|
||||
@@ -187,13 +191,24 @@ export class ExtensionManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新单个扩展配置并应用到所有视图
|
||||
* 更新单个扩展配置并应用到所有视图(带防抖功能)
|
||||
* @param id 扩展ID
|
||||
* @param enabled 是否启用
|
||||
* @param config 扩展配置
|
||||
*/
|
||||
updateExtension(id: ExtensionID, enabled: boolean, config: any = {}): void {
|
||||
this.updateExtensionImmediate(id, enabled, config)
|
||||
// 清除之前的定时器
|
||||
if (this.debounceTimers.has(id)) {
|
||||
window.clearTimeout(this.debounceTimers.get(id))
|
||||
}
|
||||
|
||||
// 设置新的定时器
|
||||
const timerId = window.setTimeout(() => {
|
||||
this.updateExtensionImmediate(id, enabled, config)
|
||||
this.debounceTimers.delete(id)
|
||||
}, this.debounceDelay)
|
||||
|
||||
this.debounceTimers.set(id, timerId)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -262,6 +277,14 @@ export class ExtensionManager {
|
||||
enabled: boolean
|
||||
config: any
|
||||
}>): void {
|
||||
// 清除所有相关的防抖定时器
|
||||
for (const update of updates) {
|
||||
if (this.debounceTimers.has(update.id)) {
|
||||
window.clearTimeout(this.debounceTimers.get(update.id))
|
||||
this.debounceTimers.delete(update.id)
|
||||
}
|
||||
}
|
||||
|
||||
// 更新所有扩展状态
|
||||
for (const update of updates) {
|
||||
// 获取扩展状态
|
||||
@@ -357,6 +380,12 @@ export class ExtensionManager {
|
||||
* 销毁管理器
|
||||
*/
|
||||
destroy(): void {
|
||||
// 清除所有防抖定时器
|
||||
for (const timerId of this.debounceTimers.values()) {
|
||||
window.clearTimeout(timerId)
|
||||
}
|
||||
this.debounceTimers.clear()
|
||||
|
||||
this.viewsMap.clear()
|
||||
this.activeViewId = null
|
||||
this.extensionFactories.clear()
|
||||
|
@@ -2,203 +2,205 @@ import {EditorView} from '@codemirror/view';
|
||||
import {HighlightStyle, syntaxHighlighting} from '@codemirror/language';
|
||||
import {tags} from '@lezer/highlight';
|
||||
|
||||
const colors = {
|
||||
// 基础色调
|
||||
background: '#252B37', // 主背景色
|
||||
// backgroundAlt: '#252B37', // 交替背景色
|
||||
backgroundSecondary: '#213644', // 次要背景色
|
||||
surface: '#474747', // 面板背景
|
||||
// 默认深色主题颜色
|
||||
export const defaultDarkColors = {
|
||||
// 基础色调
|
||||
background: '#252B37', // 主背景色
|
||||
backgroundSecondary: '#213644', // 次要背景色
|
||||
surface: '#474747', // 面板背景
|
||||
|
||||
// 文本颜色
|
||||
foreground: '#9BB586', // 主文本色
|
||||
foregroundSecondary: '#9c9c9c', // 次要文本色
|
||||
comment: '#6272a4', // 注释色
|
||||
// 文本颜色
|
||||
foreground: '#9BB586', // 主文本色
|
||||
foregroundSecondary: '#9c9c9c', // 次要文本色
|
||||
comment: '#6272a4', // 注释色
|
||||
|
||||
// 语法高亮色
|
||||
keyword: '#ff79c6', // 关键字
|
||||
string: '#f1fa8c', // 字符串
|
||||
function: '#50fa7b', // 函数名
|
||||
number: '#bd93f9', // 数字
|
||||
operator: '#ff79c6', // 操作符
|
||||
variable: '#8fbcbb', // 变量
|
||||
type: '#8be9fd', // 类型
|
||||
// 语法高亮色
|
||||
keyword: '#ff79c6', // 关键字
|
||||
string: '#f1fa8c', // 字符串
|
||||
function: '#50fa7b', // 函数名
|
||||
number: '#bd93f9', // 数字
|
||||
operator: '#ff79c6', // 操作符
|
||||
variable: '#8fbcbb', // 变量
|
||||
type: '#8be9fd', // 类型
|
||||
|
||||
// 界面元素
|
||||
cursor: '#fff', // 光标
|
||||
selection: '#0865a9aa', // 选中背景
|
||||
selectionBlur: '#225377aa', // 失焦选中背景
|
||||
activeLine: 'rgba(255,255,255,0.04)', // 当前行高亮
|
||||
lineNumber: 'rgba(255,255,255, 0.15)', // 行号
|
||||
activeLineNumber: 'rgba(255,255,255, 0.6)', // 活动行号
|
||||
// 界面元素
|
||||
cursor: '#ffffff', // 光标
|
||||
selection: '#0865a9', // 选中背景
|
||||
selectionBlur: '#225377', // 失焦选中背景
|
||||
activeLine: '#ffffff0a', // 当前行高亮
|
||||
lineNumber: '#ffffff26', // 行号
|
||||
activeLineNumber: '#ffffff99', // 活动行号
|
||||
|
||||
// 边框和分割线
|
||||
border: '#1e222a', // 边框色
|
||||
borderLight: 'rgba(255,255,255, 0.1)', // 浅色边框
|
||||
// 边框和分割线
|
||||
borderColor: '#1e222a', // 边框色
|
||||
borderLight: '#ffffff19', // 浅色边框
|
||||
|
||||
// 搜索和匹配
|
||||
searchMatch: '#8fbcbb', // 搜索匹配
|
||||
matchingBracket: 'rgba(255,255,255,0.1)', // 匹配括号
|
||||
// 搜索和匹配
|
||||
searchMatch: '#8fbcbb', // 搜索匹配
|
||||
matchingBracket: '#ffffff19', // 匹配括号
|
||||
};
|
||||
|
||||
const darkTheme = EditorView.theme({
|
||||
// 创建深色主题
|
||||
export function createDarkTheme(colors = defaultDarkColors) {
|
||||
const darkTheme = EditorView.theme({
|
||||
'&': {
|
||||
color: colors.foreground,
|
||||
backgroundColor: colors.background,
|
||||
color: colors.foreground,
|
||||
backgroundColor: colors.background,
|
||||
},
|
||||
|
||||
// 确保编辑器容器背景一致
|
||||
'.cm-editor': {
|
||||
backgroundColor: colors.background,
|
||||
backgroundColor: colors.background,
|
||||
},
|
||||
|
||||
// 确保滚动区域背景一致
|
||||
'.cm-scroller': {
|
||||
backgroundColor: colors.background,
|
||||
backgroundColor: colors.background,
|
||||
},
|
||||
|
||||
// 编辑器内容
|
||||
'.cm-content': {
|
||||
caretColor: colors.cursor,
|
||||
paddingTop: '4px',
|
||||
caretColor: colors.cursor,
|
||||
paddingTop: '4px',
|
||||
},
|
||||
|
||||
// 光标
|
||||
'.cm-cursor, .cm-dropCursor': {
|
||||
borderLeftColor: colors.cursor,
|
||||
borderLeftWidth: '2px',
|
||||
paddingTop: '4px',
|
||||
marginTop: '-2px',
|
||||
borderLeftColor: colors.cursor,
|
||||
borderLeftWidth: '2px',
|
||||
paddingTop: '4px',
|
||||
marginTop: '-2px',
|
||||
},
|
||||
|
||||
// 选择
|
||||
'.cm-selectionBackground': {
|
||||
backgroundColor: colors.selectionBlur,
|
||||
backgroundColor: colors.selectionBlur,
|
||||
},
|
||||
'&.cm-focused > .cm-scroller > .cm-selectionLayer .cm-selectionBackground': {
|
||||
backgroundColor: colors.selection,
|
||||
backgroundColor: colors.selection,
|
||||
},
|
||||
'.cm-activeLine.code-empty-block-selected': {
|
||||
backgroundColor: colors.selection,
|
||||
backgroundColor: colors.selection,
|
||||
},
|
||||
|
||||
// 当前行高亮
|
||||
'.cm-activeLine': {
|
||||
backgroundColor: colors.activeLine
|
||||
backgroundColor: colors.activeLine
|
||||
},
|
||||
|
||||
// 行号区域
|
||||
'.cm-gutters': {
|
||||
backgroundColor: 'rgba(0,0,0, 0.1)',
|
||||
color: colors.lineNumber,
|
||||
border: 'none',
|
||||
padding: '0 2px 0 4px',
|
||||
userSelect: 'none',
|
||||
backgroundColor: 'rgba(0,0,0, 0.1)',
|
||||
color: colors.lineNumber,
|
||||
border: 'none',
|
||||
padding: '0 2px 0 4px',
|
||||
userSelect: 'none',
|
||||
},
|
||||
'.cm-activeLineGutter': {
|
||||
backgroundColor: 'transparent',
|
||||
color: colors.activeLineNumber,
|
||||
backgroundColor: 'transparent',
|
||||
color: colors.activeLineNumber,
|
||||
},
|
||||
|
||||
// 折叠功能
|
||||
'.cm-foldGutter': {
|
||||
marginLeft: '0px',
|
||||
marginLeft: '0px',
|
||||
},
|
||||
'.cm-foldGutter .cm-gutterElement': {
|
||||
opacity: 0,
|
||||
transition: 'opacity 400ms',
|
||||
opacity: 0,
|
||||
transition: 'opacity 400ms',
|
||||
},
|
||||
'.cm-gutters:hover .cm-gutterElement': {
|
||||
opacity: 1,
|
||||
opacity: 1,
|
||||
},
|
||||
'.cm-foldPlaceholder': {
|
||||
backgroundColor: 'transparent',
|
||||
border: 'none',
|
||||
color: '#ddd',
|
||||
backgroundColor: 'transparent',
|
||||
border: 'none',
|
||||
color: '#ddd',
|
||||
},
|
||||
|
||||
|
||||
// 搜索匹配
|
||||
'.cm-searchMatch': {
|
||||
backgroundColor: 'transparent',
|
||||
outline: `1px solid ${colors.searchMatch}`,
|
||||
backgroundColor: 'transparent',
|
||||
outline: `1px solid ${colors.searchMatch}`,
|
||||
},
|
||||
'.cm-searchMatch.cm-searchMatch-selected': {
|
||||
backgroundColor: colors.foreground,
|
||||
color: colors.background,
|
||||
backgroundColor: colors.foreground,
|
||||
color: colors.background,
|
||||
},
|
||||
'.cm-selectionMatch': {
|
||||
backgroundColor: '#50606D',
|
||||
backgroundColor: '#50606D',
|
||||
},
|
||||
|
||||
// 括号匹配
|
||||
'&.cm-focused .cm-matchingBracket, &.cm-focused .cm-nonmatchingBracket': {
|
||||
outline: `0.5px solid ${colors.searchMatch}`,
|
||||
outline: `0.5px solid ${colors.searchMatch}`,
|
||||
},
|
||||
'&.cm-focused .cm-matchingBracket': {
|
||||
backgroundColor: colors.matchingBracket,
|
||||
color: 'inherit',
|
||||
backgroundColor: colors.matchingBracket,
|
||||
color: 'inherit',
|
||||
},
|
||||
'&.cm-focused .cm-nonmatchingBracket': {
|
||||
outline: '0.5px solid #bc8f8f',
|
||||
outline: '0.5px solid #bc8f8f',
|
||||
},
|
||||
|
||||
// 编辑器焦点
|
||||
'&.cm-editor.cm-focused': {
|
||||
outline: 'none',
|
||||
outline: 'none',
|
||||
},
|
||||
|
||||
// 工具提示
|
||||
'.cm-tooltip': {
|
||||
border: 'none',
|
||||
backgroundColor: colors.surface,
|
||||
border: 'none',
|
||||
backgroundColor: colors.surface,
|
||||
},
|
||||
'.cm-tooltip .cm-tooltip-arrow:before': {
|
||||
borderTopColor: 'transparent',
|
||||
borderBottomColor: 'transparent',
|
||||
borderTopColor: 'transparent',
|
||||
borderBottomColor: 'transparent',
|
||||
},
|
||||
'.cm-tooltip .cm-tooltip-arrow:after': {
|
||||
borderTopColor: colors.surface,
|
||||
borderBottomColor: colors.surface,
|
||||
borderTopColor: colors.surface,
|
||||
borderBottomColor: colors.surface,
|
||||
},
|
||||
'.cm-tooltip-autocomplete': {
|
||||
'& > ul > li[aria-selected]': {
|
||||
backgroundColor: colors.activeLine,
|
||||
color: colors.foreground,
|
||||
},
|
||||
'& > ul > li[aria-selected]': {
|
||||
backgroundColor: colors.activeLine,
|
||||
color: colors.foreground,
|
||||
},
|
||||
},
|
||||
|
||||
// 代码块层
|
||||
'.code-blocks-layer': {
|
||||
width: '100%',
|
||||
width: '100%',
|
||||
},
|
||||
'.code-blocks-layer .block-even, .code-blocks-layer .block-odd': {
|
||||
width: '100%',
|
||||
boxSizing: 'content-box',
|
||||
width: '100%',
|
||||
boxSizing: 'content-box',
|
||||
},
|
||||
'.code-blocks-layer .block-even': {
|
||||
background: colors.background,
|
||||
borderTop: `1px solid ${colors.border}`,
|
||||
background: colors.background,
|
||||
borderTop: `1px solid ${colors.borderColor}`,
|
||||
},
|
||||
'.code-blocks-layer .block-even:first-child': {
|
||||
borderTop: 'none',
|
||||
borderTop: 'none',
|
||||
},
|
||||
'.code-blocks-layer .block-odd': {
|
||||
background: colors.backgroundSecondary,
|
||||
borderTop: `1px solid ${colors.border}`,
|
||||
background: colors.backgroundSecondary,
|
||||
borderTop: `1px solid ${colors.borderColor}`,
|
||||
},
|
||||
|
||||
// 代码块开始标记
|
||||
'.code-block-start': {
|
||||
height: '12px',
|
||||
position: 'relative',
|
||||
height: '12px',
|
||||
position: 'relative',
|
||||
},
|
||||
'.code-block-start.first': {
|
||||
height: '0px',
|
||||
height: '0px',
|
||||
},
|
||||
}, {dark: true});
|
||||
}, {dark: true});
|
||||
|
||||
// 语法高亮样式
|
||||
const darkHighlightStyle = HighlightStyle.define([
|
||||
// 语法高亮样式
|
||||
const darkHighlightStyle = HighlightStyle.define([
|
||||
{tag: tags.keyword, color: colors.keyword},
|
||||
{tag: [tags.name, tags.deleted, tags.character, tags.propertyName, tags.macroName], color: colors.variable},
|
||||
{tag: [tags.variableName], color: colors.variable},
|
||||
@@ -229,9 +231,13 @@ const darkHighlightStyle = HighlightStyle.define([
|
||||
{tag: [tags.heading1, tags.heading2], fontSize: '1.4em'},
|
||||
{tag: [tags.heading3, tags.heading4], fontSize: '1.2em'},
|
||||
{tag: [tags.heading5, tags.heading6], fontSize: '1.1em'},
|
||||
]);
|
||||
]);
|
||||
|
||||
export const dark = [
|
||||
return [
|
||||
darkTheme,
|
||||
syntaxHighlighting(darkHighlightStyle),
|
||||
];
|
||||
];
|
||||
}
|
||||
|
||||
// 默认深色主题
|
||||
export const dark = createDarkTheme(defaultDarkColors);
|
@@ -2,10 +2,10 @@ import { EditorView } from '@codemirror/view';
|
||||
import { HighlightStyle, syntaxHighlighting } from '@codemirror/language';
|
||||
import { tags } from '@lezer/highlight';
|
||||
|
||||
const colors = {
|
||||
// 默认浅色主题颜色
|
||||
export const defaultLightColors = {
|
||||
// 基础色调
|
||||
background: '#ffffff', // 主背景色
|
||||
// backgroundAlt: '#f4f8f4', // 交替背景色
|
||||
backgroundSecondary: '#f1faf1', // 次要背景色
|
||||
surface: '#f5f5f5', // 面板背景
|
||||
|
||||
@@ -24,216 +24,221 @@ const colors = {
|
||||
type: '#6f42c1', // 类型
|
||||
|
||||
// 界面元素
|
||||
cursor: '#000', // 光标
|
||||
selection: '#77baff8c', // 选中背景
|
||||
selectionBlur: '#b2c2ca85', // 失焦选中背景
|
||||
activeLine: 'rgba(0,0,0, 0.04)', // 当前行高亮
|
||||
lineNumber: 'rgba(0,0,0, 0.25)', // 行号
|
||||
activeLineNumber: 'rgba(0,0,0, 0.6)', // 活动行号
|
||||
cursor: '#000000', // 光标
|
||||
selection: '#77baff', // 选中背景
|
||||
selectionBlur: '#b2c2ca', // 失焦选中背景
|
||||
activeLine: '#0000000a', // 当前行高亮
|
||||
lineNumber: '#00000040', // 行号
|
||||
activeLineNumber: '#000000aa', // 活动行号
|
||||
|
||||
// 边框和分割线
|
||||
border: '#dfdfdf', // 边框色
|
||||
borderLight: 'rgba(0,0,0, 0.05)', // 浅色边框
|
||||
borderColor: '#dfdfdf', // 边框色
|
||||
borderLight: '#0000000c', // 浅色边框
|
||||
|
||||
// 搜索和匹配
|
||||
searchMatch: '#005cc5', // 搜索匹配
|
||||
matchingBracket: 'rgba(0,0,0,0.1)', // 匹配括号
|
||||
|
||||
matchingBracket: '#00000019', // 匹配括号
|
||||
};
|
||||
|
||||
const lightTheme = EditorView.theme({
|
||||
'&': {
|
||||
color: colors.foreground,
|
||||
backgroundColor: colors.background,
|
||||
},
|
||||
|
||||
// 确保编辑器容器背景一致
|
||||
'.cm-editor': {
|
||||
backgroundColor: colors.background,
|
||||
},
|
||||
|
||||
// 确保滚动区域背景一致
|
||||
'.cm-scroller': {
|
||||
backgroundColor: colors.background,
|
||||
},
|
||||
|
||||
// 编辑器内容
|
||||
'.cm-content': {
|
||||
caretColor: colors.cursor,
|
||||
paddingTop: '4px',
|
||||
},
|
||||
|
||||
// 光标
|
||||
'.cm-cursor, .cm-dropCursor': {
|
||||
borderLeftColor: colors.cursor,
|
||||
borderLeftWidth: '2px',
|
||||
paddingTop: '4px',
|
||||
marginTop: '-2px',
|
||||
},
|
||||
|
||||
// 选择
|
||||
'.cm-selectionBackground': {
|
||||
backgroundColor: colors.selectionBlur,
|
||||
},
|
||||
'&.cm-focused > .cm-scroller > .cm-selectionLayer .cm-selectionBackground': {
|
||||
backgroundColor: colors.selection,
|
||||
},
|
||||
'.cm-activeLine.code-empty-block-selected': {
|
||||
backgroundColor: colors.selection,
|
||||
},
|
||||
|
||||
// 当前行高亮
|
||||
'.cm-activeLine': {
|
||||
backgroundColor: colors.activeLine
|
||||
},
|
||||
|
||||
// 行号区域
|
||||
'.cm-gutters': {
|
||||
backgroundColor: 'rgba(0,0,0, 0.04)',
|
||||
color: colors.lineNumber,
|
||||
border: 'none',
|
||||
borderRight: `1px solid ${colors.borderLight}`,
|
||||
padding: '0 2px 0 4px',
|
||||
userSelect: 'none',
|
||||
},
|
||||
'.cm-activeLineGutter': {
|
||||
backgroundColor: 'transparent',
|
||||
color: colors.activeLineNumber,
|
||||
},
|
||||
|
||||
// 折叠功能
|
||||
'.cm-foldGutter': {
|
||||
marginLeft: '0px',
|
||||
},
|
||||
'.cm-foldGutter .cm-gutterElement': {
|
||||
opacity: 0,
|
||||
transition: 'opacity 400ms',
|
||||
},
|
||||
'.cm-gutters:hover .cm-gutterElement': {
|
||||
opacity: 1,
|
||||
},
|
||||
'.cm-foldPlaceholder': {
|
||||
backgroundColor: 'transparent',
|
||||
border: 'none',
|
||||
color: colors.comment,
|
||||
},
|
||||
|
||||
|
||||
// 搜索匹配
|
||||
'.cm-searchMatch': {
|
||||
backgroundColor: 'transparent',
|
||||
outline: `1px solid ${colors.searchMatch}`,
|
||||
},
|
||||
'.cm-searchMatch.cm-searchMatch-selected': {
|
||||
backgroundColor: colors.searchMatch,
|
||||
color: colors.background,
|
||||
},
|
||||
'.cm-selectionMatch': {
|
||||
backgroundColor: '#e6f3ff',
|
||||
},
|
||||
|
||||
// 括号匹配
|
||||
'&.cm-focused .cm-matchingBracket, &.cm-focused .cm-nonmatchingBracket': {
|
||||
outline: `0.5px solid ${colors.searchMatch}`,
|
||||
},
|
||||
'&.cm-focused .cm-matchingBracket': {
|
||||
backgroundColor: colors.matchingBracket,
|
||||
color: 'inherit',
|
||||
},
|
||||
'&.cm-focused .cm-nonmatchingBracket': {
|
||||
outline: '0.5px solid #d73a49',
|
||||
},
|
||||
|
||||
// 编辑器焦点
|
||||
'&.cm-editor.cm-focused': {
|
||||
outline: 'none',
|
||||
},
|
||||
|
||||
// 工具提示
|
||||
'.cm-tooltip': {
|
||||
border: 'none',
|
||||
backgroundColor: colors.surface,
|
||||
boxShadow: '0 2px 8px rgba(0,0,0,0.1)',
|
||||
},
|
||||
'.cm-tooltip .cm-tooltip-arrow:before': {
|
||||
borderTopColor: 'transparent',
|
||||
borderBottomColor: 'transparent',
|
||||
},
|
||||
'.cm-tooltip .cm-tooltip-arrow:after': {
|
||||
borderTopColor: colors.surface,
|
||||
borderBottomColor: colors.surface,
|
||||
},
|
||||
'.cm-tooltip-autocomplete': {
|
||||
'& > ul > li[aria-selected]': {
|
||||
backgroundColor: colors.activeLine,
|
||||
// 创建浅色主题
|
||||
export function createLightTheme(colors = defaultLightColors) {
|
||||
const lightTheme = EditorView.theme({
|
||||
'&': {
|
||||
color: colors.foreground,
|
||||
backgroundColor: colors.background,
|
||||
},
|
||||
|
||||
// 确保编辑器容器背景一致
|
||||
'.cm-editor': {
|
||||
backgroundColor: colors.background,
|
||||
},
|
||||
|
||||
// 确保滚动区域背景一致
|
||||
'.cm-scroller': {
|
||||
backgroundColor: colors.background,
|
||||
},
|
||||
|
||||
// 编辑器内容
|
||||
'.cm-content': {
|
||||
caretColor: colors.cursor,
|
||||
paddingTop: '4px',
|
||||
},
|
||||
|
||||
// 光标
|
||||
'.cm-cursor, .cm-dropCursor': {
|
||||
borderLeftColor: colors.cursor,
|
||||
borderLeftWidth: '2px',
|
||||
paddingTop: '4px',
|
||||
marginTop: '-2px',
|
||||
},
|
||||
|
||||
// 选择
|
||||
'.cm-selectionBackground': {
|
||||
backgroundColor: colors.selectionBlur,
|
||||
},
|
||||
'&.cm-focused > .cm-scroller > .cm-selectionLayer .cm-selectionBackground': {
|
||||
backgroundColor: colors.selection,
|
||||
},
|
||||
'.cm-activeLine.code-empty-block-selected': {
|
||||
backgroundColor: colors.selection,
|
||||
},
|
||||
|
||||
// 当前行高亮
|
||||
'.cm-activeLine': {
|
||||
backgroundColor: colors.activeLine
|
||||
},
|
||||
|
||||
// 行号区域
|
||||
'.cm-gutters': {
|
||||
backgroundColor: 'rgba(0,0,0, 0.04)',
|
||||
color: colors.lineNumber,
|
||||
border: 'none',
|
||||
borderRight: `1px solid ${colors.borderLight}`,
|
||||
padding: '0 2px 0 4px',
|
||||
userSelect: 'none',
|
||||
},
|
||||
'.cm-activeLineGutter': {
|
||||
backgroundColor: 'transparent',
|
||||
color: colors.activeLineNumber,
|
||||
},
|
||||
|
||||
// 折叠功能
|
||||
'.cm-foldGutter': {
|
||||
marginLeft: '0px',
|
||||
},
|
||||
'.cm-foldGutter .cm-gutterElement': {
|
||||
opacity: 0,
|
||||
transition: 'opacity 400ms',
|
||||
},
|
||||
'.cm-gutters:hover .cm-gutterElement': {
|
||||
opacity: 1,
|
||||
},
|
||||
'.cm-foldPlaceholder': {
|
||||
backgroundColor: 'transparent',
|
||||
border: 'none',
|
||||
color: colors.comment,
|
||||
},
|
||||
},
|
||||
|
||||
// 代码块层
|
||||
'.code-blocks-layer': {
|
||||
width: '100%',
|
||||
},
|
||||
'.code-blocks-layer .block-even, .code-blocks-layer .block-odd': {
|
||||
width: '100%',
|
||||
boxSizing: 'content-box',
|
||||
},
|
||||
'.code-blocks-layer .block-even': {
|
||||
background: colors.background,
|
||||
borderTop: `1px solid ${colors.border}`,
|
||||
},
|
||||
'.code-blocks-layer .block-even:first-child': {
|
||||
borderTop: 'none',
|
||||
},
|
||||
'.code-blocks-layer .block-odd': {
|
||||
background: colors.backgroundSecondary,
|
||||
borderTop: `1px solid ${colors.border}`,
|
||||
},
|
||||
|
||||
// 代码块开始标记
|
||||
'.code-block-start': {
|
||||
height: '12px',
|
||||
},
|
||||
'.code-block-start.first': {
|
||||
height: '0px',
|
||||
},
|
||||
}, { dark: false });
|
||||
|
||||
// 语法高亮样式
|
||||
const lightHighlightStyle = HighlightStyle.define([
|
||||
{ tag: tags.keyword, color: colors.keyword },
|
||||
{ tag: [tags.name, tags.deleted, tags.character, tags.propertyName, tags.macroName], color: colors.variable },
|
||||
{ tag: [tags.variableName], color: colors.variable },
|
||||
{ tag: [tags.function(tags.variableName)], color: colors.function },
|
||||
{ tag: [tags.labelName], color: colors.operator },
|
||||
{ tag: [tags.color, tags.constant(tags.name), tags.standard(tags.name)], color: colors.keyword },
|
||||
{ tag: [tags.definition(tags.name), tags.separator], color: colors.function },
|
||||
{ tag: [tags.brace], color: colors.variable },
|
||||
{ tag: [tags.annotation], color: '#d73a49' },
|
||||
{ tag: [tags.number, tags.changed, tags.annotation, tags.modifier, tags.self, tags.namespace], color: colors.number },
|
||||
{ tag: [tags.typeName, tags.className], color: colors.type },
|
||||
{ tag: [tags.operator, tags.operatorKeyword], color: colors.operator },
|
||||
{ tag: [tags.tagName], color: colors.type },
|
||||
{ tag: [tags.squareBracket], color: colors.keyword },
|
||||
{ tag: [tags.angleBracket], color: colors.operator },
|
||||
{ tag: [tags.attributeName], color: colors.variable },
|
||||
{ tag: [tags.regexp], color: colors.string },
|
||||
{ tag: [tags.quote], color: colors.comment },
|
||||
{ tag: [tags.string], color: colors.string },
|
||||
{ tag: tags.link, color: colors.function, textDecoration: 'underline' },
|
||||
{ tag: [tags.url, tags.escape, tags.special(tags.string)], color: colors.string },
|
||||
{ tag: [tags.meta], color: colors.comment },
|
||||
{ tag: [tags.comment], color: colors.comment, fontStyle: 'italic' },
|
||||
{ tag: tags.strong, fontWeight: 'bold' },
|
||||
{ tag: tags.emphasis, fontStyle: 'italic' },
|
||||
{ tag: tags.strikethrough, textDecoration: 'line-through' },
|
||||
{ tag: tags.heading, fontWeight: 'bold', color: colors.keyword },
|
||||
{ tag: [tags.heading1, tags.heading2], fontSize: '1.4em' },
|
||||
{ tag: [tags.heading3, tags.heading4], fontSize: '1.2em' },
|
||||
{ tag: [tags.heading5, tags.heading6], fontSize: '1.1em' },
|
||||
]);
|
||||
|
||||
// 搜索匹配
|
||||
'.cm-searchMatch': {
|
||||
backgroundColor: 'transparent',
|
||||
outline: `1px solid ${colors.searchMatch}`,
|
||||
},
|
||||
'.cm-searchMatch.cm-searchMatch-selected': {
|
||||
backgroundColor: colors.searchMatch,
|
||||
color: colors.background,
|
||||
},
|
||||
'.cm-selectionMatch': {
|
||||
backgroundColor: '#e6f3ff',
|
||||
},
|
||||
|
||||
// 括号匹配
|
||||
'&.cm-focused .cm-matchingBracket, &.cm-focused .cm-nonmatchingBracket': {
|
||||
outline: `0.5px solid ${colors.searchMatch}`,
|
||||
},
|
||||
'&.cm-focused .cm-matchingBracket': {
|
||||
backgroundColor: colors.matchingBracket,
|
||||
color: 'inherit',
|
||||
},
|
||||
'&.cm-focused .cm-nonmatchingBracket': {
|
||||
outline: '0.5px solid #d73a49',
|
||||
},
|
||||
|
||||
// 编辑器焦点
|
||||
'&.cm-editor.cm-focused': {
|
||||
outline: 'none',
|
||||
},
|
||||
|
||||
// 工具提示
|
||||
'.cm-tooltip': {
|
||||
border: 'none',
|
||||
backgroundColor: colors.surface,
|
||||
boxShadow: '0 2px 8px rgba(0,0,0,0.1)',
|
||||
},
|
||||
'.cm-tooltip .cm-tooltip-arrow:before': {
|
||||
borderTopColor: 'transparent',
|
||||
borderBottomColor: 'transparent',
|
||||
},
|
||||
'.cm-tooltip .cm-tooltip-arrow:after': {
|
||||
borderTopColor: colors.surface,
|
||||
borderBottomColor: colors.surface,
|
||||
},
|
||||
'.cm-tooltip-autocomplete': {
|
||||
'& > ul > li[aria-selected]': {
|
||||
backgroundColor: colors.activeLine,
|
||||
color: colors.foreground,
|
||||
},
|
||||
},
|
||||
|
||||
// 代码块层
|
||||
'.code-blocks-layer': {
|
||||
width: '100%',
|
||||
},
|
||||
'.code-blocks-layer .block-even, .code-blocks-layer .block-odd': {
|
||||
width: '100%',
|
||||
boxSizing: 'content-box',
|
||||
},
|
||||
'.code-blocks-layer .block-even': {
|
||||
background: colors.background,
|
||||
borderTop: `1px solid ${colors.borderColor}`,
|
||||
},
|
||||
'.code-blocks-layer .block-even:first-child': {
|
||||
borderTop: 'none',
|
||||
},
|
||||
'.code-blocks-layer .block-odd': {
|
||||
background: colors.backgroundSecondary,
|
||||
borderTop: `1px solid ${colors.borderColor}`,
|
||||
},
|
||||
|
||||
// 代码块开始标记
|
||||
'.code-block-start': {
|
||||
height: '12px',
|
||||
},
|
||||
'.code-block-start.first': {
|
||||
height: '0px',
|
||||
},
|
||||
}, { dark: false });
|
||||
|
||||
export const light = [
|
||||
lightTheme,
|
||||
syntaxHighlighting(lightHighlightStyle),
|
||||
];
|
||||
// 语法高亮样式
|
||||
const lightHighlightStyle = HighlightStyle.define([
|
||||
{ tag: tags.keyword, color: colors.keyword },
|
||||
{ tag: [tags.name, tags.deleted, tags.character, tags.propertyName, tags.macroName], color: colors.variable },
|
||||
{ tag: [tags.variableName], color: colors.variable },
|
||||
{ tag: [tags.function(tags.variableName)], color: colors.function },
|
||||
{ tag: [tags.labelName], color: colors.operator },
|
||||
{ tag: [tags.color, tags.constant(tags.name), tags.standard(tags.name)], color: colors.keyword },
|
||||
{ tag: [tags.definition(tags.name), tags.separator], color: colors.function },
|
||||
{ tag: [tags.brace], color: colors.variable },
|
||||
{ tag: [tags.annotation], color: '#d73a49' },
|
||||
{ tag: [tags.number, tags.changed, tags.annotation, tags.modifier, tags.self, tags.namespace], color: colors.number },
|
||||
{ tag: [tags.typeName, tags.className], color: colors.type },
|
||||
{ tag: [tags.operator, tags.operatorKeyword], color: colors.operator },
|
||||
{ tag: [tags.tagName], color: colors.type },
|
||||
{ tag: [tags.squareBracket], color: colors.keyword },
|
||||
{ tag: [tags.angleBracket], color: colors.operator },
|
||||
{ tag: [tags.attributeName], color: colors.variable },
|
||||
{ tag: [tags.regexp], color: colors.string },
|
||||
{ tag: [tags.quote], color: colors.comment },
|
||||
{ tag: [tags.string], color: colors.string },
|
||||
{ tag: tags.link, color: colors.function, textDecoration: 'underline' },
|
||||
{ tag: [tags.url, tags.escape, tags.special(tags.string)], color: colors.string },
|
||||
{ tag: [tags.meta], color: colors.comment },
|
||||
{ tag: [tags.comment], color: colors.comment, fontStyle: 'italic' },
|
||||
{ tag: tags.strong, fontWeight: 'bold' },
|
||||
{ tag: tags.emphasis, fontStyle: 'italic' },
|
||||
{ tag: tags.strikethrough, textDecoration: 'line-through' },
|
||||
{ tag: tags.heading, fontWeight: 'bold', color: colors.keyword },
|
||||
{ tag: [tags.heading1, tags.heading2], fontSize: '1.4em' },
|
||||
{ tag: [tags.heading3, tags.heading4], fontSize: '1.2em' },
|
||||
{ tag: [tags.heading5, tags.heading6], fontSize: '1.1em' },
|
||||
]);
|
||||
|
||||
return [
|
||||
lightTheme,
|
||||
syntaxHighlighting(lightHighlightStyle),
|
||||
];
|
||||
}
|
||||
|
||||
// 默认浅色主题
|
||||
export const light = createLightTheme(defaultLightColors);
|
@@ -6,7 +6,12 @@ defineProps<{
|
||||
|
||||
<template>
|
||||
<div class="setting-section">
|
||||
<h2 class="section-title">{{ title }}</h2>
|
||||
<div class="section-header">
|
||||
<h2 class="section-title">{{ title }}</h2>
|
||||
<div class="section-title-right">
|
||||
<slot name="title-right"></slot>
|
||||
</div>
|
||||
</div>
|
||||
<div class="section-content">
|
||||
<slot></slot>
|
||||
</div>
|
||||
@@ -22,18 +27,29 @@ defineProps<{
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
||||
border: 1px solid var(--settings-border);
|
||||
|
||||
.section-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 10px 14px;
|
||||
background-color: var(--settings-hover);
|
||||
border-bottom: 1px solid var(--settings-border);
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
margin: 0;
|
||||
padding: 10px 14px;
|
||||
background-color: var(--settings-hover);
|
||||
color: var(--settings-text);
|
||||
border-bottom: 1px solid var(--settings-border);
|
||||
}
|
||||
|
||||
.section-title-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.section-content {
|
||||
padding: 6px 14px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</style>
|
@@ -2,14 +2,260 @@
|
||||
import { useConfigStore } from '@/stores/configStore';
|
||||
import { useThemeStore } from '@/stores/themeStore';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { computed, watch, onMounted, ref } from 'vue';
|
||||
import SettingSection from '../components/SettingSection.vue';
|
||||
import SettingItem from '../components/SettingItem.vue';
|
||||
import { SystemThemeType, LanguageType } from '@/../bindings/voidraft/internal/models/models';
|
||||
import { defaultDarkColors } from '@/views/editor/theme/dark';
|
||||
import { defaultLightColors } from '@/views/editor/theme/light';
|
||||
import PickColors from 'vue-pick-colors';
|
||||
|
||||
const { t } = useI18n();
|
||||
const configStore = useConfigStore();
|
||||
const themeStore = useThemeStore();
|
||||
|
||||
// 添加临时颜色状态
|
||||
const tempColors = ref({
|
||||
darkTheme: { ...configStore.config.appearance.customTheme?.darkTheme || defaultDarkColors },
|
||||
lightTheme: { ...configStore.config.appearance.customTheme?.lightTheme || defaultLightColors }
|
||||
});
|
||||
|
||||
// 标记是否有未保存的更改
|
||||
const hasUnsavedChanges = ref(false);
|
||||
|
||||
// 重置按钮状态
|
||||
const resetButtonState = ref({
|
||||
confirming: false,
|
||||
timer: null as number | null
|
||||
});
|
||||
|
||||
// 防抖函数
|
||||
const debounce = <T extends (...args: any[]) => any>(
|
||||
func: T,
|
||||
wait: number
|
||||
): ((...args: Parameters<T>) => void) => {
|
||||
let timeout: number | undefined;
|
||||
|
||||
return function(...args: Parameters<T>): void {
|
||||
clearTimeout(timeout);
|
||||
timeout = window.setTimeout(() => {
|
||||
func(...args);
|
||||
}, wait);
|
||||
};
|
||||
};
|
||||
|
||||
// 当前激活的主题类型(基于当前系统主题)
|
||||
const activeThemeType = computed(() => {
|
||||
const isDark =
|
||||
themeStore.currentTheme === SystemThemeType.SystemThemeDark ||
|
||||
(themeStore.currentTheme === SystemThemeType.SystemThemeAuto &&
|
||||
window.matchMedia('(prefers-color-scheme: dark)').matches);
|
||||
|
||||
return isDark ? 'darkTheme' : 'lightTheme';
|
||||
});
|
||||
|
||||
// 当前主题的颜色配置 - 使用临时状态
|
||||
const currentColors = computed(() => {
|
||||
const themeType = activeThemeType.value;
|
||||
return tempColors.value[themeType] ||
|
||||
(themeType === 'darkTheme' ? defaultDarkColors : defaultLightColors);
|
||||
});
|
||||
|
||||
// 获取当前主题模式
|
||||
const currentThemeMode = computed(() => {
|
||||
const isDark =
|
||||
themeStore.currentTheme === SystemThemeType.SystemThemeDark ||
|
||||
(themeStore.currentTheme === SystemThemeType.SystemThemeAuto &&
|
||||
window.matchMedia('(prefers-color-scheme: dark)').matches);
|
||||
|
||||
return isDark ? 'dark' : 'light';
|
||||
});
|
||||
|
||||
// 监听配置变更,更新临时颜色
|
||||
watch(
|
||||
() => configStore.config.appearance.customTheme,
|
||||
(newValue) => {
|
||||
if (!hasUnsavedChanges.value) {
|
||||
tempColors.value = {
|
||||
darkTheme: { ...newValue.darkTheme },
|
||||
lightTheme: { ...newValue.lightTheme }
|
||||
};
|
||||
}
|
||||
},
|
||||
{ deep: true, immediate: true }
|
||||
);
|
||||
|
||||
// 初始化时加载主题颜色
|
||||
onMounted(() => {
|
||||
// 使用themeStore中的颜色作为初始值
|
||||
tempColors.value = {
|
||||
darkTheme: { ...themeStore.themeColors.darkTheme },
|
||||
lightTheme: { ...themeStore.themeColors.lightTheme }
|
||||
};
|
||||
});
|
||||
|
||||
// 颜色配置分组
|
||||
const colorGroups = computed(() => [
|
||||
{
|
||||
key: 'basic',
|
||||
title: t('settings.themeColors.basic'),
|
||||
colors: [
|
||||
{ key: 'background', label: t('settings.themeColors.background') },
|
||||
{ key: 'backgroundSecondary', label: t('settings.themeColors.backgroundSecondary') },
|
||||
{ key: 'surface', label: t('settings.themeColors.surface') }
|
||||
]
|
||||
},
|
||||
{
|
||||
key: 'text',
|
||||
title: t('settings.themeColors.text'),
|
||||
colors: [
|
||||
{ key: 'foreground', label: t('settings.themeColors.foreground') },
|
||||
{ key: 'foregroundSecondary', label: t('settings.themeColors.foregroundSecondary') },
|
||||
{ key: 'comment', label: t('settings.themeColors.comment') }
|
||||
]
|
||||
},
|
||||
{
|
||||
key: 'syntax',
|
||||
title: t('settings.themeColors.syntax'),
|
||||
colors: [
|
||||
{ key: 'keyword', label: t('settings.themeColors.keyword') },
|
||||
{ key: 'string', label: t('settings.themeColors.string') },
|
||||
{ key: 'function', label: t('settings.themeColors.function') },
|
||||
{ key: 'number', label: t('settings.themeColors.number') },
|
||||
{ key: 'operator', label: t('settings.themeColors.operator') },
|
||||
{ key: 'variable', label: t('settings.themeColors.variable') },
|
||||
{ key: 'type', label: t('settings.themeColors.type') }
|
||||
]
|
||||
},
|
||||
{
|
||||
key: 'interface',
|
||||
title: t('settings.themeColors.interface'),
|
||||
colors: [
|
||||
{ key: 'cursor', label: t('settings.themeColors.cursor') },
|
||||
{ key: 'selection', label: t('settings.themeColors.selection') },
|
||||
{ key: 'selectionBlur', label: t('settings.themeColors.selectionBlur') },
|
||||
{ key: 'activeLine', label: t('settings.themeColors.activeLine') },
|
||||
{ key: 'lineNumber', label: t('settings.themeColors.lineNumber') },
|
||||
{ key: 'activeLineNumber', label: t('settings.themeColors.activeLineNumber') }
|
||||
]
|
||||
},
|
||||
{
|
||||
key: 'border',
|
||||
title: t('settings.themeColors.border'),
|
||||
colors: [
|
||||
{ key: 'borderColor', label: t('settings.themeColors.borderColor') },
|
||||
{ key: 'borderLight', label: t('settings.themeColors.borderLight') }
|
||||
]
|
||||
},
|
||||
{
|
||||
key: 'search',
|
||||
title: t('settings.themeColors.search'),
|
||||
colors: [
|
||||
{ key: 'searchMatch', label: t('settings.themeColors.searchMatch') },
|
||||
{ key: 'matchingBracket', label: t('settings.themeColors.matchingBracket') }
|
||||
]
|
||||
}
|
||||
]);
|
||||
|
||||
// 处理重置按钮点击
|
||||
const handleResetClick = () => {
|
||||
if (resetButtonState.value.confirming) {
|
||||
// 如果已经在确认状态,执行重置操作
|
||||
resetCurrentTheme();
|
||||
|
||||
// 重置按钮状态
|
||||
resetButtonState.value.confirming = false;
|
||||
if (resetButtonState.value.timer !== null) {
|
||||
clearTimeout(resetButtonState.value.timer);
|
||||
resetButtonState.value.timer = null;
|
||||
}
|
||||
} else {
|
||||
// 进入确认状态
|
||||
resetButtonState.value.confirming = true;
|
||||
|
||||
// 设置3秒后自动恢复
|
||||
resetButtonState.value.timer = window.setTimeout(() => {
|
||||
resetButtonState.value.confirming = false;
|
||||
resetButtonState.value.timer = null;
|
||||
}, 3000);
|
||||
}
|
||||
};
|
||||
|
||||
// 重置当前主题为默认配置
|
||||
const resetCurrentTheme = debounce(async () => {
|
||||
// 使用themeStore的原子重置操作
|
||||
const themeType = activeThemeType.value;
|
||||
const success = await themeStore.resetThemeColors(themeType);
|
||||
|
||||
if (success) {
|
||||
// 更新临时颜色状态
|
||||
tempColors.value = {
|
||||
darkTheme: { ...themeStore.themeColors.darkTheme },
|
||||
lightTheme: { ...themeStore.themeColors.lightTheme }
|
||||
};
|
||||
|
||||
// 标记没有未保存的更改
|
||||
hasUnsavedChanges.value = false;
|
||||
}
|
||||
}, 300);
|
||||
|
||||
// 更新本地颜色配置 - 仅更新临时状态,不提交到后端
|
||||
const updateLocalColor = (colorKey: string, value: string) => {
|
||||
const themeType = activeThemeType.value;
|
||||
|
||||
// 更新临时颜色
|
||||
tempColors.value = {
|
||||
...tempColors.value,
|
||||
[themeType]: {
|
||||
...tempColors.value[themeType],
|
||||
[colorKey]: value
|
||||
}
|
||||
};
|
||||
|
||||
// 标记有未保存的更改
|
||||
hasUnsavedChanges.value = true;
|
||||
};
|
||||
|
||||
// 防抖包装的颜色更新函数
|
||||
const updateColor = debounce(updateLocalColor, 100);
|
||||
|
||||
// 应用颜色更改到系统
|
||||
const applyChanges = async () => {
|
||||
try {
|
||||
// 获取当前主题的自定义颜色
|
||||
const customTheme = {
|
||||
darkTheme: tempColors.value.darkTheme,
|
||||
lightTheme: tempColors.value.lightTheme
|
||||
};
|
||||
|
||||
// 更新themeStore中的颜色
|
||||
themeStore.updateThemeColors(customTheme.darkTheme, customTheme.lightTheme);
|
||||
|
||||
// 保存到配置
|
||||
await themeStore.saveThemeColors();
|
||||
|
||||
// 刷新编辑器主题
|
||||
themeStore.refreshEditorTheme();
|
||||
|
||||
// 清除未保存标记
|
||||
hasUnsavedChanges.value = false;
|
||||
} catch (error) {
|
||||
console.error('Failed to apply theme change:', error);
|
||||
}
|
||||
};
|
||||
|
||||
// 取消颜色更改
|
||||
const cancelChanges = () => {
|
||||
// 恢复到themeStore中的颜色
|
||||
tempColors.value = {
|
||||
darkTheme: { ...themeStore.themeColors.darkTheme },
|
||||
lightTheme: { ...themeStore.themeColors.lightTheme }
|
||||
};
|
||||
|
||||
// 清除未保存标记
|
||||
hasUnsavedChanges.value = false;
|
||||
};
|
||||
|
||||
// 语言选项
|
||||
const languageOptions = [
|
||||
{ value: LanguageType.LangZhCN, label: t('languages.zh-CN') },
|
||||
@@ -38,6 +284,24 @@ const updateSystemTheme = async (event: Event) => {
|
||||
|
||||
await themeStore.setTheme(selectedSystemTheme);
|
||||
};
|
||||
|
||||
// 控制颜色选择器显示状态
|
||||
const showPickerMap = ref<Record<string, boolean>>({});
|
||||
|
||||
// 切换颜色选择器显示状态
|
||||
const toggleColorPicker = (colorKey: string) => {
|
||||
showPickerMap.value[colorKey] = !showPickerMap.value[colorKey];
|
||||
};
|
||||
|
||||
// 颜色变更处理
|
||||
const handleColorChange = (colorKey: string, value: string) => {
|
||||
updateColor(colorKey, value);
|
||||
};
|
||||
|
||||
// 颜色选择器关闭处理
|
||||
const handlePickerClose = () => {
|
||||
// 可以在此添加额外的逻辑
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -61,6 +325,70 @@ const updateSystemTheme = async (event: Event) => {
|
||||
</select>
|
||||
</SettingItem>
|
||||
</SettingSection>
|
||||
|
||||
<!-- 自定义主题颜色配置 -->
|
||||
<SettingSection :title="t('settings.customThemeColors')">
|
||||
<template #title-right>
|
||||
<div class="theme-controls">
|
||||
<button
|
||||
v-if="!hasUnsavedChanges"
|
||||
:class="['reset-button', resetButtonState.confirming ? 'reset-button-confirming' : '']"
|
||||
@click="handleResetClick"
|
||||
>
|
||||
{{ resetButtonState.confirming ? t('settings.confirmReset') : t('settings.resetToDefault') }}
|
||||
</button>
|
||||
<template v-else>
|
||||
<button class="apply-button" @click="applyChanges">
|
||||
{{ t('settings.apply') }}
|
||||
</button>
|
||||
<button class="cancel-button" @click="cancelChanges">
|
||||
{{ t('settings.cancel') }}
|
||||
</button>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div class="color-groups">
|
||||
<div v-for="group in colorGroups" :key="group.key" class="color-group">
|
||||
<h4 class="group-title">{{ group.title }}</h4>
|
||||
<div class="color-items">
|
||||
<SettingItem
|
||||
v-for="color in group.colors"
|
||||
:key="color.key"
|
||||
:title="color.label"
|
||||
class="color-setting-item"
|
||||
>
|
||||
<div class="color-input-wrapper">
|
||||
<div class="color-picker-wrapper">
|
||||
<PickColors
|
||||
v-model:value="currentColors[color.key]"
|
||||
v-model:show-picker="showPickerMap[color.key]"
|
||||
:size="28"
|
||||
show-alpha
|
||||
:theme="currentThemeMode"
|
||||
:colors="[]"
|
||||
format="hex"
|
||||
:format-options="['rgb', 'hex', 'hsl', 'hsv']"
|
||||
placement="bottom"
|
||||
position="absolute"
|
||||
:z-index="1000"
|
||||
@change="(val) => handleColorChange(color.key, val)"
|
||||
@close-picker="handlePickerClose"
|
||||
/>
|
||||
</div>
|
||||
<input
|
||||
type="text"
|
||||
:value="currentColors[color.key] || ''"
|
||||
@input="updateColor(color.key, ($event.target as HTMLInputElement).value)"
|
||||
class="color-text-input"
|
||||
:placeholder="t('settings.colorValue')"
|
||||
/>
|
||||
</div>
|
||||
</SettingItem>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</SettingSection>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -95,4 +423,146 @@ const updateSystemTheme = async (event: Event) => {
|
||||
color: var(--settings-text);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
// 主题控制区域
|
||||
.theme-controls {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
// 主题颜色配置样式
|
||||
.reset-button, .apply-button, .cancel-button {
|
||||
padding: 6px 12px;
|
||||
font-size: 12px;
|
||||
border: 1px solid var(--settings-input-border);
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
border-color: #4a9eff;
|
||||
}
|
||||
|
||||
&:active {
|
||||
transform: translateY(1px);
|
||||
}
|
||||
}
|
||||
|
||||
.reset-button {
|
||||
background-color: var(--settings-button-bg);
|
||||
color: var(--settings-button-text);
|
||||
|
||||
&:hover {
|
||||
background-color: var(--settings-button-hover-bg);
|
||||
}
|
||||
|
||||
&.reset-button-confirming {
|
||||
background-color: #e74c3c;
|
||||
color: white;
|
||||
border-color: #c0392b;
|
||||
|
||||
&:hover {
|
||||
background-color: #c0392b;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.apply-button {
|
||||
background-color: #4a9eff;
|
||||
color: white;
|
||||
font-weight: 500;
|
||||
|
||||
&:hover {
|
||||
background-color: #3a8eef;
|
||||
}
|
||||
}
|
||||
|
||||
.cancel-button {
|
||||
background-color: var(--settings-button-bg);
|
||||
color: var(--settings-button-text);
|
||||
|
||||
&:hover {
|
||||
background-color: var(--settings-button-hover-bg);
|
||||
}
|
||||
}
|
||||
|
||||
.color-groups {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
.color-group {
|
||||
.group-title {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: var(--settings-text);
|
||||
margin: 0 0 12px 0;
|
||||
padding-bottom: 6px;
|
||||
border-bottom: 1px solid var(--settings-input-border);
|
||||
}
|
||||
|
||||
.color-items {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||
gap: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.color-setting-item {
|
||||
:deep(.setting-item-content) {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
:deep(.setting-item-title) {
|
||||
font-size: 12px;
|
||||
min-width: 120px;
|
||||
}
|
||||
}
|
||||
|
||||
.color-input-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.color-picker-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 28px;
|
||||
cursor: pointer;
|
||||
|
||||
:deep(.pick-colors-trigger) {
|
||||
border: 1px solid var(--settings-input-border);
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
.color-text-input {
|
||||
flex: 1;
|
||||
min-width: 160px;
|
||||
padding: 4px 8px;
|
||||
border: 1px solid var(--settings-input-border);
|
||||
border-radius: 4px;
|
||||
background-color: var(--settings-input-bg);
|
||||
color: var(--settings-text);
|
||||
font-size: 11px;
|
||||
font-family: 'Courier New', monospace;
|
||||
transition: border-color 0.2s ease;
|
||||
height: 28px;
|
||||
box-sizing: border-box;
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
border-color: #4a9eff;
|
||||
}
|
||||
|
||||
&::placeholder {
|
||||
color: var(--settings-text-secondary);
|
||||
}
|
||||
}
|
||||
</style>
|
@@ -1,6 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { useConfigStore } from '@/stores/configStore';
|
||||
import { FONT_OPTIONS } from '@/stores/configStore';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import {computed, onMounted } from 'vue';
|
||||
import SettingSection from '../components/SettingSection.vue';
|
||||
@@ -19,7 +18,7 @@ onMounted(async () => {
|
||||
});
|
||||
|
||||
// 字体选择选项
|
||||
const fontFamilyOptions = FONT_OPTIONS;
|
||||
const fontFamilyOptions = computed(() => configStore.localizedFontOptions);
|
||||
const currentFontFamily = computed(() => configStore.config.editing.fontFamily);
|
||||
|
||||
// 字体选择
|
||||
@@ -33,15 +32,15 @@ const handleFontFamilyChange = async (event: Event) => {
|
||||
|
||||
// 字体粗细选项
|
||||
const fontWeightOptions = [
|
||||
{ value: '100', label: '极细 (100)' },
|
||||
{ value: '200', label: '超细 (200)' },
|
||||
{ value: '300', label: '细 (300)' },
|
||||
{ value: 'normal', label: '正常 (400)' },
|
||||
{ value: '500', label: '中等 (500)' },
|
||||
{ value: '600', label: '半粗 (600)' },
|
||||
{ value: 'bold', label: '粗体 (700)' },
|
||||
{ value: '800', label: '超粗 (800)' },
|
||||
{ value: '900', label: '极粗 (900)' }
|
||||
{ value: '100', label: t('settings.fontWeights.100') },
|
||||
{ value: '200', label: t('settings.fontWeights.200') },
|
||||
{ value: '300', label: t('settings.fontWeights.300') },
|
||||
{ value: 'normal', label: t('settings.fontWeights.normal') },
|
||||
{ value: '500', label: t('settings.fontWeights.500') },
|
||||
{ value: '600', label: t('settings.fontWeights.600') },
|
||||
{ value: 'bold', label: t('settings.fontWeights.bold') },
|
||||
{ value: '800', label: t('settings.fontWeights.800') },
|
||||
{ value: '900', label: t('settings.fontWeights.900') }
|
||||
];
|
||||
|
||||
// 字体粗细选择
|
||||
@@ -213,7 +212,7 @@ const handleAutoSaveDelayChange = async (event: Event) => {
|
||||
</SettingSection>
|
||||
|
||||
<SettingSection :title="t('settings.saveOptions')">
|
||||
<SettingItem :title="t('settings.autoSaveDelay')" :description="'定时保存间隔,每隔指定时间自动保存(仅在有变更时)'">
|
||||
<SettingItem :title="t('settings.autoSaveDelay')">
|
||||
<input
|
||||
type="number"
|
||||
class="number-input"
|
||||
|
@@ -321,8 +321,8 @@ onUnmounted(() => {
|
||||
</div>
|
||||
|
||||
<div class="hotkey-preview">
|
||||
<span class="preview-label">预览:</span>
|
||||
<span class="preview-hotkey">{{ hotkeyPreview || '无' }}</span>
|
||||
<span class="preview-label">{{ t('settings.hotkeyPreview') }}</span>
|
||||
<span class="preview-hotkey">{{ hotkeyPreview || t('settings.none') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</SettingSection>
|
||||
|
10
go.mod
10
go.mod
@@ -10,11 +10,10 @@ require (
|
||||
github.com/knadh/koanf/providers/structs v1.0.0
|
||||
github.com/knadh/koanf/v2 v2.2.1
|
||||
github.com/robertkrimen/otto v0.5.1
|
||||
github.com/wailsapp/wails/v3 v3.0.0-alpha.9
|
||||
github.com/wailsapp/wails/v3 v3.0.0-alpha.10
|
||||
golang.org/x/net v0.41.0
|
||||
golang.org/x/sys v0.33.0
|
||||
golang.org/x/text v0.26.0
|
||||
modernc.org/sqlite v1.38.0
|
||||
)
|
||||
|
||||
require (
|
||||
@@ -54,14 +53,14 @@ require (
|
||||
github.com/leaanthony/go-ansi-parser v1.6.1 // indirect
|
||||
github.com/leaanthony/u v1.1.1 // indirect
|
||||
github.com/lmittmann/tint v1.1.2 // indirect
|
||||
github.com/matryer/is v1.4.1 // indirect
|
||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mitchellh/copystructure v1.2.0 // indirect
|
||||
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
||||
github.com/ncruces/go-strftime v0.1.9 // indirect
|
||||
github.com/pjbgf/sha1cd v0.3.2 // indirect
|
||||
github.com/pjbgf/sha1cd v0.4.0 // indirect
|
||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||
github.com/rivo/uniseg v0.4.7 // indirect
|
||||
github.com/samber/lo v1.51.0 // indirect
|
||||
@@ -80,7 +79,8 @@ require (
|
||||
gopkg.in/sourcemap.v1 v1.0.5 // indirect
|
||||
gopkg.in/warnings.v0 v0.1.2 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
modernc.org/libc v1.66.2 // indirect
|
||||
modernc.org/libc v1.66.3 // indirect
|
||||
modernc.org/mathutil v1.7.1 // indirect
|
||||
modernc.org/memory v1.11.0 // indirect
|
||||
modernc.org/sqlite v1.38.0 // indirect
|
||||
)
|
||||
|
16
go.sum
16
go.sum
@@ -129,8 +129,8 @@ github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdh
|
||||
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
|
||||
github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k=
|
||||
github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY=
|
||||
github.com/pjbgf/sha1cd v0.3.2 h1:a9wb0bp1oC2TGwStyn0Umc/IGKQnEgF0vVaZ8QF8eo4=
|
||||
github.com/pjbgf/sha1cd v0.3.2/go.mod h1:zQWigSxVmsHEZow5qaLtPYxpcKMMQpa09ixqBxuCS6A=
|
||||
github.com/pjbgf/sha1cd v0.4.0 h1:NXzbL1RvjTUi6kgYZCX3fPwwl27Q1LJndxtUDVfJGRY=
|
||||
github.com/pjbgf/sha1cd v0.4.0/go.mod h1:zQWigSxVmsHEZow5qaLtPYxpcKMMQpa09ixqBxuCS6A=
|
||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
|
||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
@@ -164,8 +164,8 @@ github.com/wailsapp/go-webview2 v1.0.21 h1:k3dtoZU4KCoN/AEIbWiPln3P2661GtA2oEgA2
|
||||
github.com/wailsapp/go-webview2 v1.0.21/go.mod h1:qJmWAmAmaniuKGZPWwne+uor3AHMB5PFhqiK0Bbj8kc=
|
||||
github.com/wailsapp/mimetype v1.4.1 h1:pQN9ycO7uo4vsUUuPeHEYoUkLVkaRntMnHJxVwYhwHs=
|
||||
github.com/wailsapp/mimetype v1.4.1/go.mod h1:9aV5k31bBOv5z6u+QP8TltzvNGJPmNJD4XlAL3U+j3o=
|
||||
github.com/wailsapp/wails/v3 v3.0.0-alpha.9 h1:b8CfRrhPno8Fra0xFp4Ifyj+ogmXBc35rsQWvcrHtsI=
|
||||
github.com/wailsapp/wails/v3 v3.0.0-alpha.9/go.mod h1:dSv6s722nSWaUyUiapAM1DHc5HKggNGY1a79shO85/g=
|
||||
github.com/wailsapp/wails/v3 v3.0.0-alpha.10 h1:SrxwhkBcdtaSxQ/zujJuifJN5q8hxyba5UKv5oaM/X4=
|
||||
github.com/wailsapp/wails/v3 v3.0.0-alpha.10/go.mod h1:4LCCW7s9e4PuSmu7l9OTvfWIGMO8TaSiftSeR5NpBIc=
|
||||
github.com/xanzy/go-gitlab v0.115.0 h1:6DmtItNcVe+At/liXSgfE/DZNZrGfalQmBRmOcJjOn8=
|
||||
github.com/xanzy/go-gitlab v0.115.0/go.mod h1:5XCDtM7AM6WMKmfDdOiEpyRWUqui2iS9ILfvCZ2gJ5M=
|
||||
github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=
|
||||
@@ -242,10 +242,10 @@ modernc.org/fileutil v1.3.8 h1:qtzNm7ED75pd1C7WgAGcK4edm4fvhtBsEiI/0NQ54YM=
|
||||
modernc.org/fileutil v1.3.8/go.mod h1:HxmghZSZVAz/LXcMNwZPA/DRrQZEVP9VX0V4LQGQFOc=
|
||||
modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI=
|
||||
modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=
|
||||
modernc.org/goabi0 v0.1.2 h1:9mfG19tFBypPnlSKRAjI5nXGMLmVy+jLyKNVKsMzt/8=
|
||||
modernc.org/goabi0 v0.1.2/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI=
|
||||
modernc.org/libc v1.66.2 h1:JCBxlJzZOIwZY54fzjHN3Wsn8Ty5PUTPr/xioRkmecI=
|
||||
modernc.org/libc v1.66.2/go.mod h1:ceIGzvXxP+JV3pgVjP9avPZo6Chlsfof2egXBH3YT5Q=
|
||||
modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks=
|
||||
modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI=
|
||||
modernc.org/libc v1.66.3 h1:cfCbjTUcdsKyyZZfEUKfoHcP3S0Wkvz3jgSzByEWVCQ=
|
||||
modernc.org/libc v1.66.3/go.mod h1:XD9zO8kt59cANKvHPXpx7yS2ELPheAey0vjIuZOhOU8=
|
||||
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
|
||||
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
|
||||
modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
|
||||
|
@@ -33,13 +33,13 @@ func RegisterTrayEvents(app *application.App, systray *application.SystemTray, m
|
||||
|
||||
// RegisterTrayMenuEvents 注册系统托盘菜单事件
|
||||
func RegisterTrayMenuEvents(app *application.App, menu *application.Menu, mainWindow *application.WebviewWindow) {
|
||||
menu.Add("主窗口").OnClick(func(data *application.Context) {
|
||||
menu.Add("Main window").OnClick(func(data *application.Context) {
|
||||
mainWindow.Show()
|
||||
})
|
||||
|
||||
menu.AddSeparator()
|
||||
|
||||
menu.Add("退出").OnClick(func(data *application.Context) {
|
||||
menu.Add("Quit").OnClick(func(data *application.Context) {
|
||||
app.Quit()
|
||||
})
|
||||
}
|
||||
|
@@ -101,8 +101,9 @@ type EditingConfig struct {
|
||||
|
||||
// AppearanceConfig 外观设置配置
|
||||
type AppearanceConfig struct {
|
||||
Language LanguageType `json:"language"` // 界面语言
|
||||
SystemTheme SystemThemeType `json:"systemTheme"` // 系统界面主题
|
||||
Language LanguageType `json:"language"` // 界面语言
|
||||
SystemTheme SystemThemeType `json:"systemTheme"` // 系统界面主题
|
||||
CustomTheme CustomThemeConfig `json:"customTheme"` // 自定义主题配置
|
||||
}
|
||||
|
||||
// UpdatesConfig 更新设置配置
|
||||
@@ -164,11 +165,12 @@ func NewDefaultAppConfig() *AppConfig {
|
||||
TabSize: 4,
|
||||
TabType: TabTypeTab,
|
||||
// 保存选项
|
||||
AutoSaveDelay: 2000, // 2秒后自动保存
|
||||
AutoSaveDelay: 2000,
|
||||
},
|
||||
Appearance: AppearanceConfig{
|
||||
Language: LangEnUS,
|
||||
SystemTheme: SystemThemeAuto, // 默认使用深色系统主题
|
||||
SystemTheme: SystemThemeAuto,
|
||||
CustomTheme: *NewDefaultCustomThemeConfig(),
|
||||
},
|
||||
Updates: UpdatesConfig{
|
||||
Version: "1.0.0",
|
||||
|
127
internal/models/theme.go
Normal file
127
internal/models/theme.go
Normal file
@@ -0,0 +1,127 @@
|
||||
package models
|
||||
|
||||
// ThemeColorConfig 主题颜色配置
|
||||
type ThemeColorConfig struct {
|
||||
// 基础色调
|
||||
Background string `json:"background"` // 主背景色
|
||||
BackgroundSecondary string `json:"backgroundSecondary"` // 次要背景色
|
||||
Surface string `json:"surface"` // 面板背景
|
||||
Foreground string `json:"foreground"` // 主文本色
|
||||
ForegroundSecondary string `json:"foregroundSecondary"` // 次要文本色
|
||||
|
||||
// 语法高亮
|
||||
Comment string `json:"comment"` // 注释色
|
||||
Keyword string `json:"keyword"` // 关键字
|
||||
String string `json:"string"` // 字符串
|
||||
Function string `json:"function"` // 函数名
|
||||
Number string `json:"number"` // 数字
|
||||
Operator string `json:"operator"` // 操作符
|
||||
Variable string `json:"variable"` // 变量
|
||||
Type string `json:"type"` // 类型
|
||||
|
||||
// 界面元素
|
||||
Cursor string `json:"cursor"` // 光标
|
||||
Selection string `json:"selection"` // 选中背景
|
||||
SelectionBlur string `json:"selectionBlur"` // 失焦选中背景
|
||||
ActiveLine string `json:"activeLine"` // 当前行高亮
|
||||
LineNumber string `json:"lineNumber"` // 行号
|
||||
ActiveLineNumber string `json:"activeLineNumber"` // 活动行号
|
||||
|
||||
// 边框分割线
|
||||
BorderColor string `json:"borderColor"` // 边框色
|
||||
BorderLight string `json:"borderLight"` // 浅色边框
|
||||
|
||||
// 搜索匹配
|
||||
SearchMatch string `json:"searchMatch"` // 搜索匹配
|
||||
MatchingBracket string `json:"matchingBracket"` // 匹配括号
|
||||
}
|
||||
|
||||
// CustomThemeConfig 自定义主题配置
|
||||
type CustomThemeConfig struct {
|
||||
DarkTheme ThemeColorConfig `json:"darkTheme"` // 深色主题配置
|
||||
LightTheme ThemeColorConfig `json:"lightTheme"` // 浅色主题配置
|
||||
}
|
||||
|
||||
// NewDefaultDarkTheme 创建默认深色主题配置
|
||||
func NewDefaultDarkTheme() ThemeColorConfig {
|
||||
return ThemeColorConfig{
|
||||
// 基础色调
|
||||
Background: "#252B37",
|
||||
BackgroundSecondary: "#213644",
|
||||
Surface: "#474747",
|
||||
Foreground: "#9BB586",
|
||||
ForegroundSecondary: "#9c9c9c",
|
||||
|
||||
// 语法高亮
|
||||
Comment: "#6272a4",
|
||||
Keyword: "#ff79c6",
|
||||
String: "#f1fa8c",
|
||||
Function: "#50fa7b",
|
||||
Number: "#bd93f9",
|
||||
Operator: "#ff79c6",
|
||||
Variable: "#8fbcbb",
|
||||
Type: "#8be9fd",
|
||||
|
||||
// 界面元素
|
||||
Cursor: "#ffffff",
|
||||
Selection: "#0865a9",
|
||||
SelectionBlur: "#225377",
|
||||
ActiveLine: "#ffffff",
|
||||
LineNumber: "#ffffff",
|
||||
ActiveLineNumber: "#ffffff",
|
||||
|
||||
// 边框分割线
|
||||
BorderColor: "#1e222a",
|
||||
BorderLight: "#ffffff1a",
|
||||
|
||||
// 搜索匹配
|
||||
SearchMatch: "#8fbcbb",
|
||||
MatchingBracket: "#ffffff1a",
|
||||
}
|
||||
}
|
||||
|
||||
// NewDefaultLightTheme 创建默认浅色主题配置
|
||||
func NewDefaultLightTheme() ThemeColorConfig {
|
||||
return ThemeColorConfig{
|
||||
// 基础色调
|
||||
Background: "#ffffff",
|
||||
BackgroundSecondary: "#f1faf1",
|
||||
Surface: "#f5f5f5",
|
||||
Foreground: "#444d56",
|
||||
ForegroundSecondary: "#6a737d",
|
||||
|
||||
// 语法高亮
|
||||
Comment: "#6a737d",
|
||||
Keyword: "#d73a49",
|
||||
String: "#032f62",
|
||||
Function: "#005cc5",
|
||||
Number: "#005cc5",
|
||||
Operator: "#d73a49",
|
||||
Variable: "#24292e",
|
||||
Type: "#6f42c1",
|
||||
|
||||
// 界面元素
|
||||
Cursor: "#000000",
|
||||
Selection: "#77baff",
|
||||
SelectionBlur: "#b2c2ca",
|
||||
ActiveLine: "#000000",
|
||||
LineNumber: "#000000",
|
||||
ActiveLineNumber: "#000000",
|
||||
|
||||
// 边框分割线
|
||||
BorderColor: "#dfdfdf",
|
||||
BorderLight: "#0000000d",
|
||||
|
||||
// 搜索匹配
|
||||
SearchMatch: "#005cc5",
|
||||
MatchingBracket: "#0000001a",
|
||||
}
|
||||
}
|
||||
|
||||
// NewDefaultCustomThemeConfig 创建默认自定义主题配置
|
||||
func NewDefaultCustomThemeConfig() *CustomThemeConfig {
|
||||
return &CustomThemeConfig{
|
||||
DarkTheme: NewDefaultDarkTheme(),
|
||||
LightTheme: NewDefaultLightTheme(),
|
||||
}
|
||||
}
|
@@ -19,7 +19,7 @@ import (
|
||||
|
||||
const (
|
||||
// CurrentAppConfigVersion 当前应用配置版本
|
||||
CurrentAppConfigVersion = "1.0.0"
|
||||
CurrentAppConfigVersion = "1.2.0"
|
||||
// BackupFilePattern 备份文件名模式
|
||||
BackupFilePattern = "%s.backup.%s.json"
|
||||
|
||||
@@ -38,7 +38,7 @@ type Migratable interface {
|
||||
|
||||
// ConfigMigrationService 配置迁移服务
|
||||
type ConfigMigrationService[T Migratable] struct {
|
||||
logger *log.LoggerService
|
||||
logger *log.Service
|
||||
configDir string
|
||||
configName string
|
||||
targetVersion string
|
||||
@@ -54,7 +54,7 @@ type MigrationResult struct {
|
||||
|
||||
// NewConfigMigrationService 创建配置迁移服务
|
||||
func NewConfigMigrationService[T Migratable](
|
||||
logger *log.LoggerService,
|
||||
logger *log.Service,
|
||||
configDir string,
|
||||
configName, targetVersion, configPath string,
|
||||
) *ConfigMigrationService[T] {
|
||||
@@ -312,7 +312,7 @@ func chainLoad(k *koanf.Koanf, loaders ...func() error) error {
|
||||
}
|
||||
|
||||
// 工厂函数
|
||||
func NewAppConfigMigrationService(logger *log.LoggerService, configDir, settingsPath string) *ConfigMigrationService[*models.AppConfig] {
|
||||
func NewAppConfigMigrationService(logger *log.Service, configDir, settingsPath string) *ConfigMigrationService[*models.AppConfig] {
|
||||
return NewConfigMigrationService[*models.AppConfig](
|
||||
logger, configDir, "settings", CurrentAppConfigVersion, settingsPath)
|
||||
}
|
||||
|
@@ -49,7 +49,7 @@ type ConfigListener struct {
|
||||
type ConfigNotificationService struct {
|
||||
listeners map[ConfigChangeType][]*ConfigListener // 支持多监听器的map
|
||||
mu sync.RWMutex // 监听器map的读写锁
|
||||
logger *log.LoggerService // 日志服务
|
||||
logger *log.Service // 日志服务
|
||||
koanf *koanf.Koanf // koanf实例
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
@@ -57,7 +57,7 @@ type ConfigNotificationService struct {
|
||||
}
|
||||
|
||||
// NewConfigNotificationService 创建配置通知服务
|
||||
func NewConfigNotificationService(k *koanf.Koanf, logger *log.LoggerService) *ConfigNotificationService {
|
||||
func NewConfigNotificationService(k *koanf.Koanf, logger *log.Service) *ConfigNotificationService {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
return &ConfigNotificationService{
|
||||
listeners: make(map[ConfigChangeType][]*ConfigListener),
|
||||
@@ -445,8 +445,8 @@ func CreateDataPathListener(name string, callback func() error) *ConfigListener
|
||||
}
|
||||
}
|
||||
|
||||
// OnShutdown 关闭服务
|
||||
func (cns *ConfigNotificationService) OnShutdown() error {
|
||||
// ServiceShutdown 关闭服务
|
||||
func (cns *ConfigNotificationService) ServiceShutdown() error {
|
||||
cns.Cleanup()
|
||||
return nil
|
||||
}
|
||||
|
@@ -18,12 +18,12 @@ import (
|
||||
|
||||
// ConfigService 应用配置服务
|
||||
type ConfigService struct {
|
||||
koanf *koanf.Koanf // koanf 实例
|
||||
logger *log.LoggerService // 日志服务
|
||||
configDir string // 配置目录
|
||||
settingsPath string // 设置文件路径
|
||||
mu sync.RWMutex // 读写锁
|
||||
fileProvider *file.File // 文件提供器,用于监听
|
||||
koanf *koanf.Koanf // koanf 实例
|
||||
logger *log.Service // 日志服务
|
||||
configDir string // 配置目录
|
||||
settingsPath string // 设置文件路径
|
||||
mu sync.RWMutex // 读写锁
|
||||
fileProvider *file.File // 文件提供器,用于监听
|
||||
|
||||
// 配置通知服务
|
||||
notificationService *ConfigNotificationService
|
||||
@@ -55,7 +55,7 @@ func (e *ConfigError) Is(target error) bool {
|
||||
}
|
||||
|
||||
// NewConfigService 创建新的配置服务实例
|
||||
func NewConfigService(logger *log.LoggerService) *ConfigService {
|
||||
func NewConfigService(logger *log.Service) *ConfigService {
|
||||
// 获取用户主目录
|
||||
homeDir, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
@@ -298,8 +298,8 @@ func (cs *ConfigService) SetDataPathChangeCallback(callback func() error) error
|
||||
return cs.notificationService.RegisterListener(dataPathListener)
|
||||
}
|
||||
|
||||
// OnShutdown 关闭服务
|
||||
func (cs *ConfigService) OnShutdown() error {
|
||||
// ServiceShutdown 关闭服务
|
||||
func (cs *ConfigService) ServiceShutdown() error {
|
||||
cs.stopWatching()
|
||||
if cs.notificationService != nil {
|
||||
cs.notificationService.Cleanup()
|
||||
|
@@ -2,7 +2,6 @@ package services
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@@ -10,7 +9,7 @@ import (
|
||||
|
||||
"github.com/wailsapp/wails/v3/pkg/application"
|
||||
"github.com/wailsapp/wails/v3/pkg/services/log"
|
||||
_ "modernc.org/sqlite" // SQLite driver
|
||||
"github.com/wailsapp/wails/v3/pkg/services/sqlite"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -64,14 +63,14 @@ CREATE TABLE IF NOT EXISTS key_bindings (
|
||||
// DatabaseService provides shared database functionality
|
||||
type DatabaseService struct {
|
||||
configService *ConfigService
|
||||
logger *log.LoggerService
|
||||
db *sql.DB
|
||||
logger *log.Service
|
||||
SQLite *sqlite.Service
|
||||
mu sync.RWMutex
|
||||
ctx context.Context
|
||||
}
|
||||
|
||||
// NewDatabaseService creates a new database service
|
||||
func NewDatabaseService(configService *ConfigService, logger *log.LoggerService) *DatabaseService {
|
||||
func NewDatabaseService(configService *ConfigService, logger *log.Service) *DatabaseService {
|
||||
if logger == nil {
|
||||
logger = log.New()
|
||||
}
|
||||
@@ -79,11 +78,12 @@ func NewDatabaseService(configService *ConfigService, logger *log.LoggerService)
|
||||
return &DatabaseService{
|
||||
configService: configService,
|
||||
logger: logger,
|
||||
SQLite: sqlite.New(),
|
||||
}
|
||||
}
|
||||
|
||||
// OnStartup initializes the service when the application starts
|
||||
func (ds *DatabaseService) OnStartup(ctx context.Context, _ application.ServiceOptions) error {
|
||||
// ServiceStartup initializes the service when the application starts
|
||||
func (ds *DatabaseService) ServiceStartup(ctx context.Context, options application.ServiceOptions) error {
|
||||
ds.ctx = ctx
|
||||
return ds.initDatabase()
|
||||
}
|
||||
@@ -111,24 +111,26 @@ func (ds *DatabaseService) initDatabase() error {
|
||||
file.Close()
|
||||
}
|
||||
|
||||
db, err := sql.Open("sqlite", dbPath)
|
||||
if err != nil {
|
||||
// 配置SQLite服务
|
||||
ds.SQLite.Configure(&sqlite.Config{
|
||||
DBSource: dbPath,
|
||||
})
|
||||
|
||||
// 打开数据库连接
|
||||
if err := ds.SQLite.Open(); err != nil {
|
||||
return fmt.Errorf("failed to open database: %w", err)
|
||||
}
|
||||
|
||||
ds.db = db
|
||||
|
||||
// Apply optimization settings
|
||||
if _, err := db.Exec(sqlOptimizationSettings); err != nil {
|
||||
// 应用性能优化设置
|
||||
if err := ds.SQLite.Execute(sqlOptimizationSettings); err != nil {
|
||||
return fmt.Errorf("failed to apply optimization settings: %w", err)
|
||||
}
|
||||
|
||||
// Create all tables
|
||||
// 创建表和索引
|
||||
if err := ds.createTables(); err != nil {
|
||||
return fmt.Errorf("failed to create tables: %w", err)
|
||||
}
|
||||
|
||||
// Create indexes
|
||||
if err := ds.createIndexes(); err != nil {
|
||||
return fmt.Errorf("failed to create indexes: %w", err)
|
||||
}
|
||||
@@ -154,7 +156,7 @@ func (ds *DatabaseService) createTables() error {
|
||||
}
|
||||
|
||||
for _, table := range tables {
|
||||
if _, err := ds.db.Exec(table); err != nil {
|
||||
if err := ds.SQLite.Execute(table); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -177,35 +179,25 @@ func (ds *DatabaseService) createIndexes() error {
|
||||
}
|
||||
|
||||
for _, index := range indexes {
|
||||
if _, err := ds.db.Exec(index); err != nil {
|
||||
if err := ds.SQLite.Execute(index); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetDB returns the database connection
|
||||
func (ds *DatabaseService) GetDB() *sql.DB {
|
||||
ds.mu.RLock()
|
||||
defer ds.mu.RUnlock()
|
||||
return ds.db
|
||||
}
|
||||
|
||||
// OnShutdown shuts down the service when the application closes
|
||||
func (ds *DatabaseService) OnShutdown() error {
|
||||
if ds.db != nil {
|
||||
return ds.db.Close()
|
||||
}
|
||||
return nil
|
||||
// ServiceShutdown shuts down the service when the application closes
|
||||
func (ds *DatabaseService) ServiceShutdown() error {
|
||||
return ds.SQLite.Close()
|
||||
}
|
||||
|
||||
// OnDataPathChanged handles data path changes
|
||||
func (ds *DatabaseService) OnDataPathChanged() error {
|
||||
// Close existing database
|
||||
if ds.db != nil {
|
||||
ds.db.Close()
|
||||
// 关闭当前连接
|
||||
if err := ds.SQLite.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Reinitialize with new path
|
||||
// 用新路径重新初始化
|
||||
return ds.initDatabase()
|
||||
}
|
||||
|
@@ -7,12 +7,12 @@ import (
|
||||
|
||||
// DialogService 对话框服务,处理文件选择等对话框操作
|
||||
type DialogService struct {
|
||||
logger *log.LoggerService
|
||||
logger *log.Service
|
||||
window *application.WebviewWindow // 绑定的窗口
|
||||
}
|
||||
|
||||
// NewDialogService 创建新的对话框服务实例
|
||||
func NewDialogService(logger *log.LoggerService) *DialogService {
|
||||
func NewDialogService(logger *log.Service) *DialogService {
|
||||
if logger == nil {
|
||||
logger = log.New()
|
||||
}
|
||||
@@ -30,7 +30,8 @@ func (ds *DialogService) SetWindow(window *application.WebviewWindow) {
|
||||
|
||||
// SelectDirectory 打开目录选择对话框
|
||||
func (ds *DialogService) SelectDirectory() (string, error) {
|
||||
dialog := application.OpenFileDialogWithOptions(&application.OpenFileDialogOptions{
|
||||
dialog := application.OpenFileDialog()
|
||||
dialog.SetOptions(&application.OpenFileDialogOptions{
|
||||
// 目录选择配置
|
||||
CanChooseDirectories: true, // 允许选择目录
|
||||
CanChooseFiles: false, // 不允许选择文件
|
||||
|
@@ -11,7 +11,6 @@ import (
|
||||
|
||||
"github.com/wailsapp/wails/v3/pkg/application"
|
||||
"github.com/wailsapp/wails/v3/pkg/services/log"
|
||||
_ "modernc.org/sqlite" // SQLite driver
|
||||
)
|
||||
|
||||
// SQL constants for document operations
|
||||
@@ -70,13 +69,13 @@ SELECT id FROM documents WHERE is_deleted = 0 ORDER BY id LIMIT 1`
|
||||
// DocumentService provides document management functionality
|
||||
type DocumentService struct {
|
||||
databaseService *DatabaseService
|
||||
logger *log.LoggerService
|
||||
logger *log.Service
|
||||
mu sync.RWMutex
|
||||
ctx context.Context
|
||||
}
|
||||
|
||||
// NewDocumentService creates a new document service
|
||||
func NewDocumentService(databaseService *DatabaseService, logger *log.LoggerService) *DocumentService {
|
||||
func NewDocumentService(databaseService *DatabaseService, logger *log.Service) *DocumentService {
|
||||
if logger == nil {
|
||||
logger = log.New()
|
||||
}
|
||||
@@ -87,8 +86,8 @@ func NewDocumentService(databaseService *DatabaseService, logger *log.LoggerServ
|
||||
}
|
||||
}
|
||||
|
||||
// OnStartup initializes the service when the application starts
|
||||
func (ds *DocumentService) OnStartup(ctx context.Context, _ application.ServiceOptions) error {
|
||||
// ServiceStartup initializes the service when the application starts
|
||||
func (ds *DocumentService) ServiceStartup(ctx context.Context, options application.ServiceOptions) error {
|
||||
ds.ctx = ctx
|
||||
// Ensure default document exists
|
||||
if err := ds.ensureDefaultDocument(); err != nil {
|
||||
@@ -100,13 +99,20 @@ func (ds *DocumentService) OnStartup(ctx context.Context, _ application.ServiceO
|
||||
// ensureDefaultDocument ensures a default document exists
|
||||
func (ds *DocumentService) ensureDefaultDocument() error {
|
||||
// Check if any document exists
|
||||
var count int
|
||||
db := ds.databaseService.GetDB()
|
||||
err := db.QueryRow(sqlCountDocuments).Scan(&count)
|
||||
rows, err := ds.databaseService.SQLite.Query(sqlCountDocuments)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(rows) == 0 {
|
||||
return fmt.Errorf("failed to query document count")
|
||||
}
|
||||
|
||||
count, ok := rows[0]["COUNT(*)"].(int64)
|
||||
if !ok {
|
||||
return fmt.Errorf("failed to convert count to int64")
|
||||
}
|
||||
|
||||
// If no documents exist, create default document
|
||||
if count == 0 {
|
||||
defaultDoc := models.NewDefaultDocument()
|
||||
@@ -121,20 +127,50 @@ func (ds *DocumentService) GetDocumentByID(id int64) (*models.Document, error) {
|
||||
ds.mu.RLock()
|
||||
defer ds.mu.RUnlock()
|
||||
|
||||
var doc models.Document
|
||||
var isDeletedInt int
|
||||
db := ds.databaseService.GetDB()
|
||||
row := db.QueryRow(sqlGetDocumentByID, id)
|
||||
err := row.Scan(&doc.ID, &doc.Title, &doc.Content, &doc.CreatedAt, &doc.UpdatedAt, &isDeletedInt)
|
||||
rows, err := ds.databaseService.SQLite.Query(sqlGetDocumentByID, id)
|
||||
if err != nil {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, fmt.Errorf("failed to get document by ID: %w", err)
|
||||
}
|
||||
doc.IsDeleted = isDeletedInt == 1
|
||||
|
||||
return &doc, nil
|
||||
if len(rows) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
row := rows[0]
|
||||
doc := &models.Document{}
|
||||
|
||||
// 从Row中提取数据
|
||||
if idVal, ok := row["id"].(int64); ok {
|
||||
doc.ID = idVal
|
||||
}
|
||||
|
||||
if title, ok := row["title"].(string); ok {
|
||||
doc.Title = title
|
||||
}
|
||||
|
||||
if content, ok := row["content"].(string); ok {
|
||||
doc.Content = content
|
||||
}
|
||||
|
||||
if createdAt, ok := row["created_at"].(string); ok {
|
||||
t, err := time.Parse("2006-01-02 15:04:05", createdAt)
|
||||
if err == nil {
|
||||
doc.CreatedAt = t
|
||||
}
|
||||
}
|
||||
|
||||
if updatedAt, ok := row["updated_at"].(string); ok {
|
||||
t, err := time.Parse("2006-01-02 15:04:05", updatedAt)
|
||||
if err == nil {
|
||||
doc.UpdatedAt = t
|
||||
}
|
||||
}
|
||||
|
||||
if isDeletedInt, ok := row["is_deleted"].(int64); ok {
|
||||
doc.IsDeleted = isDeletedInt == 1
|
||||
}
|
||||
|
||||
return doc, nil
|
||||
}
|
||||
|
||||
// CreateDocument creates a new document and returns the created document with ID
|
||||
@@ -152,20 +188,30 @@ func (ds *DocumentService) CreateDocument(title string) (*models.Document, error
|
||||
IsDeleted: false,
|
||||
}
|
||||
|
||||
db := ds.databaseService.GetDB()
|
||||
result, err := db.Exec(sqlInsertDocument, doc.Title, doc.Content, doc.CreatedAt, doc.UpdatedAt)
|
||||
if err != nil {
|
||||
// 执行插入操作
|
||||
if err := ds.databaseService.SQLite.Execute(sqlInsertDocument,
|
||||
doc.Title, doc.Content, doc.CreatedAt, doc.UpdatedAt); err != nil {
|
||||
return nil, fmt.Errorf("failed to create document: %w", err)
|
||||
}
|
||||
|
||||
// Get the auto-generated ID
|
||||
id, err := result.LastInsertId()
|
||||
// 获取自增ID
|
||||
lastIDRows, err := ds.databaseService.SQLite.Query("SELECT last_insert_rowid()")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get last insert ID: %w", err)
|
||||
}
|
||||
|
||||
// Return the created document with ID
|
||||
doc.ID = id
|
||||
if len(lastIDRows) == 0 {
|
||||
return nil, fmt.Errorf("no rows returned for last insert ID query")
|
||||
}
|
||||
|
||||
// 从结果中提取ID
|
||||
lastID, ok := lastIDRows[0]["last_insert_rowid()"].(int64)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("failed to convert last insert ID to int64")
|
||||
}
|
||||
|
||||
// 返回带ID的文档
|
||||
doc.ID = lastID
|
||||
return doc, nil
|
||||
}
|
||||
|
||||
@@ -174,8 +220,7 @@ func (ds *DocumentService) UpdateDocumentContent(id int64, content string) error
|
||||
ds.mu.Lock()
|
||||
defer ds.mu.Unlock()
|
||||
|
||||
db := ds.databaseService.GetDB()
|
||||
_, err := db.Exec(sqlUpdateDocumentContent, content, time.Now(), id)
|
||||
err := ds.databaseService.SQLite.Execute(sqlUpdateDocumentContent, content, time.Now(), id)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to update document content: %w", err)
|
||||
}
|
||||
@@ -187,8 +232,7 @@ func (ds *DocumentService) UpdateDocumentTitle(id int64, title string) error {
|
||||
ds.mu.Lock()
|
||||
defer ds.mu.Unlock()
|
||||
|
||||
db := ds.databaseService.GetDB()
|
||||
_, err := db.Exec(sqlUpdateDocumentTitle, title, time.Now(), id)
|
||||
err := ds.databaseService.SQLite.Execute(sqlUpdateDocumentTitle, title, time.Now(), id)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to update document title: %w", err)
|
||||
}
|
||||
@@ -205,8 +249,7 @@ func (ds *DocumentService) DeleteDocument(id int64) error {
|
||||
return fmt.Errorf("cannot delete the default document")
|
||||
}
|
||||
|
||||
db := ds.databaseService.GetDB()
|
||||
_, err := db.Exec(sqlMarkDocumentAsDeleted, time.Now(), id)
|
||||
err := ds.databaseService.SQLite.Execute(sqlMarkDocumentAsDeleted, time.Now(), id)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to mark document as deleted: %w", err)
|
||||
}
|
||||
@@ -218,8 +261,7 @@ func (ds *DocumentService) RestoreDocument(id int64) error {
|
||||
ds.mu.Lock()
|
||||
defer ds.mu.Unlock()
|
||||
|
||||
db := ds.databaseService.GetDB()
|
||||
_, err := db.Exec(sqlRestoreDocument, time.Now(), id)
|
||||
err := ds.databaseService.SQLite.Execute(sqlRestoreDocument, time.Now(), id)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to restore document: %w", err)
|
||||
}
|
||||
@@ -231,22 +273,38 @@ func (ds *DocumentService) ListAllDocumentsMeta() ([]*models.Document, error) {
|
||||
ds.mu.RLock()
|
||||
defer ds.mu.RUnlock()
|
||||
|
||||
db := ds.databaseService.GetDB()
|
||||
rows, err := db.Query(sqlListAllDocumentsMeta)
|
||||
rows, err := ds.databaseService.SQLite.Query(sqlListAllDocumentsMeta)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to list document meta: %w", err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var documents []*models.Document
|
||||
for rows.Next() {
|
||||
var doc models.Document
|
||||
err := rows.Scan(&doc.ID, &doc.Title, &doc.CreatedAt, &doc.UpdatedAt)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to scan document meta: %w", err)
|
||||
for _, row := range rows {
|
||||
doc := &models.Document{IsDeleted: false}
|
||||
|
||||
if id, ok := row["id"].(int64); ok {
|
||||
doc.ID = id
|
||||
}
|
||||
doc.IsDeleted = false
|
||||
documents = append(documents, &doc)
|
||||
|
||||
if title, ok := row["title"].(string); ok {
|
||||
doc.Title = title
|
||||
}
|
||||
|
||||
if createdAt, ok := row["created_at"].(string); ok {
|
||||
t, err := time.Parse("2006-01-02 15:04:05", createdAt)
|
||||
if err == nil {
|
||||
doc.CreatedAt = t
|
||||
}
|
||||
}
|
||||
|
||||
if updatedAt, ok := row["updated_at"].(string); ok {
|
||||
t, err := time.Parse("2006-01-02 15:04:05", updatedAt)
|
||||
if err == nil {
|
||||
doc.UpdatedAt = t
|
||||
}
|
||||
}
|
||||
|
||||
documents = append(documents, doc)
|
||||
}
|
||||
|
||||
return documents, nil
|
||||
@@ -257,22 +315,38 @@ func (ds *DocumentService) ListDeletedDocumentsMeta() ([]*models.Document, error
|
||||
ds.mu.RLock()
|
||||
defer ds.mu.RUnlock()
|
||||
|
||||
db := ds.databaseService.GetDB()
|
||||
rows, err := db.Query(sqlListDeletedDocumentsMeta)
|
||||
rows, err := ds.databaseService.SQLite.Query(sqlListDeletedDocumentsMeta)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to list deleted document meta: %w", err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var documents []*models.Document
|
||||
for rows.Next() {
|
||||
var doc models.Document
|
||||
err := rows.Scan(&doc.ID, &doc.Title, &doc.CreatedAt, &doc.UpdatedAt)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to scan deleted document meta: %w", err)
|
||||
for _, row := range rows {
|
||||
doc := &models.Document{IsDeleted: true}
|
||||
|
||||
if id, ok := row["id"].(int64); ok {
|
||||
doc.ID = id
|
||||
}
|
||||
doc.IsDeleted = true
|
||||
documents = append(documents, &doc)
|
||||
|
||||
if title, ok := row["title"].(string); ok {
|
||||
doc.Title = title
|
||||
}
|
||||
|
||||
if createdAt, ok := row["created_at"].(string); ok {
|
||||
t, err := time.Parse("2006-01-02 15:04:05", createdAt)
|
||||
if err == nil {
|
||||
doc.CreatedAt = t
|
||||
}
|
||||
}
|
||||
|
||||
if updatedAt, ok := row["updated_at"].(string); ok {
|
||||
t, err := time.Parse("2006-01-02 15:04:05", updatedAt)
|
||||
if err == nil {
|
||||
doc.UpdatedAt = t
|
||||
}
|
||||
}
|
||||
|
||||
documents = append(documents, doc)
|
||||
}
|
||||
|
||||
return documents, nil
|
||||
@@ -283,14 +357,22 @@ func (ds *DocumentService) GetFirstDocumentID() (int64, error) {
|
||||
ds.mu.RLock()
|
||||
defer ds.mu.RUnlock()
|
||||
|
||||
db := ds.databaseService.GetDB()
|
||||
var id int64
|
||||
err := db.QueryRow(sqlGetFirstDocumentID).Scan(&id)
|
||||
rows, err := ds.databaseService.SQLite.Query(sqlGetFirstDocumentID)
|
||||
if err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return 0, nil // No documents exist
|
||||
}
|
||||
return 0, fmt.Errorf("failed to get first document ID: %w", err)
|
||||
}
|
||||
|
||||
if len(rows) == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
id, ok := rows[0]["id"].(int64)
|
||||
if !ok {
|
||||
return 0, fmt.Errorf("failed to convert ID to int64")
|
||||
}
|
||||
|
||||
return id, nil
|
||||
}
|
||||
|
@@ -41,7 +41,7 @@ WHERE id = ?`
|
||||
// ExtensionService 扩展管理服务
|
||||
type ExtensionService struct {
|
||||
databaseService *DatabaseService
|
||||
logger *log.LoggerService
|
||||
logger *log.Service
|
||||
|
||||
mu sync.RWMutex
|
||||
ctx context.Context
|
||||
@@ -73,7 +73,7 @@ func (e *ExtensionError) Is(target error) bool {
|
||||
}
|
||||
|
||||
// NewExtensionService 创建扩展服务实例
|
||||
func NewExtensionService(databaseService *DatabaseService, logger *log.LoggerService) *ExtensionService {
|
||||
func NewExtensionService(databaseService *DatabaseService, logger *log.Service) *ExtensionService {
|
||||
if logger == nil {
|
||||
logger = log.New()
|
||||
}
|
||||
@@ -105,29 +105,26 @@ func (es *ExtensionService) initDatabase() error {
|
||||
defer es.mu.Unlock()
|
||||
|
||||
// 检查是否已有扩展数据
|
||||
db := es.databaseService.GetDB()
|
||||
if db == nil {
|
||||
return &ExtensionError{"get_database", "", fmt.Errorf("database connection is nil")}
|
||||
}
|
||||
|
||||
var count int
|
||||
err := db.QueryRow("SELECT COUNT(*) FROM extensions").Scan(&count)
|
||||
rows, err := es.databaseService.SQLite.Query("SELECT COUNT(*) FROM extensions")
|
||||
if err != nil {
|
||||
return &ExtensionError{"check_extensions_count", "", err}
|
||||
}
|
||||
|
||||
es.logger.Info("Extension database check", "existing_count", count)
|
||||
if len(rows) == 0 {
|
||||
return &ExtensionError{"check_extensions_count", "", fmt.Errorf("no rows returned")}
|
||||
}
|
||||
|
||||
count, ok := rows[0]["COUNT(*)"].(int64)
|
||||
if !ok {
|
||||
return &ExtensionError{"convert_count", "", fmt.Errorf("failed to convert count to int64")}
|
||||
}
|
||||
|
||||
// 如果没有数据,插入默认配置
|
||||
if count == 0 {
|
||||
es.logger.Info("No extensions found, inserting default extensions...")
|
||||
if err := es.insertDefaultExtensions(); err != nil {
|
||||
es.logger.Error("Failed to insert default extensions", "error", err)
|
||||
return err
|
||||
}
|
||||
es.logger.Info("Default extensions inserted successfully")
|
||||
} else {
|
||||
es.logger.Info("Extensions already exist, skipping default insertion")
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -136,21 +133,16 @@ func (es *ExtensionService) initDatabase() error {
|
||||
// insertDefaultExtensions 插入默认扩展配置
|
||||
func (es *ExtensionService) insertDefaultExtensions() error {
|
||||
defaultSettings := models.NewDefaultExtensionSettings()
|
||||
db := es.databaseService.GetDB()
|
||||
now := time.Now()
|
||||
|
||||
es.logger.Info("Starting to insert default extensions", "count", len(defaultSettings.Extensions))
|
||||
|
||||
for i, ext := range defaultSettings.Extensions {
|
||||
es.logger.Info("Inserting extension", "index", i+1, "id", ext.ID, "enabled", ext.Enabled)
|
||||
for _, ext := range defaultSettings.Extensions {
|
||||
|
||||
configJSON, err := json.Marshal(ext.Config)
|
||||
if err != nil {
|
||||
es.logger.Error("Failed to marshal config", "extension", ext.ID, "error", err)
|
||||
return &ExtensionError{"marshal_config", string(ext.ID), err}
|
||||
}
|
||||
|
||||
_, err = db.Exec(sqlInsertExtension,
|
||||
err = es.databaseService.SQLite.Execute(sqlInsertExtension,
|
||||
string(ext.ID),
|
||||
ext.Enabled,
|
||||
ext.IsDefault,
|
||||
@@ -159,31 +151,24 @@ func (es *ExtensionService) insertDefaultExtensions() error {
|
||||
now,
|
||||
)
|
||||
if err != nil {
|
||||
es.logger.Error("Failed to insert extension", "extension", ext.ID, "error", err)
|
||||
return &ExtensionError{"insert_extension", string(ext.ID), err}
|
||||
}
|
||||
|
||||
es.logger.Info("Successfully inserted extension", "id", ext.ID)
|
||||
}
|
||||
|
||||
es.logger.Info("Completed inserting all default extensions")
|
||||
return nil
|
||||
}
|
||||
|
||||
// OnStartup 启动时调用
|
||||
func (es *ExtensionService) OnStartup(ctx context.Context, _ application.ServiceOptions) error {
|
||||
// ServiceStartup 启动时调用
|
||||
func (es *ExtensionService) ServiceStartup(ctx context.Context, options application.ServiceOptions) error {
|
||||
es.ctx = ctx
|
||||
es.logger.Info("Extension service starting up")
|
||||
|
||||
// 初始化数据库
|
||||
var initErr error
|
||||
es.initOnce.Do(func() {
|
||||
es.logger.Info("Initializing extension database...")
|
||||
if err := es.initDatabase(); err != nil {
|
||||
es.logger.Error("failed to initialize extension database", "error", err)
|
||||
initErr = err
|
||||
} else {
|
||||
es.logger.Info("Extension database initialized successfully")
|
||||
}
|
||||
})
|
||||
return initErr
|
||||
@@ -194,28 +179,36 @@ func (es *ExtensionService) GetAllExtensions() ([]models.Extension, error) {
|
||||
es.mu.RLock()
|
||||
defer es.mu.RUnlock()
|
||||
|
||||
db := es.databaseService.GetDB()
|
||||
rows, err := db.Query(sqlGetAllExtensions)
|
||||
rows, err := es.databaseService.SQLite.Query(sqlGetAllExtensions)
|
||||
if err != nil {
|
||||
return nil, &ExtensionError{"query_extensions", "", err}
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var extensions []models.Extension
|
||||
for rows.Next() {
|
||||
for _, row := range rows {
|
||||
var ext models.Extension
|
||||
var configJSON string
|
||||
if err := rows.Scan(&ext.ID, &ext.Enabled, &ext.IsDefault, &configJSON); err != nil {
|
||||
return nil, &ExtensionError{"scan_extension", "", err}
|
||||
}
|
||||
if err := json.Unmarshal([]byte(configJSON), &ext.Config); err != nil {
|
||||
return nil, &ExtensionError{"unmarshal_config", string(ext.ID), err}
|
||||
}
|
||||
extensions = append(extensions, ext)
|
||||
}
|
||||
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, &ExtensionError{"rows_error", "", err}
|
||||
if id, ok := row["id"].(string); ok {
|
||||
ext.ID = models.ExtensionID(id)
|
||||
}
|
||||
|
||||
if enabled, ok := row["enabled"].(int64); ok {
|
||||
ext.Enabled = enabled == 1
|
||||
}
|
||||
|
||||
if isDefault, ok := row["is_default"].(int64); ok {
|
||||
ext.IsDefault = isDefault == 1
|
||||
}
|
||||
|
||||
if configJSON, ok := row["config"].(string); ok {
|
||||
var config models.ExtensionConfig
|
||||
if err := json.Unmarshal([]byte(configJSON), &config); err != nil {
|
||||
return nil, &ExtensionError{"unmarshal_config", string(ext.ID), err}
|
||||
}
|
||||
ext.Config = config
|
||||
}
|
||||
|
||||
extensions = append(extensions, ext)
|
||||
}
|
||||
|
||||
return extensions, nil
|
||||
@@ -231,7 +224,6 @@ func (es *ExtensionService) UpdateExtensionState(id models.ExtensionID, enabled
|
||||
es.mu.Lock()
|
||||
defer es.mu.Unlock()
|
||||
|
||||
db := es.databaseService.GetDB()
|
||||
var configJSON []byte
|
||||
var err error
|
||||
|
||||
@@ -242,20 +234,28 @@ func (es *ExtensionService) UpdateExtensionState(id models.ExtensionID, enabled
|
||||
}
|
||||
} else {
|
||||
// 如果没有提供配置,保持原有配置
|
||||
var currentConfigJSON string
|
||||
err = db.QueryRow("SELECT config FROM extensions WHERE id = ?", string(id)).Scan(¤tConfigJSON)
|
||||
rows, err := es.databaseService.SQLite.Query("SELECT config FROM extensions WHERE id = ?", string(id))
|
||||
if err != nil {
|
||||
return &ExtensionError{"query_current_config", string(id), err}
|
||||
}
|
||||
|
||||
if len(rows) == 0 {
|
||||
return &ExtensionError{"query_current_config", string(id), fmt.Errorf("extension not found")}
|
||||
}
|
||||
|
||||
currentConfigJSON, ok := rows[0]["config"].(string)
|
||||
if !ok {
|
||||
return &ExtensionError{"convert_config", string(id), fmt.Errorf("failed to get current config")}
|
||||
}
|
||||
|
||||
configJSON = []byte(currentConfigJSON)
|
||||
}
|
||||
|
||||
_, err = db.Exec(sqlUpdateExtension, enabled, string(configJSON), time.Now(), string(id))
|
||||
err = es.databaseService.SQLite.Execute(sqlUpdateExtension, enabled, string(configJSON), time.Now(), string(id))
|
||||
if err != nil {
|
||||
return &ExtensionError{"update_extension", string(id), err}
|
||||
}
|
||||
|
||||
es.logger.Info("extension state updated", "id", id, "enabled", enabled)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -277,8 +277,7 @@ func (es *ExtensionService) ResetAllExtensionsToDefault() error {
|
||||
defer es.mu.Unlock()
|
||||
|
||||
// 删除所有现有扩展
|
||||
db := es.databaseService.GetDB()
|
||||
_, err := db.Exec(sqlDeleteAllExtensions)
|
||||
err := es.databaseService.SQLite.Execute(sqlDeleteAllExtensions)
|
||||
if err != nil {
|
||||
return &ExtensionError{"delete_all_extensions", "", err}
|
||||
}
|
||||
@@ -288,6 +287,5 @@ func (es *ExtensionService) ResetAllExtensionsToDefault() error {
|
||||
return err
|
||||
}
|
||||
|
||||
es.logger.Info("all extensions reset to default")
|
||||
return nil
|
||||
}
|
||||
|
@@ -24,7 +24,7 @@ import (
|
||||
|
||||
// HotkeyService Windows全局热键服务
|
||||
type HotkeyService struct {
|
||||
logger *log.LoggerService
|
||||
logger *log.Service
|
||||
configService *ConfigService
|
||||
app *application.App
|
||||
|
||||
@@ -52,7 +52,7 @@ func (e *HotkeyError) Unwrap() error {
|
||||
}
|
||||
|
||||
// NewHotkeyService 创建热键服务实例
|
||||
func NewHotkeyService(configService *ConfigService, logger *log.LoggerService) *HotkeyService {
|
||||
func NewHotkeyService(configService *ConfigService, logger *log.Service) *HotkeyService {
|
||||
if logger == nil {
|
||||
logger = log.New()
|
||||
}
|
||||
@@ -202,7 +202,7 @@ func cBool(b bool) C.int {
|
||||
// toggleWindow 切换窗口
|
||||
func (hs *HotkeyService) toggleWindow() {
|
||||
if hs.app != nil {
|
||||
hs.app.EmitEvent("hotkey:toggle-window", nil)
|
||||
hs.app.Event.Emit("hotkey:toggle-window", nil)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -259,7 +259,7 @@ func (hs *HotkeyService) IsRegistered() bool {
|
||||
return hs.isRegistered.Load()
|
||||
}
|
||||
|
||||
// OnShutdown 关闭服务
|
||||
// ServiceShutdown 关闭服务
|
||||
func (hs *HotkeyService) ServiceShutdown() error {
|
||||
hs.cancel()
|
||||
hs.wg.Wait()
|
||||
|
@@ -80,7 +80,7 @@ var globalHotkeyService *HotkeyService
|
||||
|
||||
// HotkeyService macOS全局热键服务
|
||||
type HotkeyService struct {
|
||||
logger *log.LoggerService
|
||||
logger *log.Service
|
||||
configService *ConfigService
|
||||
app *application.App
|
||||
mu sync.RWMutex
|
||||
@@ -105,7 +105,7 @@ func (e *HotkeyError) Unwrap() error {
|
||||
}
|
||||
|
||||
// NewHotkeyService 创建新的热键服务实例
|
||||
func NewHotkeyService(configService *ConfigService, logger *log.LoggerService) *HotkeyService {
|
||||
func NewHotkeyService(configService *ConfigService, logger *log.Service) *HotkeyService {
|
||||
if logger == nil {
|
||||
logger = log.New()
|
||||
}
|
||||
@@ -283,8 +283,15 @@ func (hs *HotkeyService) IsRegistered() bool {
|
||||
return hs.isRegistered.Load()
|
||||
}
|
||||
|
||||
// OnShutdown 关闭热键服务
|
||||
func (hs *HotkeyService) OnShutdown() error {
|
||||
// ToggleWindow 切换窗口显示状态
|
||||
func (hs *HotkeyService) ToggleWindow() {
|
||||
if hs.app != nil {
|
||||
hs.app.EmitEvent("hotkey:toggle-window", nil)
|
||||
}
|
||||
}
|
||||
|
||||
// ServiceShutdown 关闭热键服务
|
||||
func (hs *HotkeyService) ServiceShutdown() error {
|
||||
return hs.UnregisterHotkey()
|
||||
}
|
||||
|
||||
|
@@ -141,7 +141,7 @@ import (
|
||||
|
||||
// HotkeyService Linux全局热键服务
|
||||
type HotkeyService struct {
|
||||
logger *log.LoggerService
|
||||
logger *log.Service
|
||||
configService *ConfigService
|
||||
app *application.App
|
||||
|
||||
@@ -170,7 +170,7 @@ func (e *HotkeyError) Unwrap() error {
|
||||
}
|
||||
|
||||
// NewHotkeyService 创建热键服务实例
|
||||
func NewHotkeyService(configService *ConfigService, logger *log.LoggerService) *HotkeyService {
|
||||
func NewHotkeyService(configService *ConfigService, logger *log.Service) *HotkeyService {
|
||||
if logger == nil {
|
||||
logger = log.New()
|
||||
}
|
||||
@@ -384,8 +384,8 @@ func (hs *HotkeyService) IsRegistered() bool {
|
||||
return hs.isRegistered.Load()
|
||||
}
|
||||
|
||||
// OnShutdown 关闭服务
|
||||
func (hs *HotkeyService) OnShutdown() error {
|
||||
// ServiceShutdown 关闭服务
|
||||
func (hs *HotkeyService) ServiceShutdown() error {
|
||||
hs.cancel()
|
||||
hs.wg.Wait()
|
||||
C.closeX11Display()
|
||||
|
@@ -51,7 +51,7 @@ const (
|
||||
// KeyBindingService 快捷键管理服务
|
||||
type KeyBindingService struct {
|
||||
databaseService *DatabaseService
|
||||
logger *log.LoggerService
|
||||
logger *log.Service
|
||||
|
||||
mu sync.RWMutex
|
||||
ctx context.Context
|
||||
@@ -83,7 +83,7 @@ func (e *KeyBindingError) Is(target error) bool {
|
||||
}
|
||||
|
||||
// NewKeyBindingService 创建快捷键服务实例
|
||||
func NewKeyBindingService(databaseService *DatabaseService, logger *log.LoggerService) *KeyBindingService {
|
||||
func NewKeyBindingService(databaseService *DatabaseService, logger *log.Service) *KeyBindingService {
|
||||
if logger == nil {
|
||||
logger = log.New()
|
||||
}
|
||||
@@ -106,29 +106,26 @@ func (kbs *KeyBindingService) initDatabase() error {
|
||||
defer kbs.mu.Unlock()
|
||||
|
||||
// 检查是否已有快捷键数据
|
||||
db := kbs.databaseService.GetDB()
|
||||
if db == nil {
|
||||
return &KeyBindingError{"get_database", "", fmt.Errorf("database connection is nil")}
|
||||
}
|
||||
|
||||
var count int
|
||||
err := db.QueryRow("SELECT COUNT(*) FROM key_bindings").Scan(&count)
|
||||
rows, err := kbs.databaseService.SQLite.Query("SELECT COUNT(*) FROM key_bindings")
|
||||
if err != nil {
|
||||
return &KeyBindingError{"check_keybindings_count", "", err}
|
||||
}
|
||||
|
||||
kbs.logger.Info("KeyBinding database check", "existing_count", count)
|
||||
if len(rows) == 0 {
|
||||
return &KeyBindingError{"check_keybindings_count", "", fmt.Errorf("no rows returned")}
|
||||
}
|
||||
|
||||
count, ok := rows[0]["COUNT(*)"].(int64)
|
||||
if !ok {
|
||||
return &KeyBindingError{"convert_count", "", fmt.Errorf("failed to convert count to int64")}
|
||||
}
|
||||
|
||||
// 如果没有数据,插入默认配置
|
||||
if count == 0 {
|
||||
kbs.logger.Info("No key bindings found, inserting default key bindings...")
|
||||
if err := kbs.insertDefaultKeyBindings(); err != nil {
|
||||
kbs.logger.Error("Failed to insert default key bindings", "error", err)
|
||||
return err
|
||||
}
|
||||
kbs.logger.Info("Default key bindings inserted successfully")
|
||||
} else {
|
||||
kbs.logger.Info("Key bindings already exist, skipping default insertion")
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -137,17 +134,13 @@ func (kbs *KeyBindingService) initDatabase() error {
|
||||
// insertDefaultKeyBindings 插入默认快捷键配置
|
||||
func (kbs *KeyBindingService) insertDefaultKeyBindings() error {
|
||||
defaultConfig := models.NewDefaultKeyBindingConfig()
|
||||
db := kbs.databaseService.GetDB()
|
||||
now := time.Now()
|
||||
|
||||
kbs.logger.Info("Starting to insert default key bindings", "count", len(defaultConfig.KeyBindings))
|
||||
for _, kb := range defaultConfig.KeyBindings {
|
||||
|
||||
for i, kb := range defaultConfig.KeyBindings {
|
||||
kbs.logger.Info("Inserting key binding", "index", i+1, "command", kb.Command, "key", kb.Key, "extension", kb.Extension)
|
||||
|
||||
_, err := db.Exec(sqlInsertKeyBinding,
|
||||
kb.Command,
|
||||
kb.Extension,
|
||||
err := kbs.databaseService.SQLite.Execute(sqlInsertKeyBinding,
|
||||
string(kb.Command), // 转换为字符串存储
|
||||
string(kb.Extension), // 转换为字符串存储
|
||||
kb.Key,
|
||||
kb.Enabled,
|
||||
kb.IsDefault,
|
||||
@@ -155,72 +148,63 @@ func (kbs *KeyBindingService) insertDefaultKeyBindings() error {
|
||||
now,
|
||||
)
|
||||
if err != nil {
|
||||
kbs.logger.Error("Failed to insert key binding", "command", kb.Command, "error", err)
|
||||
return &KeyBindingError{"insert_keybinding", string(kb.Command), err}
|
||||
}
|
||||
|
||||
kbs.logger.Info("Successfully inserted key binding", "command", kb.Command)
|
||||
}
|
||||
|
||||
kbs.logger.Info("Completed inserting all default key bindings")
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetKeyBindingConfig 获取完整快捷键配置
|
||||
func (kbs *KeyBindingService) GetKeyBindingConfig() (*models.KeyBindingConfig, error) {
|
||||
keyBindings, err := kbs.GetAllKeyBindings()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
config := &models.KeyBindingConfig{
|
||||
KeyBindings: keyBindings,
|
||||
}
|
||||
return config, nil
|
||||
}
|
||||
|
||||
// GetAllKeyBindings 获取所有快捷键配置
|
||||
func (kbs *KeyBindingService) GetAllKeyBindings() ([]models.KeyBinding, error) {
|
||||
kbs.mu.RLock()
|
||||
defer kbs.mu.RUnlock()
|
||||
|
||||
db := kbs.databaseService.GetDB()
|
||||
rows, err := db.Query(sqlGetAllKeyBindings)
|
||||
rows, err := kbs.databaseService.SQLite.Query(sqlGetAllKeyBindings)
|
||||
if err != nil {
|
||||
return nil, &KeyBindingError{"query_keybindings", "", err}
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var keyBindings []models.KeyBinding
|
||||
for rows.Next() {
|
||||
for _, row := range rows {
|
||||
var kb models.KeyBinding
|
||||
if err := rows.Scan(&kb.Command, &kb.Extension, &kb.Key, &kb.Enabled, &kb.IsDefault); err != nil {
|
||||
return nil, &KeyBindingError{"scan_keybinding", "", err}
|
||||
}
|
||||
keyBindings = append(keyBindings, kb)
|
||||
}
|
||||
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, &KeyBindingError{"rows_error", "", err}
|
||||
if command, ok := row["command"].(string); ok {
|
||||
kb.Command = models.KeyBindingCommand(command)
|
||||
}
|
||||
|
||||
if extension, ok := row["extension"].(string); ok {
|
||||
kb.Extension = models.ExtensionID(extension)
|
||||
}
|
||||
|
||||
if key, ok := row["key"].(string); ok {
|
||||
kb.Key = key
|
||||
}
|
||||
|
||||
if enabled, ok := row["enabled"].(int64); ok {
|
||||
kb.Enabled = enabled == 1
|
||||
}
|
||||
|
||||
if isDefault, ok := row["is_default"].(int64); ok {
|
||||
kb.IsDefault = isDefault == 1
|
||||
}
|
||||
|
||||
keyBindings = append(keyBindings, kb)
|
||||
}
|
||||
|
||||
return keyBindings, nil
|
||||
}
|
||||
|
||||
// OnStartup 启动时调用
|
||||
func (kbs *KeyBindingService) OnStartup(ctx context.Context, _ application.ServiceOptions) error {
|
||||
// ServiceStartup 启动时调用
|
||||
func (kbs *KeyBindingService) ServiceStartup(ctx context.Context, options application.ServiceOptions) error {
|
||||
kbs.ctx = ctx
|
||||
kbs.logger.Info("KeyBinding service starting up")
|
||||
|
||||
// 初始化数据库
|
||||
var initErr error
|
||||
kbs.initOnce.Do(func() {
|
||||
kbs.logger.Info("Initializing keybinding database...")
|
||||
if err := kbs.initDatabase(); err != nil {
|
||||
kbs.logger.Error("failed to initialize keybinding database", "error", err)
|
||||
initErr = err
|
||||
} else {
|
||||
kbs.logger.Info("KeyBinding database initialized successfully")
|
||||
}
|
||||
})
|
||||
return initErr
|
||||
|
@@ -33,7 +33,7 @@ type MigrationProgress struct {
|
||||
|
||||
// MigrationService 迁移服务
|
||||
type MigrationService struct {
|
||||
logger *log.LoggerService
|
||||
logger *log.Service
|
||||
mu sync.RWMutex
|
||||
progress atomic.Value // stores MigrationProgress
|
||||
|
||||
@@ -42,7 +42,7 @@ type MigrationService struct {
|
||||
}
|
||||
|
||||
// NewMigrationService 创建迁移服务
|
||||
func NewMigrationService(logger *log.LoggerService) *MigrationService {
|
||||
func NewMigrationService(logger *log.Service) *MigrationService {
|
||||
if logger == nil {
|
||||
logger = log.New()
|
||||
}
|
||||
@@ -417,8 +417,8 @@ func (ms *MigrationService) CancelMigration() error {
|
||||
return fmt.Errorf("no active migration to cancel")
|
||||
}
|
||||
|
||||
// OnShutdown 服务关闭
|
||||
func (ms *MigrationService) OnShutdown() error {
|
||||
// ServiceShutdown 服务关闭
|
||||
func (ms *MigrationService) ServiceShutdown() error {
|
||||
ms.CancelMigration()
|
||||
return nil
|
||||
}
|
||||
|
112
internal/services/restart_darwin.go
Normal file
112
internal/services/restart_darwin.go
Normal file
@@ -0,0 +1,112 @@
|
||||
//go:build darwin
|
||||
|
||||
package services
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
// restartApplication Darwin(macOS)平台的重启实现
|
||||
func (s *SelfUpdateService) restartApplication() error {
|
||||
// 获取当前可执行文件路径
|
||||
exe, err := os.Executable()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get executable path: %w", err)
|
||||
}
|
||||
|
||||
// 获取当前工作目录
|
||||
workDir, err := os.Getwd()
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to get working directory", "error", err)
|
||||
workDir = filepath.Dir(exe) // 如果获取失败,使用可执行文件所在目录
|
||||
}
|
||||
|
||||
// 在macOS上,我们使用一个shell脚本来重启应用程序
|
||||
// 创建一个唯一的临时shell脚本
|
||||
scriptPath := fmt.Sprintf("/tmp/restart_voidraft_%d_%d.sh", os.Getpid(), time.Now().Unix())
|
||||
scriptContent := fmt.Sprintf(`#!/bin/bash
|
||||
sleep 1
|
||||
cd %s
|
||||
%s %s &
|
||||
rm "%s"
|
||||
`,
|
||||
shellEscape(workDir), shellEscape(exe),
|
||||
shellEscapeArgs(os.Args[1:]), scriptPath)
|
||||
|
||||
s.logger.Info("Creating restart script", "path", scriptPath)
|
||||
|
||||
// 写入脚本文件
|
||||
err = os.WriteFile(scriptPath, []byte(scriptContent), 0755)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create restart script: %w", err)
|
||||
}
|
||||
|
||||
// 启动脚本
|
||||
cmd := exec.Command("/bin/bash", scriptPath)
|
||||
cmd.SysProcAttr = &syscall.SysProcAttr{
|
||||
Setsid: true, // 创建新的会话,使进程独立于父进程
|
||||
}
|
||||
|
||||
err = cmd.Start()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to start restart script: %w", err)
|
||||
}
|
||||
|
||||
// 给脚本一点时间启动
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
||||
// 立即退出当前进程
|
||||
os.Exit(0)
|
||||
|
||||
return nil // 不会执行到这里
|
||||
}
|
||||
|
||||
// shellEscape 转义单个shell参数或路径
|
||||
func shellEscape(arg string) string {
|
||||
if arg == "" {
|
||||
return "''"
|
||||
}
|
||||
|
||||
// 如果参数只包含安全字符,不需要转义
|
||||
if isSafeShellString(arg) {
|
||||
return arg
|
||||
}
|
||||
|
||||
// 使用单引号转义,但需要处理参数中的单引号
|
||||
// 将单引号替换为 '"'"'
|
||||
escaped := strings.ReplaceAll(arg, "'", `'"'"'`)
|
||||
return "'" + escaped + "'"
|
||||
}
|
||||
|
||||
// shellEscapeArgs 转义多个shell参数
|
||||
func shellEscapeArgs(args []string) string {
|
||||
if len(args) == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
var escaped []string
|
||||
for _, arg := range args {
|
||||
escaped = append(escaped, shellEscape(arg))
|
||||
}
|
||||
|
||||
return strings.Join(escaped, " ")
|
||||
}
|
||||
|
||||
// isSafeShellString 检查字符串是否包含需要转义的字符
|
||||
func isSafeShellString(s string) bool {
|
||||
// 只包含字母、数字、下划线、连字符、点号和斜杠的字符串是安全的
|
||||
for _, r := range s {
|
||||
if !((r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z') ||
|
||||
(r >= '0' && r <= '9') || r == '_' || r == '-' ||
|
||||
r == '.' || r == '/') {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return len(s) > 0
|
||||
}
|
112
internal/services/restart_linux.go
Normal file
112
internal/services/restart_linux.go
Normal file
@@ -0,0 +1,112 @@
|
||||
//go:build linux
|
||||
|
||||
package services
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
// restartApplication Linux平台的重启实现
|
||||
func (s *SelfUpdateService) restartApplication() error {
|
||||
// 获取当前可执行文件路径
|
||||
exe, err := os.Executable()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get executable path: %w", err)
|
||||
}
|
||||
|
||||
// 获取当前工作目录
|
||||
workDir, err := os.Getwd()
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to get working directory", "error", err)
|
||||
workDir = filepath.Dir(exe) // 如果获取失败,使用可执行文件所在目录
|
||||
}
|
||||
|
||||
// 在Linux上,我们使用一个shell脚本来重启应用程序
|
||||
// 创建一个唯一的临时shell脚本
|
||||
scriptPath := fmt.Sprintf("/tmp/restart_voidraft_%d_%d.sh", os.Getpid(), time.Now().Unix())
|
||||
scriptContent := fmt.Sprintf(`#!/bin/bash
|
||||
sleep 1
|
||||
cd %s
|
||||
%s %s &
|
||||
rm "%s"
|
||||
`,
|
||||
shellEscape(workDir), shellEscape(exe),
|
||||
shellEscapeArgs(os.Args[1:]), scriptPath)
|
||||
|
||||
s.logger.Info("Creating restart script", "path", scriptPath)
|
||||
|
||||
// 写入脚本文件
|
||||
err = os.WriteFile(scriptPath, []byte(scriptContent), 0755)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create restart script: %w", err)
|
||||
}
|
||||
|
||||
// 启动脚本
|
||||
cmd := exec.Command("/bin/bash", scriptPath)
|
||||
cmd.SysProcAttr = &syscall.SysProcAttr{
|
||||
Setsid: true, // 创建新的会话,使进程独立于父进程
|
||||
}
|
||||
|
||||
err = cmd.Start()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to start restart script: %w", err)
|
||||
}
|
||||
|
||||
// 给脚本一点时间启动
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
||||
// 立即退出当前进程
|
||||
os.Exit(0)
|
||||
|
||||
return nil // 不会执行到这里
|
||||
}
|
||||
|
||||
// shellEscape 转义单个shell参数或路径
|
||||
func shellEscape(arg string) string {
|
||||
if arg == "" {
|
||||
return "''"
|
||||
}
|
||||
|
||||
// 如果参数只包含安全字符,不需要转义
|
||||
if isSafeShellString(arg) {
|
||||
return arg
|
||||
}
|
||||
|
||||
// 使用单引号转义,但需要处理参数中的单引号
|
||||
// 将单引号替换为 '"'"'
|
||||
escaped := strings.ReplaceAll(arg, "'", `'"'"'`)
|
||||
return "'" + escaped + "'"
|
||||
}
|
||||
|
||||
// shellEscapeArgs 转义多个shell参数
|
||||
func shellEscapeArgs(args []string) string {
|
||||
if len(args) == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
var escaped []string
|
||||
for _, arg := range args {
|
||||
escaped = append(escaped, shellEscape(arg))
|
||||
}
|
||||
|
||||
return strings.Join(escaped, " ")
|
||||
}
|
||||
|
||||
// isSafeShellString 检查字符串是否包含需要转义的字符
|
||||
func isSafeShellString(s string) bool {
|
||||
// 只包含字母、数字、下划线、连字符、点号和斜杠的字符串是安全的
|
||||
for _, r := range s {
|
||||
if !((r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z') ||
|
||||
(r >= '0' && r <= '9') || r == '_' || r == '-' ||
|
||||
r == '.' || r == '/') {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return len(s) > 0
|
||||
}
|
118
internal/services/restart_windows.go
Normal file
118
internal/services/restart_windows.go
Normal file
@@ -0,0 +1,118 @@
|
||||
//go:build windows
|
||||
|
||||
package services
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
// restartApplication Windows平台的重启实现
|
||||
func (s *SelfUpdateService) restartApplication() error {
|
||||
// 获取当前可执行文件路径
|
||||
exe, err := os.Executable()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get executable path: %w", err)
|
||||
}
|
||||
|
||||
// 获取当前工作目录
|
||||
workDir, err := os.Getwd()
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to get working directory", "error", err)
|
||||
workDir = filepath.Dir(exe) // 如果获取失败,使用可执行文件所在目录
|
||||
}
|
||||
|
||||
// 创建唯一的批处理文件来重启应用程序
|
||||
// 使用进程ID和时间戳确保文件名唯一性
|
||||
batchFile := filepath.Join(os.TempDir(), fmt.Sprintf("restart_voidraft_%d_%d.bat", os.Getpid(), time.Now().Unix()))
|
||||
|
||||
// 正确转义命令行参数
|
||||
escapedArgs := escapeWindowsArgs(os.Args[1:])
|
||||
batchContent := fmt.Sprintf(`@echo off
|
||||
timeout /t 1 /nobreak > NUL
|
||||
cd /d "%s"
|
||||
start "" "%s" %s
|
||||
del "%s"
|
||||
`, workDir, exe, escapedArgs, batchFile)
|
||||
|
||||
s.logger.Info("Creating batch file", "path", batchFile, "content", batchContent)
|
||||
|
||||
// 写入批处理文件
|
||||
err = os.WriteFile(batchFile, []byte(batchContent), 0644)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create batch file: %w", err)
|
||||
}
|
||||
|
||||
// 启动批处理文件
|
||||
cmd := exec.Command("cmd.exe", "/C", batchFile)
|
||||
cmd.Stdout = nil
|
||||
cmd.Stderr = nil
|
||||
cmd.Stdin = nil
|
||||
// 分离进程,这样即使父进程退出,批处理文件仍然会继续执行
|
||||
cmd.SysProcAttr = &syscall.SysProcAttr{
|
||||
CreationFlags: syscall.CREATE_NEW_PROCESS_GROUP,
|
||||
}
|
||||
|
||||
err = cmd.Start()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to start batch file: %w", err)
|
||||
}
|
||||
|
||||
// 立即退出当前进程
|
||||
os.Exit(0)
|
||||
|
||||
return nil // 不会执行到这里
|
||||
}
|
||||
|
||||
// escapeWindowsArgs 转义Windows命令行参数
|
||||
func escapeWindowsArgs(args []string) string {
|
||||
if len(args) == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
var escaped []string
|
||||
for _, arg := range args {
|
||||
escaped = append(escaped, escapeWindowsArg(arg))
|
||||
}
|
||||
|
||||
return strings.Join(escaped, " ")
|
||||
}
|
||||
|
||||
// escapeWindowsArg 转义单个Windows命令行参数
|
||||
func escapeWindowsArg(arg string) string {
|
||||
// 如果参数不包含空格、制表符、换行符、双引号或反斜杠,则不需要转义
|
||||
if !strings.ContainsAny(arg, " \t\n\r\"\\") {
|
||||
return arg
|
||||
}
|
||||
|
||||
// 需要转义的参数用双引号包围
|
||||
var result strings.Builder
|
||||
result.WriteByte('"')
|
||||
|
||||
for i := 0; i < len(arg); i++ {
|
||||
c := arg[i]
|
||||
switch c {
|
||||
case '"':
|
||||
// 双引号需要转义
|
||||
result.WriteString(`\"`)
|
||||
case '\\':
|
||||
// 反斜杠需要特殊处理
|
||||
// 如果后面跟着双引号,需要转义反斜杠
|
||||
if i+1 < len(arg) && arg[i+1] == '"' {
|
||||
result.WriteString(`\\`)
|
||||
} else {
|
||||
result.WriteByte(c)
|
||||
}
|
||||
default:
|
||||
result.WriteByte(c)
|
||||
}
|
||||
}
|
||||
|
||||
result.WriteByte('"')
|
||||
return result.String()
|
||||
}
|
@@ -7,11 +7,7 @@ import (
|
||||
"github.com/creativeprojects/go-selfupdate"
|
||||
"github.com/wailsapp/wails/v3/pkg/services/log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
"voidraft/internal/models"
|
||||
)
|
||||
@@ -30,7 +26,7 @@ type SelfUpdateResult struct {
|
||||
|
||||
// SelfUpdateService 自我更新服务
|
||||
type SelfUpdateService struct {
|
||||
logger *log.LoggerService
|
||||
logger *log.Service
|
||||
configService *ConfigService
|
||||
config *models.AppConfig
|
||||
|
||||
@@ -39,7 +35,7 @@ type SelfUpdateService struct {
|
||||
}
|
||||
|
||||
// NewSelfUpdateService 创建自我更新服务实例
|
||||
func NewSelfUpdateService(configService *ConfigService, logger *log.LoggerService) (*SelfUpdateService, error) {
|
||||
func NewSelfUpdateService(configService *ConfigService, logger *log.Service) (*SelfUpdateService, error) {
|
||||
// 获取配置
|
||||
appConfig, err := configService.GetConfig()
|
||||
if err != nil {
|
||||
@@ -428,69 +424,7 @@ func (s *SelfUpdateService) getUpdateFromSource(ctx context.Context, sourceType
|
||||
|
||||
// RestartApplication 重启应用程序
|
||||
func (s *SelfUpdateService) RestartApplication() error {
|
||||
|
||||
// 获取当前可执行文件路径
|
||||
exe, err := os.Executable()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get executable path: %w", err)
|
||||
}
|
||||
|
||||
// Windows平台需要特殊处理
|
||||
if runtime.GOOS == "windows" {
|
||||
|
||||
// 获取当前工作目录
|
||||
workDir, err := os.Getwd()
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to get working directory", "error", err)
|
||||
workDir = filepath.Dir(exe) // 如果获取失败,使用可执行文件所在目录
|
||||
}
|
||||
|
||||
// 创建批处理文件来重启应用程序
|
||||
// 批处理文件会等待当前进程退出,然后启动新进程
|
||||
batchFile := filepath.Join(os.TempDir(), "restart_voidraft.bat")
|
||||
batchContent := fmt.Sprintf(`@echo off
|
||||
timeout /t 1 /nobreak > NUL
|
||||
cd /d "%s"
|
||||
start "" "%s" %s
|
||||
del "%s"
|
||||
`, workDir, exe, strings.Join(os.Args[1:], " "), batchFile)
|
||||
|
||||
s.logger.Info("Creating batch file", "path", batchFile, "content", batchContent)
|
||||
|
||||
// 写入批处理文件
|
||||
err = os.WriteFile(batchFile, []byte(batchContent), 0644)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create batch file: %w", err)
|
||||
}
|
||||
|
||||
// 启动批处理文件
|
||||
cmd := exec.Command("cmd.exe", "/C", batchFile)
|
||||
cmd.Stdout = nil
|
||||
cmd.Stderr = nil
|
||||
cmd.Stdin = nil
|
||||
// 分离进程,这样即使父进程退出,批处理文件仍然会继续执行
|
||||
cmd.SysProcAttr = &syscall.SysProcAttr{
|
||||
CreationFlags: syscall.CREATE_NEW_PROCESS_GROUP,
|
||||
}
|
||||
|
||||
err = cmd.Start()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to start batch file: %w", err)
|
||||
}
|
||||
|
||||
// 立即退出当前进程
|
||||
os.Exit(0)
|
||||
|
||||
return nil // 不会执行到这里
|
||||
}
|
||||
|
||||
// 使用syscall.Exec替换当前进程
|
||||
err = syscall.Exec(exe, os.Args, os.Environ())
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to exec: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
return s.restartApplication()
|
||||
}
|
||||
|
||||
// updateConfigVersion 更新配置中的版本号
|
||||
|
@@ -5,13 +5,16 @@ import (
|
||||
|
||||
"github.com/wailsapp/wails/v3/pkg/application"
|
||||
"github.com/wailsapp/wails/v3/pkg/services/log"
|
||||
"github.com/wailsapp/wails/v3/pkg/services/sqlite"
|
||||
)
|
||||
|
||||
// ServiceManager 服务管理器,负责协调各个服务
|
||||
type ServiceManager struct {
|
||||
configService *ConfigService
|
||||
databaseService *DatabaseService
|
||||
sqliteService *sqlite.Service
|
||||
documentService *DocumentService
|
||||
windowService *WindowService
|
||||
migrationService *MigrationService
|
||||
systemService *SystemService
|
||||
hotkeyService *HotkeyService
|
||||
@@ -22,7 +25,7 @@ type ServiceManager struct {
|
||||
startupService *StartupService
|
||||
selfUpdateService *SelfUpdateService
|
||||
translationService *TranslationService
|
||||
logger *log.LoggerService
|
||||
logger *log.Service
|
||||
}
|
||||
|
||||
// NewServiceManager 创建新的服务管理器实例
|
||||
@@ -33,6 +36,9 @@ func NewServiceManager() *ServiceManager {
|
||||
// 初始化配置服务
|
||||
configService := NewConfigService(logger)
|
||||
|
||||
// 初始化SQLite服务
|
||||
sqliteService := sqlite.New()
|
||||
|
||||
// 初始化数据库服务
|
||||
databaseService := NewDatabaseService(configService, logger)
|
||||
|
||||
@@ -42,6 +48,9 @@ func NewServiceManager() *ServiceManager {
|
||||
// 初始化文档服务
|
||||
documentService := NewDocumentService(databaseService, logger)
|
||||
|
||||
// 初始化窗口服务
|
||||
windowService := NewWindowService(logger, documentService)
|
||||
|
||||
// 初始化系统服务
|
||||
systemService := NewSystemService(logger)
|
||||
|
||||
@@ -91,7 +100,9 @@ func NewServiceManager() *ServiceManager {
|
||||
return &ServiceManager{
|
||||
configService: configService,
|
||||
databaseService: databaseService,
|
||||
sqliteService: sqliteService,
|
||||
documentService: documentService,
|
||||
windowService: windowService,
|
||||
migrationService: migrationService,
|
||||
systemService: systemService,
|
||||
hotkeyService: hotkeyService,
|
||||
@@ -107,12 +118,13 @@ func NewServiceManager() *ServiceManager {
|
||||
}
|
||||
|
||||
// GetServices 获取所有wails服务列表
|
||||
// 注意:服务启动顺序很重要,DatabaseService 必须在依赖数据库的服务之前启动
|
||||
func (sm *ServiceManager) GetServices() []application.Service {
|
||||
services := []application.Service{
|
||||
application.NewService(sm.configService),
|
||||
application.NewService(sm.sqliteService),
|
||||
application.NewService(sm.databaseService),
|
||||
application.NewService(sm.documentService),
|
||||
application.NewService(sm.windowService),
|
||||
application.NewService(sm.keyBindingService),
|
||||
application.NewService(sm.extensionService),
|
||||
application.NewService(sm.migrationService),
|
||||
@@ -138,7 +150,7 @@ func (sm *ServiceManager) GetDialogService() *DialogService {
|
||||
}
|
||||
|
||||
// GetLogger 获取日志服务实例
|
||||
func (sm *ServiceManager) GetLogger() *log.LoggerService {
|
||||
func (sm *ServiceManager) GetLogger() *log.Service {
|
||||
return sm.logger
|
||||
}
|
||||
|
||||
@@ -181,3 +193,18 @@ func (sm *ServiceManager) GetTranslationService() *TranslationService {
|
||||
func (sm *ServiceManager) GetDatabaseService() *DatabaseService {
|
||||
return sm.databaseService
|
||||
}
|
||||
|
||||
// GetSQLiteService 获取SQLite服务实例
|
||||
func (sm *ServiceManager) GetSQLiteService() *sqlite.Service {
|
||||
return sm.sqliteService
|
||||
}
|
||||
|
||||
// GetWindowService 获取窗口服务实例
|
||||
func (sm *ServiceManager) GetWindowService() *WindowService {
|
||||
return sm.windowService
|
||||
}
|
||||
|
||||
// GetDocumentService 获取文档服务实例
|
||||
func (sm *ServiceManager) GetDocumentService() *DocumentService {
|
||||
return sm.documentService
|
||||
}
|
||||
|
@@ -9,21 +9,20 @@ import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/wailsapp/wails/v3/pkg/application"
|
||||
"github.com/wailsapp/wails/v3/pkg/mac"
|
||||
"github.com/wailsapp/wails/v3/pkg/services/log"
|
||||
)
|
||||
|
||||
// DarwinStartupImpl macOS 平台开机启动实现
|
||||
type DarwinStartupImpl struct {
|
||||
logger *log.LoggerService
|
||||
logger *log.Service
|
||||
disabled bool
|
||||
appPath string
|
||||
appName string
|
||||
}
|
||||
|
||||
// newStartupImplementation 创建平台特定的开机启动实现
|
||||
func newStartupImplementation(logger *log.LoggerService) StartupImplementation {
|
||||
func newStartupImplementation(logger *log.Service) StartupImplementation {
|
||||
return &DarwinStartupImpl{
|
||||
logger: logger,
|
||||
}
|
||||
|
@@ -13,7 +13,7 @@ import (
|
||||
|
||||
// LinuxStartupImpl Linux 平台开机启动实现
|
||||
type LinuxStartupImpl struct {
|
||||
logger *log.LoggerService
|
||||
logger *log.Service
|
||||
autostartDir string
|
||||
execPath string
|
||||
appName string
|
||||
@@ -37,7 +37,7 @@ X-GNOME-Autostart-enabled=true
|
||||
`
|
||||
|
||||
// newStartupImplementation 创建平台特定的开机启动实现
|
||||
func newStartupImplementation(logger *log.LoggerService) StartupImplementation {
|
||||
func newStartupImplementation(logger *log.Service) StartupImplementation {
|
||||
return &LinuxStartupImpl{
|
||||
logger: logger,
|
||||
}
|
||||
|
@@ -7,7 +7,7 @@ import (
|
||||
// StartupService 开机启动服务
|
||||
type StartupService struct {
|
||||
configService *ConfigService
|
||||
logger *log.LoggerService
|
||||
logger *log.Service
|
||||
impl StartupImplementation
|
||||
initError error
|
||||
}
|
||||
@@ -19,7 +19,7 @@ type StartupImplementation interface {
|
||||
}
|
||||
|
||||
// NewStartupService 创建开机启动服务实例
|
||||
func NewStartupService(configService *ConfigService, logger *log.LoggerService) *StartupService {
|
||||
func NewStartupService(configService *ConfigService, logger *log.Service) *StartupService {
|
||||
service := &StartupService{
|
||||
configService: configService,
|
||||
logger: logger,
|
||||
|
@@ -15,7 +15,7 @@ import (
|
||||
|
||||
// WindowsStartupImpl Windows 平台开机启动实现
|
||||
type WindowsStartupImpl struct {
|
||||
logger *log.LoggerService
|
||||
logger *log.Service
|
||||
registryKey string
|
||||
execPath string
|
||||
workingDir string
|
||||
@@ -23,7 +23,7 @@ type WindowsStartupImpl struct {
|
||||
}
|
||||
|
||||
// newStartupImplementation 创建平台特定的开机启动实现
|
||||
func newStartupImplementation(logger *log.LoggerService) StartupImplementation {
|
||||
func newStartupImplementation(logger *log.Service) StartupImplementation {
|
||||
return &WindowsStartupImpl{
|
||||
logger: logger,
|
||||
}
|
||||
|
@@ -15,7 +15,7 @@ import (
|
||||
type StoreOption struct {
|
||||
FilePath string
|
||||
AutoSave bool
|
||||
Logger *log.LoggerService
|
||||
Logger *log.Service
|
||||
}
|
||||
|
||||
// Store 泛型存储服务
|
||||
@@ -25,7 +25,7 @@ type Store[T any] struct {
|
||||
dataMap sync.Map // thread-safe map
|
||||
unsaved atomic.Bool
|
||||
initOnce sync.Once
|
||||
logger *log.LoggerService
|
||||
logger *log.Service
|
||||
}
|
||||
|
||||
// NewStore 存储服务
|
||||
|
@@ -9,7 +9,7 @@ import (
|
||||
|
||||
// SystemService 系统监控服务
|
||||
type SystemService struct {
|
||||
logger *log.LoggerService
|
||||
logger *log.Service
|
||||
}
|
||||
|
||||
// MemoryStats 内存统计信息
|
||||
@@ -29,7 +29,7 @@ type MemoryStats struct {
|
||||
}
|
||||
|
||||
// NewSystemService 创建新的系统服务实例
|
||||
func NewSystemService(logger *log.LoggerService) *SystemService {
|
||||
func NewSystemService(logger *log.Service) *SystemService {
|
||||
return &SystemService{
|
||||
logger: logger,
|
||||
}
|
||||
|
@@ -10,7 +10,7 @@ import (
|
||||
|
||||
// TranslationService 翻译服务
|
||||
type TranslationService struct {
|
||||
logger *log.LoggerService
|
||||
logger *log.Service
|
||||
factory *translator.TranslatorFactory
|
||||
defaultTimeout time.Duration
|
||||
translators map[translator.TranslatorType]translator.Translator
|
||||
@@ -18,7 +18,7 @@ type TranslationService struct {
|
||||
}
|
||||
|
||||
// NewTranslationService 创建翻译服务实例
|
||||
func NewTranslationService(logger *log.LoggerService) *TranslationService {
|
||||
func NewTranslationService(logger *log.Service) *TranslationService {
|
||||
service := &TranslationService{
|
||||
logger: logger,
|
||||
factory: translator.NewTranslatorFactory(),
|
||||
|
@@ -7,14 +7,14 @@ import (
|
||||
|
||||
// TrayService 系统托盘服务
|
||||
type TrayService struct {
|
||||
logger *log.LoggerService
|
||||
logger *log.Service
|
||||
configService *ConfigService
|
||||
app *application.App
|
||||
mainWindow *application.WebviewWindow
|
||||
}
|
||||
|
||||
// NewTrayService 创建新的系统托盘服务实例
|
||||
func NewTrayService(logger *log.LoggerService, configService *ConfigService) *TrayService {
|
||||
func NewTrayService(logger *log.Service, configService *ConfigService) *TrayService {
|
||||
return &TrayService{
|
||||
logger: logger,
|
||||
configService: configService,
|
||||
@@ -42,7 +42,7 @@ func (ts *TrayService) HandleWindowClose() {
|
||||
if ts.ShouldMinimizeToTray() {
|
||||
// 隐藏到托盘
|
||||
ts.mainWindow.Hide()
|
||||
ts.app.EmitEvent("window:hidden", nil)
|
||||
ts.app.Event.Emit("window:hidden", nil)
|
||||
} else {
|
||||
// 直接退出应用
|
||||
ts.app.Quit()
|
||||
@@ -54,7 +54,7 @@ func (ts *TrayService) HandleWindowMinimize() {
|
||||
if ts.ShouldMinimizeToTray() {
|
||||
// 隐藏到托盘
|
||||
ts.mainWindow.Hide()
|
||||
ts.app.EmitEvent("window:hidden", nil)
|
||||
ts.app.Event.Emit("window:hidden", nil)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,7 +65,7 @@ func (ts *TrayService) ShowWindow() {
|
||||
ts.mainWindow.Restore()
|
||||
ts.mainWindow.Focus()
|
||||
if ts.app != nil {
|
||||
ts.app.EmitEvent("window:shown", nil)
|
||||
ts.app.Event.Emit("window:shown", nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
149
internal/services/window_service.go
Normal file
149
internal/services/window_service.go
Normal file
@@ -0,0 +1,149 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"github.com/wailsapp/wails/v3/pkg/application"
|
||||
"github.com/wailsapp/wails/v3/pkg/events"
|
||||
"github.com/wailsapp/wails/v3/pkg/services/log"
|
||||
)
|
||||
|
||||
// WindowInfo 窗口信息
|
||||
type WindowInfo struct {
|
||||
Window *application.WebviewWindow
|
||||
DocumentID int64
|
||||
Title string
|
||||
}
|
||||
|
||||
// WindowService 窗口管理服务
|
||||
type WindowService struct {
|
||||
logger *log.Service
|
||||
documentService *DocumentService
|
||||
app *application.App
|
||||
mainWindow *application.WebviewWindow
|
||||
windows map[int64]*WindowInfo // documentID -> WindowInfo
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
// NewWindowService 创建新的窗口服务实例
|
||||
func NewWindowService(logger *log.Service, documentService *DocumentService) *WindowService {
|
||||
if logger == nil {
|
||||
logger = log.New()
|
||||
}
|
||||
|
||||
return &WindowService{
|
||||
logger: logger,
|
||||
documentService: documentService,
|
||||
windows: make(map[int64]*WindowInfo),
|
||||
}
|
||||
}
|
||||
|
||||
// SetAppReferences 设置应用和主窗口引用
|
||||
func (ws *WindowService) SetAppReferences(app *application.App, mainWindow *application.WebviewWindow) {
|
||||
ws.app = app
|
||||
ws.mainWindow = mainWindow
|
||||
}
|
||||
|
||||
// OpenDocumentWindow 为指定文档ID打开新窗口
|
||||
func (ws *WindowService) OpenDocumentWindow(documentID int64) error {
|
||||
ws.mu.Lock()
|
||||
defer ws.mu.Unlock()
|
||||
|
||||
// 检查窗口是否已经存在
|
||||
if windowInfo, exists := ws.windows[documentID]; exists {
|
||||
// 窗口已存在,显示并聚焦
|
||||
windowInfo.Window.Show()
|
||||
windowInfo.Window.Restore()
|
||||
windowInfo.Window.Focus()
|
||||
return nil
|
||||
}
|
||||
|
||||
// 获取文档信息
|
||||
doc, err := ws.documentService.GetDocumentByID(documentID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get document: %w", err)
|
||||
}
|
||||
if doc == nil {
|
||||
return fmt.Errorf("document not found: %d", documentID)
|
||||
}
|
||||
|
||||
// 创建新窗口
|
||||
newWindow := ws.app.Window.NewWithOptions(application.WebviewWindowOptions{
|
||||
Title: fmt.Sprintf("voidraft - %s", doc.Title),
|
||||
Width: 700,
|
||||
Height: 800,
|
||||
Hidden: false,
|
||||
Frameless: true,
|
||||
DevToolsEnabled: false,
|
||||
DefaultContextMenuDisabled: false,
|
||||
Mac: application.MacWindow{
|
||||
InvisibleTitleBarHeight: 50,
|
||||
Backdrop: application.MacBackdropTranslucent,
|
||||
TitleBar: application.MacTitleBarHiddenInset,
|
||||
},
|
||||
Windows: application.WindowsWindow{
|
||||
Theme: application.SystemDefault,
|
||||
},
|
||||
BackgroundColour: application.NewRGB(27, 38, 54),
|
||||
URL: fmt.Sprintf("/?documentId=%d", documentID),
|
||||
})
|
||||
|
||||
newWindow.Center()
|
||||
|
||||
ws.app.Window.Add(newWindow)
|
||||
|
||||
// 保存窗口信息
|
||||
windowInfo := &WindowInfo{
|
||||
Window: newWindow,
|
||||
DocumentID: documentID,
|
||||
Title: doc.Title,
|
||||
}
|
||||
ws.windows[documentID] = windowInfo
|
||||
|
||||
// 注册窗口关闭事件
|
||||
ws.registerWindowEvents(newWindow, documentID)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// registerWindowEvents 注册窗口事件
|
||||
func (ws *WindowService) registerWindowEvents(window *application.WebviewWindow, documentID int64) {
|
||||
// 注册窗口关闭事件
|
||||
window.RegisterHook(events.Common.WindowClosing, func(event *application.WindowEvent) {
|
||||
ws.onWindowClosing(documentID)
|
||||
})
|
||||
}
|
||||
|
||||
// onWindowClosing 处理窗口关闭事件
|
||||
func (ws *WindowService) onWindowClosing(documentID int64) {
|
||||
ws.mu.Lock()
|
||||
defer ws.mu.Unlock()
|
||||
windowInfo, exists := ws.windows[documentID]
|
||||
if exists {
|
||||
windowInfo.Window.Close()
|
||||
delete(ws.windows, documentID)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// GetOpenWindows 获取所有打开的窗口信息
|
||||
func (ws *WindowService) GetOpenWindows() []WindowInfo {
|
||||
ws.mu.RLock()
|
||||
defer ws.mu.RUnlock()
|
||||
|
||||
var windows []WindowInfo
|
||||
for _, windowInfo := range ws.windows {
|
||||
windows = append(windows, *windowInfo)
|
||||
}
|
||||
return windows
|
||||
}
|
||||
|
||||
// IsDocumentWindowOpen 检查指定文档的窗口是否已打开
|
||||
func (ws *WindowService) IsDocumentWindowOpen(documentID int64) bool {
|
||||
ws.mu.RLock()
|
||||
defer ws.mu.RUnlock()
|
||||
|
||||
_, exists := ws.windows[documentID]
|
||||
return exists
|
||||
}
|
@@ -10,7 +10,7 @@ import (
|
||||
// SetupSystemTray 设置系统托盘及其功能
|
||||
func SetupSystemTray(app *application.App, mainWindow *application.WebviewWindow, assets embed.FS, trayService *services.TrayService) {
|
||||
// 创建系统托盘
|
||||
systray := app.NewSystemTray()
|
||||
systray := app.SystemTray.New()
|
||||
|
||||
// 设置图标
|
||||
iconBytes, _ := assets.ReadFile("appicon.png")
|
||||
|
26
main.go
26
main.go
@@ -32,7 +32,6 @@ func main() {
|
||||
0x0e, 0x0f, 0x0c, 0x0d, 0x0a, 0x0b, 0x08, 0x09,
|
||||
0x06, 0x07, 0x04, 0x05, 0x02, 0x03, 0x00, 0x01,
|
||||
}
|
||||
var window *application.WebviewWindow
|
||||
// Create a new Wails application by providing the necessary options.
|
||||
// Variables 'Name' and 'Description' are for application metadata.
|
||||
// 'Assets' configures the asset server with the 'FS' variable pointing to the frontend files.
|
||||
@@ -52,18 +51,6 @@ func main() {
|
||||
SingleInstance: &application.SingleInstanceOptions{
|
||||
UniqueID: "com.voidraft",
|
||||
EncryptionKey: encryptionKey,
|
||||
OnSecondInstanceLaunch: func(data application.SecondInstanceData) {
|
||||
if window != nil {
|
||||
window.EmitEvent("secondInstanceLaunched", data)
|
||||
window.Restore()
|
||||
window.Focus()
|
||||
}
|
||||
log.Printf("Second instance launched with args: %v\n", data.Args)
|
||||
log.Printf("Working directory: %s\n", data.WorkingDir)
|
||||
if data.AdditionalData != nil {
|
||||
log.Printf("Additional data: %v\n", data.AdditionalData)
|
||||
}
|
||||
},
|
||||
AdditionalData: map[string]string{
|
||||
"launchtime": time.Now().Local().String(),
|
||||
},
|
||||
@@ -75,7 +62,7 @@ func main() {
|
||||
// 'Mac' options tailor the window when running on macOS.
|
||||
// 'BackgroundColour' is the background colour of the window.
|
||||
// 'URL' is the URL that will be loaded into the webview.
|
||||
mainWindow := app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
|
||||
mainWindow := app.Window.NewWithOptions(application.WebviewWindowOptions{
|
||||
Title: "voidraft",
|
||||
Width: 700,
|
||||
Height: 800,
|
||||
@@ -92,7 +79,7 @@ func main() {
|
||||
Theme: application.SystemDefault,
|
||||
},
|
||||
BackgroundColour: application.NewRGB(27, 38, 54),
|
||||
URL: "/#/",
|
||||
URL: "/",
|
||||
})
|
||||
mainWindow.Center()
|
||||
|
||||
@@ -100,6 +87,10 @@ func main() {
|
||||
trayService := serviceManager.GetTrayService()
|
||||
trayService.SetAppReferences(app, mainWindow)
|
||||
|
||||
// 获取窗口服务并设置应用引用
|
||||
windowService := serviceManager.GetWindowService()
|
||||
windowService.SetAppReferences(app, mainWindow)
|
||||
|
||||
// 设置系统托盘
|
||||
systray.SetupSystemTray(app, mainWindow, assets, trayService)
|
||||
|
||||
@@ -114,15 +105,12 @@ func main() {
|
||||
dialogService := serviceManager.GetDialogService()
|
||||
dialogService.SetWindow(mainWindow)
|
||||
|
||||
// 设置全局变量供单实例处理使用
|
||||
window = mainWindow
|
||||
|
||||
// Create a goroutine that emits an event containing the current time every second.
|
||||
// The frontend can listen to this event and update the UI accordingly.
|
||||
go func() {
|
||||
for {
|
||||
now := time.Now().Format(time.RFC1123)
|
||||
app.EmitEvent("time", now)
|
||||
app.Event.Emit("time", now)
|
||||
time.Sleep(time.Second)
|
||||
}
|
||||
}()
|
||||
|
Reference in New Issue
Block a user