Compare commits
4 Commits
v1.3.3
...
fa72ff8061
| Author | SHA1 | Date | |
|---|---|---|---|
| fa72ff8061 | |||
| 65f24860e6 | |||
|
|
4881233211 | ||
| bc01fdf362 |
142
.cursor/rules/thxan-2-cn.mdc
Normal file
142
.cursor/rules/thxan-2-cn.mdc
Normal file
@@ -0,0 +1,142 @@
|
||||
---
|
||||
description: THXAN-2-CN
|
||||
globs:
|
||||
alwaysApply: false
|
||||
---
|
||||
|
||||
---
|
||||
name: AI Chief Architect (AI首席架构师)
|
||||
version: 7.3.0
|
||||
author: thxan & Gemini
|
||||
---
|
||||
|
||||
# 核心身份 (Core Identity)
|
||||
- 你是一个顶尖的AI首席架构师,你的核心交互界面是IDE。
|
||||
- 你的首要任务是通过严谨的、文档驱动的工作流来管理和开发软件项目。
|
||||
- 你的每一次操作和决策都必须是可追溯、可审计的。
|
||||
|
||||
# 指导原则 (Guiding Principles)
|
||||
- **首要原则:文档驱动 (Documentation-Driven)。** 整个项目以`.docs/`目录作为其单一事实来源(Single Source of Truth, SSoT)。对话是临时的,文档是永恒的。你必须通过读写文档来持久化上下文、状态和知识。
|
||||
- **第二原则:提案先行 (Proposal-First)。** 对于任何非原子性的复杂任务(如功能开发、重构),必须先生成一份结构化的提案并通过用户审批,才能进入实施阶段。
|
||||
- **第三原则:持续自省 (Continuous Introspection)。** 在完成任务后,你必须回顾流程,提炼可复用的模式,并将其固化为新的规则,以实现持续进化。
|
||||
|
||||
# 核心机制与工具 (Core Mechanics & Tools)
|
||||
- **工具集授权:** 你被授权使用以下工具来与文件系统交互:
|
||||
- `read_file(path)`: 读取指定文件的内容。
|
||||
- `write_file(path, content)`: 将内容写入指定文件,会覆盖原有内容。
|
||||
- `list_files(path)`: 列出指定目录下的文件和文件夹。
|
||||
- **状态报告协议:** 在你的每次响应结束时,**必须**附加一行来说明你的当前状态,格式为:`状态: [工作流代码][当前模块代码][子状态]`。例如: `状态: [W2][M4][AwaitingApproval]`.
|
||||
- **SSoT 结构:**
|
||||
- **背景上下文位置:** `.docs/`
|
||||
- **启动加载 (MUST LOAD):**
|
||||
- **BOOTLOADER:** `.docs/README-DOCS.md`
|
||||
- **MEMORY:** `.docs/STATE/MEMORY.md`
|
||||
- **RULES:** `.docs/RULES/*` (所有规则文件)
|
||||
- **按需加载 (Optional):** `.docs/*` 下的其他相关文档。
|
||||
|
||||
---
|
||||
|
||||
# 核心工作流 (Core Workflow)
|
||||
这是你的决策核心。你必须首先分析用户请求,并结合项目的当前状态,从以下工作流中选择一个且仅一个来执行,并**在回应的开头明确声明你选择了哪个工作流**。
|
||||
|
||||
### **决策触发器 (Decision Triggers):**
|
||||
1. **IF** `BOOTLOADER` 文件不存在
|
||||
* **THEN** 激活 **[W1_ProjectInitialization]**
|
||||
2. **ELSE IF** 用户意图涉及一个需要规划、设计和分步执行的复杂开发或修改任务 (关键词如: "实现", "开发新功能", "重构", "修改需求")
|
||||
* **THEN** 激活 **[W2_ProposalDrivenDevelopment]**
|
||||
3. **ELSE IF** 用户意图是一个可以直接执行的原子性任务 (关键词如: "运行测试", "画架构图", "审计文档", "检查同步")
|
||||
* **THEN** 激活 **[W3_DirectCommandExecution]**
|
||||
4. **ELSE** (对于所有其他情况,如请求模糊不清或前置条件不足)
|
||||
* **THEN** 激活 **[W4_ContextClarification]**
|
||||
|
||||
### **工作流定义 (Workflow Definitions):**
|
||||
- **[W1_ProjectInitialization]**
|
||||
- **执行模块:** `M1_Project_Onboarding`
|
||||
- **[W2_ProposalDrivenDevelopment]**
|
||||
- **执行模块:** `M2_Context_Loading` -> `M4_Analyze_And_Propose` -> `M5_Implement_From_Proposal` -> `M6_Sync_With_Truth` -> `M7_Review_And_Generalize`
|
||||
- **[W3_DirectCommandExecution]**
|
||||
- **执行模块:** `M2_Context_Loading` -> (根据用户具体指令选择 `M8_Test_And_Validate`, `M9_Visualize_Architecture`, 或 `M10_Audit_Documentation` 中的一个)
|
||||
- **[W4_ContextClarification]**
|
||||
- **执行模块:** `M2_Context_Loading` -> `M3_Clarification_And_Retry` (执行后**必须返回决策触发器**重新开始分析流程)
|
||||
|
||||
---
|
||||
|
||||
# 核心模块库 (Core Modules Library)
|
||||
这是你的能力单元,由上述工作流进行调用。
|
||||
|
||||
- **`[M1_Project_Onboarding]`**: 初始化新项目,建立SSoT的基础架构。
|
||||
- [1a] 识别为新项目,进入初始化流程。
|
||||
- [1b] 主动提问以明确项目目标、技术栈。
|
||||
- [1c] 创建`.docs/`目录结构及核心模板。
|
||||
- [1d] 创建`README-DOCS.md`并建立核心“状态基线”文档初稿。
|
||||
- [1e] 调用 `[M11_State_Management]` 初始化并保存初始状态。
|
||||
|
||||
- **`[M2_Context_Loading]`**: 在执行任何任务前,加载必要的上下文。
|
||||
- [2a] 调用 `[M11]` 加载当前状态。
|
||||
- [2b] **[必须加载]:** 根据`核心机制与工具`部分的定义,加载所有启动文件。
|
||||
- [2c] **[智能加载]:** 从用户请求中提取关键词,查找并阅读最相关的附加上下文文件。
|
||||
- [2d] 加载完毕后,在内存中形成上下文基准,并更新状态文件。
|
||||
|
||||
- **`[M3_Clarification_And_Retry]`**:
|
||||
- [3a] 明确报告哪个任务无法执行,以及缺失了什么具体信息。
|
||||
- [3b] 分析问题原因,并向用户提供一个或多个可执行的、用于解决问题的建议。
|
||||
|
||||
- **`[M4_Analyze_And_Propose]`**:
|
||||
- [4a] **意图分析:** 复述核心需求以确认理解。
|
||||
- [4b] **提案驱动协议:** 对于复杂任务,生成一份结构化提案,并等待用户批准。
|
||||
```mermaid
|
||||
stateDiagram-v2
|
||||
direction LR
|
||||
[*] --> AnalyzingIntent
|
||||
AnalyzingIntent --> GeneratingProposal: on Complex task
|
||||
GeneratingProposal --> AwaitingApproval
|
||||
AwaitingApproval --> Implementing: Approved
|
||||
AwaitingApproval --> GeneratingProposal: Rejected
|
||||
Implementing --> SyncingSSoT
|
||||
SyncingSSoT --> Learning
|
||||
Learning --> [*]
|
||||
```
|
||||
- [4c] **提案生成:** 调用 `[M12_Generate_File_Creation_Command]` 和 `[M13_Generate_Structured_Proposal]` 创建提案文件并起草内容,然后报告状态为 `[AwaitingApproval]`。
|
||||
|
||||
- **`[M5_Implement_From_Proposal]`**:
|
||||
- [5a] 读取用户指定的 `proposal_file`。
|
||||
- [5b] **原子化执行:** 严格按文件中的清单,将“代码-文档-测试”视为不可分割的单元同步完成。
|
||||
- [5c] **状态同步:** 每完成一步,更新提案文件(`[ ]` -> `[x]`)并调用 `[M11]` 保存进度。
|
||||
- [5d] 完成后,自动触发 `[M6]` 和 `[M7]`。
|
||||
|
||||
- **`[M6_Sync_With_Truth]`**:
|
||||
- [6a] 分析最近的代码变更集,交叉比对 `.docs/STATE/` 下的基线文档。
|
||||
- [6b] 报告 `[✅ SSoT同步完成]` 或提出 `[⚠️ 关键文档更新]` 建议。
|
||||
|
||||
- **`[M7_Review_And_Generalize]`**:
|
||||
- [7a] 分析任务全流程,若发现可复用的模式,主动起草新规则并提请用户批准存入 `.docs/RULES/`。
|
||||
|
||||
- **`[M8_Test_And_Validate]`**:
|
||||
- [8a] 分析目标文件,若测试不存在则生成测试骨架。
|
||||
- [8b] 若测试存在,则运行相关测试脚本并报告结果。
|
||||
|
||||
- **`[M9_Visualize_Architecture]`**:
|
||||
- [9a] 扫描指定范围代码,识别依赖关系,并使用Mermaid.js语法生成图表。
|
||||
- [9b] 主动询问是否需要将图表保存到 `.docs/ARCHITECTURE/`。
|
||||
|
||||
- **`[M10_Audit_Documentation]`**:
|
||||
- [10a] 执行交叉引用分析,生成包含 `[🚫 Missing Docs]`、`[🔗 Broken Links]`、`[⚠️ Stale Docs]`的审计报告。
|
||||
|
||||
- **`[M11_State_Management]`**:
|
||||
- [11a] 通过读写 `.docs/STATE/MEMORY.md` 来加载、保存或清理当前任务状态。
|
||||
|
||||
- **`[M12_Generate_File_Creation_Command]`**:
|
||||
- [12a] **职责:** 生成一个`bash`命令来创建一个带有标准时间戳的新文件(提案、决策等)。
|
||||
- [12b] **格式:** `touch {base_path}/${prefix}_$(date +%Y%m%d_%H%M%S)_${description}.md`
|
||||
- [12c] **注意:** 命令执行后需捕获并使用该文件名进行后续读写。
|
||||
|
||||
- **`[M13_Generate_Structured_Proposal]`**:
|
||||
- [13a] 在指定文件中生成结构化的提案草稿,包含背景、目标、设计方案、风险、测试计划等。
|
||||
|
||||
- **`[M14_Self_Correction]`**:
|
||||
- [14a] 当发现自身行为违反`指导原则`时自动触发。
|
||||
- [14b] **立即停止**当前不当任务,**明确承认错误**,并引用违反的原则。
|
||||
- [14c] **重新返回核心工作流**,以正确的流程生成回答。
|
||||
|
||||
# **[其他]**
|
||||
** 如果不能确认获取的时间是否正确,请使用命令行 date来获取当前时间
|
||||
@@ -467,6 +467,17 @@ export class GeneralConfig {
|
||||
*/
|
||||
"startAtLogin": boolean;
|
||||
|
||||
/**
|
||||
* 窗口吸附设置
|
||||
* 是否启用窗口吸附功能
|
||||
*/
|
||||
"enableWindowSnap": boolean;
|
||||
|
||||
/**
|
||||
* 吸附距离阈值(像素)
|
||||
*/
|
||||
"snapThreshold": number;
|
||||
|
||||
/**
|
||||
* 全局热键设置
|
||||
* 是否启用全局热键
|
||||
@@ -492,6 +503,12 @@ export class GeneralConfig {
|
||||
if (!("startAtLogin" in $$source)) {
|
||||
this["startAtLogin"] = false;
|
||||
}
|
||||
if (!("enableWindowSnap" in $$source)) {
|
||||
this["enableWindowSnap"] = false;
|
||||
}
|
||||
if (!("snapThreshold" in $$source)) {
|
||||
this["snapThreshold"] = 0;
|
||||
}
|
||||
if (!("enableGlobalHotkey" in $$source)) {
|
||||
this["enableGlobalHotkey"] = false;
|
||||
}
|
||||
@@ -506,10 +523,10 @@ export class GeneralConfig {
|
||||
* Creates a new GeneralConfig instance from a string or object.
|
||||
*/
|
||||
static createFrom($$source: any = {}): GeneralConfig {
|
||||
const $$createField5_0 = $$createType8;
|
||||
const $$createField7_0 = $$createType8;
|
||||
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
|
||||
if ("globalHotkey" in $$parsedSource) {
|
||||
$$parsedSource["globalHotkey"] = $$createField5_0($$parsedSource["globalHotkey"]);
|
||||
$$parsedSource["globalHotkey"] = $$createField7_0($$parsedSource["globalHotkey"]);
|
||||
}
|
||||
return new GeneralConfig($$parsedSource as Partial<GeneralConfig>);
|
||||
}
|
||||
|
||||
@@ -82,6 +82,14 @@ export function SetHotkeyChangeCallback(callback: any): Promise<void> & { cancel
|
||||
return $resultPromise;
|
||||
}
|
||||
|
||||
/**
|
||||
* SetWindowSnapConfigChangeCallback 设置窗口吸附配置变更回调
|
||||
*/
|
||||
export function SetWindowSnapConfigChangeCallback(callback: any): Promise<void> & { cancel(): void } {
|
||||
let $resultPromise = $Call.ByID(2324961653, callback) as any;
|
||||
return $resultPromise;
|
||||
}
|
||||
|
||||
// Private type creation functions
|
||||
const $$createType0 = models$0.AppConfig.createFrom;
|
||||
const $$createType1 = $Create.Nullable($$createType0);
|
||||
|
||||
@@ -17,6 +17,7 @@ import * as ThemeService from "./themeservice.js";
|
||||
import * as TranslationService from "./translationservice.js";
|
||||
import * as TrayService from "./trayservice.js";
|
||||
import * as WindowService from "./windowservice.js";
|
||||
import * as WindowSnapService from "./windowsnapservice.js";
|
||||
export {
|
||||
BackupService,
|
||||
ConfigService,
|
||||
@@ -33,7 +34,8 @@ export {
|
||||
ThemeService,
|
||||
TranslationService,
|
||||
TrayService,
|
||||
WindowService
|
||||
WindowService,
|
||||
WindowSnapService
|
||||
};
|
||||
|
||||
export * from "./models.js";
|
||||
|
||||
@@ -203,7 +203,7 @@ export class SelfUpdateResult {
|
||||
}
|
||||
|
||||
/**
|
||||
* WindowInfo 窗口信息
|
||||
* WindowInfo 窗口信息(简化版)
|
||||
*/
|
||||
export class WindowInfo {
|
||||
"Window": application$0.WebviewWindow | null;
|
||||
@@ -238,6 +238,26 @@ export class WindowInfo {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* WindowSnapService 窗口吸附服务
|
||||
*/
|
||||
export class WindowSnapService {
|
||||
|
||||
/** Creates a new WindowSnapService instance. */
|
||||
constructor($$source: Partial<WindowSnapService> = {}) {
|
||||
|
||||
Object.assign(this, $$source);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new WindowSnapService instance from a string or object.
|
||||
*/
|
||||
static createFrom($$source: any = {}): WindowSnapService {
|
||||
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
|
||||
return new WindowSnapService($$parsedSource as Partial<WindowSnapService>);
|
||||
}
|
||||
}
|
||||
|
||||
// Private type creation functions
|
||||
const $$createType0 = application$0.WebviewWindow.createFrom;
|
||||
const $$createType1 = $Create.Nullable($$createType0);
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// This file is automatically generated. DO NOT EDIT
|
||||
|
||||
/**
|
||||
* WindowService 窗口管理服务
|
||||
* WindowService 窗口管理服务(专注于窗口生命周期管理)
|
||||
* @module
|
||||
*/
|
||||
|
||||
@@ -46,6 +46,14 @@ export function OpenDocumentWindow(documentID: number): Promise<void> & { cancel
|
||||
return $resultPromise;
|
||||
}
|
||||
|
||||
/**
|
||||
* ServiceShutdown 实现服务关闭接口
|
||||
*/
|
||||
export function ServiceShutdown(): Promise<void> & { cancel(): void } {
|
||||
let $resultPromise = $Call.ByID(202192783) as any;
|
||||
return $resultPromise;
|
||||
}
|
||||
|
||||
/**
|
||||
* SetAppReferences 设置应用和主窗口引用
|
||||
*/
|
||||
@@ -54,6 +62,14 @@ export function SetAppReferences(app: application$0.App | null, mainWindow: appl
|
||||
return $resultPromise;
|
||||
}
|
||||
|
||||
/**
|
||||
* SetWindowSnapService 设置窗口吸附服务引用
|
||||
*/
|
||||
export function SetWindowSnapService(snapService: $models.WindowSnapService | null): Promise<void> & { cancel(): void } {
|
||||
let $resultPromise = $Call.ByID(1105193745, snapService) as any;
|
||||
return $resultPromise;
|
||||
}
|
||||
|
||||
// Private type creation functions
|
||||
const $$createType0 = $models.WindowInfo.createFrom;
|
||||
const $$createType1 = $Create.Array($$createType0);
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||
// This file is automatically generated. DO NOT EDIT
|
||||
|
||||
/**
|
||||
* WindowSnapService 窗口吸附服务
|
||||
* @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";
|
||||
|
||||
/**
|
||||
* Cleanup 清理资源
|
||||
*/
|
||||
export function Cleanup(): Promise<void> & { cancel(): void } {
|
||||
let $resultPromise = $Call.ByID(2155505498) as any;
|
||||
return $resultPromise;
|
||||
}
|
||||
|
||||
/**
|
||||
* OnWindowSnapConfigChanged 处理窗口吸附配置变更
|
||||
*/
|
||||
export function OnWindowSnapConfigChanged(enabled: boolean, threshold: number): Promise<void> & { cancel(): void } {
|
||||
let $resultPromise = $Call.ByID(3794787039, enabled, threshold) as any;
|
||||
return $resultPromise;
|
||||
}
|
||||
|
||||
/**
|
||||
* RegisterWindow 注册需要吸附管理的窗口
|
||||
*/
|
||||
export function RegisterWindow(documentID: number, window: application$0.WebviewWindow | null, title: string): Promise<void> & { cancel(): void } {
|
||||
let $resultPromise = $Call.ByID(1000222723, documentID, window, title) as any;
|
||||
return $resultPromise;
|
||||
}
|
||||
|
||||
/**
|
||||
* ServiceShutdown 实现服务关闭接口
|
||||
*/
|
||||
export function ServiceShutdown(): Promise<void> & { cancel(): void } {
|
||||
let $resultPromise = $Call.ByID(1172710495) 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(1782093351, app, mainWindow) as any;
|
||||
return $resultPromise;
|
||||
}
|
||||
|
||||
/**
|
||||
* SetSnapEnabled 设置是否启用窗口吸附
|
||||
*/
|
||||
export function SetSnapEnabled(enabled: boolean): Promise<void> & { cancel(): void } {
|
||||
let $resultPromise = $Call.ByID(2280126835, enabled) as any;
|
||||
return $resultPromise;
|
||||
}
|
||||
|
||||
/**
|
||||
* SetSnapThreshold 设置窗口吸附阈值
|
||||
*/
|
||||
export function SetSnapThreshold(threshold: number): Promise<void> & { cancel(): void } {
|
||||
let $resultPromise = $Call.ByID(586790049, threshold) as any;
|
||||
return $resultPromise;
|
||||
}
|
||||
|
||||
/**
|
||||
* StartWindowSnapMonitor 启动窗口吸附监听器
|
||||
*/
|
||||
export function StartWindowSnapMonitor(): Promise<void> & { cancel(): void } {
|
||||
let $resultPromise = $Call.ByID(84533508) as any;
|
||||
return $resultPromise;
|
||||
}
|
||||
|
||||
/**
|
||||
* UnregisterWindow 取消注册窗口
|
||||
*/
|
||||
export function UnregisterWindow(documentID: number): Promise<void> & { cancel(): void } {
|
||||
let $resultPromise = $Call.ByID(2844230768, documentID) as any;
|
||||
return $resultPromise;
|
||||
}
|
||||
321
frontend/package-lock.json
generated
321
frontend/package-lock.json
generated
@@ -52,27 +52,27 @@
|
||||
"prettier": "^3.6.2",
|
||||
"remarkable": "^2.0.1",
|
||||
"sass": "^1.90.0",
|
||||
"vue": "^3.5.18",
|
||||
"vue": "^3.5.19",
|
||||
"vue-i18n": "^11.1.11",
|
||||
"vue-pick-colors": "^1.8.0",
|
||||
"vue-router": "^4.5.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.33.0",
|
||||
"@eslint/js": "^9.34.0",
|
||||
"@lezer/generator": "^1.8.0",
|
||||
"@types/node": "^24.3.0",
|
||||
"@types/remarkable": "^2.0.8",
|
||||
"@vitejs/plugin-vue": "^6.0.1",
|
||||
"@wailsio/runtime": "latest",
|
||||
"eslint": "^9.33.0",
|
||||
"eslint": "^9.34.0",
|
||||
"eslint-plugin-vue": "^10.4.0",
|
||||
"globals": "^16.3.0",
|
||||
"typescript": "^5.9.2",
|
||||
"typescript-eslint": "^8.39.1",
|
||||
"typescript-eslint": "^8.40.0",
|
||||
"unplugin-vue-components": "^29.0.0",
|
||||
"vite": "^7.1.2",
|
||||
"vite": "^7.1.3",
|
||||
"vue-eslint-parser": "^10.2.0",
|
||||
"vue-tsc": "^3.0.5"
|
||||
"vue-tsc": "^3.0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-string-parser": {
|
||||
@@ -1060,9 +1060,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@eslint/js": {
|
||||
"version": "9.33.0",
|
||||
"resolved": "https://registry.npmmirror.com/@eslint/js/-/js-9.33.0.tgz",
|
||||
"integrity": "sha512-5K1/mKhWaMfreBGJTwval43JJmkip0RmM+3+IuqupeSKNC/Th2Kc7ucaq5ovTSra/OOKB9c58CGSz3QMVbWt0A==",
|
||||
"version": "9.34.0",
|
||||
"resolved": "https://registry.npmmirror.com/@eslint/js/-/js-9.34.0.tgz",
|
||||
"integrity": "sha512-EoyvqQnBNsV1CWaEJ559rxXL4c8V92gxirbawSmVUOWXlsRxxQXl6LmCpdUblgxgSkDIqKnhzba2SjRTI/A5Rw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -2128,17 +2128,17 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@typescript-eslint/eslint-plugin": {
|
||||
"version": "8.39.1",
|
||||
"resolved": "https://registry.npmmirror.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.39.1.tgz",
|
||||
"integrity": "sha512-yYegZ5n3Yr6eOcqgj2nJH8cH/ZZgF+l0YIdKILSDjYFRjgYQMgv/lRjV5Z7Up04b9VYUondt8EPMqg7kTWgJ2g==",
|
||||
"version": "8.40.0",
|
||||
"resolved": "https://registry.npmmirror.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.40.0.tgz",
|
||||
"integrity": "sha512-w/EboPlBwnmOBtRbiOvzjD+wdiZdgFeo17lkltrtn7X37vagKKWJABvyfsJXTlHe6XBzugmYgd4A4nW+k8Mixw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@eslint-community/regexpp": "^4.10.0",
|
||||
"@typescript-eslint/scope-manager": "8.39.1",
|
||||
"@typescript-eslint/type-utils": "8.39.1",
|
||||
"@typescript-eslint/utils": "8.39.1",
|
||||
"@typescript-eslint/visitor-keys": "8.39.1",
|
||||
"@typescript-eslint/scope-manager": "8.40.0",
|
||||
"@typescript-eslint/type-utils": "8.40.0",
|
||||
"@typescript-eslint/utils": "8.40.0",
|
||||
"@typescript-eslint/visitor-keys": "8.40.0",
|
||||
"graphemer": "^1.4.0",
|
||||
"ignore": "^7.0.0",
|
||||
"natural-compare": "^1.4.0",
|
||||
@@ -2152,7 +2152,7 @@
|
||||
"url": "https://opencollective.com/typescript-eslint"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@typescript-eslint/parser": "^8.39.1",
|
||||
"@typescript-eslint/parser": "^8.40.0",
|
||||
"eslint": "^8.57.0 || ^9.0.0",
|
||||
"typescript": ">=4.8.4 <6.0.0"
|
||||
}
|
||||
@@ -2168,16 +2168,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/parser": {
|
||||
"version": "8.39.1",
|
||||
"resolved": "https://registry.npmmirror.com/@typescript-eslint/parser/-/parser-8.39.1.tgz",
|
||||
"integrity": "sha512-pUXGCuHnnKw6PyYq93lLRiZm3vjuslIy7tus1lIQTYVK9bL8XBgJnCWm8a0KcTtHC84Yya1Q6rtll+duSMj0dg==",
|
||||
"version": "8.40.0",
|
||||
"resolved": "https://registry.npmmirror.com/@typescript-eslint/parser/-/parser-8.40.0.tgz",
|
||||
"integrity": "sha512-jCNyAuXx8dr5KJMkecGmZ8KI61KBUhkCob+SD+C+I5+Y1FWI2Y3QmY4/cxMCC5WAsZqoEtEETVhUiUMIGCf6Bw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/scope-manager": "8.39.1",
|
||||
"@typescript-eslint/types": "8.39.1",
|
||||
"@typescript-eslint/typescript-estree": "8.39.1",
|
||||
"@typescript-eslint/visitor-keys": "8.39.1",
|
||||
"@typescript-eslint/scope-manager": "8.40.0",
|
||||
"@typescript-eslint/types": "8.40.0",
|
||||
"@typescript-eslint/typescript-estree": "8.40.0",
|
||||
"@typescript-eslint/visitor-keys": "8.40.0",
|
||||
"debug": "^4.3.4"
|
||||
},
|
||||
"engines": {
|
||||
@@ -2193,14 +2193,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/project-service": {
|
||||
"version": "8.39.1",
|
||||
"resolved": "https://registry.npmmirror.com/@typescript-eslint/project-service/-/project-service-8.39.1.tgz",
|
||||
"integrity": "sha512-8fZxek3ONTwBu9ptw5nCKqZOSkXshZB7uAxuFF0J/wTMkKydjXCzqqga7MlFMpHi9DoG4BadhmTkITBcg8Aybw==",
|
||||
"version": "8.40.0",
|
||||
"resolved": "https://registry.npmmirror.com/@typescript-eslint/project-service/-/project-service-8.40.0.tgz",
|
||||
"integrity": "sha512-/A89vz7Wf5DEXsGVvcGdYKbVM9F7DyFXj52lNYUDS1L9yJfqjW/fIp5PgMuEJL/KeqVTe2QSbXAGUZljDUpArw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/tsconfig-utils": "^8.39.1",
|
||||
"@typescript-eslint/types": "^8.39.1",
|
||||
"@typescript-eslint/tsconfig-utils": "^8.40.0",
|
||||
"@typescript-eslint/types": "^8.40.0",
|
||||
"debug": "^4.3.4"
|
||||
},
|
||||
"engines": {
|
||||
@@ -2215,14 +2215,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/scope-manager": {
|
||||
"version": "8.39.1",
|
||||
"resolved": "https://registry.npmmirror.com/@typescript-eslint/scope-manager/-/scope-manager-8.39.1.tgz",
|
||||
"integrity": "sha512-RkBKGBrjgskFGWuyUGz/EtD8AF/GW49S21J8dvMzpJitOF1slLEbbHnNEtAHtnDAnx8qDEdRrULRnWVx27wGBw==",
|
||||
"version": "8.40.0",
|
||||
"resolved": "https://registry.npmmirror.com/@typescript-eslint/scope-manager/-/scope-manager-8.40.0.tgz",
|
||||
"integrity": "sha512-y9ObStCcdCiZKzwqsE8CcpyuVMwRouJbbSrNuThDpv16dFAj429IkM6LNb1dZ2m7hK5fHyzNcErZf7CEeKXR4w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.39.1",
|
||||
"@typescript-eslint/visitor-keys": "8.39.1"
|
||||
"@typescript-eslint/types": "8.40.0",
|
||||
"@typescript-eslint/visitor-keys": "8.40.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
@@ -2233,9 +2233,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/tsconfig-utils": {
|
||||
"version": "8.39.1",
|
||||
"resolved": "https://registry.npmmirror.com/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.39.1.tgz",
|
||||
"integrity": "sha512-ePUPGVtTMR8XMU2Hee8kD0Pu4NDE1CN9Q1sxGSGd/mbOtGZDM7pnhXNJnzW63zk/q+Z54zVzj44HtwXln5CvHA==",
|
||||
"version": "8.40.0",
|
||||
"resolved": "https://registry.npmmirror.com/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.40.0.tgz",
|
||||
"integrity": "sha512-jtMytmUaG9d/9kqSl/W3E3xaWESo4hFDxAIHGVW/WKKtQhesnRIJSAJO6XckluuJ6KDB5woD1EiqknriCtAmcw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -2250,15 +2250,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/type-utils": {
|
||||
"version": "8.39.1",
|
||||
"resolved": "https://registry.npmmirror.com/@typescript-eslint/type-utils/-/type-utils-8.39.1.tgz",
|
||||
"integrity": "sha512-gu9/ahyatyAdQbKeHnhT4R+y3YLtqqHyvkfDxaBYk97EcbfChSJXyaJnIL3ygUv7OuZatePHmQvuH5ru0lnVeA==",
|
||||
"version": "8.40.0",
|
||||
"resolved": "https://registry.npmmirror.com/@typescript-eslint/type-utils/-/type-utils-8.40.0.tgz",
|
||||
"integrity": "sha512-eE60cK4KzAc6ZrzlJnflXdrMqOBaugeukWICO2rB0KNvwdIMaEaYiywwHMzA1qFpTxrLhN9Lp4E/00EgWcD3Ow==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.39.1",
|
||||
"@typescript-eslint/typescript-estree": "8.39.1",
|
||||
"@typescript-eslint/utils": "8.39.1",
|
||||
"@typescript-eslint/types": "8.40.0",
|
||||
"@typescript-eslint/typescript-estree": "8.40.0",
|
||||
"@typescript-eslint/utils": "8.40.0",
|
||||
"debug": "^4.3.4",
|
||||
"ts-api-utils": "^2.1.0"
|
||||
},
|
||||
@@ -2275,9 +2275,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/types": {
|
||||
"version": "8.39.1",
|
||||
"resolved": "https://registry.npmmirror.com/@typescript-eslint/types/-/types-8.39.1.tgz",
|
||||
"integrity": "sha512-7sPDKQQp+S11laqTrhHqeAbsCfMkwJMrV7oTDvtDds4mEofJYir414bYKUEb8YPUm9QL3U+8f6L6YExSoAGdQw==",
|
||||
"version": "8.40.0",
|
||||
"resolved": "https://registry.npmmirror.com/@typescript-eslint/types/-/types-8.40.0.tgz",
|
||||
"integrity": "sha512-ETdbFlgbAmXHyFPwqUIYrfc12ArvpBhEVgGAxVYSwli26dn8Ko+lIo4Su9vI9ykTZdJn+vJprs/0eZU0YMAEQg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -2289,16 +2289,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/typescript-estree": {
|
||||
"version": "8.39.1",
|
||||
"resolved": "https://registry.npmmirror.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.39.1.tgz",
|
||||
"integrity": "sha512-EKkpcPuIux48dddVDXyQBlKdeTPMmALqBUbEk38McWv0qVEZwOpVJBi7ugK5qVNgeuYjGNQxrrnoM/5+TI/BPw==",
|
||||
"version": "8.40.0",
|
||||
"resolved": "https://registry.npmmirror.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.40.0.tgz",
|
||||
"integrity": "sha512-k1z9+GJReVVOkc1WfVKs1vBrR5MIKKbdAjDTPvIK3L8De6KbFfPFt6BKpdkdk7rZS2GtC/m6yI5MYX+UsuvVYQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/project-service": "8.39.1",
|
||||
"@typescript-eslint/tsconfig-utils": "8.39.1",
|
||||
"@typescript-eslint/types": "8.39.1",
|
||||
"@typescript-eslint/visitor-keys": "8.39.1",
|
||||
"@typescript-eslint/project-service": "8.40.0",
|
||||
"@typescript-eslint/tsconfig-utils": "8.40.0",
|
||||
"@typescript-eslint/types": "8.40.0",
|
||||
"@typescript-eslint/visitor-keys": "8.40.0",
|
||||
"debug": "^4.3.4",
|
||||
"fast-glob": "^3.3.2",
|
||||
"is-glob": "^4.0.3",
|
||||
@@ -2344,16 +2344,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/utils": {
|
||||
"version": "8.39.1",
|
||||
"resolved": "https://registry.npmmirror.com/@typescript-eslint/utils/-/utils-8.39.1.tgz",
|
||||
"integrity": "sha512-VF5tZ2XnUSTuiqZFXCZfZs1cgkdd3O/sSYmdo2EpSyDlC86UM/8YytTmKnehOW3TGAlivqTDT6bS87B/GQ/jyg==",
|
||||
"version": "8.40.0",
|
||||
"resolved": "https://registry.npmmirror.com/@typescript-eslint/utils/-/utils-8.40.0.tgz",
|
||||
"integrity": "sha512-Cgzi2MXSZyAUOY+BFwGs17s7ad/7L+gKt6Y8rAVVWS+7o6wrjeFN4nVfTpbE25MNcxyJ+iYUXflbs2xR9h4UBg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@eslint-community/eslint-utils": "^4.7.0",
|
||||
"@typescript-eslint/scope-manager": "8.39.1",
|
||||
"@typescript-eslint/types": "8.39.1",
|
||||
"@typescript-eslint/typescript-estree": "8.39.1"
|
||||
"@typescript-eslint/scope-manager": "8.40.0",
|
||||
"@typescript-eslint/types": "8.40.0",
|
||||
"@typescript-eslint/typescript-estree": "8.40.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
@@ -2368,13 +2368,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/visitor-keys": {
|
||||
"version": "8.39.1",
|
||||
"resolved": "https://registry.npmmirror.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.39.1.tgz",
|
||||
"integrity": "sha512-W8FQi6kEh2e8zVhQ0eeRnxdvIoOkAp/CPAahcNio6nO9dsIwb9b34z90KOlheoyuVf6LSOEdjlkxSkapNEc+4A==",
|
||||
"version": "8.40.0",
|
||||
"resolved": "https://registry.npmmirror.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.40.0.tgz",
|
||||
"integrity": "sha512-8CZ47QwalyRjsypfwnbI3hKy5gJDPmrkLjkgMxhi0+DZZ2QNx2naS6/hWoVYUHU7LU2zleF68V9miaVZvhFfTA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.39.1",
|
||||
"@typescript-eslint/types": "8.40.0",
|
||||
"eslint-visitor-keys": "^4.2.1"
|
||||
},
|
||||
"engines": {
|
||||
@@ -2403,68 +2403,68 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@volar/language-core": {
|
||||
"version": "2.4.22",
|
||||
"resolved": "https://registry.npmmirror.com/@volar/language-core/-/language-core-2.4.22.tgz",
|
||||
"integrity": "sha512-gp4M7Di5KgNyIyO903wTClYBavRt6UyFNpc5LWfyZr1lBsTUY+QrVZfmbNF2aCyfklBOVk9YC4p+zkwoyT7ECg==",
|
||||
"version": "2.4.23",
|
||||
"resolved": "https://registry.npmmirror.com/@volar/language-core/-/language-core-2.4.23.tgz",
|
||||
"integrity": "sha512-hEEd5ET/oSmBC6pi1j6NaNYRWoAiDhINbT8rmwtINugR39loROSlufGdYMF9TaKGfz+ViGs1Idi3mAhnuPcoGQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@volar/source-map": "2.4.22"
|
||||
"@volar/source-map": "2.4.23"
|
||||
}
|
||||
},
|
||||
"node_modules/@volar/source-map": {
|
||||
"version": "2.4.22",
|
||||
"resolved": "https://registry.npmmirror.com/@volar/source-map/-/source-map-2.4.22.tgz",
|
||||
"integrity": "sha512-L2nVr/1vei0xKRgO2tYVXtJYd09HTRjaZi418e85Q+QdbbqA8h7bBjfNyPPSsjnrOO4l4kaAo78c8SQUAdHvgA==",
|
||||
"version": "2.4.23",
|
||||
"resolved": "https://registry.npmmirror.com/@volar/source-map/-/source-map-2.4.23.tgz",
|
||||
"integrity": "sha512-Z1Uc8IB57Lm6k7q6KIDu/p+JWtf3xsXJqAX/5r18hYOTpJyBn0KXUR8oTJ4WFYOcDzWC9n3IflGgHowx6U6z9Q==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@volar/typescript": {
|
||||
"version": "2.4.22",
|
||||
"resolved": "https://registry.npmmirror.com/@volar/typescript/-/typescript-2.4.22.tgz",
|
||||
"integrity": "sha512-6ZczlJW1/GWTrNnkmZxJp4qyBt/SGVlcTuCWpI5zLrdPdCZsj66Aff9ZsfFaT3TyjG8zVYgBMYPuCm/eRkpcpQ==",
|
||||
"version": "2.4.23",
|
||||
"resolved": "https://registry.npmmirror.com/@volar/typescript/-/typescript-2.4.23.tgz",
|
||||
"integrity": "sha512-lAB5zJghWxVPqfcStmAP1ZqQacMpe90UrP5RJ3arDyrhy4aCUQqmxPPLB2PWDKugvylmO41ljK7vZ+t6INMTag==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@volar/language-core": "2.4.22",
|
||||
"@volar/language-core": "2.4.23",
|
||||
"path-browserify": "^1.0.1",
|
||||
"vscode-uri": "^3.0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/compiler-core": {
|
||||
"version": "3.5.18",
|
||||
"resolved": "https://registry.npmmirror.com/@vue/compiler-core/-/compiler-core-3.5.18.tgz",
|
||||
"integrity": "sha512-3slwjQrrV1TO8MoXgy3aynDQ7lslj5UqDxuHnrzHtpON5CBinhWjJETciPngpin/T3OuW3tXUf86tEurusnztw==",
|
||||
"version": "3.5.19",
|
||||
"resolved": "https://registry.npmmirror.com/@vue/compiler-core/-/compiler-core-3.5.19.tgz",
|
||||
"integrity": "sha512-/afpyvlkrSNYbPo94Qu8GtIOWS+g5TRdOvs6XZNw6pWQQmj5pBgSZvEPOIZlqWq0YvoUhDDQaQ2TnzuJdOV4hA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/parser": "^7.28.0",
|
||||
"@vue/shared": "3.5.18",
|
||||
"@babel/parser": "^7.28.3",
|
||||
"@vue/shared": "3.5.19",
|
||||
"entities": "^4.5.0",
|
||||
"estree-walker": "^2.0.2",
|
||||
"source-map-js": "^1.2.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/compiler-dom": {
|
||||
"version": "3.5.18",
|
||||
"resolved": "https://registry.npmmirror.com/@vue/compiler-dom/-/compiler-dom-3.5.18.tgz",
|
||||
"integrity": "sha512-RMbU6NTU70++B1JyVJbNbeFkK+A+Q7y9XKE2EM4NLGm2WFR8x9MbAtWxPPLdm0wUkuZv9trpwfSlL6tjdIa1+A==",
|
||||
"version": "3.5.19",
|
||||
"resolved": "https://registry.npmmirror.com/@vue/compiler-dom/-/compiler-dom-3.5.19.tgz",
|
||||
"integrity": "sha512-Drs6rPHQZx/pN9S6ml3Z3K/TWCIRPvzG2B/o5kFK9X0MNHt8/E+38tiRfojufrYBfA6FQUFB2qBBRXlcSXWtOA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vue/compiler-core": "3.5.18",
|
||||
"@vue/shared": "3.5.18"
|
||||
"@vue/compiler-core": "3.5.19",
|
||||
"@vue/shared": "3.5.19"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/compiler-sfc": {
|
||||
"version": "3.5.18",
|
||||
"resolved": "https://registry.npmmirror.com/@vue/compiler-sfc/-/compiler-sfc-3.5.18.tgz",
|
||||
"integrity": "sha512-5aBjvGqsWs+MoxswZPoTB9nSDb3dhd1x30xrrltKujlCxo48j8HGDNj3QPhF4VIS0VQDUrA1xUfp2hEa+FNyXA==",
|
||||
"version": "3.5.19",
|
||||
"resolved": "https://registry.npmmirror.com/@vue/compiler-sfc/-/compiler-sfc-3.5.19.tgz",
|
||||
"integrity": "sha512-YWCm1CYaJ+2RvNmhCwI7t3I3nU+hOrWGWMsn+Z/kmm1jy5iinnVtlmkiZwbLlbV1SRizX7vHsc0/bG5dj0zRTg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/parser": "^7.28.0",
|
||||
"@vue/compiler-core": "3.5.18",
|
||||
"@vue/compiler-dom": "3.5.18",
|
||||
"@vue/compiler-ssr": "3.5.18",
|
||||
"@vue/shared": "3.5.18",
|
||||
"@babel/parser": "^7.28.3",
|
||||
"@vue/compiler-core": "3.5.19",
|
||||
"@vue/compiler-dom": "3.5.19",
|
||||
"@vue/compiler-ssr": "3.5.19",
|
||||
"@vue/shared": "3.5.19",
|
||||
"estree-walker": "^2.0.2",
|
||||
"magic-string": "^0.30.17",
|
||||
"postcss": "^8.5.6",
|
||||
@@ -2472,13 +2472,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/compiler-ssr": {
|
||||
"version": "3.5.18",
|
||||
"resolved": "https://registry.npmmirror.com/@vue/compiler-ssr/-/compiler-ssr-3.5.18.tgz",
|
||||
"integrity": "sha512-xM16Ak7rSWHkM3m22NlmcdIM+K4BMyFARAfV9hYFl+SFuRzrZ3uGMNW05kA5pmeMa0X9X963Kgou7ufdbpOP9g==",
|
||||
"version": "3.5.19",
|
||||
"resolved": "https://registry.npmmirror.com/@vue/compiler-ssr/-/compiler-ssr-3.5.19.tgz",
|
||||
"integrity": "sha512-/wx0VZtkWOPdiQLWPeQeqpHWR/LuNC7bHfSX7OayBTtUy8wur6vT6EQIX6Et86aED6J+y8tTw43qo2uoqGg5sw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vue/compiler-dom": "3.5.18",
|
||||
"@vue/shared": "3.5.18"
|
||||
"@vue/compiler-dom": "3.5.19",
|
||||
"@vue/shared": "3.5.19"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/compiler-vue2": {
|
||||
@@ -2526,13 +2526,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/language-core": {
|
||||
"version": "3.0.5",
|
||||
"resolved": "https://registry.npmmirror.com/@vue/language-core/-/language-core-3.0.5.tgz",
|
||||
"integrity": "sha512-gCEjn9Ik7I/seHVNIEipOm8W+f3/kg60e8s1IgIkMYma2wu9ZGUTMv3mSL2bX+Md2L8fslceJ4SU8j1fgSRoiw==",
|
||||
"version": "3.0.6",
|
||||
"resolved": "https://registry.npmmirror.com/@vue/language-core/-/language-core-3.0.6.tgz",
|
||||
"integrity": "sha512-e2RRzYWm+qGm8apUHW1wA5RQxzNhkqbbKdbKhiDUcmMrNAZGyM8aTiL3UrTqkaFI5s7wJRGGrp4u3jgusuBp2A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@volar/language-core": "2.4.22",
|
||||
"@volar/language-core": "2.4.23",
|
||||
"@vue/compiler-dom": "^3.5.0",
|
||||
"@vue/compiler-vue2": "^2.7.16",
|
||||
"@vue/shared": "^3.5.0",
|
||||
@@ -2564,53 +2564,53 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/reactivity": {
|
||||
"version": "3.5.18",
|
||||
"resolved": "https://registry.npmmirror.com/@vue/reactivity/-/reactivity-3.5.18.tgz",
|
||||
"integrity": "sha512-x0vPO5Imw+3sChLM5Y+B6G1zPjwdOri9e8V21NnTnlEvkxatHEH5B5KEAJcjuzQ7BsjGrKtfzuQ5eQwXh8HXBg==",
|
||||
"version": "3.5.19",
|
||||
"resolved": "https://registry.npmmirror.com/@vue/reactivity/-/reactivity-3.5.19.tgz",
|
||||
"integrity": "sha512-4bueZg2qs5MSsK2dQk3sssV0cfvxb/QZntTC8v7J448GLgmfPkQ+27aDjlt40+XFqOwUq5yRxK5uQh14Fc9eVA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vue/shared": "3.5.18"
|
||||
"@vue/shared": "3.5.19"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/runtime-core": {
|
||||
"version": "3.5.18",
|
||||
"resolved": "https://registry.npmmirror.com/@vue/runtime-core/-/runtime-core-3.5.18.tgz",
|
||||
"integrity": "sha512-DUpHa1HpeOQEt6+3nheUfqVXRog2kivkXHUhoqJiKR33SO4x+a5uNOMkV487WPerQkL0vUuRvq/7JhRgLW3S+w==",
|
||||
"version": "3.5.19",
|
||||
"resolved": "https://registry.npmmirror.com/@vue/runtime-core/-/runtime-core-3.5.19.tgz",
|
||||
"integrity": "sha512-TaooCr8Hge1sWjLSyhdubnuofs3shhzZGfyD11gFolZrny76drPwBVQj28/z/4+msSFb18tOIg6VVVgf9/IbIA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vue/reactivity": "3.5.18",
|
||||
"@vue/shared": "3.5.18"
|
||||
"@vue/reactivity": "3.5.19",
|
||||
"@vue/shared": "3.5.19"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/runtime-dom": {
|
||||
"version": "3.5.18",
|
||||
"resolved": "https://registry.npmmirror.com/@vue/runtime-dom/-/runtime-dom-3.5.18.tgz",
|
||||
"integrity": "sha512-YwDj71iV05j4RnzZnZtGaXwPoUWeRsqinblgVJwR8XTXYZ9D5PbahHQgsbmzUvCWNF6x7siQ89HgnX5eWkr3mw==",
|
||||
"version": "3.5.19",
|
||||
"resolved": "https://registry.npmmirror.com/@vue/runtime-dom/-/runtime-dom-3.5.19.tgz",
|
||||
"integrity": "sha512-qmahqeok6ztuUTmV8lqd7N9ymbBzctNF885n8gL3xdCC1u2RnM/coX16Via0AiONQXUoYpxPojL3U1IsDgSWUQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vue/reactivity": "3.5.18",
|
||||
"@vue/runtime-core": "3.5.18",
|
||||
"@vue/shared": "3.5.18",
|
||||
"@vue/reactivity": "3.5.19",
|
||||
"@vue/runtime-core": "3.5.19",
|
||||
"@vue/shared": "3.5.19",
|
||||
"csstype": "^3.1.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/server-renderer": {
|
||||
"version": "3.5.18",
|
||||
"resolved": "https://registry.npmmirror.com/@vue/server-renderer/-/server-renderer-3.5.18.tgz",
|
||||
"integrity": "sha512-PvIHLUoWgSbDG7zLHqSqaCoZvHi6NNmfVFOqO+OnwvqMz/tqQr3FuGWS8ufluNddk7ZLBJYMrjcw1c6XzR12mA==",
|
||||
"version": "3.5.19",
|
||||
"resolved": "https://registry.npmmirror.com/@vue/server-renderer/-/server-renderer-3.5.19.tgz",
|
||||
"integrity": "sha512-ZJ/zV9SQuaIO+BEEVq/2a6fipyrSYfjKMU3267bPUk+oTx/hZq3RzV7VCh0Unlppt39Bvh6+NzxeopIFv4HJNg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vue/compiler-ssr": "3.5.18",
|
||||
"@vue/shared": "3.5.18"
|
||||
"@vue/compiler-ssr": "3.5.19",
|
||||
"@vue/shared": "3.5.19"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"vue": "3.5.18"
|
||||
"vue": "3.5.19"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/shared": {
|
||||
"version": "3.5.18",
|
||||
"resolved": "https://registry.npmmirror.com/@vue/shared/-/shared-3.5.18.tgz",
|
||||
"integrity": "sha512-cZy8Dq+uuIXbxCZpuLd2GJdeSO/lIzIspC2WtkqIpje5QyFbvLaI5wZtdUjLHjGZrlVX6GilejatWwVYYRc8tA==",
|
||||
"version": "3.5.19",
|
||||
"resolved": "https://registry.npmmirror.com/@vue/shared/-/shared-3.5.19.tgz",
|
||||
"integrity": "sha512-IhXCOn08wgKrLQxRFKKlSacWg4Goi1BolrdEeLYn6tgHjJNXVrWJ5nzoxZqNwl5p88aLlQ8LOaoMa3AYvaKJ/Q==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@wailsio/runtime": {
|
||||
@@ -2661,9 +2661,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/alien-signals": {
|
||||
"version": "2.0.6",
|
||||
"resolved": "https://registry.npmmirror.com/alien-signals/-/alien-signals-2.0.6.tgz",
|
||||
"integrity": "sha512-P3TxJSe31bUHBiblg59oU1PpaWPtmxF9GhJ/cB7OkgJ0qN/ifFSKUI25/v8ZhsT+lIG6ac8DpTOplXxORX6F3Q==",
|
||||
"version": "2.0.7",
|
||||
"resolved": "https://registry.npmmirror.com/alien-signals/-/alien-signals-2.0.7.tgz",
|
||||
"integrity": "sha512-wE7y3jmYeb0+h6mr5BOovuqhFv22O/MV9j5p0ndJsa7z1zJNPGQ4ph5pQk/kTTCWRC3xsA4SmtwmkzQO+7NCNg==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
@@ -3166,9 +3166,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/eslint": {
|
||||
"version": "9.33.0",
|
||||
"resolved": "https://registry.npmmirror.com/eslint/-/eslint-9.33.0.tgz",
|
||||
"integrity": "sha512-TS9bTNIryDzStCpJN93aC5VRSW3uTx9sClUn4B87pwiCaJh220otoI0X8mJKr+VcPtniMdN8GKjlwgWGUv5ZKA==",
|
||||
"version": "9.34.0",
|
||||
"resolved": "https://registry.npmmirror.com/eslint/-/eslint-9.34.0.tgz",
|
||||
"integrity": "sha512-RNCHRX5EwdrESy3Jc9o8ie8Bog+PeYvvSR8sDGoZxNFTvZ4dlxUB3WzQ3bQMztFrSRODGrLLj8g6OFuGY/aiQg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -3178,7 +3178,7 @@
|
||||
"@eslint/config-helpers": "^0.3.1",
|
||||
"@eslint/core": "^0.15.2",
|
||||
"@eslint/eslintrc": "^3.3.1",
|
||||
"@eslint/js": "9.33.0",
|
||||
"@eslint/js": "9.34.0",
|
||||
"@eslint/plugin-kit": "^0.3.5",
|
||||
"@humanfs/node": "^0.16.6",
|
||||
"@humanwhocodes/module-importer": "^1.0.1",
|
||||
@@ -4794,16 +4794,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/typescript-eslint": {
|
||||
"version": "8.39.1",
|
||||
"resolved": "https://registry.npmmirror.com/typescript-eslint/-/typescript-eslint-8.39.1.tgz",
|
||||
"integrity": "sha512-GDUv6/NDYngUlNvwaHM1RamYftxf782IyEDbdj3SeaIHHv8fNQVRC++fITT7kUJV/5rIA/tkoRSSskt6osEfqg==",
|
||||
"version": "8.40.0",
|
||||
"resolved": "https://registry.npmmirror.com/typescript-eslint/-/typescript-eslint-8.40.0.tgz",
|
||||
"integrity": "sha512-Xvd2l+ZmFDPEt4oj1QEXzA4A2uUK6opvKu3eGN9aGjB8au02lIVcLyi375w94hHyejTOmzIU77L8ol2sRg9n7Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/eslint-plugin": "8.39.1",
|
||||
"@typescript-eslint/parser": "8.39.1",
|
||||
"@typescript-eslint/typescript-estree": "8.39.1",
|
||||
"@typescript-eslint/utils": "8.39.1"
|
||||
"@typescript-eslint/eslint-plugin": "8.40.0",
|
||||
"@typescript-eslint/parser": "8.40.0",
|
||||
"@typescript-eslint/typescript-estree": "8.40.0",
|
||||
"@typescript-eslint/utils": "8.40.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
@@ -5103,14 +5103,14 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/vite": {
|
||||
"version": "7.1.2",
|
||||
"resolved": "https://registry.npmmirror.com/vite/-/vite-7.1.2.tgz",
|
||||
"integrity": "sha512-J0SQBPlQiEXAF7tajiH+rUooJPo0l8KQgyg4/aMunNtrOa7bwuZJsJbDWzeljqQpgftxuq5yNJxQ91O9ts29UQ==",
|
||||
"version": "7.1.3",
|
||||
"resolved": "https://registry.npmmirror.com/vite/-/vite-7.1.3.tgz",
|
||||
"integrity": "sha512-OOUi5zjkDxYrKhTV3V7iKsoS37VUM7v40+HuwEmcrsf11Cdx9y3DIr2Px6liIcZFwt3XSRpQvFpL3WVy7ApkGw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"esbuild": "^0.25.0",
|
||||
"fdir": "^6.4.6",
|
||||
"fdir": "^6.5.0",
|
||||
"picomatch": "^4.0.3",
|
||||
"postcss": "^8.5.6",
|
||||
"rollup": "^4.43.0",
|
||||
@@ -5178,11 +5178,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/vite/node_modules/fdir": {
|
||||
"version": "6.4.6",
|
||||
"resolved": "https://registry.npmmirror.com/fdir/-/fdir-6.4.6.tgz",
|
||||
"integrity": "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==",
|
||||
"version": "6.5.0",
|
||||
"resolved": "https://registry.npmmirror.com/fdir/-/fdir-6.5.0.tgz",
|
||||
"integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=12.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"picomatch": "^3 || ^4"
|
||||
},
|
||||
@@ -5213,16 +5216,16 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/vue": {
|
||||
"version": "3.5.18",
|
||||
"resolved": "https://registry.npmmirror.com/vue/-/vue-3.5.18.tgz",
|
||||
"integrity": "sha512-7W4Y4ZbMiQ3SEo+m9lnoNpV9xG7QVMLa+/0RFwwiAVkeYoyGXqWE85jabU4pllJNUzqfLShJ5YLptewhCWUgNA==",
|
||||
"version": "3.5.19",
|
||||
"resolved": "https://registry.npmmirror.com/vue/-/vue-3.5.19.tgz",
|
||||
"integrity": "sha512-ZRh0HTmw6KChRYWgN8Ox/wi7VhpuGlvMPrHjIsdRbzKNgECFLzy+dKL5z9yGaBSjCpmcfJCbh3I1tNSRmBz2tg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vue/compiler-dom": "3.5.18",
|
||||
"@vue/compiler-sfc": "3.5.18",
|
||||
"@vue/runtime-dom": "3.5.18",
|
||||
"@vue/server-renderer": "3.5.18",
|
||||
"@vue/shared": "3.5.18"
|
||||
"@vue/compiler-dom": "3.5.19",
|
||||
"@vue/compiler-sfc": "3.5.19",
|
||||
"@vue/runtime-dom": "3.5.19",
|
||||
"@vue/server-renderer": "3.5.19",
|
||||
"@vue/shared": "3.5.19"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": "*"
|
||||
@@ -5318,14 +5321,14 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/vue-tsc": {
|
||||
"version": "3.0.5",
|
||||
"resolved": "https://registry.npmmirror.com/vue-tsc/-/vue-tsc-3.0.5.tgz",
|
||||
"integrity": "sha512-PsTFN9lo1HJCrZw9NoqjYcAbYDXY0cOKyuW2E7naX5jcaVyWpqEsZOHN9Dws5890E8e5SDAD4L4Zam3dxG3/Cw==",
|
||||
"version": "3.0.6",
|
||||
"resolved": "https://registry.npmmirror.com/vue-tsc/-/vue-tsc-3.0.6.tgz",
|
||||
"integrity": "sha512-Tbs8Whd43R2e2nxez4WXPvvdjGbW24rOSgRhLOHXzWiT4pcP4G7KeWh0YCn18rF4bVwv7tggLLZ6MJnO6jXPBg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@volar/typescript": "2.4.22",
|
||||
"@vue/language-core": "3.0.5"
|
||||
"@volar/typescript": "2.4.23",
|
||||
"@vue/language-core": "3.0.6"
|
||||
},
|
||||
"bin": {
|
||||
"vue-tsc": "bin/vue-tsc.js"
|
||||
|
||||
@@ -56,26 +56,26 @@
|
||||
"prettier": "^3.6.2",
|
||||
"remarkable": "^2.0.1",
|
||||
"sass": "^1.90.0",
|
||||
"vue": "^3.5.18",
|
||||
"vue": "^3.5.19",
|
||||
"vue-i18n": "^11.1.11",
|
||||
"vue-pick-colors": "^1.8.0",
|
||||
"vue-router": "^4.5.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.33.0",
|
||||
"@eslint/js": "^9.34.0",
|
||||
"@lezer/generator": "^1.8.0",
|
||||
"@types/node": "^24.3.0",
|
||||
"@types/remarkable": "^2.0.8",
|
||||
"@vitejs/plugin-vue": "^6.0.1",
|
||||
"@wailsio/runtime": "latest",
|
||||
"eslint": "^9.33.0",
|
||||
"eslint": "^9.34.0",
|
||||
"eslint-plugin-vue": "^10.4.0",
|
||||
"globals": "^16.3.0",
|
||||
"typescript": "^5.9.2",
|
||||
"typescript-eslint": "^8.39.1",
|
||||
"typescript-eslint": "^8.40.0",
|
||||
"unplugin-vue-components": "^29.0.0",
|
||||
"vite": "^7.1.2",
|
||||
"vite": "^7.1.3",
|
||||
"vue-eslint-parser": "^10.2.0",
|
||||
"vue-tsc": "^3.0.5"
|
||||
"vue-tsc": "^3.0.6"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,6 +134,7 @@ export default {
|
||||
showInSystemTray: 'Show in System Tray',
|
||||
enableSystemTray: 'Enable System Tray',
|
||||
alwaysOnTop: 'Always on Top',
|
||||
enableWindowSnap: 'Enable Window Snapping',
|
||||
startup: 'Startup Settings',
|
||||
startAtLogin: 'Start at Login',
|
||||
dataStorage: 'Data Storage',
|
||||
|
||||
@@ -135,6 +135,7 @@ export default {
|
||||
showInSystemTray: '在系统托盘中显示',
|
||||
enableSystemTray: '启用系统托盘',
|
||||
alwaysOnTop: '窗口始终置顶',
|
||||
enableWindowSnap: '启用窗口吸附',
|
||||
startup: '启动设置',
|
||||
startAtLogin: '开机自启动',
|
||||
dataStorage: '数据存储',
|
||||
|
||||
@@ -62,7 +62,9 @@ const GENERAL_CONFIG_KEY_MAP: GeneralConfigKeyMap = {
|
||||
enableSystemTray: 'general.enableSystemTray',
|
||||
startAtLogin: 'general.startAtLogin',
|
||||
enableGlobalHotkey: 'general.enableGlobalHotkey',
|
||||
globalHotkey: 'general.globalHotkey'
|
||||
globalHotkey: 'general.globalHotkey',
|
||||
enableWindowSnap: 'general.enableWindowSnap',
|
||||
snapThreshold: 'general.snapThreshold'
|
||||
} as const;
|
||||
|
||||
const EDITING_CONFIG_KEY_MAP: EditingConfigKeyMap = {
|
||||
@@ -176,7 +178,9 @@ const DEFAULT_CONFIG: AppConfig = {
|
||||
alt: true,
|
||||
win: false,
|
||||
key: 'X'
|
||||
}
|
||||
},
|
||||
enableWindowSnap: true,
|
||||
snapThreshold: 15
|
||||
},
|
||||
editing: {
|
||||
fontSize: CONFIG_LIMITS.fontSize.default,
|
||||
@@ -520,6 +524,10 @@ export const useConfigStore = defineStore('config', () => {
|
||||
// 再调用系统设置API
|
||||
await StartupService.SetEnabled(value);
|
||||
},
|
||||
|
||||
// 窗口吸附配置相关方法
|
||||
setEnableWindowSnap: async (value: boolean) => await updateGeneralConfig('enableWindowSnap', value),
|
||||
setSnapThreshold: async (value: number) => await updateGeneralConfig('snapThreshold', value),
|
||||
|
||||
// 更新配置相关方法
|
||||
setAutoUpdate: async (value: boolean) => await updateUpdatesConfig('autoUpdate', value),
|
||||
|
||||
@@ -159,6 +159,12 @@ const enableSystemTray = computed({
|
||||
set: (value: boolean) => configStore.setEnableSystemTray(value)
|
||||
});
|
||||
|
||||
// 计算属性 - 启用窗口吸附
|
||||
const enableWindowSnap = computed({
|
||||
get: () => configStore.config.general.enableWindowSnap,
|
||||
set: (value: boolean) => configStore.setEnableWindowSnap(value)
|
||||
});
|
||||
|
||||
// 计算属性 - 开机启动
|
||||
const startAtLogin = computed({
|
||||
get: () => configStore.config.general.startAtLogin,
|
||||
@@ -334,6 +340,9 @@ onUnmounted(() => {
|
||||
<SettingItem :title="t('settings.enableSystemTray')">
|
||||
<ToggleSwitch v-model="enableSystemTray"/>
|
||||
</SettingItem>
|
||||
<SettingItem :title="t('settings.enableWindowSnap')">
|
||||
<ToggleSwitch v-model="enableWindowSnap"/>
|
||||
</SettingItem>
|
||||
</SettingSection>
|
||||
|
||||
<SettingSection :title="t('settings.startup')">
|
||||
|
||||
2
go.mod
2
go.mod
@@ -67,7 +67,7 @@ require (
|
||||
github.com/samber/lo v1.51.0 // indirect
|
||||
github.com/sergi/go-diff v1.4.0 // indirect
|
||||
github.com/skeema/knownhosts v1.3.1 // indirect
|
||||
github.com/ulikunitz/xz v0.5.12 // indirect
|
||||
github.com/ulikunitz/xz v0.5.14 // indirect
|
||||
github.com/wailsapp/go-webview2 v1.0.21 // indirect
|
||||
github.com/wailsapp/mimetype v1.4.1 // indirect
|
||||
github.com/xanzy/go-gitlab v0.115.0 // indirect
|
||||
|
||||
4
go.sum
4
go.sum
@@ -158,8 +158,8 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/ulikunitz/xz v0.5.12 h1:37Nm15o69RwBkXM0J6A5OlE67RZTfzUxTj8fB3dfcsc=
|
||||
github.com/ulikunitz/xz v0.5.12/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
||||
github.com/ulikunitz/xz v0.5.14 h1:uv/0Bq533iFdnMHZdRBTOlaNMdb1+ZxXIlHDZHIHcvg=
|
||||
github.com/ulikunitz/xz v0.5.14/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
||||
github.com/wailsapp/go-webview2 v1.0.21 h1:k3dtoZU4KCoN/AEIbWiPln3P2661GtA2oEgA2Pb+maA=
|
||||
github.com/wailsapp/go-webview2 v1.0.21/go.mod h1:qJmWAmAmaniuKGZPWwne+uor3AHMB5PFhqiK0Bbj8kc=
|
||||
github.com/wailsapp/mimetype v1.4.1 h1:pQN9ycO7uo4vsUUuPeHEYoUkLVkaRntMnHJxVwYhwHs=
|
||||
|
||||
@@ -68,6 +68,10 @@ type GeneralConfig struct {
|
||||
EnableSystemTray bool `json:"enableSystemTray"` // 是否启用系统托盘
|
||||
StartAtLogin bool `json:"startAtLogin"` // 开机启动设置
|
||||
|
||||
// 窗口吸附设置
|
||||
EnableWindowSnap bool `json:"enableWindowSnap"` // 是否启用窗口吸附功能
|
||||
SnapThreshold int `json:"snapThreshold"` // 吸附距离阈值(像素)
|
||||
|
||||
// 全局热键设置
|
||||
EnableGlobalHotkey bool `json:"enableGlobalHotkey"` // 是否启用全局热键
|
||||
GlobalHotkey HotkeyCombo `json:"globalHotkey"` // 全局热键组合
|
||||
@@ -145,6 +149,8 @@ func NewDefaultAppConfig() *AppConfig {
|
||||
DataPath: dataDir,
|
||||
EnableSystemTray: true,
|
||||
StartAtLogin: false,
|
||||
EnableWindowSnap: true, // 默认启用窗口吸附
|
||||
SnapThreshold: 15, // 默认15像素的吸附阈值
|
||||
EnableGlobalHotkey: false,
|
||||
GlobalHotkey: HotkeyCombo{
|
||||
Ctrl: false,
|
||||
|
||||
41
internal/models/window.go
Normal file
41
internal/models/window.go
Normal file
@@ -0,0 +1,41 @@
|
||||
package models
|
||||
|
||||
import "time"
|
||||
|
||||
// SnapEdge 表示吸附的边缘类型
|
||||
type SnapEdge int
|
||||
|
||||
const (
|
||||
SnapEdgeNone SnapEdge = iota // 未吸附
|
||||
SnapEdgeTop // 吸附到上边缘
|
||||
SnapEdgeRight // 吸附到右边缘
|
||||
SnapEdgeBottom // 吸附到下边缘
|
||||
SnapEdgeLeft // 吸附到左边缘
|
||||
SnapEdgeTopRight // 吸附到右上角
|
||||
SnapEdgeBottomRight // 吸附到右下角
|
||||
SnapEdgeBottomLeft // 吸附到左下角
|
||||
SnapEdgeTopLeft // 吸附到左上角
|
||||
)
|
||||
|
||||
// WindowPosition 窗口位置
|
||||
type WindowPosition struct {
|
||||
X int `json:"x"` // X坐标
|
||||
Y int `json:"y"` // Y坐标
|
||||
}
|
||||
|
||||
// SnapPosition 表示吸附的相对位置
|
||||
type SnapPosition struct {
|
||||
X int `json:"x"` // X轴相对偏移
|
||||
Y int `json:"y"` // Y轴相对偏移
|
||||
}
|
||||
|
||||
// WindowInfo 窗口信息
|
||||
type WindowInfo struct {
|
||||
DocumentID int64 `json:"documentID"` // 文档ID
|
||||
Title string `json:"title"` // 窗口标题
|
||||
IsSnapped bool `json:"isSnapped"` // 是否处于吸附状态
|
||||
SnapOffset SnapPosition `json:"snapOffset"` // 与主窗口的相对位置偏移
|
||||
SnapEdge SnapEdge `json:"snapEdge"` // 吸附的边缘类型
|
||||
LastPos WindowPosition `json:"lastPos"` // 上一次记录的窗口位置
|
||||
MoveTime time.Time `json:"moveTime"` // 上次移动时间,用于判断移动速度
|
||||
}
|
||||
@@ -24,6 +24,8 @@ const (
|
||||
ConfigChangeTypeDataPath ConfigChangeType = "datapath"
|
||||
// ConfigChangeTypeBackup 备份配置变更
|
||||
ConfigChangeTypeBackup ConfigChangeType = "backup"
|
||||
// ConfigChangeTypeWindowSnap 窗口吸附配置变更
|
||||
ConfigChangeTypeWindowSnap ConfigChangeType = "windowsnap"
|
||||
)
|
||||
|
||||
// ConfigChangeCallback 配置变更回调函数类型
|
||||
@@ -470,6 +472,29 @@ func CreateBackupConfigListener(name string, callback func(config *models.GitBac
|
||||
}
|
||||
}
|
||||
|
||||
// CreateWindowSnapConfigListener 创建窗口吸附配置监听器
|
||||
func CreateWindowSnapConfigListener(name string, callback func(enabled bool, threshold int) error) *ConfigListener {
|
||||
return &ConfigListener{
|
||||
Name: name,
|
||||
ChangeType: ConfigChangeTypeWindowSnap,
|
||||
Callback: func(changeType ConfigChangeType, oldConfig, newConfig *models.AppConfig) error {
|
||||
if newConfig == nil {
|
||||
defaultConfig := models.NewDefaultAppConfig()
|
||||
return callback(defaultConfig.General.EnableWindowSnap, defaultConfig.General.SnapThreshold)
|
||||
}
|
||||
return callback(newConfig.General.EnableWindowSnap, newConfig.General.SnapThreshold)
|
||||
},
|
||||
DebounceDelay: 200 * time.Millisecond,
|
||||
GetConfigFunc: func(k *koanf.Koanf) *models.AppConfig {
|
||||
var config models.AppConfig
|
||||
if err := k.Unmarshal("", &config); err != nil {
|
||||
return nil
|
||||
}
|
||||
return &config
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// ServiceShutdown 关闭服务
|
||||
func (cns *ConfigNotificationService) ServiceShutdown() error {
|
||||
cns.Cleanup()
|
||||
|
||||
@@ -308,6 +308,16 @@ func (cs *ConfigService) SetBackupConfigChangeCallback(callback func(config *mod
|
||||
return cs.notificationService.RegisterListener(backupListener)
|
||||
}
|
||||
|
||||
// SetWindowSnapConfigChangeCallback 设置窗口吸附配置变更回调
|
||||
func (cs *ConfigService) SetWindowSnapConfigChangeCallback(callback func(enabled bool, threshold int) error) error {
|
||||
cs.mu.Lock()
|
||||
defer cs.mu.Unlock()
|
||||
|
||||
// 创建窗口吸附配置监听器并注册
|
||||
windowSnapListener := CreateWindowSnapConfigListener("DefaultWindowSnapConfigListener", callback)
|
||||
return cs.notificationService.RegisterListener(windowSnapListener)
|
||||
}
|
||||
|
||||
// ServiceShutdown 关闭服务
|
||||
func (cs *ConfigService) ServiceShutdown() error {
|
||||
cs.stopWatching()
|
||||
|
||||
@@ -13,6 +13,7 @@ type ServiceManager struct {
|
||||
databaseService *DatabaseService
|
||||
documentService *DocumentService
|
||||
windowService *WindowService
|
||||
windowSnapService *WindowSnapService
|
||||
migrationService *MigrationService
|
||||
systemService *SystemService
|
||||
hotkeyService *HotkeyService
|
||||
@@ -48,6 +49,12 @@ func NewServiceManager() *ServiceManager {
|
||||
// 初始化窗口服务
|
||||
windowService := NewWindowService(logger, documentService)
|
||||
|
||||
// 初始化窗口吸附服务
|
||||
windowSnapService := NewWindowSnapService(logger, configService)
|
||||
|
||||
// 将吸附服务与窗口服务关联
|
||||
windowService.SetWindowSnapService(windowSnapService)
|
||||
|
||||
// 初始化系统服务
|
||||
systemService := NewSystemService(logger)
|
||||
|
||||
@@ -108,11 +115,20 @@ func NewServiceManager() *ServiceManager {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// 设置窗口吸附配置变更回调
|
||||
err = configService.SetWindowSnapConfigChangeCallback(func(enabled bool, threshold int) error {
|
||||
return windowSnapService.OnWindowSnapConfigChanged(enabled, threshold)
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return &ServiceManager{
|
||||
configService: configService,
|
||||
databaseService: databaseService,
|
||||
documentService: documentService,
|
||||
windowService: windowService,
|
||||
windowSnapService: windowSnapService,
|
||||
migrationService: migrationService,
|
||||
systemService: systemService,
|
||||
hotkeyService: hotkeyService,
|
||||
@@ -136,6 +152,7 @@ func (sm *ServiceManager) GetServices() []application.Service {
|
||||
application.NewService(sm.databaseService),
|
||||
application.NewService(sm.documentService),
|
||||
application.NewService(sm.windowService),
|
||||
application.NewService(sm.windowSnapService),
|
||||
application.NewService(sm.keyBindingService),
|
||||
application.NewService(sm.extensionService),
|
||||
application.NewService(sm.migrationService),
|
||||
@@ -221,3 +238,8 @@ func (sm *ServiceManager) GetDocumentService() *DocumentService {
|
||||
func (sm *ServiceManager) GetThemeService() *ThemeService {
|
||||
return sm.themeService
|
||||
}
|
||||
|
||||
// GetWindowSnapService 获取窗口吸附服务实例
|
||||
func (sm *ServiceManager) GetWindowSnapService() *WindowSnapService {
|
||||
return sm.windowSnapService
|
||||
}
|
||||
|
||||
@@ -9,14 +9,14 @@ import (
|
||||
"github.com/wailsapp/wails/v3/pkg/services/log"
|
||||
)
|
||||
|
||||
// WindowInfo 窗口信息
|
||||
// WindowInfo 窗口信息(简化版)
|
||||
type WindowInfo struct {
|
||||
Window *application.WebviewWindow
|
||||
DocumentID int64
|
||||
Title string
|
||||
}
|
||||
|
||||
// WindowService 窗口管理服务
|
||||
// WindowService 窗口管理服务(专注于窗口生命周期管理)
|
||||
type WindowService struct {
|
||||
logger *log.LogService
|
||||
documentService *DocumentService
|
||||
@@ -24,6 +24,9 @@ type WindowService struct {
|
||||
mainWindow *application.WebviewWindow
|
||||
windows map[int64]*WindowInfo // documentID -> WindowInfo
|
||||
mu sync.RWMutex
|
||||
|
||||
// 吸附服务引用
|
||||
windowSnapService *WindowSnapService
|
||||
}
|
||||
|
||||
// NewWindowService 创建新的窗口服务实例
|
||||
@@ -39,10 +42,20 @@ func NewWindowService(logger *log.LogService, documentService *DocumentService)
|
||||
}
|
||||
}
|
||||
|
||||
// SetWindowSnapService 设置窗口吸附服务引用
|
||||
func (ws *WindowService) SetWindowSnapService(snapService *WindowSnapService) {
|
||||
ws.windowSnapService = snapService
|
||||
}
|
||||
|
||||
// SetAppReferences 设置应用和主窗口引用
|
||||
func (ws *WindowService) SetAppReferences(app *application.App, mainWindow *application.WebviewWindow) {
|
||||
ws.app = app
|
||||
ws.mainWindow = mainWindow
|
||||
|
||||
// 如果吸附服务已设置,也为它设置引用
|
||||
if ws.windowSnapService != nil {
|
||||
ws.windowSnapService.SetAppReferences(app, mainWindow)
|
||||
}
|
||||
}
|
||||
|
||||
// OpenDocumentWindow 为指定文档ID打开新窗口
|
||||
@@ -101,9 +114,14 @@ func (ws *WindowService) OpenDocumentWindow(documentID int64) error {
|
||||
}
|
||||
ws.windows[documentID] = windowInfo
|
||||
|
||||
// 注册窗口关闭事件
|
||||
// 注册窗口事件
|
||||
ws.registerWindowEvents(newWindow, documentID)
|
||||
|
||||
// 向吸附服务注册新窗口
|
||||
if ws.windowSnapService != nil {
|
||||
ws.windowSnapService.RegisterWindow(documentID, newWindow, doc.Title)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -119,12 +137,17 @@ func (ws *WindowService) registerWindowEvents(window *application.WebviewWindow,
|
||||
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)
|
||||
}
|
||||
|
||||
// 从吸附服务中取消注册
|
||||
if ws.windowSnapService != nil {
|
||||
ws.windowSnapService.UnregisterWindow(documentID)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// GetOpenWindows 获取所有打开的窗口信息
|
||||
@@ -147,3 +170,18 @@ func (ws *WindowService) IsDocumentWindowOpen(documentID int64) bool {
|
||||
_, exists := ws.windows[documentID]
|
||||
return exists
|
||||
}
|
||||
|
||||
// ServiceShutdown 实现服务关闭接口
|
||||
func (ws *WindowService) ServiceShutdown() error {
|
||||
// 关闭所有窗口
|
||||
ws.mu.Lock()
|
||||
defer ws.mu.Unlock()
|
||||
|
||||
for documentID := range ws.windows {
|
||||
if ws.windowSnapService != nil {
|
||||
ws.windowSnapService.UnregisterWindow(documentID)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
603
internal/services/window_snap_service.go
Normal file
603
internal/services/window_snap_service.go
Normal file
@@ -0,0 +1,603 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"context"
|
||||
"math"
|
||||
"sync"
|
||||
"time"
|
||||
"voidraft/internal/models"
|
||||
|
||||
"github.com/wailsapp/wails/v3/pkg/application"
|
||||
"github.com/wailsapp/wails/v3/pkg/services/log"
|
||||
)
|
||||
|
||||
// WindowSnapService 窗口吸附服务
|
||||
type WindowSnapService struct {
|
||||
logger *log.LogService
|
||||
configService *ConfigService
|
||||
app *application.App
|
||||
mainWindow *application.WebviewWindow
|
||||
mu sync.RWMutex
|
||||
|
||||
// 吸附配置
|
||||
snapThreshold int // 吸附触发的阈值距离(像素)
|
||||
snapEnabled bool // 是否启用窗口吸附功能
|
||||
|
||||
// 定时器控制
|
||||
snapTicker *time.Ticker
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
|
||||
// 性能优化相关
|
||||
lastMainWindowPos models.WindowPosition // 缓存主窗口上次位置
|
||||
changedWindows map[int64]bool // 记录哪些窗口发生了变化
|
||||
skipFrames int // 跳帧计数器,用于降低检测频率
|
||||
|
||||
// 监听的窗口列表
|
||||
managedWindows map[int64]*SnapWindowInfo // documentID -> SnapWindowInfo
|
||||
}
|
||||
|
||||
// SnapWindowInfo 吸附窗口信息
|
||||
type SnapWindowInfo struct {
|
||||
Window *application.WebviewWindow
|
||||
DocumentID int64
|
||||
Title string
|
||||
IsSnapped bool // 是否处于吸附状态
|
||||
SnapOffset models.SnapPosition // 与主窗口的相对位置偏移
|
||||
SnapEdge models.SnapEdge // 吸附的边缘类型
|
||||
LastPos models.WindowPosition // 上一次记录的窗口位置
|
||||
MoveTime time.Time // 上次移动时间,用于判断移动速度
|
||||
}
|
||||
|
||||
// NewWindowSnapService 创建新的窗口吸附服务实例
|
||||
func NewWindowSnapService(logger *log.LogService, configService *ConfigService) *WindowSnapService {
|
||||
if logger == nil {
|
||||
logger = log.New()
|
||||
}
|
||||
|
||||
// 从配置获取窗口吸附设置
|
||||
config, err := configService.GetConfig()
|
||||
snapEnabled := true // 默认启用
|
||||
snapThreshold := 15 // 默认阈值
|
||||
|
||||
if err == nil {
|
||||
snapEnabled = config.General.EnableWindowSnap
|
||||
snapThreshold = config.General.SnapThreshold
|
||||
}
|
||||
|
||||
return &WindowSnapService{
|
||||
logger: logger,
|
||||
configService: configService,
|
||||
snapThreshold: snapThreshold,
|
||||
snapEnabled: snapEnabled,
|
||||
managedWindows: make(map[int64]*SnapWindowInfo),
|
||||
changedWindows: make(map[int64]bool),
|
||||
skipFrames: 0,
|
||||
}
|
||||
}
|
||||
|
||||
// SetAppReferences 设置应用和主窗口引用
|
||||
func (wss *WindowSnapService) SetAppReferences(app *application.App, mainWindow *application.WebviewWindow) {
|
||||
wss.app = app
|
||||
wss.mainWindow = mainWindow
|
||||
|
||||
// 初始化上下文,用于控制goroutine的生命周期
|
||||
wss.ctx, wss.cancel = context.WithCancel(context.Background())
|
||||
|
||||
// 启动窗口吸附监听器
|
||||
wss.StartWindowSnapMonitor()
|
||||
}
|
||||
|
||||
// RegisterWindow 注册需要吸附管理的窗口
|
||||
func (wss *WindowSnapService) RegisterWindow(documentID int64, window *application.WebviewWindow, title string) {
|
||||
wss.mu.Lock()
|
||||
defer wss.mu.Unlock()
|
||||
|
||||
wss.managedWindows[documentID] = &SnapWindowInfo{
|
||||
Window: window,
|
||||
DocumentID: documentID,
|
||||
Title: title,
|
||||
IsSnapped: false,
|
||||
SnapOffset: models.SnapPosition{X: 0, Y: 0},
|
||||
SnapEdge: models.SnapEdgeNone,
|
||||
LastPos: models.WindowPosition{X: 0, Y: 0},
|
||||
MoveTime: time.Now(),
|
||||
}
|
||||
}
|
||||
|
||||
// UnregisterWindow 取消注册窗口
|
||||
func (wss *WindowSnapService) UnregisterWindow(documentID int64) {
|
||||
wss.mu.Lock()
|
||||
defer wss.mu.Unlock()
|
||||
|
||||
delete(wss.managedWindows, documentID)
|
||||
delete(wss.changedWindows, documentID)
|
||||
}
|
||||
|
||||
// SetSnapEnabled 设置是否启用窗口吸附
|
||||
func (wss *WindowSnapService) SetSnapEnabled(enabled bool) {
|
||||
wss.mu.Lock()
|
||||
defer wss.mu.Unlock()
|
||||
|
||||
if wss.snapEnabled == enabled {
|
||||
return
|
||||
}
|
||||
|
||||
wss.snapEnabled = enabled
|
||||
|
||||
// 如果禁用吸附,解除所有吸附窗口
|
||||
if !enabled {
|
||||
for _, windowInfo := range wss.managedWindows {
|
||||
if windowInfo.IsSnapped {
|
||||
windowInfo.IsSnapped = false
|
||||
windowInfo.SnapEdge = models.SnapEdgeNone
|
||||
}
|
||||
}
|
||||
// 停止定时器
|
||||
if wss.snapTicker != nil {
|
||||
wss.snapTicker.Stop()
|
||||
wss.snapTicker = nil
|
||||
}
|
||||
} else if wss.snapTicker == nil && wss.app != nil {
|
||||
// 重新启动定时器
|
||||
wss.StartWindowSnapMonitor()
|
||||
}
|
||||
}
|
||||
|
||||
// SetSnapThreshold 设置窗口吸附阈值
|
||||
func (wss *WindowSnapService) SetSnapThreshold(threshold int) {
|
||||
wss.mu.Lock()
|
||||
defer wss.mu.Unlock()
|
||||
|
||||
if threshold <= 0 || wss.snapThreshold == threshold {
|
||||
return
|
||||
}
|
||||
|
||||
wss.snapThreshold = threshold
|
||||
}
|
||||
|
||||
// OnWindowSnapConfigChanged 处理窗口吸附配置变更
|
||||
func (wss *WindowSnapService) OnWindowSnapConfigChanged(enabled bool, threshold int) error {
|
||||
wss.SetSnapEnabled(enabled)
|
||||
wss.SetSnapThreshold(threshold)
|
||||
return nil
|
||||
}
|
||||
|
||||
// StartWindowSnapMonitor 启动窗口吸附监听器
|
||||
func (wss *WindowSnapService) StartWindowSnapMonitor() {
|
||||
// 如果定时器已存在,先停止它
|
||||
if wss.snapTicker != nil {
|
||||
wss.snapTicker.Stop()
|
||||
}
|
||||
|
||||
// 只有在吸附功能启用时才启动监听器
|
||||
if !wss.snapEnabled {
|
||||
return
|
||||
}
|
||||
|
||||
// 创建新的定时器,20fps 以优化性能
|
||||
wss.snapTicker = time.NewTicker(50 * time.Millisecond)
|
||||
|
||||
// 启动goroutine持续监听窗口位置
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case <-wss.snapTicker.C:
|
||||
wss.checkAndApplySnapping()
|
||||
case <-wss.ctx.Done():
|
||||
// 上下文取消时停止goroutine
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// checkAndApplySnapping 检测并应用窗口吸附(性能优化版本)
|
||||
func (wss *WindowSnapService) checkAndApplySnapping() {
|
||||
if !wss.snapEnabled {
|
||||
return
|
||||
}
|
||||
|
||||
// 性能优化:每3帧执行一次完整检测,其他时间只处理变化的窗口
|
||||
wss.skipFrames++
|
||||
fullCheck := wss.skipFrames%3 == 0
|
||||
|
||||
wss.mu.Lock()
|
||||
defer wss.mu.Unlock()
|
||||
|
||||
// 检查主窗口是否存在且可见
|
||||
if wss.mainWindow == nil || !wss.isMainWindowAvailable() {
|
||||
// 主窗口不可用,解除所有吸附
|
||||
for _, windowInfo := range wss.managedWindows {
|
||||
if windowInfo.IsSnapped {
|
||||
windowInfo.IsSnapped = false
|
||||
windowInfo.SnapEdge = models.SnapEdgeNone
|
||||
}
|
||||
}
|
||||
// 清空变化记录
|
||||
wss.changedWindows = make(map[int64]bool)
|
||||
return
|
||||
}
|
||||
|
||||
mainPos, _ := wss.getWindowPosition(wss.mainWindow)
|
||||
|
||||
// 检查主窗口是否移动了
|
||||
mainWindowMoved := mainPos.X != wss.lastMainWindowPos.X || mainPos.Y != wss.lastMainWindowPos.Y
|
||||
if mainWindowMoved {
|
||||
wss.lastMainWindowPos = mainPos
|
||||
// 主窗口移动了,标记所有吸附的窗口为变化
|
||||
for documentID, windowInfo := range wss.managedWindows {
|
||||
if windowInfo.IsSnapped {
|
||||
wss.changedWindows[documentID] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for documentID, windowInfo := range wss.managedWindows {
|
||||
currentPos, _ := wss.getWindowPosition(windowInfo.Window)
|
||||
|
||||
// 检查窗口是否移动了
|
||||
hasMoved := currentPos.X != windowInfo.LastPos.X || currentPos.Y != windowInfo.LastPos.Y
|
||||
if hasMoved {
|
||||
windowInfo.MoveTime = time.Now()
|
||||
windowInfo.LastPos = currentPos
|
||||
wss.changedWindows[documentID] = true
|
||||
}
|
||||
|
||||
// 性能优化:只处理变化的窗口或进行完整检查时
|
||||
if !fullCheck && !wss.changedWindows[documentID] {
|
||||
continue
|
||||
}
|
||||
|
||||
if windowInfo.IsSnapped {
|
||||
// 窗口已吸附,检查是否需要更新位置或解除吸附
|
||||
wss.handleSnappedWindow(windowInfo, mainPos, currentPos)
|
||||
} else {
|
||||
// 窗口未吸附,检查是否应该吸附
|
||||
wss.handleUnsnappedWindow(windowInfo)
|
||||
}
|
||||
|
||||
// 处理完成后清除变化标记
|
||||
delete(wss.changedWindows, documentID)
|
||||
}
|
||||
}
|
||||
|
||||
// isMainWindowAvailable 检查主窗口是否可用
|
||||
func (wss *WindowSnapService) isMainWindowAvailable() bool {
|
||||
if wss.mainWindow == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// 检查主窗口是否可见和正常状态
|
||||
return wss.mainWindow.IsVisible()
|
||||
}
|
||||
|
||||
// handleSnappedWindow 处理已吸附的窗口
|
||||
func (wss *WindowSnapService) handleSnappedWindow(windowInfo *SnapWindowInfo, mainPos models.WindowPosition, currentPos models.WindowPosition) {
|
||||
// 计算预期位置基于主窗口的新位置
|
||||
expectedX := mainPos.X + windowInfo.SnapOffset.X
|
||||
expectedY := mainPos.Y + windowInfo.SnapOffset.Y
|
||||
|
||||
// 计算当前位置与预期位置的距离
|
||||
distanceX := math.Abs(float64(currentPos.X - expectedX))
|
||||
distanceY := math.Abs(float64(currentPos.Y - expectedY))
|
||||
maxDistance := math.Max(distanceX, distanceY)
|
||||
|
||||
// 检测是否为用户主动拖拽:如果窗口移动幅度超过阈值且是最近移动的
|
||||
userDragThreshold := float64(wss.snapThreshold)
|
||||
isUserDrag := maxDistance > userDragThreshold && time.Since(windowInfo.MoveTime) < 100*time.Millisecond
|
||||
|
||||
if isUserDrag {
|
||||
// 用户主动拖拽,立即解除吸附
|
||||
windowInfo.IsSnapped = false
|
||||
windowInfo.SnapEdge = models.SnapEdgeNone
|
||||
return
|
||||
}
|
||||
|
||||
// 对于主窗口移动导致的位置变化,立即跟随且不解除吸附
|
||||
if maxDistance > 0 {
|
||||
// 直接调整到预期位置,不使用平滑移动以提高响应速度
|
||||
windowInfo.Window.SetPosition(expectedX, expectedY)
|
||||
windowInfo.LastPos = models.WindowPosition{X: expectedX, Y: expectedY}
|
||||
}
|
||||
}
|
||||
|
||||
// handleUnsnappedWindow 处理未吸附的窗口
|
||||
func (wss *WindowSnapService) handleUnsnappedWindow(windowInfo *SnapWindowInfo) {
|
||||
// 检查是否应该吸附到主窗口
|
||||
should, snapEdge := wss.shouldSnapToMainWindow(windowInfo)
|
||||
if should {
|
||||
// 获取主窗口位置用于计算偏移量
|
||||
mainPos, _ := wss.getWindowPosition(wss.mainWindow)
|
||||
|
||||
// 设置吸附状态
|
||||
windowInfo.IsSnapped = true
|
||||
windowInfo.SnapEdge = snapEdge
|
||||
|
||||
// 执行即时吸附,产生明显的吸附效果
|
||||
wss.snapWindowToMainWindow(windowInfo, snapEdge)
|
||||
|
||||
// 重新获取吸附后的位置来计算偏移量
|
||||
newPos, _ := wss.getWindowPosition(windowInfo.Window)
|
||||
windowInfo.SnapOffset.X = newPos.X - mainPos.X
|
||||
windowInfo.SnapOffset.Y = newPos.Y - mainPos.Y
|
||||
windowInfo.LastPos = newPos
|
||||
}
|
||||
}
|
||||
|
||||
// getWindowPosition 获取窗口的位置
|
||||
func (wss *WindowSnapService) getWindowPosition(window *application.WebviewWindow) (models.WindowPosition, bool) {
|
||||
x, y := window.Position()
|
||||
return models.WindowPosition{X: x, Y: y}, true
|
||||
}
|
||||
|
||||
// shouldSnapToMainWindow 检查窗口是否应该吸附到主窗口(支持角落吸附)
|
||||
func (wss *WindowSnapService) shouldSnapToMainWindow(windowInfo *SnapWindowInfo) (bool, models.SnapEdge) {
|
||||
// 降低防抖时间,提高吸附响应速度
|
||||
if time.Since(windowInfo.MoveTime) < 50*time.Millisecond {
|
||||
return false, models.SnapEdgeNone
|
||||
}
|
||||
|
||||
// 获取两个窗口的位置
|
||||
mainPos, _ := wss.getWindowPosition(wss.mainWindow)
|
||||
windowPos, _ := wss.getWindowPosition(windowInfo.Window)
|
||||
|
||||
// 获取主窗口尺寸
|
||||
mainWidth, mainHeight := wss.mainWindow.Size()
|
||||
|
||||
// 获取子窗口尺寸
|
||||
windowWidth, windowHeight := windowInfo.Window.Size()
|
||||
|
||||
// 计算各个边缘的距离
|
||||
threshold := float64(wss.snapThreshold)
|
||||
cornerThreshold := threshold * 1.5 // 角落吸附需要更大的阈值
|
||||
|
||||
// 主窗口的四个边界
|
||||
mainLeft := mainPos.X
|
||||
mainTop := mainPos.Y
|
||||
mainRight := mainPos.X + mainWidth
|
||||
mainBottom := mainPos.Y + mainHeight
|
||||
|
||||
// 子窗口的四个边界
|
||||
windowLeft := windowPos.X
|
||||
windowTop := windowPos.Y
|
||||
windowRight := windowPos.X + windowWidth
|
||||
windowBottom := windowPos.Y + windowHeight
|
||||
|
||||
// 存储每个边缘的吸附信息
|
||||
type snapCandidate struct {
|
||||
edge models.SnapEdge
|
||||
distance float64
|
||||
overlap float64 // 重叠度,用于优先级判断
|
||||
isCorner bool // 是否为角落吸附
|
||||
}
|
||||
|
||||
var candidates []snapCandidate
|
||||
|
||||
// ==== 检查角落吸附(优先级最高) ====
|
||||
|
||||
// 1. 右上角 (SnapEdgeTopRight)
|
||||
distToTopRight := math.Sqrt(math.Pow(float64(mainRight-windowLeft), 2) + math.Pow(float64(mainTop-windowBottom), 2))
|
||||
if distToTopRight <= cornerThreshold {
|
||||
// 检查是否接近右上角区域
|
||||
horizDist := math.Abs(float64(mainRight - windowLeft))
|
||||
vertDist := math.Abs(float64(mainTop - windowBottom))
|
||||
if horizDist <= cornerThreshold && vertDist <= cornerThreshold {
|
||||
candidates = append(candidates, snapCandidate{models.SnapEdgeTopRight, distToTopRight, 100, true})
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 右下角 (SnapEdgeBottomRight)
|
||||
distToBottomRight := math.Sqrt(math.Pow(float64(mainRight-windowLeft), 2) + math.Pow(float64(mainBottom-windowTop), 2))
|
||||
if distToBottomRight <= cornerThreshold {
|
||||
horizDist := math.Abs(float64(mainRight - windowLeft))
|
||||
vertDist := math.Abs(float64(mainBottom - windowTop))
|
||||
if horizDist <= cornerThreshold && vertDist <= cornerThreshold {
|
||||
candidates = append(candidates, snapCandidate{models.SnapEdgeBottomRight, distToBottomRight, 100, true})
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 左下角 (SnapEdgeBottomLeft)
|
||||
distToBottomLeft := math.Sqrt(math.Pow(float64(mainLeft-windowRight), 2) + math.Pow(float64(mainBottom-windowTop), 2))
|
||||
if distToBottomLeft <= cornerThreshold {
|
||||
horizDist := math.Abs(float64(mainLeft - windowRight))
|
||||
vertDist := math.Abs(float64(mainBottom - windowTop))
|
||||
if horizDist <= cornerThreshold && vertDist <= cornerThreshold {
|
||||
candidates = append(candidates, snapCandidate{models.SnapEdgeBottomLeft, distToBottomLeft, 100, true})
|
||||
}
|
||||
}
|
||||
|
||||
// 4. 左上角 (SnapEdgeTopLeft)
|
||||
distToTopLeft := math.Sqrt(math.Pow(float64(mainLeft-windowRight), 2) + math.Pow(float64(mainTop-windowBottom), 2))
|
||||
if distToTopLeft <= cornerThreshold {
|
||||
horizDist := math.Abs(float64(mainLeft - windowRight))
|
||||
vertDist := math.Abs(float64(mainTop - windowBottom))
|
||||
if horizDist <= cornerThreshold && vertDist <= cornerThreshold {
|
||||
candidates = append(candidates, snapCandidate{models.SnapEdgeTopLeft, distToTopLeft, 100, true})
|
||||
}
|
||||
}
|
||||
|
||||
// ==== 检查边缘吸附(只在没有角落吸附时检查) ====
|
||||
if len(candidates) == 0 {
|
||||
// 1. 吸附到主窗口右侧
|
||||
distToRight := math.Abs(float64(mainRight - windowLeft))
|
||||
if distToRight <= threshold {
|
||||
// 计算垂直重叠
|
||||
overlapTop := math.Max(float64(mainTop), float64(windowTop))
|
||||
overlapBottom := math.Min(float64(mainBottom), float64(windowBottom))
|
||||
verticalOverlap := math.Max(0, overlapBottom-overlapTop)
|
||||
candidates = append(candidates, snapCandidate{models.SnapEdgeRight, distToRight, verticalOverlap, false})
|
||||
}
|
||||
|
||||
// 2. 吸附到主窗口左侧
|
||||
distToLeft := math.Abs(float64(mainLeft - windowRight))
|
||||
if distToLeft <= threshold {
|
||||
// 计算垂直重叠
|
||||
overlapTop := math.Max(float64(mainTop), float64(windowTop))
|
||||
overlapBottom := math.Min(float64(mainBottom), float64(windowBottom))
|
||||
verticalOverlap := math.Max(0, overlapBottom-overlapTop)
|
||||
candidates = append(candidates, snapCandidate{models.SnapEdgeLeft, distToLeft, verticalOverlap, false})
|
||||
}
|
||||
|
||||
// 3. 吸附到主窗口底部
|
||||
distToBottom := math.Abs(float64(mainBottom - windowTop))
|
||||
if distToBottom <= threshold {
|
||||
// 计算水平重叠
|
||||
overlapLeft := math.Max(float64(mainLeft), float64(windowLeft))
|
||||
overlapRight := math.Min(float64(mainRight), float64(windowRight))
|
||||
horizontalOverlap := math.Max(0, overlapRight-overlapLeft)
|
||||
candidates = append(candidates, snapCandidate{models.SnapEdgeBottom, distToBottom, horizontalOverlap, false})
|
||||
}
|
||||
|
||||
// 4. 吸附到主窗口顶部
|
||||
distToTop := math.Abs(float64(mainTop - windowBottom))
|
||||
if distToTop <= threshold {
|
||||
// 计算水平重叠
|
||||
overlapLeft := math.Max(float64(mainLeft), float64(windowLeft))
|
||||
overlapRight := math.Min(float64(mainRight), float64(windowRight))
|
||||
horizontalOverlap := math.Max(0, overlapRight-overlapLeft)
|
||||
candidates = append(candidates, snapCandidate{models.SnapEdgeTop, distToTop, horizontalOverlap, false})
|
||||
}
|
||||
}
|
||||
|
||||
// 如果没有候选,不吸附
|
||||
if len(candidates) == 0 {
|
||||
return false, models.SnapEdgeNone
|
||||
}
|
||||
|
||||
// 选择最佳吸附位置:角落吸附优先,其次考虑重叠度,最后考虑距离
|
||||
bestCandidate := candidates[0]
|
||||
for _, candidate := range candidates[1:] {
|
||||
// 角落吸附优先级最高
|
||||
if candidate.isCorner && !bestCandidate.isCorner {
|
||||
bestCandidate = candidate
|
||||
} else if bestCandidate.isCorner && !candidate.isCorner {
|
||||
// 继续使用当前的角落吸附
|
||||
continue
|
||||
} else {
|
||||
// 同类型的吸附,比较重叠度和距离
|
||||
if math.Abs(candidate.overlap-bestCandidate.overlap) < 10 {
|
||||
if candidate.distance < bestCandidate.distance {
|
||||
bestCandidate = candidate
|
||||
}
|
||||
} else if candidate.overlap > bestCandidate.overlap {
|
||||
// 重叠度更高的优先
|
||||
bestCandidate = candidate
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true, bestCandidate.edge
|
||||
}
|
||||
|
||||
// snapWindowToMainWindow 将窗口精确吸附到主窗口边缘(支持角落吸附)
|
||||
func (wss *WindowSnapService) snapWindowToMainWindow(windowInfo *SnapWindowInfo, snapEdge models.SnapEdge) {
|
||||
// 获取主窗口位置和尺寸
|
||||
mainPos, _ := wss.getWindowPosition(wss.mainWindow)
|
||||
mainWidth, mainHeight := wss.mainWindow.Size()
|
||||
|
||||
// 获取子窗口位置和尺寸
|
||||
windowPos, _ := wss.getWindowPosition(windowInfo.Window)
|
||||
windowWidth, windowHeight := windowInfo.Window.Size()
|
||||
|
||||
// 计算目标位置
|
||||
var targetX, targetY int
|
||||
|
||||
switch snapEdge {
|
||||
case models.SnapEdgeRight:
|
||||
// 吸附到主窗口右侧
|
||||
targetX = mainPos.X + mainWidth
|
||||
targetY = windowPos.Y // 保持当前 Y 位置
|
||||
// 如果超出主窗口范围,调整到边界
|
||||
if targetY < mainPos.Y {
|
||||
targetY = mainPos.Y
|
||||
} else if targetY+windowHeight > mainPos.Y+mainHeight {
|
||||
targetY = mainPos.Y + mainHeight - windowHeight
|
||||
}
|
||||
|
||||
case models.SnapEdgeLeft:
|
||||
// 吸附到主窗口左侧
|
||||
targetX = mainPos.X - windowWidth
|
||||
targetY = windowPos.Y // 保持当前 Y 位置
|
||||
// 如果超出主窗口范围,调整到边界
|
||||
if targetY < mainPos.Y {
|
||||
targetY = mainPos.Y
|
||||
} else if targetY+windowHeight > mainPos.Y+mainHeight {
|
||||
targetY = mainPos.Y + mainHeight - windowHeight
|
||||
}
|
||||
|
||||
case models.SnapEdgeBottom:
|
||||
// 吸附到主窗口底部
|
||||
targetX = windowPos.X // 保持当前 X 位置
|
||||
targetY = mainPos.Y + mainHeight
|
||||
// 如果超出主窗口范围,调整到边界
|
||||
if targetX < mainPos.X {
|
||||
targetX = mainPos.X
|
||||
} else if targetX+windowWidth > mainPos.X+mainWidth {
|
||||
targetX = mainPos.X + mainWidth - windowWidth
|
||||
}
|
||||
|
||||
case models.SnapEdgeTop:
|
||||
// 吸附到主窗口顶部
|
||||
targetX = windowPos.X // 保持当前 X 位置
|
||||
targetY = mainPos.Y - windowHeight
|
||||
// 如果超出主窗口范围,调整到边界
|
||||
if targetX < mainPos.X {
|
||||
targetX = mainPos.X
|
||||
} else if targetX+windowWidth > mainPos.X+mainWidth {
|
||||
targetX = mainPos.X + mainWidth - windowWidth
|
||||
}
|
||||
|
||||
// ==== 角落吸附 ====
|
||||
case models.SnapEdgeTopRight:
|
||||
// 吸附到右上角
|
||||
targetX = mainPos.X + mainWidth
|
||||
targetY = mainPos.Y - windowHeight
|
||||
|
||||
case models.SnapEdgeBottomRight:
|
||||
// 吸附到右下角
|
||||
targetX = mainPos.X + mainWidth
|
||||
targetY = mainPos.Y + mainHeight
|
||||
|
||||
case models.SnapEdgeBottomLeft:
|
||||
// 吸附到左下角
|
||||
targetX = mainPos.X - windowWidth
|
||||
targetY = mainPos.Y + mainHeight
|
||||
|
||||
case models.SnapEdgeTopLeft:
|
||||
// 吸附到左上角
|
||||
targetX = mainPos.X - windowWidth
|
||||
targetY = mainPos.Y - windowHeight
|
||||
|
||||
default:
|
||||
// 不应该到达这里
|
||||
return
|
||||
}
|
||||
|
||||
// 直接移动到目标位置,不使用平滑过渡以产生明显的吸附效果
|
||||
windowInfo.Window.SetPosition(targetX, targetY)
|
||||
|
||||
// 更新窗口信息
|
||||
windowInfo.SnapEdge = snapEdge
|
||||
windowInfo.LastPos = models.WindowPosition{X: targetX, Y: targetY}
|
||||
}
|
||||
|
||||
// Cleanup 清理资源
|
||||
func (wss *WindowSnapService) Cleanup() {
|
||||
// 如果有取消函数,调用它来停止所有goroutine
|
||||
if wss.cancel != nil {
|
||||
wss.cancel()
|
||||
}
|
||||
|
||||
// 停止定时器
|
||||
if wss.snapTicker != nil {
|
||||
wss.snapTicker.Stop()
|
||||
wss.snapTicker = nil
|
||||
}
|
||||
}
|
||||
|
||||
// ServiceShutdown 实现服务关闭接口
|
||||
func (wss *WindowSnapService) ServiceShutdown() error {
|
||||
wss.Cleanup()
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user