diff --git a/plugins/serious-reading/.gitignore b/plugins/serious-reading/.gitignore new file mode 100644 index 00000000..eb85031e --- /dev/null +++ b/plugins/serious-reading/.gitignore @@ -0,0 +1,6 @@ +node_modules/ +!preload/node_modules/ +dist/ +.git/ +*.log +.env* \ No newline at end of file diff --git a/plugins/serious-reading/AGENTS.md b/plugins/serious-reading/AGENTS.md new file mode 100644 index 00000000..36f5e498 --- /dev/null +++ b/plugins/serious-reading/AGENTS.md @@ -0,0 +1,137 @@ +# AGENTS.md + +---- + +## 0. Non-negotiables + +These rules override everything else in this file when in conflict: + +1. **No flattery, no filler.** Skip openers like "Great question", "You're absolutely right", "Excellent idea", "I'd be happy to". Start with the answer or the action. +2. **Disagree when you disagree.** If the user's premise is wrong, say so before doing the work. Agreeing with false premises to be polite is the single worst failure mode in coding agents. +3. **Never fabricate.** Not file paths, not commit hashes, not API names, not test results, not library functions. If you don't know, read the file, run the command, or say "I don't know, let me check." +4. **Stop when confused.** If the task has two plausible interpretations, ask. Do not pick silently and proceed. +5. **Touch only what you must.** Every changed line must trace directly to the user's request. No drive-by refactors, reformatting, or "while I was in there" cleanups. + +--- + +## 1. Before writing code + +**Goal: understand the problem and the codebase before producing a diff.** + +- State your plan in one or two sentences before editing. For anything non-trivial, produce a numbered list of steps with a verification check for each. +- Read the files you will touch. Read the files that call the files you will touch. Claude Code: use subagents for exploration so the main context stays clean. +- Match existing patterns in the codebase. If the project uses pattern X, use pattern X, even if you'd do it differently in a greenfield repo. +- Surface assumptions out loud: "I'm assuming you want X, Y, Z. If that's wrong, say so." Do not bury assumptions inside the implementation. +- If two approaches exist, present both with tradeoffs. Do not pick one silently. Exception: trivial tasks (typo, rename, log line) where the diff fits in one sentence. + +--- + +## 2. Writing code: simplicity first + +**Goal: the minimum code that solves the stated problem. Nothing speculative.** + +- No features beyond what was asked. +- No abstractions for single-use code. No configurability, flexibility, or hooks that were not requested. +- No error handling for impossible scenarios. Handle the failures that can actually happen. +- If the solution runs 200 lines and could be 50, rewrite it before showing it. +- If you find yourself adding "for future extensibility", stop. Future extensibility is a future decision. +- Bias toward deleting code over adding code. Shipping less is almost always better. + +The test: would a senior engineer reading the diff call this overcomplicated? If yes, simplify. + +--- + +## 3. Surgical changes + +**Goal: clean, reviewable diffs. Change only what the request requires.** + +- Do not "improve" adjacent code, comments, formatting, or imports that are not part of the task. +- Do not refactor code that works just because you are in the file. +- Do not delete pre-existing dead code unless asked. If you notice it, mention it in the summary. +- Do clean up orphans created by your own changes (unused imports, variables, functions your edit made obsolete). +- Match the project's existing style exactly: indentation, quotes, naming, file layout. + +The test: every changed line traces directly to the user's request. If a line fails that test, revert it. + +--- + +## 4. Goal-driven execution + +**Goal: define success as something you can verify, then loop until verified.** + +Rewrite vague asks into verifiable goals before starting: + +- "Add validation" becomes "Write tests for invalid inputs (empty, malformed, oversized), then make them pass." +- "Fix the bug" becomes "Write a failing test that reproduces the reported symptom, then make it pass." +- "Refactor X" becomes "Ensure the existing test suite passes before and after, and no public API changes." +- "Make it faster" becomes "Benchmark the current hot path, identify the bottleneck with profiling, change it, show the benchmark is faster." + +For every task: + +1. State the success criteria before writing code. +2. Write the verification (test, script, benchmark, screenshot diff) where practical. +3. Run the verification. Read the output. Do not claim success without checking. +4. If the verification fails, fix the cause, not the test. + +--- + +## 5. Tool use and verification + +- Prefer running the code to guessing about the code. If a test suite exists, run it. If a linter exists, run it. If a type checker exists, run it. +- Never report "done" based on a plausible-looking diff alone. Plausibility is not correctness. +- When debugging, address root causes, not symptoms. Suppressing the error is not fixing the error. +- For UI changes, verify visually: screenshot before, screenshot after, describe the diff. +- Use CLI tools (gh, aws, gcloud, kubectl) when they exist. They are more context-efficient than reading docs or hitting APIs unauthenticated. +- When reading logs, errors, or stack traces, read the whole thing. Half-read traces produce wrong fixes. + +--- + +## 6. Session hygiene + +- Context is the constraint. Long sessions with accumulated failed attempts perform worse than fresh sessions with a better prompt. +- After two failed corrections on the same issue, stop. Summarize what you learned and ask the user to reset the session with a sharper prompt. +- Use subagents (Claude Code: "use subagents to investigate X") for exploration tasks that would otherwise pollute the main context with dozens of file reads. +- When committing, write descriptive commit messages (subject under 72 chars, body explains the why). No "update file" or "fix bug" commits. No "Co-Authored-By: Claude" attribution unless the project explicitly wants it. + +--- + +## 7. Communication style + +- Direct, not diplomatic. "This won't scale because X" beats "That's an interesting approach, but have you considered...". +- Concise by default. Two or three short paragraphs unless the user asks for depth. No padding, no restating the question, no ceremonial closings. +- When a question has a clear answer, give it. When it does not, say so and give your best read on the tradeoffs. +- Celebrate only what matters: shipping, solving genuinely hard problems, metrics that moved. Not feature ideas, not scope creep, not "wouldn't it be cool if". +- No excessive bullet points, no unprompted headers, no emoji. Prose is usually clearer than structure for short answers. + +--- + +## 8. When to ask, when to proceed + +**Ask before proceeding when:** + +- The request has two plausible interpretations and the choice materially affects the output. +- The change touches something you've been told is load-bearing, versioned, or has a migration path. +- You need a credential, a secret, or a production resource you don't have access to. +- The user's stated goal and the literal request appear to conflict. + +**Proceed without asking when:** + +- The task is trivial and reversible (typo, rename a local variable, add a log line). +- The ambiguity can be resolved by reading the code or running a command. +- The user has already answered the question once in this session. + +--- + +## 9. Self-improvement loop + +**This file is living. Keep it short by keeping it honest.** + +After every session where the agent did something wrong: + +1. Ask: was the mistake because this file lacks a rule, or because the agent ignored a rule? +2. If lacking: add the rule under "Project Learnings" below, written as concretely as possible ("Always use X for Y" not "be careful with Y"). +3. If ignored: the rule may be too long, too vague, or buried. Tighten it or move it up. +4. Every few weeks, prune. For each line, ask: "Would removing this cause the agent to make a mistake?" If no, delete. Bloated AGENTS.md files get ignored wholesale. + +Boris Cherny (creator of Claude Code) keeps his team's file around 100 lines. Under 300 is a good ceiling. Over 500 and you are fighting your own config. + diff --git a/plugins/serious-reading/CHANGELOG.md b/plugins/serious-reading/CHANGELOG.md new file mode 100644 index 00000000..5815b801 --- /dev/null +++ b/plugins/serious-reading/CHANGELOG.md @@ -0,0 +1,52 @@ +# Changelog + +本文件记录严肃阅读的版本变更历史。 + +格式参考 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.1.0/)。 + +## [1.1.0] - 2026-07-03 + +### 变更 + +- 插件名称从 "Serious Reading" 改为 "严肃阅读"(plugin.json title、UI 标题、README、index.html) + +### 安全修复 + +- **XSS 漏洞修复** — EPUB 章节内容的 HTML 清洗从正则替换改为 DOMPurify,防止恶意 EPUB 通过 XSS 载荷读取本地文件 (`src/shared/parser.ts`) + +### Bug 修复 + +- **EPUB 嵌套分页** — 高度测量分页算法改为递归展平嵌套块级元素,修复 EPUB 章节被 `
` 包裹时整章塞入一页导致内容截断的问题 (`src/reader/App.tsx`) +- **搜索定位跳转末章** — 搜索结果落在章节标题空隙时不再错误跳转到最后一章,改用下一章节的 charOffset 作为上界判断 (`src/main/components/SearchDialog.tsx`) +- **EPUB 冗余解析** — 打开 EPUB 文件时不再重复调用 `readEpub`,首次解析结果缓存后直接复用提取封面 (`src/main/App.tsx`) +- **preload 全局引用** — `preload/main.js` 和 `preload/reader.js` 中裸 `ztools` 引用改为 `window.ztools`,避免上下文隔离环境下 ReferenceError (`preload/main.js`, `preload/reader.js`) +- **EPUB 章节标题不一致** — 阅读窗 preload 的 `readEpub` 引入 NCX TOC 解析,与主窗 preload 保持一致,不再将所有章节标题硬编码为"第 X 章" (`preload/reader.js`) + +## [1.0.0] - 2026-07-03 + +### 新增 + +- 字体选择下拉菜单,内置 11 种中英文字体 +- 「清理空行」阅读选项,压缩 TXT 连续空行为单个段落间距 +- `toggle_reader` 命令(切换阅读窗显隐) +- `reader_open` 文件打开命令,支持拖入 TXT/EPUB/PDF 直接阅读 + +### 变更 + +- Logo 由 `logo.png` 更换为 `logo.svg` +- 字体设置从文本输入框改为下拉菜单选择 +- TXT 渲染逻辑重构:默认保留原文换行,「清理空行」模式压缩连续空行 +- `keepFormat` 设置项重命名为 `cleanEmptyLines` + +## [0.1.0] - 2026-06-30 + +### 新增 + +- TXT/EPUB/PDF 三格式支持(编码检测、章节解析、PDF canvas 渲染) +- 书架管理(网格书架、封面缩略图、进度展示、最近阅读历史) +- 章节跳转、全文搜索、百分比跳转 +- 高度测量自动分页 +- 透明留窗(Stealth)伪装模式、真隐藏、三功能触发器自定义 +- 自动翻页、阅读窗拖拽/缩放、窗口位置记忆 +- 明暗主题、阅读配色、全屏截图取色、排版控制 +- React 18 + TypeScript + Vite 双入口架构 diff --git a/plugins/serious-reading/README.md b/plugins/serious-reading/README.md new file mode 100644 index 00000000..4536efc3 --- /dev/null +++ b/plugins/serious-reading/README.md @@ -0,0 +1,83 @@ +# 严肃阅读 + +> 一款对待摸鱼阅读很严肃的阅读插件 +支持 TXT / EPUB / PDF 三种格式,专为「在工作间隙低调阅读」设计——悬浮透明阅读窗、老板键伪装隐藏、自动翻页、全屏取色配色,让你严肃地摸鱼。 + +## 快速开始 + + + +| 命令 | 说明 | +|------|------| +| `阅读` / `书架` / `serious` | 打开书架 | +| `继续阅读` | 继续上次阅读 | +| `显示阅读器` / `show` | 显示阅读窗 | +| `切换阅读器` / `toggle` | 切换阅读窗显隐 | +| 拖入 TXT/EPUB/PDF 文件 | 直接打开阅读 | + +## 阅读体验 + +- **三格式支持** — TXT(自动编码检测,GBK/UTF-8/UTF-16 均可)、EPUB(自动解析章节和封面)、PDF(canvas 渲染) +- **书架管理** — 网格书架,EPUB 显示封面缩略图,TXT/PDF 显示书名色块;进度百分比一目了然,最近阅读历史快速回到上次的书 +- **章节跳转** — 右键打开章节列表,支持标题搜索过滤,一键跳转 +- **全文搜索** — TXT 全文搜索,关键字上下文高亮,点击结果直接跳转到对应位置 +- **百分比跳转** — 阅读窗右下角输入百分比,精确跳转到全书对应位置 +- **自动分页** — 按实际渲染高度自动分页,调整字号或窗口大小后自动重排 +- **进度记忆** — 自动保存每本书的阅读位置,下次打开恢复到上次位置 + +## 摸鱼伪装 + +老板来了怎么办?三种隐藏方式,触发动作全部可自定义: + +| 功能 | 默认触发 | 效果 | +|------|----------|------| +| 隐身 | Esc / 双击 / 鼠标离开窗口边缘 | 内容透明化,窗口保留,鼠标移回即恢复 | +| 显示 | 中键 | 恢复内容可见 | +| 真隐藏 | 右键 | 窗口彻底消失,需命令恢复 | + +- 在设置面板中可为三个功能分别绑定触发动作(双击、中键、右键、Esc、鼠标离开/进入边缘),系统自动检测冲突 +- 隐身状态下可配置自动暂停翻页,避免恢复时位置跑偏 + +## 阅读窗操作 + +- **拖拽移动** — 鼠标按住窗口中间区域拖动 +- **缩放** — 四边和四角均有缩放把手 +- **窗口记忆** — 自动保存阅读窗位置和尺寸 +- **翻页方式** — 键盘 ←→ / 滚轮 / 点击左右两侧 / PageUp Down / 空格 / 触摸滑动,可任意组合开关 +- **翻页动画** — 无动画 / 滑动两种模式 + +## 外观定制 + +- **明暗主题** — 跟随系统 / 手动明亮 / 手动暗黑 +- **阅读配色** — 背景色、文字色自定义,支持**全屏截图取色**(截取屏幕任意区域的颜色作为背景或文字色) +- **排版控制** — 字号(8-32px)、行高(1.0-3.0)、字重(50-1000)、11 种中英文字体、透明度(10%-100%)、清理空行 +- **实时生效** — 修改设置后自动推送到已打开的阅读窗,无需重新打开 + +## 开发 + +```bash +# 1. 安装前端依赖 +npm install + +# 2. 安装 preload 原生依赖(不编译,随源码提交) +cd preload && npm install && cd .. + +# 3. 开发模式(Vite dev server :5173,ZTools 开发者工具以本目录为根加载) +npm run dev + +# 4. 构建产物到 dist/ +npm run build +``` + +## 打包发布 + +```bash +# 安装 ZTools 插件 CLI +npm install -g @ztools-center/plugin-cli + +# 发布到 ZTools 插件中心 +ztools publish +``` +为什么做这个插件? + +之前一直使用utools的插件摸鱼阅读,但是使用时一直感觉摸鱼阅读限制太多,不能随时移动和调整阅读框大小,于是用AI重新开发了这个插件,感谢摸鱼阅读的开发者。 diff --git a/plugins/serious-reading/index.html b/plugins/serious-reading/index.html new file mode 100644 index 00000000..62830221 --- /dev/null +++ b/plugins/serious-reading/index.html @@ -0,0 +1,13 @@ + + + + + + + 严肃阅读 + + +
+ + + \ No newline at end of file diff --git a/plugins/serious-reading/logo.svg b/plugins/serious-reading/logo.svg new file mode 100644 index 00000000..4c39fd48 --- /dev/null +++ b/plugins/serious-reading/logo.svg @@ -0,0 +1,4 @@ + + + R + diff --git a/plugins/serious-reading/package-lock.json b/plugins/serious-reading/package-lock.json new file mode 100644 index 00000000..04623da8 --- /dev/null +++ b/plugins/serious-reading/package-lock.json @@ -0,0 +1,3616 @@ +{ + "name": "serious-reading-zt", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "serious-reading-zt", + "version": "1.0.0", + "dependencies": { + "@radix-ui/react-checkbox": "^1.1.2", + "@radix-ui/react-dialog": "^1.1.2", + "@radix-ui/react-dropdown-menu": "^2.1.2", + "@radix-ui/react-scroll-area": "^1.2.0", + "@radix-ui/react-slider": "^1.2.1", + "@radix-ui/react-slot": "^1.1.0", + "@radix-ui/react-switch": "^1.1.1", + "class-variance-authority": "^0.7.0", + "clsx": "^2.1.1", + "cmdk": "^1.0.4", + "dompurify": "^3.4.11", + "lucide-react": "^0.453.0", + "pdfjs-dist": "^4.6.82", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "tailwind-merge": "^2.5.4" + }, + "devDependencies": { + "@types/node": "^22.7.5", + "@types/react": "^18.3.11", + "@types/react-dom": "^18.3.1", + "@vitejs/plugin-react": "^4.3.2", + "autoprefixer": "^10.4.20", + "postcss": "^8.4.47", + "tailwindcss": "^3.4.14", + "typescript": "^5.6.3", + "vite": "^5.4.9" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@alloc/quick-lru": { + "version": "5.2.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.7", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.29.7", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.7", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.7", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helpers": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.7", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.29.7", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.29.7", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.29.7", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.29.7", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.29.7", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.29.7", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.29.7", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.7", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.7" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.29.7", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.29.7", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.29.7", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.7", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.7", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@floating-ui/core": { + "version": "1.7.5", + "license": "MIT", + "dependencies": { + "@floating-ui/utils": "^0.2.11" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.7.6", + "license": "MIT", + "dependencies": { + "@floating-ui/core": "^1.7.5", + "@floating-ui/utils": "^0.2.11" + } + }, + "node_modules/@floating-ui/react-dom": { + "version": "2.1.8", + "license": "MIT", + "dependencies": { + "@floating-ui/dom": "^1.7.6" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.11", + "license": "MIT" + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@napi-rs/canvas": { + "version": "0.1.100", + "license": "MIT", + "optional": true, + "workspaces": [ + "e2e/*" + ], + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "optionalDependencies": { + "@napi-rs/canvas-android-arm64": "0.1.100", + "@napi-rs/canvas-darwin-arm64": "0.1.100", + "@napi-rs/canvas-darwin-x64": "0.1.100", + "@napi-rs/canvas-linux-arm-gnueabihf": "0.1.100", + "@napi-rs/canvas-linux-arm64-gnu": "0.1.100", + "@napi-rs/canvas-linux-arm64-musl": "0.1.100", + "@napi-rs/canvas-linux-riscv64-gnu": "0.1.100", + "@napi-rs/canvas-linux-x64-gnu": "0.1.100", + "@napi-rs/canvas-linux-x64-musl": "0.1.100", + "@napi-rs/canvas-win32-arm64-msvc": "0.1.100", + "@napi-rs/canvas-win32-x64-msvc": "0.1.100" + } + }, + "node_modules/@napi-rs/canvas-android-arm64": { + "version": "0.1.100", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-android-arm64/-/canvas-android-arm64-0.1.100.tgz", + "integrity": "sha512-hjhCKhntPv9+t4ckHymdx0phYNcVW+GKQR6Lzw2zE+pOVjOplSmtx9nNNknTjbEDLcuLZqA1y8ufKg1XfgftzQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + } + }, + "node_modules/@napi-rs/canvas-darwin-arm64": { + "version": "0.1.100", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-darwin-arm64/-/canvas-darwin-arm64-0.1.100.tgz", + "integrity": "sha512-2PcswRaC7Ly645DGt88///zuFDhJxJYdKAs1uU3mfk1atYkXufgcgLfBpk6Tm12nCQBaNt1wpybuPZ4qOhTo8A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + } + }, + "node_modules/@napi-rs/canvas-darwin-x64": { + "version": "0.1.100", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-darwin-x64/-/canvas-darwin-x64-0.1.100.tgz", + "integrity": "sha512-ePNZtj7pNIva/siZMg+HmbeozkIjqUIYdoymH8HaA3qK7LfzFN4WMBM8G6HQ9ZC+H3+Dnn5pqtiXpgLykaPOhw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + } + }, + "node_modules/@napi-rs/canvas-linux-arm-gnueabihf": { + "version": "0.1.100", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm-gnueabihf/-/canvas-linux-arm-gnueabihf-0.1.100.tgz", + "integrity": "sha512-d5cDB48oWFGU8/XPhUOFAlySgb/VAu7D+s8fi55K1Pcfg8aPplHWqMgibhVLU8ky7Pyg/fuiVLz4Nf3JrSTuUA==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + } + }, + "node_modules/@napi-rs/canvas-linux-arm64-gnu": { + "version": "0.1.100", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm64-gnu/-/canvas-linux-arm64-gnu-0.1.100.tgz", + "integrity": "sha512-rDxgxRu69RvDlX/bh9o22DxLsGr8EqsNgotL9+RwQE1S0b0cqeatqsw6aW45mukm0B42DIAaAacKaYQ8cqS1nw==", + "cpu": [ + "arm64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + } + }, + "node_modules/@napi-rs/canvas-linux-arm64-musl": { + "version": "0.1.100", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm64-musl/-/canvas-linux-arm64-musl-0.1.100.tgz", + "integrity": "sha512-K3mDW66N+xT2/V439u1alFANiBUjdEx2gLiNYnCmUsva5jZMxWTjafBYwTzYK+EMFMHrUoabuU+T1BIP5CgbYQ==", + "cpu": [ + "arm64" + ], + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + } + }, + "node_modules/@napi-rs/canvas-linux-riscv64-gnu": { + "version": "0.1.100", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-riscv64-gnu/-/canvas-linux-riscv64-gnu-0.1.100.tgz", + "integrity": "sha512-mooqUBTIsccZpnoQC4NgrC1v6C1vof39etLNMnBwCY+p0gajWJvAHLGQ6g/gGyS5YrpDW+GefSN4+Cvcr08UWw==", + "cpu": [ + "riscv64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + } + }, + "node_modules/@napi-rs/canvas-linux-x64-gnu": { + "version": "0.1.100", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-x64-gnu/-/canvas-linux-x64-gnu-0.1.100.tgz", + "integrity": "sha512-1eCvkDCazm7FFhsT7DfGOdSaHgZVK3bt/dSBl5EWHOWmnz+I7j8tPseJqqD81NF+MH21jKUK4wQSDjN0mdhnTg==", + "cpu": [ + "x64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + } + }, + "node_modules/@napi-rs/canvas-linux-x64-musl": { + "version": "0.1.100", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-x64-musl/-/canvas-linux-x64-musl-0.1.100.tgz", + "integrity": "sha512-20arT6lnI19S68qNlii73TSEDbECNgzMz2EpldC1V3mZFuRkeujXkcebRk0LRJe9SEUAooYiLokfMViY8IX7yA==", + "cpu": [ + "x64" + ], + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + } + }, + "node_modules/@napi-rs/canvas-win32-arm64-msvc": { + "version": "0.1.100", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-win32-arm64-msvc/-/canvas-win32-arm64-msvc-0.1.100.tgz", + "integrity": "sha512-DZFFT1wIAg37LJw37yhMRFfjATd3vTQzjZ1Yki8u2vhO6Hi5VE6BVaGQ1aaDu7xb4iMErz+9EOwjpS7xcxFeBw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + } + }, + "node_modules/@napi-rs/canvas-win32-x64-msvc": { + "version": "0.1.100", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@radix-ui/number": { + "version": "1.1.2", + "license": "MIT" + }, + "node_modules/@radix-ui/primitive": { + "version": "1.1.4", + "license": "MIT" + }, + "node_modules/@radix-ui/react-arrow": { + "version": "1.1.10", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.6" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-checkbox": { + "version": "1.3.5", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.4", + "@radix-ui/react-compose-refs": "1.1.3", + "@radix-ui/react-context": "1.1.4", + "@radix-ui/react-presence": "1.1.6", + "@radix-ui/react-primitive": "2.1.6", + "@radix-ui/react-use-controllable-state": "1.2.3", + "@radix-ui/react-use-previous": "1.1.2", + "@radix-ui/react-use-size": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-collection": { + "version": "1.1.10", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.3", + "@radix-ui/react-context": "1.1.4", + "@radix-ui/react-primitive": "2.1.6", + "@radix-ui/react-slot": "1.3.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.3", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-context": { + "version": "1.1.4", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog": { + "version": "1.1.17", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.4", + "@radix-ui/react-compose-refs": "1.1.3", + "@radix-ui/react-context": "1.1.4", + "@radix-ui/react-dismissable-layer": "1.1.13", + "@radix-ui/react-focus-guards": "1.1.4", + "@radix-ui/react-focus-scope": "1.1.10", + "@radix-ui/react-id": "1.1.2", + "@radix-ui/react-portal": "1.1.12", + "@radix-ui/react-presence": "1.1.6", + "@radix-ui/react-primitive": "2.1.6", + "@radix-ui/react-slot": "1.3.0", + "@radix-ui/react-use-controllable-state": "1.2.3", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.7.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-direction": { + "version": "1.1.2", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dismissable-layer": { + "version": "1.1.13", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.4", + "@radix-ui/react-compose-refs": "1.1.3", + "@radix-ui/react-primitive": "2.1.6", + "@radix-ui/react-use-callback-ref": "1.1.2", + "@radix-ui/react-use-escape-keydown": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dropdown-menu": { + "version": "2.1.18", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.4", + "@radix-ui/react-compose-refs": "1.1.3", + "@radix-ui/react-context": "1.1.4", + "@radix-ui/react-id": "1.1.2", + "@radix-ui/react-menu": "2.1.18", + "@radix-ui/react-primitive": "2.1.6", + "@radix-ui/react-use-controllable-state": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-focus-guards": { + "version": "1.1.4", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-focus-scope": { + "version": "1.1.10", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.3", + "@radix-ui/react-primitive": "2.1.6", + "@radix-ui/react-use-callback-ref": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-id": { + "version": "1.1.2", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-menu": { + "version": "2.1.18", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.4", + "@radix-ui/react-collection": "1.1.10", + "@radix-ui/react-compose-refs": "1.1.3", + "@radix-ui/react-context": "1.1.4", + "@radix-ui/react-direction": "1.1.2", + "@radix-ui/react-dismissable-layer": "1.1.13", + "@radix-ui/react-focus-guards": "1.1.4", + "@radix-ui/react-focus-scope": "1.1.10", + "@radix-ui/react-id": "1.1.2", + "@radix-ui/react-popper": "1.3.1", + "@radix-ui/react-portal": "1.1.12", + "@radix-ui/react-presence": "1.1.6", + "@radix-ui/react-primitive": "2.1.6", + "@radix-ui/react-roving-focus": "1.1.13", + "@radix-ui/react-slot": "1.3.0", + "@radix-ui/react-use-callback-ref": "1.1.2", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.7.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popper": { + "version": "1.3.1", + "license": "MIT", + "dependencies": { + "@floating-ui/react-dom": "^2.0.0", + "@radix-ui/react-arrow": "1.1.10", + "@radix-ui/react-compose-refs": "1.1.3", + "@radix-ui/react-context": "1.1.4", + "@radix-ui/react-primitive": "2.1.6", + "@radix-ui/react-use-callback-ref": "1.1.2", + "@radix-ui/react-use-layout-effect": "1.1.2", + "@radix-ui/react-use-rect": "1.1.2", + "@radix-ui/react-use-size": "1.1.2", + "@radix-ui/rect": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-portal": { + "version": "1.1.12", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.6", + "@radix-ui/react-use-layout-effect": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-presence": { + "version": "1.1.6", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-primitive": { + "version": "2.1.6", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.3.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-roving-focus": { + "version": "1.1.13", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.4", + "@radix-ui/react-collection": "1.1.10", + "@radix-ui/react-compose-refs": "1.1.3", + "@radix-ui/react-context": "1.1.4", + "@radix-ui/react-direction": "1.1.2", + "@radix-ui/react-id": "1.1.2", + "@radix-ui/react-primitive": "2.1.6", + "@radix-ui/react-use-callback-ref": "1.1.2", + "@radix-ui/react-use-controllable-state": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-scroll-area": { + "version": "1.2.12", + "license": "MIT", + "dependencies": { + "@radix-ui/number": "1.1.2", + "@radix-ui/primitive": "1.1.4", + "@radix-ui/react-compose-refs": "1.1.3", + "@radix-ui/react-context": "1.1.4", + "@radix-ui/react-direction": "1.1.2", + "@radix-ui/react-presence": "1.1.6", + "@radix-ui/react-primitive": "2.1.6", + "@radix-ui/react-use-callback-ref": "1.1.2", + "@radix-ui/react-use-layout-effect": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-slider": { + "version": "1.4.1", + "license": "MIT", + "dependencies": { + "@radix-ui/number": "1.1.2", + "@radix-ui/primitive": "1.1.4", + "@radix-ui/react-collection": "1.1.10", + "@radix-ui/react-compose-refs": "1.1.3", + "@radix-ui/react-context": "1.1.4", + "@radix-ui/react-direction": "1.1.2", + "@radix-ui/react-primitive": "2.1.6", + "@radix-ui/react-use-controllable-state": "1.2.3", + "@radix-ui/react-use-layout-effect": "1.1.2", + "@radix-ui/react-use-previous": "1.1.2", + "@radix-ui/react-use-size": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-slot": { + "version": "1.3.0", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.3" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-switch": { + "version": "1.3.1", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.4", + "@radix-ui/react-compose-refs": "1.1.3", + "@radix-ui/react-context": "1.1.4", + "@radix-ui/react-primitive": "2.1.6", + "@radix-ui/react-use-controllable-state": "1.2.3", + "@radix-ui/react-use-previous": "1.1.2", + "@radix-ui/react-use-size": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-callback-ref": { + "version": "1.1.2", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-controllable-state": { + "version": "1.2.3", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-effect-event": "0.0.3", + "@radix-ui/react-use-layout-effect": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-effect-event": { + "version": "0.0.3", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-escape-keydown": { + "version": "1.1.2", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-callback-ref": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-layout-effect": { + "version": "1.1.2", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-previous": { + "version": "1.1.2", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-rect": { + "version": "1.1.2", + "license": "MIT", + "dependencies": { + "@radix-ui/rect": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-size": { + "version": "1.1.2", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/rect": { + "version": "1.1.2", + "license": "MIT" + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.27", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.62.2.tgz", + "integrity": "sha512-6o7ZLZK+BeenkZCFNDXqpbjw9bD6nuWonvS/lwQJp7NoVVxm6p3qE7qQ5jGuBjiFsgvqjD8mZAU5oWxTmbOeOg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.62.2.tgz", + "integrity": "sha512-BaH7BllCACHoH1LguOU56UItGfUWjujlO65kS9LAodViaN4bwIKd7oeW/ZHJ/4ljr/7MIiENnNy3HJ0zXv8Zkw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.62.2.tgz", + "integrity": "sha512-v39RCCvj4He82I9sFmk+M1VZ0PLM9sfsLVikjfx2hYBNALhrrOR2D3JjQA6AhlaSOgcR+RzrKY7e1+bT6SUO/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.62.2.tgz", + "integrity": "sha512-yl0y2vq3S3lHeuXhEdss6TWfKW8vkujImO12tn4ZkG/4oghr09LvdYm2RElVjokTQiUvDUGXLGsYeLqUMCKpGA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.62.2.tgz", + "integrity": "sha512-tT4pvt4qXD+vEoezupCWi+a1F0vvDiksiHc+PxRlYTOH1I6/X4id9jPxTP+Fg+545euaFT1jJVs4CEdHZAU1vw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.62.2.tgz", + "integrity": "sha512-6nU5F2wCW+qvCBhTn1pdIU3bzsIoF7EUwsCDRxilWGprQR6yd508YnH9+OKFCwpfS8pjZqDUmnCAr7exax0XCg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.62.2.tgz", + "integrity": "sha512-n1GJHPOvpIfhi3TmrCeh6S6URt9BFCt0KQE3qvexyGCTAKpR4Lg+eWvNZEqu7epxwus/8ElT3hacYEucm49SZg==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.62.2.tgz", + "integrity": "sha512-JqgflS8wEB+UXV/vS1RpRbifGBeN4D5lz8D8oOFbFZw4vedvdOgCFAjfBmIMdW3yL10XpQQ0Ambepw6MXrhOnA==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.62.2.tgz", + "integrity": "sha512-wnFJkogWvN4jm/hQRF2UBaeUmk20j5+DmHvoyWii2b8HJDyvz1MF2OU/6ynXt2KR63rbZLWkFpoytpdc/yBuSA==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.62.2.tgz", + "integrity": "sha512-HVu2bp0zhvJ8xHEV9+UUs7S90VadmBSY3LcIMvozbPo4AuMGDWlz3ymHLHZPX4hR67TKTt8Qp5PJ5RBg/i+RMQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.62.2.tgz", + "integrity": "sha512-mQqqAV8QaoSgr9I2fKDLY2BAVvmKjWoGiu/cSYQonsLvtqwEn1E4QYfnCOcp5zoEqNhsDYin1s6jx/VJmrxlZg==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.62.2.tgz", + "integrity": "sha512-IxKLoxCQ2IWi6bT2akyDUBGsOImDKB+sPp4EsTmwFQ/fMwpCKm8uLSSgP/Kx/QYUgKis6SEZ5/Nlhup0DIA0PQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.62.2.tgz", + "integrity": "sha512-Mk5ha2RQSgyFfmYYLkBpPnUk8D8FriBxesO1u9O75X0mHgXL1UQcH5Itl2lurWL2tj0RxV9b9tJgipac0hRY9A==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.62.2.tgz", + "integrity": "sha512-CjvEnqJL/0/TQ3TXX3OPIJ/kmBellrWd4heXUmHeJlTnmwjKpSJzoehLaL6Xk0ZnMHBu9dZuFADNOrtjF4v+2w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.62.2.tgz", + "integrity": "sha512-1SiZbzwdkaDURsew/tSOrooKiYy7EQGT6m8ufavAi9NEyQb/6VuIxFXAL1fqa4iZe3g4NbNk4P7J32z2tw5Mgg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.62.2.tgz", + "integrity": "sha512-nQts12zJ3NQRoE6uYljOH89v7szzLDvG2JD/vsX+vGXU8w/At1GowTZ5/7qeFQ8m7L55rpR8Okugnuo5bgjy2Q==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.62.2.tgz", + "integrity": "sha512-E9/ll019jhPIJgpzfZoIkBGhcz+kKNgVWYRY0zr9srBdPPFVpvOKW8VaJKUbeK+eZXyQF9ltME+Kk6affeaPgg==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.62.2.tgz", + "integrity": "sha512-5BqxR/pshjey51iliyzTD5Xi3EN0aLmQ2lZ3lvefVV9c82BvrLo2/6OT55iifpWBufs6kdwWbuOKS841DrmK9A==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.62.2.tgz", + "integrity": "sha512-uNN83XxQrRAh/w0/pmAfibcwyb6YWt4gP+dpnQKPVJshAloQ785ii8CT8ZCIxkGg9opVsvAlGhFitSm6D1Jjpg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.62.2.tgz", + "integrity": "sha512-srjEIxSH3LRnJN6THczDHWQplqEMFiAJrTab0msUryh9kwNpkICf3Ea6q6MN/2cZwRFUNx5w+h6Hpi4QuHS6Zg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.62.2.tgz", + "integrity": "sha512-8hOJnxgbyObnCm5AlRA3A931xX19xq80RjVTKgJOvEKWqJruP/Uf12IbAOaDjjEXYRewwHLfmF0YRIdK3OwKWA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.62.2.tgz", + "integrity": "sha512-mmF4AY1i0hG/bLWUctUq59gtmgaSIRa3cu/A3JFRp/sCNEme2bgDEiDS22P9FbnJB8NJNF4jPJiSP5RHQpUTDg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.62.2.tgz", + "integrity": "sha512-DZgkknc6jhHrk46V25vbAM0zZkyP0nSDkJB8/dRkLTxv470dOmWDqGoEJl/9A0dFfS7yE3REOwNDxpHwSLSt0Q==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.62.2", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.62.2", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "22.20.0", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/prop-types": { + "version": "15.7.15", + "devOptional": true, + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "18.3.31", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@types/prop-types": "*", + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "18.3.7", + "devOptional": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^18.0.0" + } + }, + "node_modules/@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", + "license": "MIT", + "optional": true + }, + "node_modules/@vitejs/plugin-react": { + "version": "4.7.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.28.0", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-beta.27", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.17.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + } + }, + "node_modules/any-promise": { + "version": "1.3.0", + "dev": true, + "license": "MIT" + }, + "node_modules/anymatch": { + "version": "3.1.3", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/arg": { + "version": "5.0.2", + "dev": true, + "license": "MIT" + }, + "node_modules/aria-hidden": { + "version": "1.2.6", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/autoprefixer": { + "version": "10.5.2", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "browserslist": "^4.28.4", + "caniuse-lite": "^1.0.30001799", + "fraction.js": "^5.3.4", + "picocolors": "^1.1.1", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.38", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.28.4", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.10.38", + "caniuse-lite": "^1.0.30001799", + "electron-to-chromium": "^1.5.376", + "node-releases": "^2.0.48", + "update-browserslist-db": "^1.2.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/camelcase-css": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001799", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chokidar": { + "version": "3.6.0", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/class-variance-authority": { + "version": "0.7.1", + "license": "Apache-2.0", + "dependencies": { + "clsx": "^2.1.1" + }, + "funding": { + "url": "https://polar.sh/cva" + } + }, + "node_modules/clsx": { + "version": "2.1.1", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/cmdk": { + "version": "1.1.1", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "^1.1.1", + "@radix-ui/react-dialog": "^1.1.6", + "@radix-ui/react-id": "^1.1.0", + "@radix-ui/react-primitive": "^2.0.2" + }, + "peerDependencies": { + "react": "^18 || ^19 || ^19.0.0-rc", + "react-dom": "^18 || ^19 || ^19.0.0-rc" + } + }, + "node_modules/commander": { + "version": "4.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/cssesc": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "devOptional": true, + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/detect-node-es": { + "version": "1.1.0", + "license": "MIT" + }, + "node_modules/didyoumean": { + "version": "1.2.2", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/dlv": { + "version": "1.1.3", + "dev": true, + "license": "MIT" + }, + "node_modules/dompurify": { + "version": "3.4.11", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.11.tgz", + "integrity": "sha512-zhlUV12GsaRzMsf9q5M254YhA4+VuF0fG+QFqu6aYpoGlKtz+w8//jBcGVYBgQkR5GHjUomejY84AV+/uPbWdw==", + "license": "(MPL-2.0 OR Apache-2.0)", + "optionalDependencies": { + "@types/trusted-types": "^2.0.7" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.378", + "dev": true, + "license": "ISC" + }, + "node_modules/es-errors": { + "version": "1.3.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.21.5", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fastq": { + "version": "1.20.1", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fraction.js": { + "version": "5.3.4", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/rawify" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-nonce": { + "version": "1.0.1", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/hasown": { + "version": "2.0.4", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-core-module": { + "version": "2.16.2", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/jiti": { + "version": "1.21.7", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "bin/jiti.js" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "license": "MIT" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json5": { + "version": "2.2.3", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/lilconfig": { + "version": "3.1.3", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "dev": true, + "license": "MIT" + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/lucide-react": { + "version": "0.453.0", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0-rc" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "dev": true, + "license": "MIT" + }, + "node_modules/mz": { + "version": "2.7.0", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.15", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/node-releases": { + "version": "2.0.50", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-hash": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "dev": true, + "license": "MIT" + }, + "node_modules/pdfjs-dist": { + "version": "4.10.38", + "license": "Apache-2.0", + "engines": { + "node": ">=20" + }, + "optionalDependencies": { + "@napi-rs/canvas": "^0.1.65" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "2.3.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pirates": { + "version": "4.0.7", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/postcss": { + "version": "8.5.15", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.12", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-import": { + "version": "15.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.0.0", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "postcss": "^8.0.0" + } + }, + "node_modules/postcss-js": { + "version": "4.1.0", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "camelcase-css": "^2.0.1" + }, + "engines": { + "node": "^12 || ^14 || >= 16" + }, + "peerDependencies": { + "postcss": "^8.4.21" + } + }, + "node_modules/postcss-load-config": { + "version": "6.0.1", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "lilconfig": "^3.1.1" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "jiti": ">=1.21.0", + "postcss": ">=8.0.9", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + }, + "postcss": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/postcss-nested": { + "version": "6.2.0", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^6.1.1" + }, + "engines": { + "node": ">=12.0" + }, + "peerDependencies": { + "postcss": "^8.2.14" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.1.4", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "dev": true, + "license": "MIT" + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/react": { + "version": "18.3.1", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "18.3.1", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, + "node_modules/react-refresh": { + "version": "0.17.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-remove-scroll": { + "version": "2.7.2", + "license": "MIT", + "dependencies": { + "react-remove-scroll-bar": "^2.3.7", + "react-style-singleton": "^2.2.3", + "tslib": "^2.1.0", + "use-callback-ref": "^1.3.3", + "use-sidecar": "^1.1.3" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-remove-scroll-bar": { + "version": "2.3.8", + "license": "MIT", + "dependencies": { + "react-style-singleton": "^2.2.2", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-style-singleton": { + "version": "2.2.3", + "license": "MIT", + "dependencies": { + "get-nonce": "^1.0.0", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/read-cache": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "pify": "^2.3.0" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.12", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rollup": { + "version": "4.62.2", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.9" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.62.2", + "@rollup/rollup-android-arm64": "4.62.2", + "@rollup/rollup-darwin-arm64": "4.62.2", + "@rollup/rollup-darwin-x64": "4.62.2", + "@rollup/rollup-freebsd-arm64": "4.62.2", + "@rollup/rollup-freebsd-x64": "4.62.2", + "@rollup/rollup-linux-arm-gnueabihf": "4.62.2", + "@rollup/rollup-linux-arm-musleabihf": "4.62.2", + "@rollup/rollup-linux-arm64-gnu": "4.62.2", + "@rollup/rollup-linux-arm64-musl": "4.62.2", + "@rollup/rollup-linux-loong64-gnu": "4.62.2", + "@rollup/rollup-linux-loong64-musl": "4.62.2", + "@rollup/rollup-linux-ppc64-gnu": "4.62.2", + "@rollup/rollup-linux-ppc64-musl": "4.62.2", + "@rollup/rollup-linux-riscv64-gnu": "4.62.2", + "@rollup/rollup-linux-riscv64-musl": "4.62.2", + "@rollup/rollup-linux-s390x-gnu": "4.62.2", + "@rollup/rollup-linux-x64-gnu": "4.62.2", + "@rollup/rollup-linux-x64-musl": "4.62.2", + "@rollup/rollup-openbsd-x64": "4.62.2", + "@rollup/rollup-openharmony-arm64": "4.62.2", + "@rollup/rollup-win32-arm64-msvc": "4.62.2", + "@rollup/rollup-win32-ia32-msvc": "4.62.2", + "@rollup/rollup-win32-x64-gnu": "4.62.2", + "@rollup/rollup-win32-x64-msvc": "4.62.2", + "fsevents": "~2.3.2" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/scheduler": { + "version": "0.23.2", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/semver": { + "version": "6.3.1", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sucrase": { + "version": "3.35.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.2", + "commander": "^4.0.0", + "lines-and-columns": "^1.1.6", + "mz": "^2.7.0", + "pirates": "^4.0.1", + "tinyglobby": "^0.2.11", + "ts-interface-checker": "^0.1.9" + }, + "bin": { + "sucrase": "bin/sucrase", + "sucrase-node": "bin/sucrase-node" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tailwind-merge": { + "version": "2.6.1", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/dcastil" + } + }, + "node_modules/tailwindcss": { + "version": "3.4.19", + "dev": true, + "license": "MIT", + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "arg": "^5.0.2", + "chokidar": "^3.6.0", + "didyoumean": "^1.2.2", + "dlv": "^1.1.3", + "fast-glob": "^3.3.2", + "glob-parent": "^6.0.2", + "is-glob": "^4.0.3", + "jiti": "^1.21.7", + "lilconfig": "^3.1.3", + "micromatch": "^4.0.8", + "normalize-path": "^3.0.0", + "object-hash": "^3.0.0", + "picocolors": "^1.1.1", + "postcss": "^8.4.47", + "postcss-import": "^15.1.0", + "postcss-js": "^4.0.1", + "postcss-load-config": "^4.0.2 || ^5.0 || ^6.0", + "postcss-nested": "^6.2.0", + "postcss-selector-parser": "^6.1.2", + "resolve": "^1.22.8", + "sucrase": "^3.35.0" + }, + "bin": { + "tailwind": "lib/cli.js", + "tailwindcss": "lib/cli.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/thenify": { + "version": "3.3.1", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0" + } + }, + "node_modules/thenify-all": { + "version": "1.6.0", + "dev": true, + "license": "MIT", + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.5.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.4", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/ts-interface-checker": { + "version": "0.1.13", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/tslib": { + "version": "2.8.1", + "license": "0BSD" + }, + "node_modules/typescript": { + "version": "5.9.3", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "dev": true, + "license": "MIT" + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/use-callback-ref": { + "version": "1.3.3", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/use-sidecar": { + "version": "1.1.3", + "license": "MIT", + "dependencies": { + "detect-node-es": "^1.1.0", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "dev": true, + "license": "MIT" + }, + "node_modules/vite": { + "version": "5.4.21", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "dev": true, + "license": "ISC" + } + } +} diff --git a/plugins/serious-reading/package.json b/plugins/serious-reading/package.json new file mode 100644 index 00000000..ee494718 --- /dev/null +++ b/plugins/serious-reading/package.json @@ -0,0 +1,44 @@ +{ + "name": "serious-reading-zt", + "version": "1.1.0", + "description": "严肃阅读 - ZTools 插件,支持 TXT/EPUB/PDF,透明留窗伪装模式", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc --noEmit && vite build", + "typecheck": "tsc --noEmit", + "publish": "ztools publish" + }, + "dependencies": { + "@radix-ui/react-checkbox": "^1.1.2", + "@radix-ui/react-dialog": "^1.1.2", + "@radix-ui/react-dropdown-menu": "^2.1.2", + "@radix-ui/react-scroll-area": "^1.2.0", + "@radix-ui/react-slider": "^1.2.1", + "@radix-ui/react-slot": "^1.1.0", + "@radix-ui/react-switch": "^1.1.1", + "class-variance-authority": "^0.7.0", + "clsx": "^2.1.1", + "cmdk": "^1.0.4", + "dompurify": "^3.4.11", + "lucide-react": "^0.453.0", + "pdfjs-dist": "^4.6.82", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "tailwind-merge": "^2.5.4" + }, + "engines": { + "node": ">=18" + }, + "devDependencies": { + "@types/node": "^22.7.5", + "@types/react": "^18.3.11", + "@types/react-dom": "^18.3.1", + "@vitejs/plugin-react": "^4.3.2", + "autoprefixer": "^10.4.20", + "postcss": "^8.4.47", + "tailwindcss": "^3.4.14", + "typescript": "^5.6.3", + "vite": "^5.4.9" + } +} diff --git a/plugins/serious-reading/plugin.json b/plugins/serious-reading/plugin.json new file mode 100644 index 00000000..0de42e6b --- /dev/null +++ b/plugins/serious-reading/plugin.json @@ -0,0 +1,49 @@ +{ + "name": "serious-reading", + "title": "严肃阅读", + "description": "一款对待摸鱼阅读很严肃的阅读插件", + "version": "1.1.0", + "main": "dist/index.html", + "logo": "logo.svg", + "preload": "preload/main.js", + "pluginSetting": { + "single": true, + "height": 660 + }, + "features": [ + { + "code": "reader", + "explain": "打开阅读器书架", + "cmds": ["阅读", "书架", "serious"] + }, + { + "code": "reader_continue", + "explain": "继续上次阅读", + "cmds": ["继续阅读", "阅读 继续"] + }, + { + "code": "show_reader", + "explain": "显示阅读窗", + "cmds": ["显示阅读器", "show"] + }, + { + "code": "toggle_reader", + "explain": "切换阅读窗显隐", + "cmds": ["切换阅读器", "toggle"] + }, + { + "code": "reader_open", + "explain": "打开书籍文件", + "cmds": [ + { + "type": "files", + "label": "用严肃阅读打开", + "fileType": "file", + "extensions": ["txt", "epub", "pdf"], + "minLength": 1, + "maxLength": 1 + } + ] + } + ] +} diff --git a/plugins/serious-reading/postcss.config.js b/plugins/serious-reading/postcss.config.js new file mode 100644 index 00000000..e99ebc2c --- /dev/null +++ b/plugins/serious-reading/postcss.config.js @@ -0,0 +1,6 @@ +export default { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +} \ No newline at end of file diff --git a/plugins/serious-reading/preload/main.js b/plugins/serious-reading/preload/main.js new file mode 100644 index 00000000..094bfd8c --- /dev/null +++ b/plugins/serious-reading/preload/main.js @@ -0,0 +1,349 @@ +/** + * 主窗 preload —— CommonJS,不参与编译打包。 + * 职责:文件读取/解码、EPUB 解析(含封面)、PDF 取 ArrayBuffer、 + * 创建并持有悬浮阅读窗 BrowserWindow、转发 IPC(真隐藏等)。 + */ +const fs = require('fs') +const path = require('path') +const { ipcRenderer } = require('electron') +const DB_PREFIX = 'serious_reading/' +const BOOKS_DOC_ID = DB_PREFIX + 'books' + +// 第三方依赖放 preload/node_modules,原样提交,不压缩 +const iconv = require('./node_modules/iconv-lite') +const AdmZip = require('./node_modules/adm-zip') +let jschardet = null +try { + jschardet = require('./node_modules/jschardet') +} catch (e) { + /* jschardet 缺失时退化为 BOM+GBK */ +} + +/* ---------------- 编码检测 + 解码 ---------------- */ + +function detectByBom(buf) { + if (buf.length >= 3 && buf[0] === 0xef && buf[1] === 0xbb && buf[2] === 0xbf) return 'utf-8' + if (buf.length >= 2 && buf[0] === 0xff && buf[1] === 0xfe) return 'utf-16le' + if (buf.length >= 2 && buf[0] === 0xfe && buf[1] === 0xff) return 'utf-16be' + return null +} + +function detectEncoding(buf) { + const bom = detectByBom(buf) + if (bom) return bom + if (jschardet && buf.length > 0) { + try { + const sample = buf.length > 2500 ? buf.subarray(0, 2500) : buf + const r = jschardet.detect(Buffer.from(sample)) + if (r && r.confidence > 0.5 && r.encoding) return r.encoding.toLowerCase() + } catch (e) {} + } + // 无 BOM 的中文小说大概率 GBK(GB18030 为 GBK 超集,iconv 的 gbk 解码器兼容) + return 'gbk' +} + +function decodeBuffer(buf) { + const enc = detectEncoding(buf) + try { + return iconv.decode(buf, enc) + } catch (e) { + return iconv.decode(buf, 'utf-8') + } +} + +/* ---------------- 暴露给渲染进程的服务 ---------------- */ + +window.services = { + _readerWin: null, + + readTxt(filePath) { + try { + const buf = fs.readFileSync(filePath) + let s = decodeBuffer(buf) + if (s.charCodeAt(0) === 0xfeff) s = s.substring(1) + return s + } catch (e) { + return null + } + }, + + readPdf(filePath) { + try { + const buf = fs.readFileSync(filePath) + // 转 ArrayBuffer 交给 pdfjs + return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength) + } catch (e) { + return null + } + }, + + readBuffer(filePath) { + try { + return fs.readFileSync(filePath) + } catch (e) { + return null + } + }, + + readEpub(filePath) { + try { + const zip = new AdmZip(filePath) + const containerXml = zip.readAsText('META-INF/container.xml') + if (!containerXml) return null + const opfPathMatch = containerXml.match(/full-path="([^"]+)"/) + if (!opfPathMatch) return null + const opfPath = opfPathMatch[1] + const opfDir = opfPath.includes('/') ? opfPath.substring(0, opfPath.lastIndexOf('/') + 1) : '' + const opfXml = zip.readAsText(opfPath) + if (!opfXml) return null + + // manifest + const manifest = {} + const manifestRegex = /]*id="([^"]+)"[^>]*href="([^"]+)"[^>]*media-type="([^"]+)"[^>]*/g + let mMatch + while ((mMatch = manifestRegex.exec(opfXml)) !== null) { + manifest[mMatch[1]] = { href: mMatch[2], type: mMatch[3] } + } + // spine + const spineItems = [] + const spineRegex = /]*toc="([^"]+)"/) + const toc = [] + if (ncxIdMatch) { + const ncxItem = manifest[ncxIdMatch[1]] + if (ncxItem) { + const ncxXml = zip.readAsText(opfDir + ncxItem.href) + if (ncxXml) { + const navRegex = /]*playOrder="(\d+)"[^>]*>[\s\S]*?([^<]+)<\/text>[\s\S]*?]*name="cover"[^>]*content="([^"]+)"/) + if (coverMeta && manifest[coverMeta[1]]) { + try { + cover = zip.readFile(opfDir + manifest[coverMeta[1]].href) + } catch (e) {} + } + + const chapters = [] + spineItems.forEach(function (id, idx) { + const item = manifest[id] + if (!item || !item.type.match(/html|xhtml/)) return + const html = zip.readAsText(opfDir + item.href) + if (!html) return + let title = '' + const tocItem = toc.find((t) => t.src && t.src.includes(item.href)) + if (tocItem) title = tocItem.title + if (!title) title = '第 ' + (idx + 1) + ' 章' + chapters.push({ title: title, content: html, index: idx }) + }) + + const titleMatch = opfXml.match(/]*>([^<]+)<\/dc:title>/) + return { + title: titleMatch ? titleMatch[1] : extractName(filePath), + filePath: filePath, + format: 'epub', + chapters: chapters, + totalChapters: chapters.length, + cover: cover, + } + } catch (e) { + return null + } + }, + + showOpenDialog(options) { + return window.ztools.showOpenDialog(options) + }, + + /* ---------------- 悬浮阅读窗管理 ---------------- */ + + createReaderWindow(state) { + window.services._readerState = state + if (window.services._readerWin && !window.services._readerWin.isDestroyed()) { + try { + window.services._readerWin.show() + window.services._readerWin.focus() + window.services._readerWin.webContents.send('sr:reading-state', state) + } catch (e) {} + return window.services._readerWin + } + const settings = state.settings || {} + const saved = window.ztools.dbStorage.getItem('serious_reading/winpos') || {} + const w = saved.width || settings.window?.width || 520 + const h = saved.height || settings.window?.height || 780 + const x = saved.x != null ? saved.x : window.screenLeft + 90 + const y = saved.y != null ? saved.y : window.screenTop + 180 + + const readerUrl = 'dist/reader.html' + + const readerPreloadPath = path.join(__dirname, 'reader.js') + + const win = window.ztools.createBrowserWindow(readerUrl, { + width: w, + height: h, + x: x, + y: y, + title: '', + transparent: true, + frame: false, + alwaysOnTop: true, + resizable: true, + backgroundColor: 'rgba(255,255,255,0.01)', + skipTaskbar: true, + hasShadow: false, + thickFrame: false, + roundedCorners: false, + movable: true, + minimizable: false, + maximizable: false, + closeable: true, + webPreferences: { preload: readerPreloadPath }, + }, function () { + if (!win) return + win.webContents.send('sr:reading-state', state) + try { win.setAlwaysOnTop(true, 'screen-saver') } catch (e) {} + }) + if (!win) { console.warn('[SR] createReaderWindow returned null (readerUrl=' + readerUrl + ')'); return null } + + // 保存窗口位置/尺寸 + let saveTimer = null + const scheduleSave = function () { + clearTimeout(saveTimer) + saveTimer = setTimeout(function () { + if (!win || win.isDestroyed()) { try { console.log('[SR] win destroyed, skip save') } catch(e) {}; return } + try { + const p = win.getPosition() + const s = win.getSize() + if (s[0] > 120 && s[1] > 120 && s[0] < 8000 && s[1] < 8000) { + window.ztools.dbStorage.setItem('serious_reading/winpos', { x: p[0], y: p[1], width: s[0], height: s[1] }) + } + } catch (e) {} + }, 300) + } + try { win.on('move', scheduleSave) } catch (e) {} + try { win.on('resize', scheduleSave) } catch (e) {} + try { win.on('resized', scheduleSave) } catch (e) {} + try { win.on('moved', scheduleSave) } catch (e) {} + + window.services._readerWin = win + return win + }, + + sendToReader(channel, data) { + const win = window.services._readerWin + if (win && !win.isDestroyed()) { + try { win.webContents.send(channel, data) } catch (e) {} + } + }, + + showReader() { + const win = window.services._readerWin + if (win && !win.isDestroyed()) { + if (!win.isVisible()) win.show() + win.focus() + window.services.sendToReader('sr:show-reader') + return true + } + return false + }, + + toggleReader() { + const win = window.services._readerWin + if (win && !win.isDestroyed()) { + if (win.isVisible()) win.hide() + else { win.show(); win.focus(); window.services.sendToReader('sr:show-reader') } + return true + } + return false + }, +} + +window._ipcRenderer = ipcRenderer + +// 阅读窗 → 主窗:保存进度 + 更新书架 lastChapter +ipcRenderer.on('sr:save-progress', function (e, pg) { + if (!pg || !pg.filePath) return + // 保存 ReadingProgress + try { window.ztools.dbStorage.setItem(DB_PREFIX + 'progress/' + pg.filePath, pg) } catch (e2) {} + // 更新书架书籍的 lastChapter + try { + const doc = window.ztools.db.get(BOOKS_DOC_ID) + if (doc && Array.isArray(doc.data)) { + const idx = doc.data.findIndex(function (b) { return b.path === pg.filePath }) + if (idx >= 0) { + doc.data[idx].lastChapter = pg.chapterIndex + doc.data[idx].progress = pg.charOffset + doc.data[idx].lastRead = Date.now() + if (pg.totalChapters != null) doc.data[idx].totalChapters = pg.totalChapters + window.ztools.db.put(doc) + } + } + } catch (e2) {} + // 通知渲染进程刷新书架 + try { window.dispatchEvent(new CustomEvent('sr:shelf-changed')) } catch (e3) {} +}) + +// 阅读窗 → 主窗:真隐藏 +ipcRenderer.on('sr:hide-reader', function () { + const win = window.services._readerWin + if (win && !win.isDestroyed()) { try { win.hide() } catch (e) {} } +}) + +// 阅读窗 → 主窗:保存窗口位置/尺寸 +ipcRenderer.on('sr:save-bounds', function (e, data) { + if (data && data.x != null && data.width > 0) { + try { window.ztools.dbStorage.setItem('serious_reading/winpos', data) } catch (e2) {} + } +}) + +// 阅读窗 → 主窗:纯 JS 窗口移动 / 缩放(不依赖 -webkit-app-region:drag) +let _winStartBounds = null +ipcRenderer.on('sr:win-start', function () { + const win = window.services._readerWin + if (win && !win.isDestroyed()) { try { _winStartBounds = win.getBounds() } catch (e) {} } +}) +ipcRenderer.on('sr:win-delta', function (e, data) { + const win = window.services._readerWin + if (!win || win.isDestroyed() || !_winStartBounds) return + const { type, dx, dy } = data || {} + if (!type) return + const b = _winStartBounds + let { x, y, width, height } = b + if (type === 'move') { + x = b.x + dx + y = b.y + dy + } else { + if (type.includes('e')) width = b.width + dx + if (type.includes('w')) { width = b.width - dx; x = b.x + dx } + if (type.includes('s')) height = b.height + dy + if (type.includes('n')) { height = b.height - dy; y = b.y + dy } + const MINW = 100, MINH = 50 + if (width < MINW) { if (type.includes('w')) x = b.x + b.width - MINW; width = MINW } + if (height < MINH) { if (type.includes('n')) y = b.y + b.height - MINH; height = MINH } + } + try { win.setBounds({ x, y, width, height }) } catch (e2) {} +}) +ipcRenderer.on('sr:win-end', function () { + _winStartBounds = null + const win = window.services._readerWin + if (win && !win.isDestroyed()) { + try { + const b = win.getBounds() + window.ztools.dbStorage.setItem('serious_reading/winpos', { x: b.x, y: b.y, width: b.width, height: b.height }) + } catch (e) {} + } +}) + +function extractName(p) { + return (p.split(/[\\/]/).pop() || p).replace(/\.[^.]+$/, '') +} \ No newline at end of file diff --git a/plugins/serious-reading/preload/package-lock.json b/plugins/serious-reading/preload/package-lock.json new file mode 100644 index 00000000..5e81be52 --- /dev/null +++ b/plugins/serious-reading/preload/package-lock.json @@ -0,0 +1,57 @@ +{ + "name": "serious-reading-preload-deps", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "serious-reading-preload-deps", + "version": "1.0.0", + "dependencies": { + "adm-zip": "^0.5.17", + "iconv-lite": "^0.7.2", + "jschardet": "^3.1.4" + } + }, + "node_modules/adm-zip": { + "version": "0.5.18", + "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.18.tgz", + "integrity": "sha512-ufJnssQGbxzLNS1Ho9bCtX4rQKCCvoVuDLHoJyc3F9dOGDB4BkWs2Ci0kv53lqocAEQ/Cbi+I2XCsNYGqVYqng==", + "license": "MIT", + "engines": { + "node": ">=12.0" + } + }, + "node_modules/iconv-lite": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", + "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/jschardet": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/jschardet/-/jschardet-3.1.4.tgz", + "integrity": "sha512-/kmVISmrwVwtyYU40iQUOp3SUPk2dhNCMsZBQX0R1/jZ8maaXJ/oZIzUOiyOqcgtLnETFKYChbJ5iDC/eWmFHg==", + "license": "LGPL-2.1+", + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + } + } +} diff --git a/plugins/serious-reading/preload/package.json b/plugins/serious-reading/preload/package.json new file mode 100644 index 00000000..68851ced --- /dev/null +++ b/plugins/serious-reading/preload/package.json @@ -0,0 +1,11 @@ +{ + "name": "serious-reading-preload-deps", + "version": "1.0.0", + "private": true, + "description": "阅读窗/主窗 preload 所需原生依赖(不编译,不压缩,随插件源码提交)", + "dependencies": { + "adm-zip": "^0.5.17", + "iconv-lite": "^0.7.2", + "jschardet": "^3.1.4" + } +} \ No newline at end of file diff --git a/plugins/serious-reading/preload/reader.js b/plugins/serious-reading/preload/reader.js new file mode 100644 index 00000000..3f30df01 --- /dev/null +++ b/plugins/serious-reading/preload/reader.js @@ -0,0 +1,139 @@ +/** + * 阅读窗 preload —— CommonJS,不参与编译打包。 + * 职责:为阅读窗提供文件读取/解码能力(阅读窗自闭环,无需回主窗读章节), + * 暴露 ipcRenderer 用于接收主窗推送的状态、回主窗请求真隐藏/保存进度。 + */ +const fs = require('fs') +const { ipcRenderer } = require('electron') +const iconv = require('./node_modules/iconv-lite') +const AdmZip = require('./node_modules/adm-zip') +let jschardet = null +try { jschardet = require('./node_modules/jschardet') } catch (e) {} + +function detectByBom(buf) { + if (buf.length >= 3 && buf[0] === 0xef && buf[1] === 0xbb && buf[2] === 0xbf) return 'utf-8' + if (buf.length >= 2 && buf[0] === 0xff && buf[1] === 0xfe) return 'utf-16le' + if (buf.length >= 2 && buf[0] === 0xfe && buf[1] === 0xff) return 'utf-16be' + return null +} + +function detectEncoding(buf) { + const bom = detectByBom(buf) + if (bom) return bom + if (jschardet && buf.length > 0) { + try { + const sample = buf.length > 2500 ? buf.subarray(0, 2500) : buf + const r = jschardet.detect(Buffer.from(sample)) + if (r && r.confidence > 0.5 && r.encoding) return r.encoding.toLowerCase() + } catch (e) {} + } + return 'gbk' +} + +function decodeBuffer(buf) { + try { return iconv.decode(buf, detectEncoding(buf)) } catch (e) { return iconv.decode(buf, 'utf-8') } +} + +window.services = { + readTxt(filePath) { + try { + const buf = fs.readFileSync(filePath) + let s = decodeBuffer(buf) + if (s.charCodeAt(0) === 0xfeff) s = s.substring(1) + return s + } catch (e) { return null } + }, + + readPdf(filePath) { + try { + const buf = fs.readFileSync(filePath) + return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength) + } catch (e) { return null } + }, + + readEpub(filePath) { + try { + const zip = new AdmZip(filePath) + const containerXml = zip.readAsText('META-INF/container.xml') + if (!containerXml) return null + const opfPathMatch = containerXml.match(/full-path="([^"]+)"/) + if (!opfPathMatch) return null + const opfPath = opfPathMatch[1] + const opfDir = opfPath.includes('/') ? opfPath.substring(0, opfPath.lastIndexOf('/') + 1) : '' + const opfXml = zip.readAsText(opfPath) + if (!opfXml) return null + const manifest = {} + const manifestRegex = /]*id="([^"]+)"[^>]*href="([^"]+)"[^>]*media-type="([^"]+)"[^>]*/g + let mMatch + while ((mMatch = manifestRegex.exec(opfXml)) !== null) manifest[mMatch[1]] = { href: mMatch[2], type: mMatch[3] } + const spineItems = [] + const spineRegex = /]*toc="([^"]+)"/) + const toc = [] + if (ncxIdMatch) { + const ncxItem = manifest[ncxIdMatch[1]] + if (ncxItem) { + const ncxXml = zip.readAsText(opfDir + ncxItem.href) + if (ncxXml) { + const navRegex = /]*playOrder="(\d+)"[^>]*>[\s\S]*?([^<]+)<\/text>[\s\S]*?]*>([^<]+)<\/dc:title>/) + return { + title: titleMatch ? titleMatch[1] : extractName(filePath), + filePath: filePath, format: 'epub', chapters: chapters, totalChapters: chapters.length, + } + } catch (e) { return null } + }, +} + +function extractName(p) { + return (p.split(/[\\/]/).pop() || p).replace(/\.[^.]+$/, '') +} + +window._ipcRenderer = ipcRenderer + +// 捕获主窗发来的 reply 句柄,用于阅读窗 → 主窗回传 IPC +let _replyToParent = null +const _origOn = ipcRenderer.on.bind(ipcRenderer) +ipcRenderer.on = function (channel, handler) { + _origOn(channel, function (event) { + if (typeof event.reply === 'function') { + _replyToParent = event.reply.bind(event) + } + handler.apply(null, arguments) + }) +} + +// 阅读窗 → 主窗:优先用 event.reply,回退 sendTo(parentId)/send +window._sendToParent = function (channel, data) { + try { + if (_replyToParent) { + _replyToParent(channel, data) + } else if (window.ztools && window.ztools.sendToParent) { + window.ztools.sendToParent(channel, data) + } else { + ipcRenderer.send(channel, data) + } + } catch (e) {} +} \ No newline at end of file diff --git a/plugins/serious-reading/reader.html b/plugins/serious-reading/reader.html new file mode 100644 index 00000000..ecedcca9 --- /dev/null +++ b/plugins/serious-reading/reader.html @@ -0,0 +1,14 @@ + + + + + + + + +
+ + + \ No newline at end of file diff --git a/plugins/serious-reading/src/components/ui/button.tsx b/plugins/serious-reading/src/components/ui/button.tsx new file mode 100644 index 00000000..0cff78c7 --- /dev/null +++ b/plugins/serious-reading/src/components/ui/button.tsx @@ -0,0 +1,42 @@ +import * as React from 'react' +import { Slot } from '@radix-ui/react-slot' +import { cva, type VariantProps } from 'class-variance-authority' +import { cn } from '@/lib/utils' + +const buttonVariants = cva( + 'inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50', + { + variants: { + variant: { + default: 'bg-primary text-primary-foreground hover:bg-primary/90', + destructive: 'bg-destructive text-destructive-foreground hover:bg-destructive/90', + outline: 'border border-input bg-background hover:bg-accent hover:text-accent-foreground', + secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/80', + ghost: 'hover:bg-accent hover:text-accent-foreground', + link: 'text-primary underline-offset-4 hover:underline', + }, + size: { + default: 'h-9 px-4 py-2', + sm: 'h-8 rounded-md px-3 text-xs', + lg: 'h-10 rounded-md px-8', + icon: 'h-9 w-9', + }, + }, + defaultVariants: { variant: 'default', size: 'default' }, + }, +) + +export interface ButtonProps + extends React.ButtonHTMLAttributes, + VariantProps { + asChild?: boolean +} + +export const Button = React.forwardRef( + ({ className, variant, size, asChild = false, ...props }, ref) => { + const Comp = asChild ? Slot : 'button' + return + }, +) +Button.displayName = 'Button' +export { buttonVariants } \ No newline at end of file diff --git a/plugins/serious-reading/src/components/ui/checkbox.tsx b/plugins/serious-reading/src/components/ui/checkbox.tsx new file mode 100644 index 00000000..f3a65e0b --- /dev/null +++ b/plugins/serious-reading/src/components/ui/checkbox.tsx @@ -0,0 +1,23 @@ +import * as React from 'react' +import * as CheckboxPrimitive from '@radix-ui/react-checkbox' +import { Check } from 'lucide-react' +import { cn } from '@/lib/utils' + +export const Checkbox = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + + + +)) +Checkbox.displayName = 'Checkbox' \ No newline at end of file diff --git a/plugins/serious-reading/src/components/ui/command.tsx b/plugins/serious-reading/src/components/ui/command.tsx new file mode 100644 index 00000000..7b2e112a --- /dev/null +++ b/plugins/serious-reading/src/components/ui/command.tsx @@ -0,0 +1,72 @@ +import * as React from 'react' +import { Command as CommandPrimitive } from 'cmdk' +import { Search } from 'lucide-react' +import { cn } from '@/lib/utils' + +export const Command = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +Command.displayName = 'Command' + +export const CommandInput = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( +
+ + +
+)) +CommandInput.displayName = 'CommandInput' + +export const CommandList = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +CommandList.displayName = 'CommandList' + +export const CommandEmpty = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>((props, ref) => ) +CommandEmpty.displayName = 'CommandEmpty' + +export const CommandGroup = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +CommandGroup.displayName = 'CommandGroup' + +export const CommandItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +CommandItem.displayName = 'CommandItem' \ No newline at end of file diff --git a/plugins/serious-reading/src/components/ui/dialog.tsx b/plugins/serious-reading/src/components/ui/dialog.tsx new file mode 100644 index 00000000..7c7e7900 --- /dev/null +++ b/plugins/serious-reading/src/components/ui/dialog.tsx @@ -0,0 +1,66 @@ +import * as React from 'react' +import * as DialogPrimitive from '@radix-ui/react-dialog' +import { X } from 'lucide-react' +import { cn } from '@/lib/utils' + +export const Dialog = DialogPrimitive.Root +export const DialogTrigger = DialogPrimitive.Trigger +export const DialogClose = DialogPrimitive.Close +export const DialogPortal = DialogPrimitive.Portal + +export const DialogOverlay = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +DialogOverlay.displayName = 'DialogOverlay' + +export const DialogContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + + + {children} + + + 关闭 + + + +)) +DialogContent.displayName = 'DialogContent' + +export const DialogHeader = ({ className, ...props }: React.HTMLAttributes) => ( +
+) +export const DialogFooter = ({ className, ...props }: React.HTMLAttributes) => ( +
+) +export const DialogTitle = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +DialogTitle.displayName = 'DialogTitle' +export const DialogDescription = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +DialogDescription.displayName = 'DialogDescription' \ No newline at end of file diff --git a/plugins/serious-reading/src/components/ui/dropdown-menu.tsx b/plugins/serious-reading/src/components/ui/dropdown-menu.tsx new file mode 100644 index 00000000..b13359c8 --- /dev/null +++ b/plugins/serious-reading/src/components/ui/dropdown-menu.tsx @@ -0,0 +1,46 @@ +import * as React from 'react' +import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu' +import { cn } from '@/lib/utils' + +export const DropdownMenu = DropdownMenuPrimitive.Root +export const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger + +export const DropdownMenuContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, sideOffset = 4, ...props }, ref) => ( + + + +)) +DropdownMenuContent.displayName = 'DropdownMenuContent' + +export const DropdownMenuItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +DropdownMenuItem.displayName = 'DropdownMenuItem' +export const DropdownMenuSeparator = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +DropdownMenuSeparator.displayName = 'DropdownMenuSeparator' \ No newline at end of file diff --git a/plugins/serious-reading/src/components/ui/input.tsx b/plugins/serious-reading/src/components/ui/input.tsx new file mode 100644 index 00000000..4c375091 --- /dev/null +++ b/plugins/serious-reading/src/components/ui/input.tsx @@ -0,0 +1,19 @@ +import * as React from 'react' +import { cn } from '@/lib/utils' + +export interface InputProps extends React.InputHTMLAttributes {} + +export const Input = React.forwardRef( + ({ className, type, ...props }, ref) => ( + + ), +) +Input.displayName = 'Input' \ No newline at end of file diff --git a/plugins/serious-reading/src/components/ui/scroll-area.tsx b/plugins/serious-reading/src/components/ui/scroll-area.tsx new file mode 100644 index 00000000..621cabb3 --- /dev/null +++ b/plugins/serious-reading/src/components/ui/scroll-area.tsx @@ -0,0 +1,30 @@ +import * as React from 'react' +import * as ScrollAreaPrimitive from '@radix-ui/react-scroll-area' +import { cn } from '@/lib/utils' + +export const ScrollArea = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + {children} + + + +)) +ScrollArea.displayName = 'ScrollArea' + +export const ScrollBar = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, orientation = 'vertical', ...props }, ref) => ( + + + +)) +ScrollBar.displayName = 'ScrollBar' \ No newline at end of file diff --git a/plugins/serious-reading/src/components/ui/slider.tsx b/plugins/serious-reading/src/components/ui/slider.tsx new file mode 100644 index 00000000..2ce6d7a4 --- /dev/null +++ b/plugins/serious-reading/src/components/ui/slider.tsx @@ -0,0 +1,20 @@ +import * as React from 'react' +import * as SliderPrimitive from '@radix-ui/react-slider' +import { cn } from '@/lib/utils' + +export const Slider = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + + + + +)) +Slider.displayName = 'Slider' \ No newline at end of file diff --git a/plugins/serious-reading/src/components/ui/switch.tsx b/plugins/serious-reading/src/components/ui/switch.tsx new file mode 100644 index 00000000..eb018331 --- /dev/null +++ b/plugins/serious-reading/src/components/ui/switch.tsx @@ -0,0 +1,20 @@ +import * as React from 'react' +import * as SwitchPrimitives from '@radix-ui/react-switch' +import { cn } from '@/lib/utils' + +export const Switch = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + +)) +Switch.displayName = 'Switch' \ No newline at end of file diff --git a/plugins/serious-reading/src/lib/utils.ts b/plugins/serious-reading/src/lib/utils.ts new file mode 100644 index 00000000..e7351594 --- /dev/null +++ b/plugins/serious-reading/src/lib/utils.ts @@ -0,0 +1,11 @@ +import { type ClassValue, clsx } from 'clsx' +import { twMerge } from 'tailwind-merge' + +export function cn(...inputs: ClassValue[]) { + return twMerge(clsx(inputs)) +} + +export function fileExt(p: string): string { + const m = p.match(/\.([^.]+)$/) + return m ? m[1].toLowerCase() : '' +} \ No newline at end of file diff --git a/plugins/serious-reading/src/main/App.tsx b/plugins/serious-reading/src/main/App.tsx new file mode 100644 index 00000000..1594ca66 --- /dev/null +++ b/plugins/serious-reading/src/main/App.tsx @@ -0,0 +1,274 @@ +import { useEffect, useRef, useState } from 'react' +import { Plus, Settings as SettingsIcon, BookOpen } from 'lucide-react' +import type { BookFormat, ParsedBook, ShelfBook, Settings, RecentBook } from '@/shared/types' +import { getSettings, saveSettings, getShelf, addBookToShelf, removeBookFromShelf, updateBookInShelf, coverId, getCover, saveCover, bufferToDataUrl, getProgress, addRecentBook, getHistory } from '@/shared/storage' +import { parseTxt, buildEpub, buildPdf, searchChapters } from '@/shared/parser' +import { useTheme } from './theme' +import { Button } from '@/components/ui/button' +import { ScrollArea } from '@/components/ui/scroll-area' +import { BookCard } from './components/BookCard' +import { SettingsDialog } from './components/SettingsDialog' +import { ChapterDialog } from './components/ChapterDialog' +import { SearchDialog } from './components/SearchDialog' + +export default function App() { + const [settings, setSettings] = useState(() => getSettings()) + const [shelf, setShelf] = useState([]) + const [covers, setCovers] = useState>({}) + const [recents, setRecents] = useState([]) + // 缓存已解析的书籍(内存),供跳转/搜索复用 + const parsedRef = useRef>({}) + const [activeBook, setActiveBook] = useState(null) + const [openChapter, setOpenChapter] = useState(null) + const [openSearch, setOpenSearch] = useState(null) + const [settingsOpen, setSettingsOpen] = useState(false) + const { setTheme } = useTheme(settings, setSettings) + + useEffect(() => { + setShelf(getShelf()) + setRecents(getHistory()) + refreshCovers() + bindFeatures() + const onShelfChange = () => { setShelf(getShelf()); refreshCovers() } + window.addEventListener('sr:shelf-changed', onShelfChange) + return () => window.removeEventListener('sr:shelf-changed', onShelfChange) + }, []) + + function refreshCovers() { + const next: Record = {} + for (const b of getShelf()) { + if (b.cover) { + const buf = getCover(b.id) + if (buf) next[b.id] = bufferToDataUrl(buf) + } + } + setCovers(next) + } + + function bindFeatures() { + const zt = window.ztools + if (!zt) return + zt.onPluginEnter((p) => { + setShelf(getShelf()) + refreshCovers() + const code = p.code + if (code === 'reader_open' && p.type === 'files' && p.payload?.length) { + openFile(p.payload[0].path) + return + } + if (code === 'reader_continue') { + const h = getHistory() + if (h.length) openFile(h[0].filePath, h[0].format as BookFormat) + return + } + if (code === 'show_reader') { + window.services?.showReader() + zt.hideMainWindow?.(false) + return + } + if (code === 'toggle_reader') { + const ok = window.services?.toggleReader() + if (ok) zt.hideMainWindow?.(false) + else openContinue() + return + } + if (code === 'reader') { + zt.setExpendHeight?.(660) + } + }) + } + + function openContinue() { + const h = getHistory() + if (h.length) openFile(h[0].filePath, h[0].format as BookFormat) + } + + /** 解析文件 → 入书架 → 创建悬浮阅读窗 */ + function openFile(filePath: string, fmt?: BookFormat) { + const ext = (filePath.split('.').pop() || '').toLowerCase() as BookFormat + const format = fmt ?? (['txt', 'epub', 'pdf'].includes(ext) ? ext : 'txt') + let book: ParsedBook | null = null + let epubData: EBook | null = null + const svc = window.services + try { + if (format === 'txt') { + const txt = svc?.readTxt(filePath) + if (txt) book = parseTxt(txt, filePath) + } else if (format === 'epub') { + epubData = svc?.readEpub(filePath) ?? null + if (epubData) book = buildEpub(epubData, filePath) + } else if (format === 'pdf') { + const buf = svc?.readPdf(filePath) + if (buf) book = buildPdf(filePath, 0) // PDF 总页数由阅读窗 pdfjs 获取 + } + } catch (e) {} + if (!book) { + ztShow('无法打开: ' + filePath) + return + } + parsedRef.current[filePath] = book + const id = Date.now().toString() + const sb: ShelfBook = { id, type: format, name: book.title, path: filePath, totalChapters: book.totalChapters, progress: 0, lastRead: Date.now() } + if (!addBookToShelf(sb)) { + // 已存在,复用原 id,并更新 totalChapters(旧数据可能缺失) + const exist = getShelf().find((b) => b.path === filePath) + if (exist) { + sb.id = exist.id + if (book.totalChapters && exist.totalChapters !== book.totalChapters) { + updateBookInShelf(exist.id, { totalChapters: book.totalChapters }) + } + } + } else if (format === 'epub' && epubData?.cover) { + saveCover(sb.id, epubData.cover) + refreshCovers() + } + setShelf(getShelf()) + addRecentBook({ filePath, title: book.title, format, lastRead: Date.now() }) + setRecents(getHistory()) + setActiveBook(sb) + launchReader(sb, book) + } + + function launchReader(sb: ShelfBook, book: ParsedBook, skipTo?: { chapterIndex: number; charOffset?: number }) { + const prog = skipTo ? null : getProgress(sb.path) + const state: any = { + filePath: sb.path, + format: sb.type, + settings: getSettings(), + chapterIndex: skipTo?.chapterIndex ?? prog?.chapterIndex ?? 0, + pageIndex: skipTo ? 0 : (prog?.pageIndex ?? 0), + charOffset: skipTo?.charOffset ?? prog?.charOffset, + pdfPage: skipTo?.chapterIndex ?? prog?.chapterIndex, + } + zt_hide() + window.services?.createReaderWindow(state) + } + + function zt_hide() { + window.ztools?.hideMainWindow?.(false) + } + function ztShow(msg: string) { + window.ztools?.showNotification?.(msg) + } + + return ( +
+
+
+ + 严肃阅读 +
+
+ + +
+
+ + + {shelf.length === 0 ? ( +
+ +
+ ) : ( +
+ {shelf.map((b) => ( + openBook(b)} + onChapter={() => setOpenChapter(b)} + onSearch={() => setOpenSearch(b)} + onDelete={() => { + removeBookFromShelf(b.id) + setShelf(getShelf()) + }} + /> + ))} + +
+ )} + + {recents.length > 0 && shelf.length > 0 && ( +
+
最近阅读
+
+ {recents.slice(0, 8).map((r) => ( + + ))} +
+
+ )} +
+ + + {openChapter && ( + { + jumpTo(openChapter, idx, offset) + setOpenChapter(null) + }} + /> + )} + {openSearch && parsedRef.current[openSearch.path] && ( + { + jumpTo(openSearch, idx, offset) + setOpenSearch(null) + }} + /> + )} +
+ ) + + function openBook(b: ShelfBook) { + if (parsedRef.current[b.path]) { + launchReader(b, parsedRef.current[b.path]) + } else { + openFile(b.path, b.type) + } + } + + function jumpTo(b: ShelfBook, chapterIndex: number, charOffset?: number) { + updateBookInShelf(b.id, { lastChapter: chapterIndex, progress: charOffset }) + b.lastChapter = chapterIndex + b.progress = charOffset + const book = parsedRef.current[b.path] + if (book) launchReader(b, book, { chapterIndex, charOffset }) + } + + function pickFile() { + const files = window.services?.showOpenDialog?.({ + title: '选择阅读文件', + filters: [{ name: '支持的格式', extensions: ['txt', 'epub', 'pdf'] }], + properties: ['openFile'], + }) + if (files?.length) openFile(files[0]) + } +} \ No newline at end of file diff --git a/plugins/serious-reading/src/main/components/BookCard.tsx b/plugins/serious-reading/src/main/components/BookCard.tsx new file mode 100644 index 00000000..c71c19e4 --- /dev/null +++ b/plugins/serious-reading/src/main/components/BookCard.tsx @@ -0,0 +1,59 @@ +import { useState } from 'react' +import { MoreVertical, BookMarked, Search, Trash2 } from 'lucide-react' +import type { ShelfBook } from '@/shared/types' +import { + DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, +} from '@/components/ui/dropdown-menu' + +export function BookCard(props: { + book: ShelfBook + cover?: string + progress: number + onOpen: () => void + onChapter: () => void + onSearch: () => void + onDelete: () => void +}) { + const [menu, setMenu] = useState(false) + return ( +
+ +
{props.book.name}
+
+ + + + + + 章节跳转 + 搜索跳转 + + 删除 + + +
+
+ ) +} \ No newline at end of file diff --git a/plugins/serious-reading/src/main/components/ChapterDialog.tsx b/plugins/serious-reading/src/main/components/ChapterDialog.tsx new file mode 100644 index 00000000..35826079 --- /dev/null +++ b/plugins/serious-reading/src/main/components/ChapterDialog.tsx @@ -0,0 +1,50 @@ +import { useState, useMemo } from 'react' +import type { ShelfBook, ParsedBook, Settings } from '@/shared/types' +import { searchChapters } from '@/shared/parser' +import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog' +import { Input } from '@/components/ui/input' +import { Button } from '@/components/ui/button' +import { Search } from 'lucide-react' + +export function ChapterDialog(props: { + book: ShelfBook + parsed: ParsedBook | undefined + settings: Settings + onSkip: (chapterIndex: number, charOffset?: number) => void +}) { + const [kw, setKw] = useState('') + const [committed, setCommitted] = useState('') + const list = useMemo(() => { + if (!props.parsed) return [] + if (!committed) return props.parsed.chapters + return searchChapters(props.parsed.chapters, committed) + }, [props.parsed, committed]) + + const curIdx = props.book.lastChapter ?? 0 + + return ( + props.onSkip(curIdx)}> + + + 章节跳转 · {props.book.name} + +
+ setKw(e.target.value)} onKeyDown={(e) => { if (e.key === 'Enter') setCommitted(kw) }} /> + +
+
+ {list.length === 0 &&
无匹配章节
} + {list.map((ch) => ( + + ))} +
+
+
+ ) +} \ No newline at end of file diff --git a/plugins/serious-reading/src/main/components/SearchDialog.tsx b/plugins/serious-reading/src/main/components/SearchDialog.tsx new file mode 100644 index 00000000..910bbfed --- /dev/null +++ b/plugins/serious-reading/src/main/components/SearchDialog.tsx @@ -0,0 +1,80 @@ +import { useState } from 'react' +import type { ShelfBook, ParsedBook, Settings } from '@/shared/types' +import { searchFullText } from '@/shared/parser' +import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog' +import { Input } from '@/components/ui/input' +import { Button } from '@/components/ui/button' +import { Search } from 'lucide-react' + +export function SearchDialog(props: { + book: ShelfBook + parsed: ParsedBook + settings: Settings + onSkip: (chapterIndex: number, charOffset: number) => void +}) { + const [kw, setKw] = useState('') + const [items, setItems] = useState<{ index: number; snippet: string }[]>([]) + const [hasMore, setHasMore] = useState(false) + const [from, setFrom] = useState(0) + + const fullText = props.parsed.fullText + if (!fullText) { + return ( + props.onSkip(props.book.lastChapter ?? 0, props.book.progress ?? 0)}> + + 搜索跳转 +

PDF 暂不支持全文搜索,仅支持按页跳转。

+
+
+ ) + } + + function doSearch(reset: boolean) { + const start = reset ? 0 : from + const { results, hasMore: hm } = searchFullText(fullText!, kw, 10, start) + setItems(reset ? results : [...items, ...results]) + setHasMore(hm) + setFrom(start + results.length + 1) + } + + /** 由字符 offset 定位所属章节 + 页内偏移 */ + function locate(offset: number) { + const chapters = props.parsed.chapters + let chapter = 0 + for (let i = 0; i < chapters.length; i++) { + const nextOffset = chapters[i + 1] ? (chapters[i + 1].charOffset ?? Infinity) : Infinity + if ((chapters[i].charOffset ?? 0) <= offset && offset < nextOffset) { + chapter = i + break + } + } + props.onSkip(chapter, offset) + } + + return ( + props.onSkip(props.book.lastChapter ?? 0, props.book.progress ?? 0)}> + + + 搜索跳转 · {props.book.name} + +
+ setKw(e.target.value)} onKeyDown={(e) => { if (e.key === 'Enter') doSearch(true) }} /> + +
+
+ {items.map((it) => ( + + )} +
+
+
+ ) +} \ No newline at end of file diff --git a/plugins/serious-reading/src/main/components/SettingsDialog.tsx b/plugins/serious-reading/src/main/components/SettingsDialog.tsx new file mode 100644 index 00000000..2b152e75 --- /dev/null +++ b/plugins/serious-reading/src/main/components/SettingsDialog.tsx @@ -0,0 +1,293 @@ +import { useState, useEffect, useRef } from 'react' +import type { Settings, TriggerKey, HideActions } from '@/shared/types' +import { TRIGGER_OPTIONS, detectConflicts, DEFAULT_SETTINGS, FONT_OPTIONS } from '@/shared/constants' +import { saveSettings } from '@/shared/storage' +import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from '@/components/ui/dialog' +import { Button } from '@/components/ui/button' +import { Input } from '@/components/ui/input' +import { DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem } from '@/components/ui/dropdown-menu' +import { Slider } from '@/components/ui/slider' +import { Switch } from '@/components/ui/switch' +import { Checkbox } from '@/components/ui/checkbox' + +const PAGE_KEYS: { key: 'arrow' | 'wheel' | 'click' | 'pgupdn' | 'space' | 'touch'; label: string }[] = [ + { key: 'arrow', label: '键盘 ←→' }, + { key: 'wheel', label: '滚轮' }, + { key: 'click', label: '点击左右' }, + { key: 'pgupdn', label: 'PageUp/Down' }, + { key: 'space', label: '空格' }, + { key: 'touch', label: '触摸滑动' }, +] + +export function SettingsDialog(props: { + open: boolean + onOpenChange: (v: boolean) => void + settings: Settings + setSettings: (s: Settings) => void + setTheme: (t: Settings['theme']) => void +}) { + const [draft, setDraft] = useState(props.settings) + const conflicts = detectConflicts(draft.hide) + const conflictCount = Object.values(conflicts).filter(Boolean).length + + useEffect(() => { setDraft(props.settings) }, [props.settings, props.open]) + + function patch(p: Partial) { setDraft({ ...draft, ...p }) } + function patchReader(p: Partial) { setDraft({ ...draft, reader: { ...draft.reader, ...p } }) } + function patchPage(p: Partial) { setDraft({ ...draft, page: { ...draft.page, ...p } }) } + function patchHide(p: Partial) { setDraft({ ...draft, hide: { ...draft.hide, ...p } }) } + + function toggleTrigger(group: keyof HideActions, key: TriggerKey) { + const arr = draft.hide[group] + patchHide({ [group]: arr.includes(key) ? arr.filter((k) => k !== key) : [...arr, key] } as any) + } + + function save() { + if (conflictCount > 0) return + saveSettings(draft) + props.setSettings(draft) + // 推送给已打开的阅读窗,实时生效(字号/行高/透明度/触发器等) + ;(window as any).services?.sendToReader?.('sr:settings', draft) + props.onOpenChange(false) + } + function reset() { setDraft(JSON.parse(JSON.stringify(DEFAULT_SETTINGS))) } + + return ( + + + + 设置 + + +
+ {/* 主题 */} +
+
+ {(['auto', 'light', 'dark'] as const).map((t) => ( + + ))} +
+
+ + {/* 阅读外观 */} +
+
+ patchReader({ bgColor: v })} /> + patchReader({ textColor: v })} /> + + patchReader({ opacity: v[0] / 100 })} /> + + + patchReader({ fontSize: v[0] })} /> + + + patchReader({ lineHeight: v[0] })} /> + + + patchReader({ fontWeight: v[0] })} /> + + + patchReader({ fontFamily: v })} /> + + + patchReader({ cleanEmptyLines: v })} /> + +
+
+ + {/* 窗口 */} +
+
+ patch({ window: { ...draft.window, width: +e.target.value || 520 } })} /> + patch({ window: { ...draft.window, height: +e.target.value || 780 } })} /> + patch({ window: { ...draft.window, x: +e.target.value } })} /> + patch({ window: { ...draft.window, y: +e.target.value } })} /> +
+

阅读窗可见时可原地拖动边缘缩放、拖拽移动,无需在此修改。

+
+ + {/* 翻页 */} +
+
+ {PAGE_KEYS.map((p) => ( + + ))} +
+
+ 翻页过渡 + + +
+
+ + {/* 隐藏动作(三功能 · 冲突检测) */} +
+ {(['stealthHide', 'stealthShow', 'realHide'] as const).map((g) => ( +
+
+ {g === 'stealthHide' ? '隐身(显→隐)' : g === 'stealthShow' ? '显示(隐→显)' : '真隐藏(彻底消失,命令恢复)'} +
+
+ {TRIGGER_OPTIONS.map((o) => ( + + ))} +
+
+ ))} + {conflictCount > 0 && ( +

⚠ 触发动作冲突:{Object.entries(conflicts).filter(([,v]) => v).map(([k,v]) => `${k}(${v})`).join('、')},每个动作只能绑一个功能。

+ )} +
+ + {/* 自动翻页 */} +
+ + setDraft({ ...draft, autoPage: { ...draft.autoPage, interval: v[0] } })} /> + + +
+
+ + + + + +
+
+ ) +} + +function Section({ title, children }: { title: string; children: React.ReactNode }) { + return ( +
+
{title}
+ {children} +
+ ) +} +function Field({ label, children }: { label: string; children: React.ReactNode }) { + return ( +
+
{label}
+ {children} +
+ ) +} + +function ColorPicker({ value, onChange }: { value: string; onChange: (v: string) => void }) { + const ref = useRef(null) + const [hex, setHex] = useState(value) + const [picking, setPicking] = useState(false) + const [screenImg, setScreenImg] = useState(null) + const canvasRef = useRef(null) + useEffect(() => { setHex(value) }, [value]) + + useEffect(() => { + if (!screenImg || !canvasRef.current) return + const img = new Image() + img.onload = () => { + const el = canvasRef.current! + el.width = img.width; el.height = img.height + el.getContext('2d')?.drawImage(img, 0, 0) + } + img.src = screenImg + }, [screenImg]) + + function startPick() { + const zt = window.ztools + if (!zt?.screenCapture) { ref.current?.click(); return } + zt.hideMainWindow?.(true) + setTimeout(() => { + try { + zt.screenCapture!((img) => { + zt.showMainWindow?.() + if (typeof img === 'string' && img.startsWith('data:')) { + setScreenImg(img) + setPicking(true) + } else { + ref.current?.click() + } + }) + } catch { + zt.showMainWindow?.() + ref.current?.click() + } + }, 300) + } + + function handlePickClick(e: React.MouseEvent) { + if (!canvasRef.current) return + const rect = canvasRef.current.getBoundingClientRect() + const sx = canvasRef.current.width / rect.width + const sy = canvasRef.current.height / rect.height + const x = Math.floor((e.clientX - rect.left) * sx) + const y = Math.floor((e.clientY - rect.top) * sy) + const ctx = canvasRef.current.getContext('2d') + if (!ctx) return + const px = ctx.getImageData(x, y, 1, 1).data + const c = '#' + [px[0], px[1], px[2]].map((v) => v.toString(16).padStart(2, '0')).join('') + onChange(c); setHex(c); setPicking(false); setScreenImg(null) + } + + return ( + <> + {picking && screenImg && ( +
{ if (e.key === 'Escape') { setPicking(false); setScreenImg(null) } }}> + +
点击任意位置取色 · Esc 取消
+
+ )} +
+
+ 吸 +
+ { onChange(e.target.value); setHex(e.target.value) }} className="h-9 flex-1 rounded border" /> +
+ + ) +} + +function isLight(hex: string): boolean { + const r = parseInt(hex.slice(1, 3), 16), g = parseInt(hex.slice(3, 5), 16), b = parseInt(hex.slice(5, 7), 16) + return (r * 299 + g * 587 + b * 114) / 1000 > 128 +} + +function FontSelect({ value, onChange }: { value: string; onChange: (v: string) => void }) { + const current = FONT_OPTIONS.find((f) => f.value === value) + return ( + + + + + + {FONT_OPTIONS.map((f) => ( + onChange(f.value)} style={{ fontFamily: f.value === 'default' ? 'inherit' : f.value }}> + {f.label} + + ))} + + + ) +} \ No newline at end of file diff --git a/plugins/serious-reading/src/main/main.tsx b/plugins/serious-reading/src/main/main.tsx new file mode 100644 index 00000000..f137e0ac --- /dev/null +++ b/plugins/serious-reading/src/main/main.tsx @@ -0,0 +1,10 @@ +import React from 'react' +import ReactDOM from 'react-dom/client' +import App from './App' +import '@/styles/globals.css' + +ReactDOM.createRoot(document.getElementById('root')!).render( + + + , +) \ No newline at end of file diff --git a/plugins/serious-reading/src/main/theme.ts b/plugins/serious-reading/src/main/theme.ts new file mode 100644 index 00000000..0c698015 --- /dev/null +++ b/plugins/serious-reading/src/main/theme.ts @@ -0,0 +1,38 @@ +import { useEffect } from 'react' +import type { Settings } from '@/shared/types' +import { getSettings, saveSettings } from '@/shared/storage' + +const MEDIA = typeof window !== 'undefined' ? window.matchMedia('(prefers-color-scheme: dark)') : null + +export function resolveTheme(theme: Settings['theme']): 'light' | 'dark' { + if (theme === 'auto') return MEDIA?.matches ? 'dark' : 'light' + return theme +} + +export function useTheme(settings: Settings, setSettings: (s: Settings) => void) { + useEffect(() => { + const root = document.documentElement + root.classList.toggle('dark', resolveTheme(settings.theme) === 'dark') + }, [settings.theme]) + + useEffect(() => { + if (!MEDIA) return + const handler = () => { + if (settings.theme === 'auto') { + document.documentElement.classList.toggle('dark', MEDIA.matches) + } + } + MEDIA.addEventListener('change', handler) + return () => MEDIA.removeEventListener('change', handler) + }, [settings.theme]) + + const setTheme = (theme: Settings['theme']) => { + const next = { ...settings, theme } + saveSettings(next) + setSettings(next) + ;(window as any).services?.sendToReader?.('sr:settings', next) + } + return { setTheme } +} + +export { getSettings, saveSettings } \ No newline at end of file diff --git a/plugins/serious-reading/src/reader/App.tsx b/plugins/serious-reading/src/reader/App.tsx new file mode 100644 index 00000000..0eb1327a --- /dev/null +++ b/plugins/serious-reading/src/reader/App.tsx @@ -0,0 +1,471 @@ +import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react' +import type { ParsedBook, Chapter, BookFormat, Settings, ReadingProgress, TriggerKey } from '@/shared/types' +import { getSettings, getProgress, saveProgress, addRecentBook } from '@/shared/storage' +import { parseTxt, buildEpub, renderChapterHtml } from '@/shared/parser' +import { PdfView } from './components/PdfView' + +export default function App() { + const [settings, setSettings] = useState(() => getSettings()) + const [book, setBook] = useState(null) + const [chapter, setChapter] = useState(null) + const [chapterIdx, setChapterIdx] = useState(0) + const [pageIndex, setPageIndex] = useState(0) + const [charOffset, setCharOffset] = useState(0) + const [stealth, setStealth] = useState(false) + const [resizeN, setResizeN] = useState(0) + // PDF + const [pdfData, setPdfData] = useState(null) + const [pdfPage, setPdfPage] = useState(1) + const [pdfTotal, setPdfTotal] = useState(0) + // 分页 + const [pages, setPages] = useState(['

正在加载…

']) + const measureRef = useRef(null) + const [dragWin, setDragWin] = useState<{ sx: number; sy: number } | null>(null) + + const ipc = (window as any)._ipcRenderer + const sendParent = (window as any)._sendToParent as ((c: string, d?: any) => void) | undefined + + /* ---- 接收主窗推送 ---- */ + useEffect(() => { + ipc?.on('sr:reading-state', (_e: unknown, state: any) => loadState(state)) + ipc?.on('sr:show-reader', () => setStealth(false)) + ipc?.on('sr:settings', (_e: unknown, s: Settings) => { if (s) setSettings(s) }) + return () => {} + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []) + + function loadState(state: any) { + if (!state?.filePath) return + setStealth(false) + void loadBook(state.filePath, state.format as BookFormat, state) + } + + async function loadBook(filePath: string, format: BookFormat, state?: any) { + const svc = window.services + let pb: ParsedBook | null = null + if (format === 'txt') { + const txt = svc?.readTxt(filePath) + if (txt) pb = parseTxt(txt, filePath) + } else if (format === 'epub') { + const eb = svc?.readEpub(filePath) + if (eb) pb = buildEpub(eb, filePath) + } else if (format === 'pdf') { + const buf = svc?.readPdf(filePath) + if (buf) { + setPdfData(buf) + setPdfPage(state?.pdfPage ?? state?.chapterIndex ?? 1) + const fake = { title: filePath.split(/[\\/]/).pop()!, filePath, format: 'pdf' as BookFormat, chapters: [], totalChapters: 1 } + setBook(fake) + addRecentBook({ filePath, title: fake.title, format: 'pdf', lastRead: Date.now() }) + saveProgress({ filePath, format: 'pdf', chapterIndex: state?.pdfPage ?? 1, pageIndex: 0, timestamp: Date.now() }) + } + return + } + if (!pb) return + setPdfData(null) + setBook(pb) + addRecentBook({ filePath, title: pb.title, format, lastRead: Date.now() }) + const prog = getProgress(filePath) + const ci = state?.chapterIndex ?? prog?.chapterIndex ?? 0 + setChapterIdx(ci) + setCharOffset(state?.charOffset ?? prog?.charOffset ?? (pb.chapters[ci]?.charOffset ?? 0)) + setPageIndex(state?.pageIndex ?? prog?.pageIndex ?? 0) + } + + const currentChapter = book?.chapters[chapterIdx] ?? null + useEffect(() => { setChapter(currentChapter) }, [currentChapter]) + const chapterHtml = useMemo( + () => { + if (!book || !chapter) return '' + const body = renderChapterHtml(chapter.content, book.format, settings.reader.cleanEmptyLines) + if (book.format === 'txt' && chapter.title && chapter.title !== '全文') { + const esc = chapter.title.replace(/&/g, '&').replace(//g, '>') + return `

${esc}

${body}` + } + return body + }, + [book, chapter, settings.reader.cleanEmptyLines], + ) + + /* ---- 高度测量分页(txt/epub) ---- */ + useLayoutEffect(() => { + if (!book || book.format === 'pdf' || !chapterHtml) return + const measure = measureRef.current + if (!measure) return + const pageH = window.innerHeight - 16 + const pageW = window.innerWidth - 24 + measure.style.width = pageW + 'px' + measure.style.fontSize = settings.reader.fontSize + 'px' + measure.style.lineHeight = String(settings.reader.lineHeight) + measure.style.fontFamily = settings.reader.fontFamily || 'inherit' + measure.style.fontWeight = String(settings.reader.fontWeight) + measure.innerHTML = chapterHtml + const nodes: { height: number; html: string }[] = [] + const blockTags = ['P', 'DIV', 'BLOCKQUOTE', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'UL', 'OL', 'LI', 'PRE', 'TABLE', 'SECTION', 'ARTICLE', 'BODY'] + function collectFrom(parent: Node) { + const children = Array.from(parent.childNodes) + for (const node of children) { + if (node.nodeType === 3 && node.textContent?.trim()) { + const span = document.createElement('span') + span.textContent = node.textContent + node.parentNode?.replaceChild(span, node) + const cs = getComputedStyle(span) + nodes.push({ height: span.offsetHeight + parseFloat(cs.marginTop) + parseFloat(cs.marginBottom), html: span.outerHTML }) + } else if (node.nodeType === 1) { + const el = node as HTMLElement + const hasBlockChildren = Array.from(el.children).some((child) => blockTags.includes(child.tagName)) + if (hasBlockChildren) { + collectFrom(el) + } else { + const cs = getComputedStyle(el) + nodes.push({ height: el.offsetHeight + parseFloat(cs.marginTop) + parseFloat(cs.marginBottom), html: el.outerHTML }) + } + } + } + } + collectFrom(measure) + const result: string[] = [] + let cur = '' + let used = 0 + for (const n of nodes) { + if (used + n.height > pageH && used > 0) { result.push(cur); cur = ''; used = 0 } + cur += n.html + used += n.height + } + if (cur) result.push(cur) + measure.innerHTML = '' + setPages(result.length ? result : ['

本章无内容

']) + setPageIndex((p) => p === -1 ? result.length - 1 : Math.min(p, result.length - 1)) + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [chapterHtml, settings.reader.fontSize, settings.reader.lineHeight, settings.reader.fontWeight, settings.reader.fontFamily, resizeN]) + + /* ---- 百分比 ---- */ + const percent = useMemo(() => { + if (!book) return 0 + if (book.format === 'pdf') return pdfTotal ? (pdfPage / pdfTotal) * 100 : 0 + if (book.fullText) return ((charOffset / book.fullText.length) * 100) + return book.totalChapters ? ((chapterIdx + 1) / book.totalChapters) * 100 : 0 + }, [book, charOffset, chapterIdx, pdfPage, pdfTotal]) + + /* ---- 翻页 ---- */ + const nextPage = useCallback(() => { + if (book?.format === 'pdf') { setPdfPage((p) => Math.min(p + 1, pdfTotal || p + 1)); return } + if (pageIndex < pages.length - 1) setPageIndex(pageIndex + 1) + else nextChapter() + }, [book, pageIndex, pages.length]) + + const prevPage = useCallback(() => { + if (book?.format === 'pdf') { setPdfPage((p) => Math.max(1, p - 1)); return } + if (pageIndex > 0) setPageIndex(pageIndex - 1) + else prevChapter() + }, [book, pageIndex]) + + // 用 ref 持有最新回调/状态,避免 document 事件监听反复挂载 + const nextPageRef = useRef(nextPage); nextPageRef.current = nextPage + const prevPageRef = useRef(prevPage); prevPageRef.current = prevPage + const pagesRef = useRef(pages); pagesRef.current = pages + const hideRef = useRef(settings.hide); hideRef.current = settings.hide + const pageCfgRef = useRef(settings.page); pageCfgRef.current = settings.page + const stealthRef = useRef(stealth); stealthRef.current = stealth + + function nextChapter() { + if (!book || book.format === 'pdf') return + if (chapterIdx < book.totalChapters - 1) { + const ni = chapterIdx + 1 + setPages(['

']) + setChapterIdx(ni) + setCharOffset(book.chapters[ni]?.charOffset ?? 0) + setPageIndex(0) + } + } + function prevChapter() { + if (!book || book.format === 'pdf') return + if (chapterIdx > 0) { + const pi = chapterIdx - 1 + setPages(['

']) + setChapterIdx(pi) + setCharOffset(book.chapters[pi]?.charOffset ?? 0) + setPageIndex(-1) + } + } + function goChapter(idx: number) { + if (!book || book.format === 'pdf') return + if (idx >= 0 && idx < book.totalChapters) { + setChapterIdx(idx) + setCharOffset(book.chapters[idx]?.charOffset ?? 0) + setPageIndex(0) + } + } + + /* ---- 进度保存 ---- */ + useEffect(() => { + if (!book) return + const t = setTimeout(() => { + const pg: ReadingProgress = { + filePath: book.filePath, format: book.format, + chapterIndex: book.format === 'pdf' ? pdfPage : chapterIdx, + pageIndex, charOffset, totalChapters: book.totalChapters, timestamp: Date.now(), + } + saveProgress(pg) + sendParent?.('sr:save-progress', pg) + }, 400) + return () => clearTimeout(t) + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [chapterIdx, pageIndex, charOffset, pdfPage]) + + /* ---- document 级事件:阅读窗容器是 drag region,React 事件会被吞掉 ---- */ + function dispatchTrigger(key: TriggerKey, e?: Event) { + const hide = hideRef.current + if (hide.realHide.includes(key)) { e?.preventDefault?.(); sendParent?.('sr:hide-reader'); return } + const isStealth = stealthRef.current + if (!isStealth && hide.stealthHide.includes(key)) { setStealth(true); return } + if (isStealth && hide.stealthShow.includes(key)) { setStealth(false); return } + } + useEffect(() => { + window.focus() + const onKey = (e: KeyboardEvent) => { + const hide = hideRef.current + if (e.key === 'Escape' && (hide.stealthHide.includes('escape') || hide.stealthShow.includes('escape') || hide.realHide.includes('escape'))) { dispatchTrigger('escape', e); e.preventDefault(); return } + const p = pageCfgRef.current + if (p.arrow && e.key === 'ArrowRight') { nextPageRef.current(); e.preventDefault() } + if (p.arrow && e.key === 'ArrowLeft') { prevPageRef.current(); e.preventDefault() } + if (p.pgupdn && e.key === 'PageDown') { nextPageRef.current(); e.preventDefault() } + if (p.pgupdn && e.key === 'PageUp') { prevPageRef.current(); e.preventDefault() } + if (p.space && e.key === ' ') { nextPageRef.current(); e.preventDefault() } + if (e.key === 'Home') { setPageIndex(0) } + if (e.key === 'End') { setPageIndex(pagesRef.current.length - 1) } + } + const onDbl = (e: MouseEvent) => { dispatchTrigger('dblclick', e) } + const onDown = (e: MouseEvent) => { if (e.button === 1) dispatchTrigger('middleClick', e) } + const onCtx = (e: MouseEvent) => { + const hide = hideRef.current + if (hide.realHide.includes('rightClick') || hide.stealthHide.includes('rightClick') || hide.stealthShow.includes('rightClick')) { e.preventDefault(); dispatchTrigger('rightClick', e) } + } + const onLeave = () => { dispatchTrigger('mouseleave') } + const onEnter = () => { dispatchTrigger('mouseenter') } + const onWheel = (e: WheelEvent) => { + if (pageCfgRef.current.wheel) { e.preventDefault(); if (e.deltaY > 0 || e.deltaX > 0) nextPageRef.current(); else prevPageRef.current() } + } + let tx = 0, ty = 0 + const onTouchStart = (e: TouchEvent) => { tx = e.touches[0].clientX; ty = e.touches[0].clientY } + const onTouchEnd = (e: TouchEvent) => { + if (!pageCfgRef.current.touch) return + const dx = e.changedTouches[0].clientX - tx + const dy = e.changedTouches[0].clientY - ty + if (Math.abs(dx) > Math.abs(dy) && Math.abs(dx) > 50) { if (dx < 0) nextPageRef.current(); else prevPageRef.current() } + } + document.addEventListener('keydown', onKey) + document.addEventListener('dblclick', onDbl) + document.addEventListener('mousedown', onDown) + document.addEventListener('contextmenu', onCtx) + document.addEventListener('mouseleave', onLeave) + document.addEventListener('mouseenter', onEnter) + document.addEventListener('wheel', onWheel, { passive: false }) + document.addEventListener('touchstart', onTouchStart) + document.addEventListener('touchend', onTouchEnd) + return () => { + document.removeEventListener('keydown', onKey) + document.removeEventListener('dblclick', onDbl) + document.removeEventListener('mousedown', onDown) + document.removeEventListener('contextmenu', onCtx) + document.removeEventListener('mouseleave', onLeave) + document.removeEventListener('mouseenter', onEnter) + document.removeEventListener('wheel', onWheel) + document.removeEventListener('touchstart', onTouchStart) + document.removeEventListener('touchend', onTouchEnd) + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []) + + /* ---- 自动翻页 ---- */ + useEffect(() => { + const interval = settings.autoPage.interval + if (!interval || interval <= 0) return + if (stealth && settings.autoPage.pauseOnStealth) return + const t = setInterval(() => nextPage(), interval * 1000) + return () => clearInterval(t) + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [settings.autoPage, nextPage, stealth]) + + /* ---- 中间区域拖动移动窗口 ---- */ + useEffect(() => { + if (!dragWin) return + const onMove = (e: MouseEvent) => { + sendParent?.('sr:win-delta', { type: 'move', dx: e.screenX - dragWin.sx, dy: e.screenY - dragWin.sy }) + } + const onUp = () => { + sendParent?.('sr:win-end') + setDragWin(null) + } + document.addEventListener('mousemove', onMove) + document.addEventListener('mouseup', onUp) + return () => { document.removeEventListener('mousemove', onMove); document.removeEventListener('mouseup', onUp) } + }, [dragWin, sendParent]) + + /* ---- resize 重排 ---- */ + useEffect(() => { + const onRes = () => setResizeN((n) => n + 1) + window.addEventListener('resize', onRes) + const saveT = setInterval(() => sendBounds(), 5000) + window.addEventListener('beforeunload', sendBounds) + return () => { window.removeEventListener('resize', onRes); clearInterval(saveT); window.removeEventListener('beforeunload', sendBounds) } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []) + + function sendBounds() { + const x = window.screenLeft, y = window.screenTop, w = window.innerWidth, h = window.innerHeight + if (w > 120 && h > 120 && w < 8000 && h < 8000) sendParent?.('sr:save-bounds', { x, y, width: w, height: h }) + } + + /* ---- 百分比跳转 ---- */ + function jumpPercent(v: number) { + if (!book) return + if (book.format === 'pdf') { setPdfPage(Math.max(1, Math.ceil((v / 100) * pdfTotal))); return } + if (book.fullText) { + const target = Math.round((v / 100) * book.fullText.length) + let ci = 0 + for (let i = 0; i < book.chapters.length; i++) { + const c = book.chapters[i] + if ((c.charOffset ?? 0) <= target) ci = i + } + setChapterIdx(ci); setCharOffset(target); setPageIndex(0) + } else { + const ci = Math.min(book.totalChapters - 1, Math.floor((v / 100) * book.totalChapters)) + goChapter(ci) + } + } + + if (!book && !pdfData) { + return
正在加载…
+ } + + return ( +
+ {/* 纯 JS 窗口移动 + 缩放把手(不使用 drag region,避免吞掉右键/中键) */} + + + {/* 内容区:整窗无 drag region,右键/中键/双击/滚轮/翻页全部正常 */} +
e.preventDefault()} + > + {/* 测量容器 */} +
+ + {/* PDF 模式 */} + {book?.format === 'pdf' && pdfData ? ( +
+ +
+ ) : ( + <> + {/* 分页内容条:绝对定位单页切换 */} +
+ {pages.map((p, i) => ( +
+ ))} +
+ + {/* 翻页点击区(左右各 30%)+ 中间拖动移动区(40%) */} + {settings.page.click && !stealth ? ( + <> +