diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..410601c --- /dev/null +++ b/.editorconfig @@ -0,0 +1,9 @@ +root = true +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +indent_style = space +indent_size = 2 +[*.md] +trim_trailing_whitespace = false diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e82fbce --- /dev/null +++ b/.gitignore @@ -0,0 +1,199 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# UV +# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +#uv.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/latest/usage/project/#working-with-version-control +.pdm.toml +.pdm-python +.pdm-build/ + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ + +# Abstra +# Abstra is an AI-powered process automation framework. +# Ignore directories containing user credentials, local state, and settings. +# Learn more at https://abstra.io/docs +.abstra/ + +# Visual Studio Code +# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore +# that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore +# and can be added to the global gitignore or merged into this file. However, if you prefer, +# you could uncomment the following to ignore the enitre vscode folder +# .vscode/ + +# Ruff stuff: +.ruff_cache/ + +# PyPI configuration file +.pypirc + +# Cursor +# Cursor is an AI-powered code editor. `.cursorignore` specifies files/directories to +# exclude from AI features like autocomplete and code analysis. Recommended for sensitive data +# refer to https://docs.cursor.com/context/ignore-files +.cursorignore +.cursorindexingignore + +# Node.js tooling +node_modules/ +.vite/ +coverage/ diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..4926aa8 --- /dev/null +++ b/.prettierrc @@ -0,0 +1 @@ +{ "singleQuote": true, "trailingComma": "all", "printWidth": 100 } diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..6e8441f --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,8 @@ +# Changelog + +## 1.0.0 - 2026-07-19 + +- Refactored the single-file application into native ES Modules. +- Preserved existing storage keys, seed tasks, and version 1.0 backup format. +- Replaced inline handlers with static listeners and delegated actions. +- Added resilient normalization, scoped rendering, i18n interpolation, and automated tests. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..f80cf64 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 ghuang.top + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index fb3aab0..a033268 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,214 @@ -# Tasks +# 任务管理系统 +一个基于浏览器运行的现代化任务管理系统,支持任务生命周期管理、团队协作、进度跟踪、数据备份和中英文界面。项目已从单体脚本重构为原生 ES Modules,并兼容已有任务数据、偏好设置和 1.0 版 JSON 备份。 + +## 核心特性 + +### 任务管理 + +- **三状态管理**:待办事项、进行中、已完成 +- **双重描述系统**:任务描述(目标要求)和任务进展(实时状态) +- **可视化进度**:使用 0–100% 进度条跟踪任务 +- **优先级管理**:高、中、低三级优先级及颜色标识 +- **截止日期**:支持到期提醒和过期任务标识 +- **任务置顶**:重要任务在所有排序方式中优先显示 + +### 协作与记录 + +- **人员管理**:支持负责人和协作者 +- **时间轴记录**: + - 自动记录状态、进度、负责人、截止日期、优先级和进展内容变更 + - 支持手动添加、编辑和删除记录 + - 使用不同类型区分系统记录和手动记录;编辑系统记录会将其转为手动记录,并记在当前负责人名下 + - 编辑已有的手动记录只更新内容,原记录人保持不变 + - 记录按时间倒序展示,同一天内新增的记录也排在前面 + +### 搜索、排序与显示 + +- **13 种排序模式**:支持标题、数字、日期、优先级、进度和创建时间排序 +- **实时搜索**:搜索标题、描述、进展、负责人、协作者、到期日期、优先级和时间轴内容,界面固定文字不参与匹配;无匹配结果时栏内给出提示 +- **任务隐藏**:待办和已完成任务可单独隐藏,并可批量显示或隐藏 +- **国际化**:支持中英文界面切换 + +### 数据与兼容性 + +- **本地存储**:基于 `localStorage`,无需后端服务 +- **实时保存**:每次操作后自动持久化 +- **导入导出**:支持 JSON 覆盖导入和合并导入 +- **向后兼容**:保留原有存储键和 1.0 版备份格式 +- **容错处理**:损坏的任务原始数据会备份;存储不可用时当前页面会继续以内存模式运行 + +## 快速开始 + +### 安装与运行 + +```bash +npm install +npm start +``` + +打开 `serve` 输出的地址即可使用。由于应用采用原生 ES Modules,必须通过 HTTP 服务器访问,不支持使用 `file://` 直接打开 `src/index.html`。 + +也可以使用其他静态服务器: + +```bash +python -m http.server 8000 --directory src +# 或 +npx http-server src +``` + +### 质量检查 + +```bash +npm run lint +npm run format:check +npm test +``` + +如需自动格式化: + +```bash +npm run format +``` + +### 系统要求 + +- 支持原生 ES Modules 的现代浏览器 +- 支持 `localStorage`(通常约 5–10 MB) +- 可访问 Bootstrap 和 Font Awesome 的 CDN 资源 + +## 使用指南 + +### 基本操作 + +1. **添加任务**:填写标题、描述、进展、优先级等信息。 +2. **编辑任务**:点击任务卡片上的编辑按钮。 +3. **状态管理**:编辑任务并更改状态。 +4. **进度跟踪**:更新任务进度和进展内容,系统会自动生成时间轴记录。 +5. **任务置顶**:点击图钉按钮,使重要任务优先显示。 + +### 任务隐藏 + +- **适用范围**:隐藏仅对待办和已完成任务生效;进行中任务即使被标记隐藏也会正常显示、正常排序。 +- **单个隐藏**:编辑任务并勾选“隐藏此任务”,或点击卡片上的隐藏按钮。 +- **批量查看**:待办栏和已完成栏的眼睛按钮可显示或隐藏被隐藏的任务。 +- **排序规则**:被隐藏的待办和已完成任务在排序结果中自动排到非隐藏任务之后。 +- **视觉区分**:显示隐藏任务时,卡片以半透明样式呈现。 +- **数量统计**:栏目标题的计数与当前实际显示的卡片一致,会随搜索结果变化。 + +### 数据备份 + +- 导出的文件名格式为 `backup-tasks-yyyy-mm-dd.json`。 +- 覆盖导入会替换任务及兼容的排序偏好。 +- 合并导入会保留现有任务,并按 `title|status|createdDate` 跳过重复项;新导入任务会分配唯一 ID。 + +## 技术架构 + +### 技术栈 + +- **前端**:HTML5、CSS3、JavaScript(ES Modules) +- **UI 框架**:Bootstrap 5.3.3 +- **图标**:Font Awesome 6.7.2 +- **存储**:`localStorage` +- **测试**:Vitest + jsdom +- **代码质量**:ESLint + Prettier +- **部署方式**:纯静态部署 + +### 项目结构 + +- `src/index.html`:应用页面和模态框结构 +- `src/css/styles.css`:应用样式和响应式布局 +- `src/js/main.js`:初始化和委托事件处理 +- `src/js/config.js`:状态、排序选项、存储键和共享常量 +- `src/js/utils.js`:ID、日期、到期状态和 HTML 转义工具 +- `src/js/task-model.js`:任务和偏好数据标准化 +- `src/js/store.js`:应用状态及持久化变更 +- `src/js/storage.js`:唯一直接访问 `localStorage` 的应用模块 +- `src/js/sort.js`:任务比较器和 13 种排序模式 +- `src/js/render.js`:任务、时间轴、计数和排序选项渲染 +- `src/js/modal.js`:任务表单填充和保存 +- `src/js/timeline.js`:手动及系统时间轴记录 +- `src/js/import-export.js`:1.0 版备份验证、导入和导出 +- `src/js/notify.js`:通知和截止日期提醒 +- `src/js/seed-data.js`:首次运行时的示例任务 +- `src/js/i18n/`:中英文词典及插值逻辑 +- `test/`:单元测试和旧版数据兼容性测试 + +## 本地存储 + +应用保留以下六个原有存储键: + +- `tasks` +- `taskSortOrders` +- `showHiddenCompletedTasks` +- `showHiddenTodoTasks` +- `tasksInitialized` +- `tasksCorruptedBackup` + +任务、排序和显示偏好使用 JSON 编码。若任务列表格式损坏,原始文本会保存到 `tasksCorruptedBackup`。当浏览器存储不可用或空间不足时,应用会在当前页面会话中继续运行,但刷新后内存中的改动会丢失。 + +## 备份格式 + +```json +{ + "version": "1.0", + "exportDate": "2026-07-20T00:00:00.000Z", + "localDate": "2026-07-20", + "taskCount": 1, + "data": { + "tasks": [], + "sortOrders": { + "todo": "default", + "inProgress": "default", + "completed": "default" + } + } +} +``` + +## 任务数据结构 + +```javascript +{ + id: Number, // 唯一标识 + title: String, // 任务标题 + description: String, // 任务描述 + progressNotes: String, // 任务进展 + status: String, // todo | inProgress | completed + progress: Number, // 0–100 + assignee: String, // 负责人 + collaborators: Array, // 协作者 + dueDate: String, // 截止日期 + priority: String, // low | medium | high + language: String, // zh | en + isPinned: Boolean, // 是否置顶 + isHidden: Boolean, // 是否隐藏 + createdDate: String, // 创建时间 + timeline: Array // 时间轴记录 +} +``` + +## 故障排除 + +- **页面无法直接打开**:请使用 HTTP 服务器运行,原生 ES Modules 不支持 `file://` 加载。 +- **数据丢失**:检查浏览器是否允许 `localStorage`,并定期导出备份。 +- **导入失败**:确认文件为 1.0 版 JSON 备份,并检查任务字段和日期格式。 +- **界面异常**:检查 Bootstrap 和 Font Awesome CDN 是否可访问,并尝试清空缓存。 +- **性能问题**:任务数量较多时,建议归档已完成任务并清理不必要的时间轴记录。 + +## 贡献指南 + +欢迎提交 Issue 和 Pull Request。 + +- **问题反馈**:请详细描述问题场景和复现步骤。 +- **功能建议**:请说明需求和使用场景。 +- **代码贡献**:遵循现有代码风格,并运行 lint、格式检查和测试。 + +## 许可证 + +MIT License + +--- + +- **版本**:v1.0.0 +- **重构日期**:2026 年 7 月 diff --git a/deploy.sh b/deploy.sh new file mode 100644 index 0000000..bd49b7f --- /dev/null +++ b/deploy.sh @@ -0,0 +1,173 @@ +#!/usr/bin/env bash + +set -Eeuo pipefail + +REPO_URL="https://github.com/ghuang-top/Tasks" +REPO_ARCHIVE_URL="${REPO_URL}/archive/refs/heads/main.tar.gz" +TARGET_DIR="/root/data/docker_data/Nginx/html/tasks" + +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[0;33m' +BLUE='\033[0;34m' +CYAN='\033[0;36m' +NC='\033[0m' + +print_info() { + echo -e "${BLUE}[INFO]${NC} $1" +} + +print_success() { + echo -e "${GREEN}[SUCCESS]${NC} $1" +} + +print_warning() { + echo -e "${YELLOW}[WARNING]${NC} $1" +} + +print_error() { + echo -e "${RED}[ERROR]${NC} $1" >&2 +} + +check_root() { + if [[ ${EUID} -ne 0 ]]; then + print_error "此脚本需要 root 权限运行" + print_info "请切换到 root 用户,或使用 sudo bash 执行脚本" + exit 1 + fi +} + +check_dependencies() { + local command_name + for command_name in curl tar find; do + if ! command -v "$command_name" >/dev/null 2>&1; then + print_error "缺少必要命令: $command_name" + exit 1 + fi + done +} + +prepare_target() { + if [[ -e "$TARGET_DIR" && ! -d "$TARGET_DIR" ]]; then + print_error "目标路径存在,但不是文件夹: $TARGET_DIR" + exit 1 + fi + + mkdir -p "$TARGET_DIR" +} + +clear_target() { + prepare_target + print_info "正在清空目标目录 $TARGET_DIR ..." + + find "$TARGET_DIR" -mindepth 1 -maxdepth 1 -exec rm -rf -- {} + + + print_success "目标目录已清空" +} + +deploy() ( + local temp_dir archive_file source_dir + temp_dir=$(mktemp -d) + archive_file="$temp_dir/repository.tar.gz" + trap 'rm -rf -- "$temp_dir"' EXIT + + print_info "正在从 GitHub 下载最新版本..." + print_info "仓库地址: $REPO_URL" + if ! curl -fL --retry 3 --connect-timeout 15 "$REPO_ARCHIVE_URL" -o "$archive_file"; then + print_error "仓库下载失败,请检查网络连接及 main 分支是否存在" + return 1 + fi + + print_info "正在解压并验证部署文件..." + if ! tar -xzf "$archive_file" -C "$temp_dir"; then + print_error "仓库压缩包解压失败" + return 1 + fi + + source_dir=$(find "$temp_dir" -mindepth 2 -maxdepth 2 -type d -name src -print -quit) + if [[ -z "$source_dir" || ! -f "$source_dir/index.html" || ! -d "$source_dir/js" || ! -d "$source_dir/css" ]]; then + print_error "仓库中缺少 src/index.html、src/js 或 src/css,已取消部署" + return 1 + fi + + clear_target + + print_info "正在拷贝网站文件到 $TARGET_DIR ..." + cp -a "$source_dir/." "$TARGET_DIR/" + + print_success "Tasks 部署完成" + print_info "部署目录: $TARGET_DIR" +) + +confirm_clear() { + local answer + print_warning "将清空 $TARGET_DIR 中的所有内容,此操作不可恢复" + read -r -p "确认继续?[y/N]: " answer + if [[ "$answer" =~ ^[Yy]$ ]]; then + clear_target + else + print_info "已取消清空" + fi +} + +show_menu() { + clear 2>/dev/null || true + echo -e "${CYAN}============================================${NC}" + echo " Tasks 一键安装脚本" + echo -e "${CYAN}============================================${NC}" + echo -e "${GREEN}1.${NC} 下载并安装最新版本" + echo -e "${GREEN}2.${NC} 清空网站文件" + echo -e "${GREEN}0.${NC} 退出" + echo "" +} + +run_menu() { + local choice + while true; do + show_menu + read -r -p "请选择操作 [0-2]: " choice + case "$choice" in + 1) + # 部署失败时回到菜单,而不是因 set -e 直接退出脚本 + deploy || print_warning "部署失败,可稍后重试" + read -r -p "按 Enter 键继续..." + ;; + 2) + confirm_clear + read -r -p "按 Enter 键继续..." + ;; + 0) + print_info "退出脚本" + return 0 + ;; + *) + print_error "无效选择,请重新输入" + sleep 1 + ;; + esac + done +} + +main() { + check_root + check_dependencies + + case "${1:-menu}" in + deploy) + deploy + ;; + clear) + confirm_clear + ;; + menu) + run_menu + ;; + *) + print_error "未知参数: $1" + echo "用法: $0 [deploy|clear|menu]" >&2 + exit 1 + ;; + esac +} + +main "$@" diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 0000000..0a57a9a --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,14 @@ +import js from '@eslint/js'; +import globals from 'globals'; +export default [ + { ignores: ['node_modules/**'] }, + js.configs.recommended, + { + files: ['src/**/*.js'], + languageOptions: { globals: { ...globals.browser, bootstrap: 'readonly' } }, + }, + { + files: ['test/**/*.js'], + languageOptions: { globals: { ...globals.node, ...globals.browser } }, + }, +]; diff --git a/nodebook/2025.06-19.md b/nodebook/2025.06-19.md new file mode 100644 index 0000000..4143eb6 --- /dev/null +++ b/nodebook/2025.06-19.md @@ -0,0 +1,71 @@ +## 中文 + +```yaml +## 新完成 + +## 进行中 + - 1. 所有设备相机参数标准化 + 进展:仅剩DieBond贴片相机待确认。 + + - 2. DieBond设备照明优化 + - I. 定制组合光源:同轴光+环形光 + - II. 采购更窄、更亮的侧面光源,以解决支架白边问题 + 进展:配置已与供应商确认,预计2025年7月4日发货。 + + - 3. DieBond和ClipBond软件界面中英文翻译更新(SG-TATA) + 进展:SG-TATA GUI 的中英文翻译已完成。未来更新杨杰的代码后,需要重新进行翻译(任务量很少)。 + +## 下一步计划 + - 1. 所有设备相机手册(校准、位置引导、检测) + - 2. 开发 TaTa SECS/GEM & Map 软件。 + - 3. 跟进 TaTa 软件授权: + - I. Halcon + - II. CIMConnect RT + - III. CIMConnect CIM142 + - IV. Visual Studio 2019 + - V. Microsoft .NET Runtime(免费) + - 4. 完成 TaTa SECS/GEM 通讯手册,用于支持客户集成机器的 SECS/GEM 功能。 + + - 5. 扫码枪是否需要添加: 用于获取 Wafer 和 Strip 的序列号(涉及 SECS/GEM 相关内容)。 + - 6. 日志文件优化:: 为 LogFile 添加翻译(当前日志文件为中文),格式如下:日期:中文+英文 + - 7. 光源优化(ClipBond 点胶相机): 添加侧灯,以进一步优化芯片上胶点的检测图像效果。 + - 8. 光源优化(ClipBond 向下看相机): 添加侧灯。 + - 9. 光源优化(DieBond Pick Camera): 添加侧灯。当前使用的光源为手工焊接的环光。需要找灵猴定制标准化侧灯(非紧急事项)。 + +``` + +## 英文 + +```yaml +## Newly Completed + +## Work In Progress + - 1. All Machines Camera parameter standardization + Progress: Only DieBond Place camera remains to be confirmed. + + - 2. DieBond machine lighting optimization (DieBond Place Camera) + - I. Custom combination light source: coaxial light + ring light + - II. Purchase narrower and brighter side lighting to solve the white edge problem of the frame + Progress: Configuration has been confirmed with the supplier, expected delivery on July 7, 2025. + + - 3. DieBond and ClipBond Software GUI Chinese-English translation update (SG-TATA) + Progress: The Chinese and English translations for the SG-TATA GUI have been completed. After updating to Yang Jie's code in the future, the translations will need to be redone (little workload). + +## Next To Do + - 1. All Machines Camera Manual (Calibration, Position Guidance, Inspection) + - 2. Develop TaTa SECS/GEM & Map software. + - 3. Follow up on TaTa software licensing: + - I. Halcon + - II. CIMConnect RT + - III. CIMConnect CIM142 + - IV. Visual Studio 2019 + - V. Microsoft .NET Runtime (free) + - 4. Complete TaTa SECS/GEM communication manual to support customers in integrating machine SECS/GEM functionality. + + - 5. Whether to add barcode scanner: for obtaining Wafer and Strip serial numbers (related to SECS/GEM content). + - 6. Log file optimization: Add translation for LogFile (current log files are in Chinese), format as follows: Date: Chinese + English + - 7. Lighting optimization (ClipBond dispensing camera): Add side lighting to further optimize chip adhesive point detection image quality. + - 8. Lighting optimization (ClipBond downward-looking camera): Add side lighting. + - 9. Lighting optimization (DieBond Pick Camera): Add side lighting. Currently using manually welded ring light. Need to find Linghou to customize standardized side lighting (non-urgent task). + +``` diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..f8bce3d --- /dev/null +++ b/package-lock.json @@ -0,0 +1,3655 @@ +{ + "name": "task-manager", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "task-manager", + "version": "1.0.0", + "devDependencies": { + "@eslint/js": "latest", + "eslint": "latest", + "globals": "latest", + "jsdom": "latest", + "prettier": "latest", + "serve": "latest", + "vitest": "latest" + } + }, + "node_modules/@asamuzakjp/css-color": { + "version": "5.1.11", + "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-5.1.11.tgz", + "integrity": "sha512-KVw6qIiCTUQhByfTd78h2yD1/00waTmm9uy/R7Ck/ctUyAPj+AEDLkQIdJW0T8+qGgj3j5bpNKK7Q3G+LedJWg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@asamuzakjp/generational-cache": "^1.0.1", + "@csstools/css-calc": "^3.2.0", + "@csstools/css-color-parser": "^4.1.0", + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/@asamuzakjp/dom-selector": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-7.1.1.tgz", + "integrity": "sha512-67RZDnYRc8H/8MLDgQCDE//zoqVFwajkepHZgmXrbwybzXOEwOWGPYGmALYl9J2DOLfFPPs6kKCqmbzV895hTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@asamuzakjp/generational-cache": "^1.0.1", + "@asamuzakjp/nwsapi": "^2.3.9", + "bidi-js": "^1.0.3", + "css-tree": "^3.2.1", + "is-potential-custom-element-name": "^1.0.1" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/@asamuzakjp/generational-cache": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@asamuzakjp/generational-cache/-/generational-cache-1.0.1.tgz", + "integrity": "sha512-wajfB8KqzMCN2KGNFdLkReeHncd0AslUSrvHVvvYWuU8ghncRJoA50kT3zP9MVL0+9g4/67H+cdvBskj9THPzg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/@asamuzakjp/nwsapi": { + "version": "2.3.9", + "resolved": "https://registry.npmjs.org/@asamuzakjp/nwsapi/-/nwsapi-2.3.9.tgz", + "integrity": "sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@bramus/specificity": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/@bramus/specificity/-/specificity-2.4.2.tgz", + "integrity": "sha512-ctxtJ/eA+t+6q2++vj5j7FYX3nRu311q1wfYH3xjlLOsczhlhxAg2FWNUXhpGvAw3BWo1xBcvOV6/YLc2r5FJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "css-tree": "^3.0.0" + }, + "bin": { + "specificity": "bin/cli.js" + } + }, + "node_modules/@csstools/color-helpers": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-6.1.0.tgz", + "integrity": "sha512-064IFJdjTfUqnjpCVpMOdbr8FLQBhinbZj6yRv2An2E41O/pLEXqfFRWqGq/SxlE5PEUYTlvWsG2r8MswAVvkg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=20.19.0" + } + }, + "node_modules/@csstools/css-calc": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-3.2.1.tgz", + "integrity": "sha512-DtdHlgXh5ZkA43cwBcAm+huzgJiwx3ZTWVjBs94kwz2xKqSimDA3lBgCjphYgwgVUMWatSM0pDd8TILB1yrVVg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0" + } + }, + "node_modules/@csstools/css-color-parser": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-4.1.9.tgz", + "integrity": "sha512-paQcIaOO53Rk5+YrBaBjm/SgrV4INImjo2BT1DtQRYr+XeTRbeAYlS+jxXp9drqvKmtFnWRJKIalDLhZZDu42A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "dependencies": { + "@csstools/color-helpers": "^6.1.0", + "@csstools/css-calc": "^3.2.1" + }, + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0" + } + }, + "node_modules/@csstools/css-parser-algorithms": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-4.0.0.tgz", + "integrity": "sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "@csstools/css-tokenizer": "^4.0.0" + } + }, + "node_modules/@csstools/css-syntax-patches-for-csstree": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.1.6.tgz", + "integrity": "sha512-TcJCWFbXLPpJYq6z7bfOyjWYJDiDg2/I4gyUC9pqPNqHFRIey0EB0q0L5cSnQDfWJg8Jd6VadakxdIez/3zkqQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "peerDependencies": { + "css-tree": "^3.2.1" + }, + "peerDependenciesMeta": { + "css-tree": { + "optional": true + } + } + }, + "node_modules/@csstools/css-tokenizer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-4.0.0.tgz", + "integrity": "sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=20.19.0" + } + }, + "node_modules/@emnapi/core": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.1.tgz", + "integrity": "sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.2", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.1.tgz", + "integrity": "sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.2.tgz", + "integrity": "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.23.5", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.23.5.tgz", + "integrity": "sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^3.0.5", + "debug": "^4.3.1", + "minimatch": "^10.2.4" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.6.0.tgz", + "integrity": "sha512-ii6Bw9jJ2zi2cWA2Z+9/QZ/+3DX6kwaV5Q986D/CdP3Lap3w/pgQZ373FV7byY/i7L4IRH/G43I5dz1ClsCbpA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^1.2.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/core": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-1.2.1.tgz", + "integrity": "sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/js": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-10.0.1.tgz", + "integrity": "sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "eslint": "^10.0.0" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/@eslint/object-schema": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-3.0.5.tgz", + "integrity": "sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.7.2.tgz", + "integrity": "sha512-+CNAzxglkrpNf/kKywqQfk74QjtceuOE7Qm+AF8miRvPF/wmmK5+OJOgVh3AVTT3RP2mH3+FOaxlE5v72owk0A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^1.2.1", + "levn": "^0.4.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@exodus/bytes": { + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/@exodus/bytes/-/bytes-1.15.1.tgz", + "integrity": "sha512-S6mL0yNB/Abt9Ei4tq8gDhcczc4S3+vQ4ra7vxnAf+YHC02srtqxKKZghx2Dq6p0e66THKwR6r8N6P95wEty7Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + }, + "peerDependencies": { + "@noble/hashes": "^1.8.0 || ^2.0.0" + }, + "peerDependenciesMeta": { + "@noble/hashes": { + "optional": true + } + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz", + "integrity": "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/types": "^0.15.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.8.tgz", + "integrity": "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.2", + "@humanfs/types": "^0.15.0", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/types": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@humanfs/types/-/types-0.15.0.tgz", + "integrity": "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.6.tgz", + "integrity": "sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@tybys/wasm-util": "^0.10.3" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1" + } + }, + "node_modules/@oxc-project/types": { + "version": "0.139.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.139.0.tgz", + "integrity": "sha512-r9gHphtCs+1M7J0pw6Sn/hh/Wpa/iQrOOkrNAlVLF/gHq+/CJmHIWKKUUhdWjcD6CIa8idarspCsASiXCXvFUw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" + } + }, + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.1.5.tgz", + "integrity": "sha512-lZg8fqIv2v7FF237bwMgzGZEJvGL79/s5knJ/i6FmsGF4XXlzccZ4jb+TrFIxtSSxFtIpdsgrPZeMk1I9AFcyQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.1.5.tgz", + "integrity": "sha512-51Bnx9pNiMRKSUNtBfySkNJ9vMU9Hh3I1ozDd6gyPPYzaXCfnptUcEZxXGYFn+ul2dtcMUiqGR1Yai2K10uoTw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.1.5.tgz", + "integrity": "sha512-Tm+gbfC0aHu1tBA/JvKQh32S0K6YgCHkiAF4/W6xX0K0RmNuc94VeK419dJoE65R5aRxmo+noZQSWrAMF6yb6g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.1.5.tgz", + "integrity": "sha512-JMzDKCCXq93YccG5gz3hvOs1oXRKAf0XYpfOS88e+wZrC8Iugj6j68867vrYZkvpDDpKn/KoKORThmchMpF6TA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.1.5.tgz", + "integrity": "sha512-uML21j2K5TfPGutKxub+M+nLjZIrWjXQ5Grx4lCe/nimTj9B4L63zHpjXLl4y0L3mcm2htEQIb06oCG/szerNw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.1.5.tgz", + "integrity": "sha512-navSiuTMogvnQoZoM/v+l3ZWo50/NTwSHSzheABx/RCnmUPaKwq9qSo4Br2OYRs21+Fz8uFqITZM3H4opOB0/Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.1.5.tgz", + "integrity": "sha512-lAryqH7IteztmCXQXk0etKj4wBQ7Gx5S6LjKhsgp9zb8I5bsuvU/2llH1hDQcjsFeqIsovMVN339/8pUDDBXxA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.1.5.tgz", + "integrity": "sha512-fsK/sNBnxzBlL4O1JNrZakVQxPspqpED5dLtNsZS9oOKmtSpdNIzxH2kkol5HYTWJN47sE20ztMJPxfZ89qGOg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.1.5.tgz", + "integrity": "sha512-gLYb4BIadlfTOYT5gO503n8zQjXflgzpD0FcyKh0Mzx3rqCZKnHoJWV9xe1KXUJ5lx2JfcSHr/mhzS0PC/McAA==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.1.5.tgz", + "integrity": "sha512-FjcpEKUyJygHgs1o50VYNvkt5+7Le/VEdYt0AkRpkL33MnyQfwr8l5mXwMmfmTbyMPr5vJLC+8/Gd9gXnwU1QQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.1.5.tgz", + "integrity": "sha512-Me+PfPI2TMeOQk0gYWfLQZtTktrmzbr8cDboqX83XKc7UrgAi55gF+2dUkWdxd19n55Essp2yeca+O9N5rBxHg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.1.5.tgz", + "integrity": "sha512-yc5WrLzXks6zCQfn9Oxr8pORKyl/pF+QjHmW/Qx3qu0oyrrNC+y2JLTU1E2rcWYAmzlnqngWXHQjy51VzW70Vw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-wasm32-wasi": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.1.5.tgz", + "integrity": "sha512-VbQGPX2b4r48TAMIM2cjgluIM1HYutm4pcTEJsle7iEP7sB1dFqtPLBVbdLAZCxy1txCcPxf4QFf4v8uvltPqA==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "1.11.1", + "@emnapi/runtime": "1.11.1", + "@napi-rs/wasm-runtime": "^1.1.6" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.1.5.tgz", + "integrity": "sha512-gHv82k63z4qpV5+Q1y/12KrK0ltWBukVDI8nZcbT7Tt/ZlOIVwppazneq0F93oDxTo3IgAMEDIoQh3E2n6mVsw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.1.5.tgz", + "integrity": "sha512-tTZuDBPw85tEN5PQi1pnEBzDy0Z49HtScLAbD5t6hyeU92A95pRWaSMw1GZZi/RwgSgUIl0xrSlXIT/9QzvYSA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", + "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.3.tgz", + "integrity": "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/esrecurse": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@types/esrecurse/-/esrecurse-4.3.1.tgz", + "integrity": "sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@vitest/expect": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.10.tgz", + "integrity": "sha512-YsCn+qAk1GWjQOWFEsEcL2gNQ0zmVmQu3T03qP6UyjhtmdtwtbuI+DASn/7iQB3HGTXkdBwGddzxPlmiql5vlA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.1.0", + "@types/chai": "^5.2.2", + "@vitest/spy": "4.1.10", + "@vitest/utils": "4.1.10", + "chai": "^6.2.2", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.10.tgz", + "integrity": "sha512-v0xaezt+DKEmKfaxg133ldzADrwLGd7Ze1MfQQTYfvs8OqZIwbxyxaYURivwV7sWy5fqn3rH5uOrSp07bp44Ow==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "4.1.10", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.21" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/pretty-format": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.10.tgz", + "integrity": "sha512-W1HsjSH4MXQ9YfmmhLAoIYf1HRfekQCGngeIgcei6MP5QQGWUe0gkopdZQaVCFO+JDJMrAJGwa5pRpNpvy4P8Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.10.tgz", + "integrity": "sha512-IKI6kpIH+LmpROplyLwBBaCfMgOZOMsygVa6BARD6ahA04VRuJSa6OaVG7kRvSEMD870Vd91rSSw0eegtWyLGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "4.1.10", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.10.tgz", + "integrity": "sha512-xRkfOT1qpTAi/Ti4Y1LtfRc3kEuqxGw59eN2jN9pRWMtS/XDevekhcFSqvQqjUNGksfjMJu3Y+oJ+4Ypn2OaJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.1.10", + "@vitest/utils": "4.1.10", + "magic-string": "^0.30.21", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.10.tgz", + "integrity": "sha512-PLf/Ugvoq5wO/b4rwYCR1h2PSIdXz7wnkQFMiUpLdtM7l6pqVFcQIBEHyT1+l+cj7mNwAfZHzqXqDyjvOuwbDw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.10.tgz", + "integrity": "sha512-fy9am/HWxbaGt/Sawrp90vt6Y6jQwf1RX77cz3uwoJwJVMli/e1IEwRPnMNJ7vKfPTwo0diXifkpPvwH9v7nGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.1.10", + "convert-source-map": "^2.0.0", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@zeit/schemas": { + "version": "2.36.0", + "resolved": "https://registry.npmjs.org/@zeit/schemas/-/schemas-2.36.0.tgz", + "integrity": "sha512-7kjMwcChYEzMKjeex9ZFXkt1AyNov9R5HZtjBKVsmVpw7pa7ZtlCGvCBC2vnnXctaYN+aRI61HjIqeetZW5ROg==", + "dev": true, + "license": "MIT" + }, + "node_modules/acorn": { + "version": "8.17.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.17.0.tgz", + "integrity": "sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-align": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", + "integrity": "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.1.0" + } + }, + "node_modules/ansi-align/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-align/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/ansi-align/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-align/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/arch": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/arch/-/arch-2.2.0.tgz", + "integrity": "sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ==", + "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/arg": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", + "dev": true, + "license": "MIT" + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/bidi-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.3.tgz", + "integrity": "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==", + "dev": true, + "license": "MIT", + "dependencies": { + "require-from-string": "^2.0.2" + } + }, + "node_modules/boxen": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/boxen/-/boxen-7.0.0.tgz", + "integrity": "sha512-j//dBVuyacJbvW+tvZ9HuH03fZ46QcaKvvhZickZqtB271DxJ7SNRSNxrV/dZX0085m7hISRZWbzWlJvx/rHSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-align": "^3.0.1", + "camelcase": "^7.0.0", + "chalk": "^5.0.1", + "cli-boxes": "^3.0.0", + "string-width": "^5.1.2", + "type-fest": "^2.13.0", + "widest-line": "^4.0.1", + "wrap-ansi": "^8.0.1" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/brace-expansion": { + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.7.tgz", + "integrity": "sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/camelcase": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-7.0.1.tgz", + "integrity": "sha512-xlx1yCK2Oc1APsPXDL2LdlNP6+uu8OCDdhOBSVT279M/S+y75O30C2VuD8T2ogdePBBl7PfPF4504tnLgX3zfw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/chai": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", + "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/chalk": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.0.1.tgz", + "integrity": "sha512-Fo07WOYGqMfCWHOzSXOt2CxDbC6skS/jO9ynEcmpANMoPrD+W1r1K6Vx7iNm+AQmETU1Xr2t+n8nzkV9t6xh3w==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chalk-template": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/chalk-template/-/chalk-template-0.4.0.tgz", + "integrity": "sha512-/ghrgmhfY8RaSdeo43hNXxpoHAtxdbskUHjPpfqUWGttFgycUhYPGx3YZBCnUCvOa7Doivn1IZec3DEGFoMgLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/chalk-template?sponsor=1" + } + }, + "node_modules/chalk-template/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/chalk-template/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/cli-boxes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-3.0.0.tgz", + "integrity": "sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/clipboardy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/clipboardy/-/clipboardy-3.0.0.tgz", + "integrity": "sha512-Su+uU5sr1jkUy1sGRpLKjKrvEOVXgSgiSInwa/qeID6aJ07yh+5NWc3h2QfjHjBnfX4LhtFcuAWKUsJ3r+fjbg==", + "dev": true, + "license": "MIT", + "dependencies": { + "arch": "^2.2.0", + "execa": "^5.1.1", + "is-wsl": "^2.2.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/compressible": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": ">= 1.43.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/compression": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.8.1.tgz", + "integrity": "sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w==", + "dev": true, + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "compressible": "~2.0.18", + "debug": "2.6.9", + "negotiator": "~0.6.4", + "on-headers": "~1.1.0", + "safe-buffer": "5.2.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/compression/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/compression/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/content-disposition": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", + "integrity": "sha512-kRGRZw3bLlFISDBgwTSA1TMBFN6J6GWDeubmDE3AF+3+yXL8hTWv8r5rkLbqYXY4RjPk/EzHnClI3zQf1cFmHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/css-tree": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.2.1.tgz", + "integrity": "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "mdn-data": "2.27.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + } + }, + "node_modules/data-urls": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-7.0.0.tgz", + "integrity": "sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-mimetype": "^5.0.0", + "whatwg-url": "^16.0.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decimal.js": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz", + "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", + "dev": true, + "license": "MIT" + }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true, + "license": "MIT" + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/entities": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-8.0.0.tgz", + "integrity": "sha512-zwfzJecQ/Uej6tusMqwAqU/6KL2XaB2VZ2Jg54Je6ahNBGNH6Ek6g3jjNCF0fG9EWQKGZNddNjU5F1ZQn/sBnA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/es-module-lexer": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.3.1.tgz", + "integrity": "sha512-shc1dbU90Yl/xq1QrC7QRtfcwURZuVRfPhZbDoldJ1cn1gzDvBaBWlv0eFolj5+0znnPJz5TXLxsN77X/12KTA==", + "dev": true, + "license": "MIT" + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "10.7.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.7.0.tgz", + "integrity": "sha512-GVTD7s1vdIl6UYvAfriOPeY1Df8LIZjfofLvHwde+erDHGGuHyuM6xoxRxmHiebhYuD2p1vN4wWh0XzPARSGDQ==", + "dev": true, + "license": "MIT", + "workspaces": [ + "packages/*" + ], + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.2", + "@eslint/config-array": "^0.23.5", + "@eslint/config-helpers": "^0.6.0", + "@eslint/core": "^1.2.1", + "@eslint/plugin-kit": "^0.7.2", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.14.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^9.1.2", + "eslint-visitor-keys": "^5.0.1", + "espree": "^11.2.0", + "esquery": "^1.7.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "minimatch": "^10.2.4", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-scope": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-9.1.2.tgz", + "integrity": "sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@types/esrecurse": "^4.3.1", + "@types/estree": "^1.0.8", + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-11.2.0.tgz", + "integrity": "sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.16.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^5.0.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/expect-type": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.4.0.tgz", + "integrity": "sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-uri": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.3.tgz", + "integrity": "sha512-i70LwGWUduXqzicKXWshooq+sWL1K3WUU5rKZNG/0i3a1OSoX3HqhH5WbWwTmqWfor4urUakGPiRQcleRZTwOg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", + "dev": true, + "license": "ISC" + }, + "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/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "17.7.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-17.7.0.tgz", + "integrity": "sha512-Czmyns5dUsq4seFBR/Kdydhmo8y9kC79hiSkPn0YcGtNnYWnrgt0vjrSjx9tspoDGWm2CMarffRuLjM4xUz8xg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/html-encoding-sniffer": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-6.0.0.tgz", + "integrity": "sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@exodus/bytes": "^1.6.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true, + "license": "ISC" + }, + "node_modules/is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "dev": true, + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-port-reachable": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-port-reachable/-/is-port-reachable-4.0.0.tgz", + "integrity": "sha512-9UoipoxYmSk6Xy7QFgRv2HDyaysmgSG75TFQs6S+3pDM7ZhKTF/bskZV+0UlABHzKjNVhPjYCLfeZUEg1wXxig==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-docker": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/jsdom": { + "version": "29.1.1", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-29.1.1.tgz", + "integrity": "sha512-ECi4Fi2f7BdJtUKTflYRTiaMxIB0O6zfR1fX0GXpUrf6flp8QIYn1UT20YQqdSOfk2dfkCwS8LAFoJDEppNK5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@asamuzakjp/css-color": "^5.1.11", + "@asamuzakjp/dom-selector": "^7.1.1", + "@bramus/specificity": "^2.4.2", + "@csstools/css-syntax-patches-for-csstree": "^1.1.3", + "@exodus/bytes": "^1.15.0", + "css-tree": "^3.2.1", + "data-urls": "^7.0.0", + "decimal.js": "^10.6.0", + "html-encoding-sniffer": "^6.0.0", + "is-potential-custom-element-name": "^1.0.1", + "lru-cache": "^11.3.5", + "parse5": "^8.0.1", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^6.0.1", + "undici": "^7.25.0", + "w3c-xmlserializer": "^5.0.0", + "webidl-conversions": "^8.0.1", + "whatwg-mimetype": "^5.0.0", + "whatwg-url": "^16.0.1", + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24.0.0" + }, + "peerDependencies": { + "canvas": "^3.0.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lightningcss": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.32.0", + "lightningcss-darwin-arm64": "1.32.0", + "lightningcss-darwin-x64": "1.32.0", + "lightningcss-freebsd-x64": "1.32.0", + "lightningcss-linux-arm-gnueabihf": "1.32.0", + "lightningcss-linux-arm64-gnu": "1.32.0", + "lightningcss-linux-arm64-musl": "1.32.0", + "lightningcss-linux-x64-gnu": "1.32.0", + "lightningcss-linux-x64-musl": "1.32.0", + "lightningcss-win32-arm64-msvc": "1.32.0", + "lightningcss-win32-x64-msvc": "1.32.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lru-cache": { + "version": "11.5.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.2.tgz", + "integrity": "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/mdn-data": { + "version": "2.27.1", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.27.1.tgz", + "integrity": "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==", + "dev": true, + "license": "CC0-1.0" + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true, + "license": "MIT" + }, + "node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.18", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz", + "integrity": "sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "~1.33.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types/node_modules/mime-db": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz", + "integrity": "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.5" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.16", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.16.tgz", + "integrity": "sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==", + "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/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", + "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/obug": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.4.tgz", + "integrity": "sha512-4a+OsYv9UktOJKE+l1A4OufDgdRF9PifWj+tJnHURo/P+WOxpG4GzUFL9qCalmWauao6ogiG+QvnCovwPoyAWA==", + "dev": true, + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT", + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/on-headers": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz", + "integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parse5": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-8.0.1.tgz", + "integrity": "sha512-z1e/HMG90obSGeidlli3hj7cbocou0/wa5HacvI3ASx34PecNjNQeaHNo5WIZpWofN9kgkqV1q5YvXe3F0FoPw==", + "dev": true, + "license": "MIT", + "dependencies": { + "entities": "^8.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-inside": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", + "integrity": "sha512-DUWJr3+ULp4zXmol/SZkFf3JGsS9/SIv+Y3Rt93/UjPpDpklB5f1er4O3POIbUuUJ3FXgqte2Q7SrU6zAqwk8w==", + "dev": true, + "license": "(WTFPL OR MIT)" + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-to-regexp": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-3.3.0.tgz", + "integrity": "sha512-qyCH421YQPS2WFDxDjftfc1ZR5WKQzVzqsp4n9M2kQhVOo/ByahFoUNJfl58kOcEGfQ//7weFTDhm+ss8Ecxgw==", + "dev": true, + "license": "MIT" + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", + "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.19", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.19.tgz", + "integrity": "sha512-Mz8SaolMd8nB+G13WkORcxQKHZ/NE4xXevtkJHVuG+guo9/wYKlIMTKAqGdEmYOXR2ijPjTYNHssizdaVSUNdQ==", + "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/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "3.9.5", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.9.5.tgz", + "integrity": "sha512-/FVl766LpUfB5vXgCYOYa0MeV/441Ia99AeICQIQFTY/Nw0roZwULcXpku5i1/m5kt/baz+s4Zogspd839HSMg==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/range-parser": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", + "integrity": "sha512-kA5WQoNVo4t9lNx2kQNFCxKeBl5IbbSNBl1M/tLkw9WCn+hxNBAW5Qh8gdhs63CJnhjJ2zQWFoqPJP2sK1AV5A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "dev": true, + "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, + "node_modules/registry-auth-token": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-3.3.2.tgz", + "integrity": "sha512-JL39c60XlzCVgNrO+qq68FoNb56w/m7JYvGR2jT5iR1xBrUA3Mfx5Twk5rqTThPmQKMWydGmq8oFtDlxfrmxnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "rc": "^1.1.6", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/registry-url": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-3.1.0.tgz", + "integrity": "sha512-ZbgR5aZEdf4UKZVBPYIgaglBmSF2Hi94s2PcIHhRGFjKYu+chjJdYfHn4rt3hB6eCKLJ8giVIIfgMa1ehDfZKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "rc": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/rolldown": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.1.5.tgz", + "integrity": "sha512-t9z29cJjXf/vxQ8dyhCSpt6H6aSwHTk8cT5I3iy6SMXuFpk5mB6PL6XfC8PCwrPTx93udwKUm9HRteAlTGBLiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@oxc-project/types": "=0.139.0", + "@rolldown/pluginutils": "^1.0.0" + }, + "bin": { + "rolldown": "bin/cli.mjs" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm64": "1.1.5", + "@rolldown/binding-darwin-arm64": "1.1.5", + "@rolldown/binding-darwin-x64": "1.1.5", + "@rolldown/binding-freebsd-x64": "1.1.5", + "@rolldown/binding-linux-arm-gnueabihf": "1.1.5", + "@rolldown/binding-linux-arm64-gnu": "1.1.5", + "@rolldown/binding-linux-arm64-musl": "1.1.5", + "@rolldown/binding-linux-ppc64-gnu": "1.1.5", + "@rolldown/binding-linux-s390x-gnu": "1.1.5", + "@rolldown/binding-linux-x64-gnu": "1.1.5", + "@rolldown/binding-linux-x64-musl": "1.1.5", + "@rolldown/binding-openharmony-arm64": "1.1.5", + "@rolldown/binding-wasm32-wasi": "1.1.5", + "@rolldown/binding-win32-arm64-msvc": "1.1.5", + "@rolldown/binding-win32-x64-msvc": "1.1.5" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "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/saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "dev": true, + "license": "ISC", + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=v12.22.7" + } + }, + "node_modules/serve": { + "version": "14.2.6", + "resolved": "https://registry.npmjs.org/serve/-/serve-14.2.6.tgz", + "integrity": "sha512-QEjUSA+sD4Rotm1znR8s50YqA3kYpRGPmtd5GlFxbaL9n/FdUNbqMhxClqdditSk0LlZyA/dhud6XNRTOC9x2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@zeit/schemas": "2.36.0", + "ajv": "8.18.0", + "arg": "5.0.2", + "boxen": "7.0.0", + "chalk": "5.0.1", + "chalk-template": "0.4.0", + "clipboardy": "3.0.0", + "compression": "1.8.1", + "is-port-reachable": "4.0.0", + "serve-handler": "6.1.7", + "update-check": "1.5.4" + }, + "bin": { + "serve": "build/main.js" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/serve-handler": { + "version": "6.1.7", + "resolved": "https://registry.npmjs.org/serve-handler/-/serve-handler-6.1.7.tgz", + "integrity": "sha512-CinAq1xWb0vR3twAv9evEU8cNWkXCb9kd5ePAHUKJBkOsUpR1wt/CvGdeca7vqumL1U5cSaeVQ6zZMxiJ3yWsg==", + "dev": true, + "license": "MIT", + "dependencies": { + "bytes": "3.0.0", + "content-disposition": "0.5.2", + "mime-types": "2.1.18", + "minimatch": "3.1.5", + "path-is-inside": "1.0.2", + "path-to-regexp": "3.3.0", + "range-parser": "1.2.0" + } + }, + "node_modules/serve-handler/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/serve-handler/node_modules/brace-expansion": { + "version": "1.1.16", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.16.tgz", + "integrity": "sha512-IDw48K2/2kRkg9LdJxurvq3lV3aBgq0REY89duEqFRthjlPdXHKMj7EnQOXVckxzgisinf3nHfrcE2FufFLXMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/serve-handler/node_modules/bytes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + "integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/serve-handler/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/serve/node_modules/ajv": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/serve/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/std-env": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.2.0.tgz", + "integrity": "sha512-oCUKSupKTHX53EyjDtuZQ64pjLJ6yYCtpmEw0goYxtjG9KpbRe8KAsl2tBUGU9DyMcJ0RwJ8GqJAFzMXcXW1Rw==", + "dev": true, + "license": "MIT" + }, + "node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.2.4.tgz", + "integrity": "sha512-SHf/r48b7vOrjve9PxJo3MN5v5yuyjHvdUcrQffT3WXMUfnGmHDVbC4k3sHJaJTgZCwpUplIaAo5ANtMyp3YHg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "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/tinyrainbow": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.0.tgz", + "integrity": "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tldts": { + "version": "7.4.9", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.4.9.tgz", + "integrity": "sha512-3kZ8wQQ/k5DrChD4X4FVvr2D7E5uoRgAqkPyLpSCGUvqOvqu+JEdr3mwMUaVWb+vMHZaKhF5fp2PBigKsui7hA==", + "dev": true, + "license": "MIT", + "dependencies": { + "tldts-core": "^7.4.9" + }, + "bin": { + "tldts": "bin/cli.js" + } + }, + "node_modules/tldts-core": { + "version": "7.4.9", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.4.9.tgz", + "integrity": "sha512-DxKfPBI52p2msTEu7MPhdpdDTBhhVQg1a/8PjQckeyAvO13eMYElX545grIp6nnTGIMZlRvFZPvFhvI/WIz2Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tough-cookie": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.2.tgz", + "integrity": "sha512-exgYmnmL/sJpR3upZfXG5PoatXQii55xAiXGXzY+sROLZ/Y+SLcp9PgJNI9Vz37HpQ74WvDcLT8eqm+kV3FzrA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "tldts": "^7.0.5" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/tr46": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-6.0.0.tgz", + "integrity": "sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==", + "dev": true, + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD", + "optional": true + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-fest": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", + "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/undici": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.28.0.tgz", + "integrity": "sha512-cRZYrTDwWznlnRiPjggAGxZXanty6M8RV1ff8Wm4LWXBp7/IG8v5DnOm74DtUBp9OONpK75YlPnIjQqX0dBDtA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.18.1" + } + }, + "node_modules/update-check": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/update-check/-/update-check-1.5.4.tgz", + "integrity": "sha512-5YHsflzHP4t1G+8WGPlvKbJEbAJGCgw+Em+dGR1KmBUbr1J36SJBqlHLjR7oob7sco5hWHGQVcr9B2poIVDDTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "registry-auth-token": "3.3.2", + "registry-url": "3.1.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/vite": { + "version": "8.1.5", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.1.5.tgz", + "integrity": "sha512-7ULLwsCdYx/nRyrpiEwvqb5TFHrMVZyBt+rg/OAXT7rgj/z+DtTDyKFeLAdDkubDVDKD8jOsndmy7m55XcfUsw==", + "dev": true, + "license": "MIT", + "dependencies": { + "lightningcss": "^1.32.0", + "picomatch": "^4.0.5", + "postcss": "^8.5.17", + "rolldown": "~1.1.5", + "tinyglobby": "^0.2.17" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "@vitejs/devtools": "^0.3.0", + "esbuild": "^0.27.0 || ^0.28.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "@vitejs/devtools": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vitest": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.10.tgz", + "integrity": "sha512-R9jUTe5S4Qb0HCd4TNqpC7oGcrMssMRGXLW80ubjWsW9VH5GF8y1Y0SFLY9AbqSk6nt0PnOx4H4WNJYZ13GUPw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "4.1.10", + "@vitest/mocker": "4.1.10", + "@vitest/pretty-format": "4.1.10", + "@vitest/runner": "4.1.10", + "@vitest/snapshot": "4.1.10", + "@vitest/spy": "4.1.10", + "@vitest/utils": "4.1.10", + "es-module-lexer": "^2.0.0", + "expect-type": "^1.3.0", + "magic-string": "^0.30.21", + "obug": "^2.1.1", + "pathe": "^2.0.3", + "picomatch": "^4.0.3", + "std-env": "^4.0.0-rc.1", + "tinybench": "^2.9.0", + "tinyexec": "^1.0.2", + "tinyglobby": "^0.2.15", + "tinyrainbow": "^3.1.0", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@opentelemetry/api": "^1.9.0", + "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", + "@vitest/browser-playwright": "4.1.10", + "@vitest/browser-preview": "4.1.10", + "@vitest/browser-webdriverio": "4.1.10", + "@vitest/coverage-istanbul": "4.1.10", + "@vitest/coverage-v8": "4.1.10", + "@vitest/ui": "4.1.10", + "happy-dom": "*", + "jsdom": "*", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@opentelemetry/api": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser-playwright": { + "optional": true + }, + "@vitest/browser-preview": { + "optional": true + }, + "@vitest/browser-webdriverio": { + "optional": true + }, + "@vitest/coverage-istanbul": { + "optional": true + }, + "@vitest/coverage-v8": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + }, + "vite": { + "optional": false + } + } + }, + "node_modules/w3c-xmlserializer": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", + "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/webidl-conversions": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.1.tgz", + "integrity": "sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=20" + } + }, + "node_modules/whatwg-mimetype": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-5.0.0.tgz", + "integrity": "sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/whatwg-url": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-16.0.1.tgz", + "integrity": "sha512-1to4zXBxmXHV3IiSSEInrreIlu02vUOvrhxJJH5vcxYTBDAx51cqZiKdyTxlecdKNSjj8EcxGBxNf6Vg+945gw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@exodus/bytes": "^1.11.0", + "tr46": "^6.0.0", + "webidl-conversions": "^8.0.1" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/widest-line": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-4.0.1.tgz", + "integrity": "sha512-o0cyEG0e8GPzT4iGHphIOh0cJOV8fivsXxddQasHPHfoZf1ZexrfeA21w2NaEN1RHE+fXlfISmOE8R9N3u3Qig==", + "dev": true, + "license": "MIT", + "dependencies": { + "string-width": "^5.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/xml-name-validator": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", + "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "dev": true, + "license": "MIT" + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..693fd0e --- /dev/null +++ b/package.json @@ -0,0 +1,22 @@ +{ + "name": "task-manager", + "version": "1.0.0", + "private": true, + "type": "module", + "scripts": { + "start": "serve src", + "lint": "eslint .", + "format": "prettier --write .", + "format:check": "prettier --check .", + "test": "vitest run --environment jsdom" + }, + "devDependencies": { + "@eslint/js": "latest", + "eslint": "latest", + "globals": "latest", + "jsdom": "latest", + "prettier": "latest", + "serve": "latest", + "vitest": "latest" + } +} diff --git a/src/css/styles.css b/src/css/styles.css new file mode 100644 index 0000000..7a2dd30 --- /dev/null +++ b/src/css/styles.css @@ -0,0 +1,242 @@ +/* Base */ +:root { + --primary: #0d6efd; + --todo: #6c757d; + --progress: #ffc107; + --complete: #198754; + --priority-low: #198754; + --priority-medium: #fd7e14; + --priority-high: #dc3545; + --danger: #dc3545; + --warning: #fd7e14; + --surface: #fff; + --surface-raised: #fffffff0; + --background: #f4f6fa; + --overdue-background: #fff5f5; + --due-soon-background: #fff8f0; + --muted: #6c757d; + --timeline: #dee2e6; + --header-start: #667eea; + --header-end: #764ba2; + --shadow-subtle: #00000012; + --shadow-hover: #0002; + --shadow-pinned: #ffc10744; +} +body { + background: var(--background); + font-family: 'Microsoft YaHei', system-ui, sans-serif; +} +.task-header { + background: linear-gradient(135deg, var(--header-start), var(--header-end)); + color: var(--surface); + padding: 1.25rem; + border-radius: 1rem; + margin-bottom: 1.5rem; +} +.task-header h1 { + font-size: 1.8rem; + margin: 0; +} +.status-column { + background: var(--surface); + border-radius: 1rem; + padding: 1rem; + box-shadow: 0 2px 12px var(--shadow-subtle); + min-height: 12rem; +} +.status-column-header { + display: flex; + align-items: center; + justify-content: space-between; + gap: 0.5rem; + margin-bottom: 1rem; +} +.status-column-header h4 { + font-size: 1rem; + margin: 0; +} +.sort-selector { + font-size: 0.72rem; + padding: 0.25rem; + border: 1px solid var(--timeline); + border-radius: 0.35rem; + max-width: 45%; +} + +/* Task cards */ +.task-card { + position: relative; + border-left: 4px solid var(--primary); + margin-bottom: 1rem; + transition: 0.2s; +} +.task-card:hover { + transform: translateY(-2px); + box-shadow: 0 5px 14px var(--shadow-hover); +} +.task-card.todo { + border-left-color: var(--todo); +} +.task-card.inProgress { + border-left-color: var(--progress); +} +.task-card.completed { + border-left-color: var(--complete); +} +.task-card.overdue { + border-left-color: var(--danger) !important; + background: var(--overdue-background); +} +.task-card.due-soon { + border-left-color: var(--warning) !important; + background: var(--due-soon-background); +} +.task-card.hidden-task { + opacity: 0.6; +} +.task-card.pinned { + border-top: 3px solid var(--progress); + box-shadow: 0 4px 12px var(--shadow-pinned); +} +.task-actions { + position: absolute; + right: 0.35rem; + top: 0.35rem; + z-index: 2; + background: var(--surface-raised); + border-radius: 0.4rem; + opacity: 0; +} +.task-card:hover .task-actions { + opacity: 1; +} +.task-actions .btn { + padding: 0.25rem 0.4rem; +} +.priority-badge { + font-size: 0.7rem; + border-radius: 1rem; + padding: 0.2rem 0.5rem; + color: var(--surface); +} +.priority-low-badge { + background: var(--priority-low); +} +.priority-medium-badge { + background: var(--priority-medium); +} +.priority-high-badge { + background: var(--priority-high); +} +.progress-custom { + height: 0.45rem; +} +.person-badge { + font-size: 0.75rem; + margin: 0.1rem; +} +.task-description-display { + white-space: pre-wrap; + overflow-wrap: anywhere; +} + +/* Timeline */ +.timeline-toggle { + cursor: pointer; +} +.timeline { + position: relative; + padding-left: 1.25rem; + margin-top: 0.75rem; +} +.timeline:before { + content: ''; + position: absolute; + left: 0.5rem; + inset-block: 0; + width: 2px; + background: var(--timeline); +} +.timeline-item { + position: relative; + padding-bottom: 0.75rem; + margin-bottom: 0.5rem; +} +.timeline-item:before { + content: ''; + position: absolute; + left: -0.72rem; + top: 0.35rem; + width: 0.5rem; + height: 0.5rem; + border-radius: 50%; + background: var(--primary); +} +.timeline-content, +.timeline-form, +.timeline-edit-form { + background: var(--background); + padding: 0.65rem; + border-radius: 0.5rem; + font-size: 0.85rem; +} +.timeline-date { + font-size: 0.75rem; + color: var(--muted); +} +.timeline-item-actions { + float: right; + opacity: 0; +} +.timeline-item:hover .timeline-item-actions { + opacity: 1; +} + +/* Overlays and controls */ +.notification { + position: fixed; + right: 1rem; + top: 1rem; + z-index: 2000; + max-width: min(28rem, 90vw); +} +.visibility-toggle { + padding: 0.1rem; +} +.empty-state { + color: var(--muted); + text-align: center; + padding: 1rem; +} + +/* Responsive */ +@media (max-width: 768px) { + .task-actions, + .timeline-item-actions { + opacity: 0.9; + } + .toolbar .btn { + flex: 1; + } + .status-column { + margin-bottom: 0.5rem; + } +} +@media (max-width: 576px) { + .task-header h1 { + font-size: 1.35rem; + } + .status-column-header { + align-items: flex-start; + } + .sort-selector { + max-width: 50%; + } + .task-actions .btn { + min-width: 2.25rem; + min-height: 2.25rem; + } + .toolbar .btn span:not(#langBtn) { + display: none; + } +} diff --git a/src/index.html b/src/index.html new file mode 100644 index 0000000..6c70252 --- /dev/null +++ b/src/index.html @@ -0,0 +1,261 @@ + + + + + + 任务管理系统 + + + + + + +
+
+

任务管理系统

+
+
+
+ +
+
+
+ + +
+
+
+ +
+
+
+
+

+ (0) + +

+ +
+
+
+
+
+
+
+

+ (0) +

+ +
+
+
+
+
+
+
+

+ (0) + +

+ +
+
+
+
+
+
+ + +
+ + + + diff --git a/src/js/config.js b/src/js/config.js new file mode 100644 index 0000000..a7c7a4f --- /dev/null +++ b/src/js/config.js @@ -0,0 +1,31 @@ +export const STATUSES = ['todo', 'inProgress', 'completed']; +export const HIDEABLE_STATUSES = ['todo', 'completed']; +export const PRIORITIES = ['low', 'medium', 'high']; +export const PRIORITY_ORDER = { low: 1, medium: 2, high: 3 }; +export const LANGUAGES = ['zh', 'en']; +export const SORT_OPTIONS = [ + ['default', 'defaultSort'], + ['title-asc', 'titleAsc'], + ['title-desc', 'titleDesc'], + ['number-asc', 'numberAsc'], + ['number-desc', 'numberDesc'], + ['dueDate-asc', 'dueDateAsc'], + ['dueDate-desc', 'dueDateDesc'], + ['priority-asc', 'priorityAsc'], + ['priority-desc', 'priorityDesc'], + ['progress-asc', 'progressAsc'], + ['progress-desc', 'progressDesc'], + ['created-desc', 'createdDesc'], + ['created-asc', 'createdAsc'], +]; +export const SORT_VALUES = new Set(SORT_OPTIONS.map(([value]) => value)); +export const DEFAULT_SORT_ORDERS = { todo: 'default', inProgress: 'default', completed: 'default' }; +export const STORAGE_KEYS = { + tasks: 'tasks', + sortOrders: 'taskSortOrders', + initialized: 'tasksInitialized', + corruptedBackup: 'tasksCorruptedBackup', + showHidden: { completed: 'showHiddenCompletedTasks', todo: 'showHiddenTodoTasks' }, +}; +export const EXPORT_VERSION = '1.0'; +export const DUE_SOON_DAYS = 3; diff --git a/src/js/i18n/en.js b/src/js/i18n/en.js new file mode 100644 index 0000000..44075df --- /dev/null +++ b/src/js/i18n/en.js @@ -0,0 +1,123 @@ +export default { + appTitle: 'Task Management System', + todo: 'To Do', + inProgress: 'In Progress', + completed: 'Completed', + addTask: 'Add Task', + editTask: 'Edit Task', + exportData: 'Export Data', + importData: 'Import Data', + searchPlaceholder: 'Search tasks...', + taskTitleLabel: 'Task Title *', + statusLabel: 'Status', + progressNotesLabel: 'Progress Notes', + descriptionLabel: 'Task Description', + priorityLabel: 'Priority', + languageLabel: 'Language', + assigneeLabel: 'Assignee', + dueDateLabel: 'Due Date', + collaboratorsLabel: 'Collaborators', + progressLabel: 'Progress (%)', + hiddenTaskLabel: 'Hide this task (applies to completed and to-do tasks)', + saveTask: 'Save Task', + cancel: 'Cancel', + progressNotesPlaceholder: 'Record the latest progress and status...', + descriptionPlaceholder: 'Describe the task goals and requirements...', + assigneePlaceholder: 'Enter assignee name', + collaboratorsPlaceholder: 'Separate multiple people with commas', + defaultSort: 'Default Sort', + titleAsc: 'Title A-Z', + titleDesc: 'Title Z-A', + numberAsc: 'Number ↑', + numberDesc: 'Number ↓', + dueDateAsc: 'Due Date ↑', + dueDateDesc: 'Due Date ↓', + priorityAsc: 'Priority ↑ (Low→High)', + priorityDesc: 'Priority ↓ (High→Low)', + progressAsc: 'Progress ↑ (Low→High)', + progressDesc: 'Progress ↓ (High→Low)', + createdDesc: 'Newest First', + createdAsc: 'Oldest First', + lowPriority: 'Low Priority', + mediumPriority: 'Medium Priority', + highPriority: 'High Priority', + low: 'Low', + medium: 'Medium', + high: 'High', + assignee: 'Assignee: ', + collaborators: 'Collaborators: ', + dueDate: 'Due: ', + progress: 'Progress: ', + overdue: 'Overdue', + dueSoon: 'Due Soon', + noTasks: 'No tasks', + noSearchResults: 'No matching tasks', + timeline: 'Timeline', + noTimelineEntries: 'No timeline entries', + addTimelinePlaceholder: 'Add timeline entry...', + add: 'Add', + addEntry: 'Add Entry', + edit: 'Edit', + delete: 'Delete', + save: 'Save', + editContentPlaceholder: 'Edit content...', + keyboardHint: 'Ctrl+Enter to save | Esc to cancel', + systemUser: 'System', + defaultUser: 'User', + taskAdded: 'Task added', + taskUpdated: 'Task updated', + taskDeleted: 'Task deleted', + taskPinned: 'Task pinned', + taskUnpinned: 'Task unpinned', + taskHidden: 'Task hidden', + taskShown: 'Task shown', + pinTask: 'Pin task', + unpinTask: 'Unpin task', + showTask: 'Show task', + hideTask: 'Hide task', + taskTitleRequired: 'Task title cannot be empty', + taskNotFound: 'The task no longer exists', + confirmDelete: 'Are you sure you want to delete this task?', + confirmDeleteTimeline: 'Delete this timeline entry?', + contentRequired: 'Content cannot be empty', + timelineEntryAdded: 'Timeline entry added', + timelineEntryUpdated: 'Timeline entry updated', + timelineEntryDeleted: 'Timeline entry deleted', + statusChanged: 'Status Changed', + progressUpdated: 'Progress Updated', + assigneeChanged: 'Assignee Changed', + dueDateChanged: 'Due Date Changed', + priorityChanged: 'Priority Changed', + progressNotesUpdated: 'Progress Notes Updated', + progressCleared: 'Progress notes cleared', + storageTasksReset: 'Local task data is corrupted; the original data was backed up', + storageWriteFailed: + 'Could not save locally; this change is only in memory and will be lost on refresh', + overdueTasks: '{count} task(s) overdue!', + dueSoonTasks: '{count} task(s) due soon!', + allCompletedHidden: 'All completed tasks are hidden ({count})', + allTodoHidden: 'All to-do tasks are hidden ({count})', + showHiddenTasks: 'Show hidden tasks ({count})', + hideHiddenTasks: 'Hide hidden tasks', + showHiddenTitle: 'Show hidden tasks', + hideHiddenTitle: 'Hide hidden tasks', + exportSuccess: 'Data exported successfully', + exportError: 'Export failed', + importSuccess: 'Data imported successfully', + importError: 'Import failed', + invalidFileFormat: 'Invalid file format', + fileReadError: 'File read error', + confirmImport: 'Confirm Import Data', + importMode: 'Import Mode', + replaceMode: 'Replace Existing Data', + replaceModeDesc: 'Will delete all current tasks', + mergeMode: 'Merge Data', + mergeModeDesc: 'Keep existing tasks and add new tasks', + confirmImportBtn: 'Confirm Import', + importFileInfo: 'Import File Information', + fileVersion: 'File Version', + taskCount: 'Task Count', + exportDate: 'Export Date', + currentTaskCount: 'There are currently {count} tasks', + duplicateTasksSkipped: 'Skipped {count} duplicate tasks', +}; diff --git a/src/js/i18n/index.js b/src/js/i18n/index.js new file mode 100644 index 0000000..8a3b565 --- /dev/null +++ b/src/js/i18n/index.js @@ -0,0 +1,29 @@ +import zh from './zh.js'; +import en from './en.js'; +const dictionaries = { zh, en }; +let language = 'zh'; +export const getLanguage = () => language; +export function t(key, params = {}) { + const template = dictionaries[language][key] ?? key; + return Object.entries(params).reduce( + (text, [name, value]) => text.replaceAll(`{${name}}`, String(value)), + template, + ); +} +export function applyTranslations(root = document) { + document.documentElement.lang = language === 'zh' ? 'zh-CN' : 'en'; + document.title = t('appTitle'); + root.querySelectorAll('[data-key]').forEach((element) => { + element.textContent = t(element.dataset.key); + }); + root.querySelectorAll('[data-key-placeholder]').forEach((element) => { + element.placeholder = t(element.dataset.keyPlaceholder); + }); + const button = document.querySelector('#langBtn'); + if (button) button.textContent = language === 'zh' ? 'EN' : '中文'; +} +export function toggleLanguage() { + language = language === 'zh' ? 'en' : 'zh'; + applyTranslations(); + return language; +} diff --git a/src/js/i18n/zh.js b/src/js/i18n/zh.js new file mode 100644 index 0000000..8187f02 --- /dev/null +++ b/src/js/i18n/zh.js @@ -0,0 +1,122 @@ +export default { + appTitle: '任务管理系统', + todo: '待办事项', + inProgress: '进行中', + completed: '已完成', + addTask: '添加任务', + editTask: '编辑任务', + exportData: '导出数据', + importData: '导入数据', + searchPlaceholder: '搜索任务...', + taskTitleLabel: '任务标题 *', + statusLabel: '状态', + progressNotesLabel: '任务进展', + descriptionLabel: '任务描述', + priorityLabel: '优先级', + languageLabel: '语言', + assigneeLabel: '负责人', + dueDateLabel: '到期日期', + collaboratorsLabel: '协助人员', + progressLabel: '进度 (%)', + hiddenTaskLabel: '隐藏此任务(对已完成和待办事项生效)', + saveTask: '保存任务', + cancel: '取消', + progressNotesPlaceholder: '记录任务的最新进展、当前状态等...', + descriptionPlaceholder: '描述任务的目标、要求和基本信息...', + assigneePlaceholder: '输入负责人姓名', + collaboratorsPlaceholder: '用逗号分隔多个人员', + defaultSort: '默认排序', + titleAsc: '标题 A-Z', + titleDesc: '标题 Z-A', + numberAsc: '数字排序 ↑', + numberDesc: '数字排序 ↓', + dueDateAsc: '到期日期 ↑', + dueDateDesc: '到期日期 ↓', + priorityAsc: '优先级 ↑ (低→高)', + priorityDesc: '优先级 ↓ (高→低)', + progressAsc: '进度 ↑ (低→高)', + progressDesc: '进度 ↓ (高→低)', + createdDesc: '最新创建', + createdAsc: '最早创建', + lowPriority: '低优先级', + mediumPriority: '中优先级', + highPriority: '高优先级', + low: '低', + medium: '中', + high: '高', + assignee: '负责人:', + collaborators: '协助:', + dueDate: '到期:', + progress: '进度:', + overdue: '已过期', + dueSoon: '即将到期', + noTasks: '暂无任务', + noSearchResults: '没有匹配的任务', + timeline: '时间轴', + noTimelineEntries: '暂无时间轴记录', + addTimelinePlaceholder: '添加时间轴条目...', + add: '添加', + addEntry: '添加条目', + edit: '编辑', + delete: '删除', + save: '保存', + editContentPlaceholder: '编辑内容...', + keyboardHint: 'Ctrl+Enter保存 | Esc取消', + systemUser: '系统', + defaultUser: '用户', + taskAdded: '任务已添加', + taskUpdated: '任务已更新', + taskDeleted: '任务已删除', + taskPinned: '任务置顶', + taskUnpinned: '取消置顶', + taskHidden: '任务已隐藏', + taskShown: '任务已显示', + pinTask: '置顶任务', + unpinTask: '取消置顶', + showTask: '显示任务', + hideTask: '隐藏任务', + taskTitleRequired: '任务标题不能为空', + taskNotFound: '任务不存在或已被删除', + confirmDelete: '确定要删除这个任务吗?', + confirmDeleteTimeline: '确定要删除这个时间轴条目吗?', + contentRequired: '内容不能为空', + timelineEntryAdded: '时间轴条目已添加', + timelineEntryUpdated: '时间轴条目已更新', + timelineEntryDeleted: '时间轴条目已删除', + statusChanged: '状态变更', + progressUpdated: '进度更新', + assigneeChanged: '负责人变更', + dueDateChanged: '到期日期变更', + priorityChanged: '优先级变更', + progressNotesUpdated: '任务进展更新', + progressCleared: '进展内容已清空', + storageTasksReset: '本地任务数据损坏,无法加载;原始数据已备份', + storageWriteFailed: '无法保存到本地存储;当前修改仅保留在内存中,刷新后会丢失', + overdueTasks: '{count} 个任务已过期!', + dueSoonTasks: '{count} 个任务即将到期!', + allCompletedHidden: '所有已完成任务都已隐藏 ({count} 个)', + allTodoHidden: '所有待办事项都已隐藏 ({count} 个)', + showHiddenTasks: '显示隐藏任务 ({count}个)', + hideHiddenTasks: '隐藏已被隐藏的任务', + showHiddenTitle: '显示被隐藏的任务', + hideHiddenTitle: '隐藏被隐藏的任务', + exportSuccess: '数据导出成功', + exportError: '导出失败', + importSuccess: '数据导入成功', + importError: '导入失败', + invalidFileFormat: '无效的文件格式', + fileReadError: '文件读取失败', + confirmImport: '确认导入数据', + importMode: '导入模式', + replaceMode: '覆盖现有数据', + replaceModeDesc: '将删除当前所有任务', + mergeMode: '合并数据', + mergeModeDesc: '保留现有任务,添加新任务', + confirmImportBtn: '确认导入', + importFileInfo: '导入文件信息', + fileVersion: '文件版本', + taskCount: '任务数量', + exportDate: '导出时间', + currentTaskCount: '当前系统中有 {count} 个任务', + duplicateTasksSkipped: '已跳过 {count} 个重复任务', +}; diff --git a/src/js/import-export.js b/src/js/import-export.js new file mode 100644 index 0000000..9ee5127 --- /dev/null +++ b/src/js/import-export.js @@ -0,0 +1,115 @@ +import { EXPORT_VERSION, STATUSES } from './config.js'; +import { t } from './i18n/index.js'; +import { normalizeSortOrders, normalizeTask, normalizeTasks, isValidId } from './task-model.js'; +import { getSortOrders, getTasks, replaceSortOrders, replaceTasks } from './store.js'; +import { escapeHtml, formatLocalDate, generateUniqueId, parseDueDateEnd } from './utils.js'; + +const REVOKE_DELAY_MS = 10000; +let pending = null; +export function validateImportData(data) { + if ( + !data || + typeof data !== 'object' || + data.version !== EXPORT_VERSION || + !data.data || + !Array.isArray(data.data.tasks) + ) + return false; + return data.data.tasks.every( + (task) => + task && + typeof task === 'object' && + isValidId(task.id) && + typeof task.title === 'string' && + task.title.trim() && + STATUSES.includes(task.status) && + (task.dueDate == null || task.dueDate === '' || parseDueDateEnd(task.dueDate)), + ); +} +export function mergeTasks(existing, incoming) { + const keys = new Set(existing.map((task) => `${task.title}|${task.status}|${task.createdDate}`)); + const ids = new Set(existing.map((task) => task.id)); + const added = []; + let skipped = 0; + incoming.forEach((raw) => { + const created = typeof raw.createdDate === 'string' ? raw.createdDate : ''; + const task = normalizeTask({ ...raw, id: generateUniqueId(ids) }); + const key = created ? `${task.title}|${task.status}|${created}` : null; + if (key && keys.has(key)) { + skipped += 1; + return; + } + if (key) keys.add(key); + added.push(task); + }); + return { tasks: [...existing, ...added], skipped }; +} +export function createExportData(now = new Date()) { + return { + version: EXPORT_VERSION, + exportDate: now.toISOString(), + localDate: formatLocalDate(now), + taskCount: getTasks().length, + data: { tasks: getTasks(), sortOrders: getSortOrders() }, + }; +} +export function exportData() { + const data = createExportData(); + const filename = `backup-tasks-${data.localDate}.json`; + const url = URL.createObjectURL( + new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' }), + ); + const link = document.createElement('a'); + link.href = url; + link.download = filename; + // Firefox only downloads from an anchor in the document, and the blob must outlive the click + // because browsers start the transfer asynchronously. + document.body.append(link); + link.click(); + link.remove(); + setTimeout(() => URL.revokeObjectURL(url), REVOKE_DELAY_MS); + return filename; +} +export function readImportFile(file, onReady, onInvalid, onReadError) { + pending = null; + const reader = new FileReader(); + reader.onload = () => { + try { + const data = JSON.parse(reader.result); + if (!validateImportData(data)) { + onInvalid(); + return; + } + pending = data; + onReady(data); + } catch { + onInvalid(); + } + }; + reader.onerror = onReadError; + reader.readAsText(file); +} +export function showImportDialog(data) { + const date = + data.exportDate && !Number.isNaN(Date.parse(data.exportDate)) + ? new Date(data.exportDate).toLocaleString() + : '-'; + document.querySelector('#importInfo').innerHTML = + `
${escapeHtml(t('importFileInfo'))}
${escapeHtml(t('currentTaskCount', { count: getTasks().length }))}
`; + bootstrap.Modal.getOrCreateInstance(document.querySelector('#importModal')).show(); +} +export function executeImport(mode) { + if (!pending) throw new Error('No import pending'); + if (!['replace', 'merge'].includes(mode)) throw new TypeError('Invalid import mode'); + let skipped = 0; + if (mode === 'replace') replaceTasks(normalizeTasks(pending.data.tasks)); + else { + const merged = mergeTasks(getTasks(), pending.data.tasks); + replaceTasks(merged.tasks); + skipped = merged.skipped; + } + replaceSortOrders(normalizeSortOrders(pending.data.sortOrders)); + pending = null; + bootstrap.Modal.getOrCreateInstance(document.querySelector('#importModal')).hide(); + return skipped; +} diff --git a/src/js/main.js b/src/js/main.js new file mode 100644 index 0000000..136c5e8 --- /dev/null +++ b/src/js/main.js @@ -0,0 +1,266 @@ +import { SORT_VALUES, STATUSES } from './config.js'; +import { applyTranslations, t, toggleLanguage } from './i18n/index.js'; +import { executeImport, exportData, readImportFile, showImportDialog } from './import-export.js'; +import { openTaskModal, saveTask, updateProgressDisplay } from './modal.js'; +import { checkDueDates, showNotification } from './notify.js'; +import { + applySearchFilter, + isTimelineExpanded, + pruneTaskCaches, + renderAllTasks, + renderColumn, + setTimelineExpanded, + updateTaskCounts, +} from './render.js'; +import { createSeedTasks } from './seed-data.js'; +import { + getShowHidden, + getTasks, + initializeStore, + removeTask, + setShowHidden, + setSortOrder, + toggleHidden, + togglePin, + wasCorrupted, +} from './store.js'; +import { consumeStorageWriteFailure, isInitialized } from './storage.js'; +import { addTimelineEntry, deleteTimelineEntry, updateTimelineEntry } from './timeline.js'; +import { isHiddenTask } from './utils.js'; + +function renderAffected(...statuses) { + [...new Set(statuses.filter(Boolean))].forEach(renderColumn); +} +function notifyPersisted(message, type = 'info') { + if (consumeStorageWriteFailure()) { + showNotification(t('storageWriteFailed'), 'danger'); + return false; + } + if (message) showNotification(message, type); + return true; +} +function taskAction(event) { + const action = event.target.closest('[data-action]'); + if (!action) return; + const card = action.closest('[data-task-id]'); + const id = Number(card?.dataset.taskId); + const status = card?.closest('.task-list')?.dataset.status; + if (action.dataset.action === 'toggle-show-hidden') { + const value = !getShowHidden(action.dataset.status); + setShowHidden(action.dataset.status, value); + renderColumn(action.dataset.status); + notifyPersisted( + t(value ? 'showHiddenTasks' : 'hideHiddenTasks', { + count: getTasks().filter( + (task) => task.status === action.dataset.status && isHiddenTask(task), + ).length, + }), + ); + return; + } + if (!card) return; + const timelineItem = action.closest('[data-timeline-id]'); + const timelineId = Number(timelineItem?.dataset.timelineId); + switch (action.dataset.action) { + case 'pin': { + const task = togglePin(id); + if (!task) { + showNotification(t('taskNotFound'), 'warning'); + break; + } + renderColumn(status); + notifyPersisted(t(task.isPinned ? 'taskPinned' : 'taskUnpinned')); + break; + } + case 'hide': { + const task = toggleHidden(id); + if (!task) { + showNotification(t('taskNotFound'), 'warning'); + break; + } + renderColumn(status); + notifyPersisted(t(task.isHidden ? 'taskHidden' : 'taskShown')); + break; + } + case 'edit': + openTaskModal(id); + break; + case 'delete': + if (confirm(t('confirmDelete'))) { + if (!removeTask(id)) { + showNotification(t('taskNotFound'), 'warning'); + break; + } + pruneTaskCaches(); + renderColumn(status); + notifyPersisted(t('taskDeleted'), 'warning'); + } + break; + case 'toggle-timeline': + setTimelineExpanded(id, !isTimelineExpanded(id)); + renderColumn(status); + break; + case 'show-add-timeline': { + const form = action.closest('.timeline').querySelector('.timeline-form'); + form.classList.remove('d-none'); + form.querySelector('input').focus(); + break; + } + case 'cancel-add-timeline': + action.closest('.timeline-form').classList.add('d-none'); + break; + case 'add-timeline': { + const input = action.closest('.timeline-form').querySelector('input'); + if (addTimelineEntry(id, input.value)) { + setTimelineExpanded(id, true); + renderColumn(status); + notifyPersisted(t('timelineEntryAdded'), 'success'); + } else showNotification(t('contentRequired'), 'warning'); + break; + } + case 'edit-timeline': + timelineItem.querySelector('.timeline-content').classList.add('d-none'); + timelineItem.querySelector('.timeline-edit-form').classList.remove('d-none'); + timelineItem.querySelector('textarea').focus(); + break; + case 'cancel-timeline': + timelineItem.querySelector('.timeline-content').classList.remove('d-none'); + timelineItem.querySelector('.timeline-edit-form').classList.add('d-none'); + break; + case 'save-timeline': { + const value = timelineItem.querySelector('textarea').value; + if (updateTimelineEntry(id, timelineId, value)) { + setTimelineExpanded(id, true); + renderColumn(status); + notifyPersisted(t('timelineEntryUpdated'), 'success'); + } else showNotification(t('contentRequired'), 'warning'); + break; + } + case 'delete-timeline': + if (confirm(t('confirmDeleteTimeline')) && deleteTimelineEntry(id, timelineId)) { + setTimelineExpanded(id, true); + renderColumn(status); + notifyPersisted(t('timelineEntryDeleted'), 'warning'); + } + break; + } +} +function bind() { + document.querySelector('#addTaskBtn').addEventListener('click', () => openTaskModal()); + document.querySelector('#saveTaskBtn').addEventListener('click', () => { + const result = saveTask(); + if (result?.error) { + showNotification(t(result.error), 'warning'); + return; + } + if (result) { + renderAffected(result.oldStatus, result.task.status); + notifyPersisted(t(result.oldStatus ? 'taskUpdated' : 'taskAdded'), 'success'); + } + }); + document.querySelector('#taskProgress').addEventListener('input', updateProgressDisplay); + const runSearch = () => { + applySearchFilter(); + updateTaskCounts(); + }; + document.querySelector('#searchInput').addEventListener('input', runSearch); + document.querySelector('#searchBtn').addEventListener('click', runSearch); + document.querySelector('#languageBtn').addEventListener('click', () => { + toggleLanguage(); + renderAllTasks(); + }); + document.querySelector('#exportBtn').addEventListener('click', () => { + try { + showNotification(`${t('exportSuccess')} (${exportData()})`, 'success'); + } catch { + showNotification(t('exportError'), 'danger'); + } + }); + document + .querySelector('#importBtn') + .addEventListener('click', () => document.querySelector('#fileInput').click()); + document.querySelector('#fileInput').addEventListener('change', (event) => { + const file = event.target.files[0]; + if (file) + readImportFile( + file, + showImportDialog, + () => showNotification(t('invalidFileFormat'), 'danger'), + () => showNotification(t('fileReadError'), 'danger'), + ); + event.target.value = ''; + }); + document.querySelector('#confirmImportBtn').addEventListener('click', () => { + try { + const mode = document.querySelector('[name="importMode"]:checked').value; + const skipped = executeImport(mode); + pruneTaskCaches(); + renderAllTasks(); + if (notifyPersisted(t('importSuccess'), 'success') && skipped) + showNotification(t('duplicateTasksSkipped', { count: skipped })); + } catch { + showNotification(t('importError'), 'danger'); + } + }); + document.querySelector('#taskBoard').addEventListener('click', taskAction); + document.querySelector('#taskBoard').addEventListener('change', (event) => { + if ( + event.target.matches('.sort-selector') && + STATUSES.includes(event.target.dataset.status) && + SORT_VALUES.has(event.target.value) + ) { + setSortOrder(event.target.dataset.status, event.target.value); + renderColumn(event.target.dataset.status); + notifyPersisted(); + } + }); + document.querySelector('#taskBoard').addEventListener('keydown', (event) => { + const card = event.target.closest('[data-task-id]'); + if (!card) return; + const status = card.closest('.task-list').dataset.status; + if (event.key === 'Enter' && event.target.matches('[data-role="timeline-input"]')) { + event.preventDefault(); + if (addTimelineEntry(Number(card.dataset.taskId), event.target.value)) { + setTimelineExpanded(Number(card.dataset.taskId), true); + renderColumn(status); + notifyPersisted(t('timelineEntryAdded'), 'success'); + } else showNotification(t('contentRequired'), 'warning'); + } + if (event.target.matches('[data-role="timeline-edit"]') && event.key === 'Escape') { + event.preventDefault(); + event.target.closest('.timeline-edit-form').classList.add('d-none'); + event.target + .closest('.timeline-item') + .querySelector('.timeline-content') + .classList.remove('d-none'); + } + if ( + event.target.matches('[data-role="timeline-edit"]') && + event.key === 'Enter' && + event.ctrlKey + ) { + event.preventDefault(); + const item = event.target.closest('[data-timeline-id]'); + if ( + updateTimelineEntry( + Number(card.dataset.taskId), + Number(item.dataset.timelineId), + event.target.value, + ) + ) { + setTimelineExpanded(Number(card.dataset.taskId), true); + renderColumn(status); + notifyPersisted(t('timelineEntryUpdated'), 'success'); + } else showNotification(t('contentRequired'), 'warning'); + } + }); +} +document.addEventListener('DOMContentLoaded', () => { + initializeStore(createSeedTasks(), isInitialized()); + applyTranslations(); + renderAllTasks(); + bind(); + if (wasCorrupted()) showNotification(t('storageTasksReset'), 'danger'); + notifyPersisted(); + checkDueDates(); +}); diff --git a/src/js/modal.js b/src/js/modal.js new file mode 100644 index 0000000..502c633 --- /dev/null +++ b/src/js/modal.js @@ -0,0 +1,82 @@ +import { t } from './i18n/index.js'; +import { addTask, getTask, getTasks, updateTask } from './store.js'; +import { addSystemEntries } from './timeline.js'; +import { generateUniqueId } from './utils.js'; + +const byId = (id) => document.querySelector(`#${id}`); +export function updateProgressDisplay() { + byId('progressDisplay').textContent = `${byId('taskProgress').value}%`; +} +export function openTaskModal(taskId = null) { + const form = byId('taskForm'); + form.reset(); + byId('taskId').value = taskId ?? ''; + const task = taskId ? getTask(taskId) : null; + byId('modalTitle').dataset.key = task ? 'editTask' : 'addTask'; + byId('modalTitle').textContent = t(task ? 'editTask' : 'addTask'); + if (task) { + byId('taskTitle').value = task.title; + byId('taskDescription').value = task.description; + byId('taskProgressNotes').value = task.progressNotes; + byId('taskStatus').value = task.status; + byId('taskProgress').value = task.progress; + byId('taskAssignee').value = task.assignee; + byId('taskDueDate').value = task.dueDate; + byId('taskCollaborators').value = task.collaborators.join(', '); + byId('taskLanguage').value = task.language; + byId('taskPriority').value = task.priority; + byId('taskHidden').checked = task.isHidden; + } else { + byId('taskPriority').value = 'low'; + byId('taskLanguage').value = 'zh'; + } + updateProgressDisplay(); + bootstrap.Modal.getOrCreateInstance(byId('taskModal')).show(); +} +function values() { + return { + title: byId('taskTitle').value.trim(), + description: byId('taskDescription').value, + progressNotes: byId('taskProgressNotes').value, + status: byId('taskStatus').value, + progress: Number(byId('taskProgress').value), + assignee: byId('taskAssignee').value, + dueDate: byId('taskDueDate').value, + collaborators: byId('taskCollaborators') + .value.split(',') + .map((v) => v.trim()) + .filter(Boolean), + language: byId('taskLanguage').value, + priority: byId('taskPriority').value, + isHidden: byId('taskHidden').checked, + }; +} +export function saveTask() { + const data = values(); + const title = byId('taskTitle'); + title.setCustomValidity(data.title ? '' : t('taskTitleRequired')); + if (!byId('taskForm').reportValidity()) return null; + const id = Number(byId('taskId').value); + let oldStatus = null; + let task; + if (id) { + const old = getTask(id); + if (!old) return { error: 'taskNotFound' }; + oldStatus = old.status; + task = updateTask(id, { ...data, timeline: addSystemEntries(old, data) }); + } else { + task = addTask({ + id: generateUniqueId(new Set(getTasks().map((item) => item.id))), + ...data, + isPinned: false, + createdDate: new Date().toISOString(), + timeline: [], + }); + if (data.progressNotes) + task = updateTask(task.id, { + timeline: addSystemEntries({ ...task, progressNotes: '' }, data), + }); + } + bootstrap.Modal.getOrCreateInstance(byId('taskModal')).hide(); + return { task, oldStatus }; +} diff --git a/src/js/notify.js b/src/js/notify.js new file mode 100644 index 0000000..ec6453c --- /dev/null +++ b/src/js/notify.js @@ -0,0 +1,30 @@ +import { t } from './i18n/index.js'; +import { getTasks } from './store.js'; +import { getDueDateState } from './utils.js'; +export function showNotification(message, type = 'info') { + const alert = document.createElement('div'); + alert.className = `alert alert-${type} alert-dismissible fade show`; + const text = document.createElement('span'); + text.textContent = String(message); + const close = document.createElement('button'); + close.className = 'btn-close'; + close.dataset.bsDismiss = 'alert'; + alert.append(text, close); + document.querySelector('#notifications').append(alert); + setTimeout(() => alert.remove(), 5000); +} +export function checkDueDates(now = new Date()) { + let overdue = 0, + soon = 0; + getTasks().forEach((task) => { + if (!task.dueDate || task.status === 'completed') return; + const state = getDueDateState(task.dueDate, now); + if (state.isOverdue) overdue += 1; + else if (state.isDueSoon) soon += 1; + }); + const parts = []; + if (overdue) parts.push(t('overdueTasks', { count: overdue })); + if (soon) parts.push(t('dueSoonTasks', { count: soon })); + if (parts.length) showNotification(parts.join(' '), overdue ? 'danger' : 'warning'); + return { overdue, soon }; +} diff --git a/src/js/render.js b/src/js/render.js new file mode 100644 index 0000000..1bec4b5 --- /dev/null +++ b/src/js/render.js @@ -0,0 +1,192 @@ +import { HIDEABLE_STATUSES, SORT_OPTIONS, STATUSES } from './config.js'; +import { t } from './i18n/index.js'; +import { getShowHidden, getSortOrders, getTasks } from './store.js'; +import { sortTasks } from './sort.js'; +import { escapeHtml, getDueDateState, isHiddenTask } from './utils.js'; + +const expanded = new Set(); +const searchIndexes = new Map(); +export const setTimelineExpanded = (id, value) => (value ? expanded.add(id) : expanded.delete(id)); +export const isTimelineExpanded = (id) => expanded.has(id); +export function pruneTaskCaches(taskIds = getTasks().map((task) => task.id)) { + const validIds = new Set(taskIds); + [expanded, searchIndexes].forEach((cache) => { + [...cache.keys()].forEach((id) => { + if (!validIds.has(id)) cache.delete(id); + }); + }); +} +export function renderSortOptions() { + const orders = getSortOrders(); + document.querySelectorAll('.sort-selector').forEach((select) => { + select.innerHTML = SORT_OPTIONS.map( + ([value, key]) => ``, + ).join(''); + select.value = orders[select.dataset.status]; + }); +} +function timelineContent(item) { + if (!item.event) return String(item.content ?? ''); + const values = item.event.values ?? {}; + const from = values.from || '-'; + const to = values.to || '-'; + if (item.event.key === 'progressNotesUpdated') { + const value = String(values.value ?? '').trim(); + return value ? `${value.slice(0, 50)}${value.length > 50 ? '...' : ''}` : t('progressCleared'); + } + if (item.event.key === 'progressUpdated') + return `${t('progressUpdated')}: ${values.from ?? '-'}% → ${values.to ?? '-'}%`; + if (['statusChanged', 'priorityChanged'].includes(item.event.key)) + return `${t(item.event.key)}: ${t(from)} → ${t(to)}`; + return `${t(item.event.key)}: ${from} → ${to}`; +} +function entryTime(item) { + // Imported entries can carry an unparsable date; treat those as the oldest instead of comparing NaN. + const time = Date.parse(item.date); + return Number.isNaN(time) ? -Infinity : time; +} +function timelineOrder(a, b) { + // Entry dates have day granularity, so ids (allocated in increasing order) break same-day ties. + const left = entryTime(a); + const right = entryTime(b); + return left === right ? b.id - a.id : right - left; +} +function createTimeline(task) { + const items = [...task.timeline].sort(timelineOrder); + const rows = items.length + ? items + .map( + (item) => + `
${escapeHtml(item.date)} - ${escapeHtml(item.userType === 'system' ? t('systemUser') : item.user)}
${escapeHtml(timelineContent(item))}
${t('keyboardHint')}
`, + ) + .join('') + : `

${t('noTimelineEntries')}

`; + return `${rows}
`; +} +function createTaskActions(task) { + const hiddenAction = HIDEABLE_STATUSES.includes(task.status) + ? `` + : ''; + return `
${hiddenAction}
`; +} +function createPriorityBadge(task) { + return `${t(task.priority)}`; +} +function createPeople(task) { + const assignee = task.assignee + ? `
${t('assignee')}${escapeHtml(task.assignee)}
` + : ''; + const collaborators = task.collaborators + .map((name) => `${escapeHtml(name)}`) + .join(''); + return `${assignee}${collaborators ? `
${t('collaborators')}${collaborators}
` : ''}`; +} +function createProgress(task) { + const notes = task.progressNotes + ? `
${t('progressNotesLabel')}:
${escapeHtml(task.progressNotes)}
` + : ''; + const bar = + task.progress > 0 + ? `
${t('progress')}${task.progress}%` + : ''; + return `${notes}${bar}`; +} +function createDueDateBlock(task, due) { + if (!task.dueDate) return ''; + const dateClass = due.isOverdue ? 'text-danger' : due.isDueSoon ? 'text-warning' : ''; + const badge = due.isOverdue + ? `${t('overdue')}` + : due.isDueSoon + ? `${t('dueSoon')}` + : ''; + return `
${t('dueDate')}${escapeHtml(task.dueDate)} ${badge}
`; +} +function buildSearchIndex(task) { + return [ + task.title, + task.description, + task.progressNotes, + task.assignee, + ...task.collaborators, + task.dueDate, + t(task.priority), + ...task.timeline.map(timelineContent), + ] + .join(' ') + .toLowerCase(); +} +function createCard(task) { + // Indexed here so that a rendered card always has a matching entry for applySearchFilter. + searchIndexes.set(task.id, buildSearchIndex(task)); + const due = getDueDateState(task.dueDate); + const open = expanded.has(task.id); + const classes = [ + 'task-card', + 'card', + task.status, + `priority-${task.priority}`, + task.isPinned ? 'pinned' : '', + isHiddenTask(task) ? 'hidden-task' : '', + due.isOverdue ? 'overdue' : due.isDueSoon ? 'due-soon' : '', + ] + .filter(Boolean) + .join(' '); + const description = task.description + ? `

${escapeHtml(task.description)}

` + : ''; + return `
${createTaskActions(task)}
${escapeHtml(task.title)}
${createPriorityBadge(task)}
${description}${createProgress(task)}${createPeople(task)}${createDueDateBlock(task, due)}
${t('timeline')} (${task.timeline.length})
${createTimeline(task)}
`; +} +export function updateTaskCounts() { + const tasks = getTasks(); + STATUSES.forEach((status) => { + const cards = [...document.querySelectorAll(`#${status}Tasks .task-card`)]; + document.querySelector(`#${status}Count`).textContent = cards.filter( + (card) => !card.hidden, + ).length; + }); + HIDEABLE_STATUSES.forEach((status) => { + const button = document.querySelector( + `[data-action="toggle-show-hidden"][data-status="${status}"]`, + ); + const count = tasks.filter((task) => task.status === status && isHiddenTask(task)).length; + button.hidden = count === 0; + button.title = t(getShowHidden(status) ? 'hideHiddenTitle' : 'showHiddenTitle'); + button.querySelector('i').className = `fas fa-${getShowHidden(status) ? 'eye-slash' : 'eye'}`; + }); +} +function updateSearchEmptyState(list) { + const cards = [...list.querySelectorAll('.task-card')]; + const notice = list.querySelector('.search-empty-state'); + if (cards.length && cards.every((card) => card.hidden)) { + const node = notice ?? document.createElement('p'); + node.className = 'empty-state search-empty-state'; + node.textContent = t('noSearchResults'); + if (!notice) list.append(node); + } else notice?.remove(); +} +export function applySearchFilter(root = document) { + const term = document.querySelector('#searchInput').value.trim().toLowerCase(); + root.querySelectorAll('.task-card').forEach((card) => { + const index = searchIndexes.get(Number(card.dataset.taskId)) ?? ''; + card.hidden = Boolean(term && !index.includes(term)); + }); + const lists = root.matches?.('.task-list') ? [root] : [...root.querySelectorAll('.task-list')]; + lists.forEach(updateSearchEmptyState); +} +export function renderColumn(status, updateCounts = true) { + const all = getTasks().filter((task) => task.status === status); + const hidden = all.filter(isHiddenTask).length; + const shown = getShowHidden(status) ? all : all.filter((task) => !isHiddenTask(task)); + const ordered = sortTasks(shown, getSortOrders()[status]); + const key = status === 'completed' ? 'allCompletedHidden' : 'allTodoHidden'; + document.querySelector(`#${status}Tasks`).innerHTML = ordered.length + ? ordered.map(createCard).join('') + : `

${hidden ? t(key, { count: hidden }) : t('noTasks')}

`; + applySearchFilter(document.querySelector(`#${status}Tasks`)); + if (updateCounts) updateTaskCounts(); +} +export function renderAllTasks() { + STATUSES.forEach((status) => renderColumn(status, false)); + updateTaskCounts(); + renderSortOptions(); +} diff --git a/src/js/seed-data.js b/src/js/seed-data.js new file mode 100644 index 0000000..436923f --- /dev/null +++ b/src/js/seed-data.js @@ -0,0 +1,94 @@ +export function createSeedTasks(now = new Date()) { + const createdDate = now.toISOString(); + return [ + { + id: 1, + title: '所有设备相机参数标准化', + description: '完成所有生产设备的相机参数标准化工作,确保图像采集的一致性和可靠性。', + progressNotes: '目前已完成大部分设备的参数标准化工作,仅剩DieBond贴片相机待确认参数配置。', + status: 'inProgress', + progress: 90, + assignee: '技术团队', + collaborators: [], + dueDate: '2025-02-15', + createdDate, + language: 'zh', + priority: 'high', + isPinned: false, + timeline: [], + isHidden: false, + }, + { + id: 2, + title: 'DieBond设备照明优化', + description: + '定制组合光源:同轴光+环形光,采购更窄、更亮的侧面光源,以解决支架白边问题,提升图像质量。', + progressNotes: + '配置方案已与供应商确认,技术参数符合要求。供应商承诺2025年7月4日发货,目前正在生产中。', + status: 'inProgress', + progress: 60, + assignee: '硬件团队', + collaborators: ['供应商'], + dueDate: '2025-07-04', + createdDate, + language: 'zh', + priority: 'medium', + isPinned: true, + timeline: [], + isHidden: false, + }, + { + id: 3, + title: 'DieBond和ClipBond软件界面中英文翻译更新', + description: '完成DieBond和ClipBond设备软件界面的中英文翻译工作,支持多语言操作环境。', + progressNotes: + 'SG-TATA GUI 的中英文翻译已完成。未来更新杨杰的代码后,需要重新进行翻译(任务量很少)。', + status: 'inProgress', + progress: 80, + assignee: '杨杰', + collaborators: ['翻译团队'], + dueDate: '2025-02-28', + createdDate, + language: 'zh', + priority: 'medium', + isPinned: false, + timeline: [], + isHidden: false, + }, + { + id: 4, + title: '所有设备相机手册', + description: '编写所有设备相机的操作手册,包括校准流程、位置引导方法、检测标准等技术文档。', + progressNotes: '任务刚启动,正在收集各设备的相机技术规格和操作要求。', + status: 'todo', + progress: 0, + assignee: '技术文档组', + collaborators: [], + dueDate: '2025-03-31', + createdDate, + language: 'zh', + priority: 'low', + isPinned: false, + timeline: [], + isHidden: false, + }, + { + id: 5, + title: '开发 TaTa SECS/GEM & Map 软件', + description: + '开发符合SEMI标准的SECS/GEM通信协议软件,实现设备与MES系统的标准化通信,包括Map数据管理功能。', + progressNotes: '需求分析阶段,正在梳理SECS/GEM协议规范和技术架构设计。', + status: 'todo', + progress: 0, + assignee: '软件开发组', + collaborators: ['系统架构师'], + dueDate: '2025-06-30', + createdDate, + language: 'zh', + priority: 'high', + isPinned: false, + timeline: [], + isHidden: false, + }, + ]; +} diff --git a/src/js/sort.js b/src/js/sort.js new file mode 100644 index 0000000..470ff20 --- /dev/null +++ b/src/js/sort.js @@ -0,0 +1,36 @@ +import { PRIORITY_ORDER, SORT_VALUES } from './config.js'; +import { isHiddenTask } from './utils.js'; + +const number = (task) => Number.parseInt(task.title.match(/\d+/)?.[0], 10) || 0; +const date = (value) => new Date(value).getTime() || 0; +const title = (a, b) => a.title.localeCompare(b.title); +const due = (a, b) => { + if (!a.dueDate && !b.dueDate) return 0; + if (!a.dueDate) return 1; + if (!b.dueDate) return -1; + return date(a.dueDate) - date(b.dueDate); +}; +const comparators = { + title, + number: (a, b) => number(a) - number(b) || title(a, b), + dueDate: due, + priority: (a, b) => PRIORITY_ORDER[a.priority] - PRIORITY_ORDER[b.priority], + progress: (a, b) => a.progress - b.progress, + created: (a, b) => date(a.createdDate) - date(b.createdDate), +}; +export function sortTasks(taskList, sortOrder = 'default') { + const result = [...taskList]; + const valid = SORT_VALUES.has(sortOrder) ? sortOrder : 'default'; + return result.sort((a, b) => { + if (a.isPinned !== b.isPinned) return a.isPinned ? -1 : 1; + if (isHiddenTask(a) !== isHiddenTask(b)) return isHiddenTask(a) ? 1 : -1; + if (valid === 'default') + return ( + PRIORITY_ORDER[b.priority] - PRIORITY_ORDER[a.priority] || + date(b.createdDate) - date(a.createdDate) + ); + const [key, direction] = valid.split('-'); + if (key === 'dueDate' && (!a.dueDate || !b.dueDate)) return due(a, b); + return comparators[key](a, b) * (direction === 'desc' ? -1 : 1); + }); +} diff --git a/src/js/storage.js b/src/js/storage.js new file mode 100644 index 0000000..c1eca5a --- /dev/null +++ b/src/js/storage.js @@ -0,0 +1,63 @@ +import { STORAGE_KEYS } from './config.js'; + +let writeFailed = false; +export function consumeStorageWriteFailure() { + const failed = writeFailed; + writeFailed = false; + return failed; +} +function readRaw(key) { + try { + return { value: localStorage.getItem(key), available: true }; + } catch { + return { value: null, available: false }; + } +} +function writeRaw(key, value) { + try { + localStorage.setItem(key, value); + return true; + } catch { + writeFailed = true; + return false; + } +} +function readJson(key, fallback) { + const { value, available } = readRaw(key); + if (!available || value === null) return fallback; + try { + return JSON.parse(value); + } catch { + return fallback; + } +} +function writeJson(key, value) { + return writeRaw(key, JSON.stringify(value)); +} +export function loadTasksData() { + const { value: raw, available } = readRaw(STORAGE_KEYS.tasks); + if (!available) return { tasks: [], corrupted: false, exists: false, available: false }; + if (raw === null) return { tasks: [], corrupted: false, exists: false, available: true }; + writeRaw(STORAGE_KEYS.initialized, 'true'); + try { + const tasks = JSON.parse(raw); + if (!Array.isArray(tasks)) throw new TypeError('Tasks must be an array'); + return { tasks, corrupted: false, exists: true, available: true }; + } catch { + writeRaw(STORAGE_KEYS.corruptedBackup, raw); + return { tasks: [], corrupted: true, exists: true, available: true }; + } +} +export const saveTasksData = (tasks) => writeJson(STORAGE_KEYS.tasks, tasks); +export const loadSortOrdersData = () => readJson(STORAGE_KEYS.sortOrders, {}); +export const saveSortOrdersData = (orders) => writeJson(STORAGE_KEYS.sortOrders, orders); +export const loadShowHiddenData = (status) => { + const key = STORAGE_KEYS.showHidden[status]; + return key ? readJson(key, false) === true : false; +}; +export const saveShowHiddenData = (status, value) => { + const key = STORAGE_KEYS.showHidden[status]; + return key ? writeJson(key, value) : false; +}; +export const isInitialized = () => readRaw(STORAGE_KEYS.initialized).value !== null; +export const markInitialized = () => writeRaw(STORAGE_KEYS.initialized, 'true'); diff --git a/src/js/store.js b/src/js/store.js new file mode 100644 index 0000000..7c7f4f7 --- /dev/null +++ b/src/js/store.js @@ -0,0 +1,107 @@ +import { DEFAULT_SORT_ORDERS, HIDEABLE_STATUSES, SORT_VALUES, STATUSES } from './config.js'; +import { normalizeSortOrders, normalizeTask, normalizeTasks } from './task-model.js'; +import { generateUniqueId } from './utils.js'; +import { + loadShowHiddenData, + loadSortOrdersData, + loadTasksData, + markInitialized, + saveShowHiddenData, + saveSortOrdersData, + saveTasksData, +} from './storage.js'; + +let tasks = []; +let sortOrders = { ...DEFAULT_SORT_ORDERS }; +let showHidden = Object.fromEntries(HIDEABLE_STATUSES.map((status) => [status, false])); +let corrupted = false; +const persistTasks = () => saveTasksData(tasks); +export function initializeStore(seedTasks, initialized) { + const loaded = loadTasksData(); + corrupted = loaded.corrupted; + tasks = normalizeTasks(loaded.tasks); + if (loaded.exists && !loaded.corrupted) persistTasks(); + if (!initialized && !loaded.exists) { + tasks = normalizeTasks(seedTasks); + persistTasks(); + markInitialized(); + } + sortOrders = { ...DEFAULT_SORT_ORDERS, ...normalizeSortOrders(loadSortOrdersData()) }; + showHidden = Object.fromEntries( + HIDEABLE_STATUSES.map((status) => [status, loadShowHiddenData(status)]), + ); +} +export const wasCorrupted = () => corrupted; +export const getTasks = () => tasks.map((task) => structuredClone(task)); +export const getTask = (id) => { + const task = tasks.find((item) => item.id === id); + return task ? structuredClone(task) : null; +}; +export const getSortOrders = () => ({ ...sortOrders }); +export const getShowHidden = (status) => showHidden[status] === true; +export function addTask(task) { + const normalized = normalizeTask(task); + const ids = new Set(tasks.map((item) => item.id)); + if (ids.has(normalized.id)) normalized.id = generateUniqueId(ids); + tasks.push(normalized); + persistTasks(); + return structuredClone(normalized); +} +export function updateTask(id, changes) { + const index = tasks.findIndex((task) => task.id === id); + if (index < 0) return null; + tasks[index] = normalizeTask({ ...tasks[index], ...changes, id }); + persistTasks(); + return structuredClone(tasks[index]); +} +export function removeTask(id) { + const before = tasks.length; + tasks = tasks.filter((task) => task.id !== id); + if (tasks.length !== before) persistTasks(); + return tasks.length !== before; +} +export function togglePin(id) { + const task = tasks.find((item) => item.id === id); + if (!task) return null; + task.isPinned = !task.isPinned; + persistTasks(); + return structuredClone(task); +} +export function toggleHidden(id) { + const task = tasks.find((item) => item.id === id); + if (!task) return null; + task.isHidden = !task.isHidden; + persistTasks(); + return structuredClone(task); +} +export function setSortOrder(status, value) { + if (!STATUSES.includes(status) || !SORT_VALUES.has(value)) return false; + sortOrders[status] = value; + saveSortOrdersData(sortOrders); + return true; +} +export function setShowHidden(status, value) { + if (!HIDEABLE_STATUSES.includes(status) || typeof value !== 'boolean') return false; + showHidden[status] = value; + saveShowHiddenData(status, value); + return true; +} +export function replaceTasks(value) { + tasks = normalizeTasks(value); + persistTasks(); +} +export function appendTasks(value) { + const ids = new Set(tasks.map((task) => task.id)); + const additions = normalizeTasks(value).map((task) => { + if (ids.has(task.id)) task.id = generateUniqueId(ids); + else ids.add(task.id); + return task; + }); + tasks = [...tasks, ...additions]; + persistTasks(); + return additions.map((task) => structuredClone(task)); +} +export function replaceSortOrders(value) { + sortOrders = { ...sortOrders, ...normalizeSortOrders(value) }; + saveSortOrdersData(sortOrders); +} diff --git a/src/js/task-model.js b/src/js/task-model.js new file mode 100644 index 0000000..770f76d --- /dev/null +++ b/src/js/task-model.js @@ -0,0 +1,56 @@ +import { LANGUAGES, PRIORITIES, SORT_VALUES, STATUSES } from './config.js'; +import { generateId, generateUniqueId, isValidId } from './utils.js'; + +const TIMELINE_TYPES = ['manual-entry', 'status-change', 'progress-update', 'progressNotesUpdated']; +export function normalizeTask(input) { + const task = input && typeof input === 'object' ? { ...input } : {}; + task.title = String(task.title ?? '').trim(); + task.description = String(task.description ?? ''); + task.progressNotes = String(task.progressNotes ?? ''); + task.assignee = String(task.assignee ?? ''); + task.dueDate = typeof task.dueDate === 'string' ? task.dueDate : ''; + task.createdDate = + task.createdDate && !Number.isNaN(Date.parse(task.createdDate)) + ? task.createdDate + : new Date().toISOString(); + task.status = STATUSES.includes(task.status) ? task.status : 'todo'; + task.priority = PRIORITIES.includes(task.priority) ? task.priority : 'medium'; + task.language = LANGUAGES.includes(task.language) ? task.language : 'zh'; + task.progress = Math.min(100, Math.max(0, Number.parseInt(task.progress, 10) || 0)); + task.collaborators = Array.isArray(task.collaborators) ? task.collaborators.map(String) : []; + task.isPinned = task.isPinned === true; + task.isHidden = task.isHidden === true; + task.id = isValidId(task.id) ? task.id : generateId(); + const timelineIds = new Set(); + task.timeline = (Array.isArray(task.timeline) ? task.timeline : []) + .filter((item) => item && typeof item === 'object' && !Array.isArray(item)) + .map((item) => { + let id = isValidId(item.id) ? item.id : generateId(); + while (timelineIds.has(id)) id = generateId(); + timelineIds.add(id); + return { ...item, id, type: TIMELINE_TYPES.includes(item.type) ? item.type : 'manual-entry' }; + }); + return task; +} +export function normalizeTasks(items) { + if (!Array.isArray(items)) return []; + const ids = new Set(); + return items + .filter((item) => item && typeof item === 'object') + .map((item) => { + const task = normalizeTask(item); + if (ids.has(task.id)) task.id = generateUniqueId(ids); + else ids.add(task.id); + return task; + }); +} +export function normalizeSortOrders(value) { + if (!value || typeof value !== 'object' || Array.isArray(value)) return {}; + return Object.fromEntries( + STATUSES.filter((status) => SORT_VALUES.has(value[status])).map((status) => [ + status, + value[status], + ]), + ); +} +export { isValidId } from './utils.js'; diff --git a/src/js/timeline.js b/src/js/timeline.js new file mode 100644 index 0000000..b6640d7 --- /dev/null +++ b/src/js/timeline.js @@ -0,0 +1,63 @@ +import { t } from './i18n/index.js'; +import { getTask, updateTask } from './store.js'; +import { formatLocalDate, generateUniqueId } from './utils.js'; + +export function addSystemEntries(task, changes) { + const timeline = [...task.timeline]; + const fields = [ + ['progressNotes', 'progressNotesUpdated', 'progressNotesUpdated', (a, b) => ({ value: b })], + ['status', 'status-change', 'statusChanged', (a, b) => ({ from: a, to: b })], + ['progress', 'progress-update', 'progressUpdated', (a, b) => ({ from: a, to: b })], + ['assignee', 'status-change', 'assigneeChanged', (a, b) => ({ from: a, to: b })], + ['dueDate', 'status-change', 'dueDateChanged', (a, b) => ({ from: a, to: b })], + ['priority', 'status-change', 'priorityChanged', (a, b) => ({ from: a, to: b })], + ]; + const used = new Set(timeline.map((item) => item.id)); + fields.forEach(([field, type, key, values]) => { + if (Object.hasOwn(changes, field) && task[field] !== changes[field]) + timeline.push({ + id: generateUniqueId(used), + date: formatLocalDate(), + type, + content: '', + event: { key, values: values(task[field], changes[field]) }, + userType: 'system', + }); + }); + return timeline; +} +export function addTimelineEntry(taskId, content) { + const task = getTask(taskId); + if (!task || !content.trim()) return false; + const id = generateUniqueId(new Set(task.timeline.map((item) => item.id))); + task.timeline.push({ + id, + date: formatLocalDate(), + type: 'manual-entry', + content: content.trim(), + user: task.assignee || t('defaultUser'), + }); + updateTask(taskId, { timeline: task.timeline }); + return true; +} +export function updateTimelineEntry(taskId, timelineId, content) { + const task = getTask(taskId); + const item = task?.timeline.find((entry) => entry.id === timelineId); + if (!item || !content.trim()) return false; + const wasSystem = item.userType === 'system'; + item.content = content.trim(); + item.type = 'manual-entry'; + if (wasSystem || !item.user) item.user = task.assignee || t('defaultUser'); + delete item.event; + delete item.userType; + updateTask(taskId, { timeline: task.timeline }); + return true; +} +export function deleteTimelineEntry(taskId, timelineId) { + const task = getTask(taskId); + if (!task) return false; + const timeline = task.timeline.filter((item) => item.id !== timelineId); + if (timeline.length === task.timeline.length) return false; + updateTask(taskId, { timeline }); + return true; +} diff --git a/src/js/utils.js b/src/js/utils.js new file mode 100644 index 0000000..9b6449a --- /dev/null +++ b/src/js/utils.js @@ -0,0 +1,43 @@ +import { DUE_SOON_DAYS, HIDEABLE_STATUSES } from './config.js'; + +// Only todo and completed tasks can be hidden, so the flag is inert in any other column. +export const isHiddenTask = (task) => + task.isHidden === true && HIDEABLE_STATUSES.includes(task.status); + +let idCounter = 0; +export const generateId = () => Date.now() * 1000 + (idCounter++ % 1000); +export function generateUniqueId(usedIds) { + let id = generateId(); + while (usedIds.has(id)) id = generateId(); + usedIds.add(id); + return id; +} +export const isValidId = (value) => Number.isSafeInteger(value) && value > 0; +export function escapeHtml(value) { + return String(value ?? '') + .replaceAll('&', '&') + .replaceAll('<', '<') + .replaceAll('>', '>') + .replaceAll('"', '"') + .replaceAll("'", '''); +} +export function formatLocalDate(date = new Date()) { + return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`; +} +export function parseDueDateEnd(value) { + const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(value ?? ''); + if (!match) return null; + const [, y, m, d] = match.map(Number); + const date = new Date(y, m - 1, d, 23, 59, 59, 999); + return date.getFullYear() === y && date.getMonth() === m - 1 && date.getDate() === d + ? date + : null; +} +export function getDueDateState(value, now = new Date()) { + const dueEnd = parseDueDateEnd(value); + if (!dueEnd) return { isOverdue: false, isDueSoon: false }; + const soonEnd = new Date(now); + soonEnd.setDate(soonEnd.getDate() + DUE_SOON_DAYS); + soonEnd.setHours(23, 59, 59, 999); + return { isOverdue: dueEnd < now, isDueSoon: dueEnd >= now && dueEnd <= soonEnd }; +} diff --git a/task.md b/task.md new file mode 100644 index 0000000..8b3c6ad --- /dev/null +++ b/task.md @@ -0,0 +1,41 @@ + +### 待办 +- 1、打印任务功能 +- 2、任务中英文切换 +- 3、记录创建日期,用于 Completed 分类中的排序 + + +### 进行中 + +- V1.0 #1、拆分成 html、cs、js +- V1.0 #2、将Json文件 改名为backup-tasks-yyyy-mm-dd.json + + +### 已完成 + +### V1.0 + +```yaml + +``` + +### V0.3 + +```yaml +- 1、【待办事项】添加隐藏任务功能 +``` + +### V0.2 + +```yaml +- 1、数据保存到本地。Json 文件格式 +- 2、去除按钮 【检测到期按钮提醒】,保留刷新时自动检测 +- 3、已完成栏,可以隐藏任务功能 +``` + +### V0.1 + +```yaml +- 1、时间轴的内容可以编辑,修改,添加 +- 2、添加软件图标 +``` diff --git a/test/backup-tasks-v1.json b/test/backup-tasks-v1.json new file mode 100644 index 0000000..e3eb483 --- /dev/null +++ b/test/backup-tasks-v1.json @@ -0,0 +1,57 @@ +{ + "version": "1.0", + "exportDate": "2025-01-10T08:00:00.000Z", + "localDate": "2025-01-10", + "taskCount": 2, + "data": { + "tasks": [ + { + "id": 101, + "title": "Legacy todo", + "description": "Created by the pre-refactor app", + "progressNotes": "Existing progress", + "status": "todo", + "progress": 25, + "assignee": "Alice", + "collaborators": ["Bob"], + "dueDate": "2025-02-01", + "createdDate": "2025-01-01T08:00:00.000Z", + "language": "en", + "priority": "high", + "isPinned": true, + "isHidden": false, + "timeline": [ + { + "id": 1001, + "date": "2025-01-01", + "type": "manual-entry", + "content": "Legacy timeline entry", + "user": "Alice" + } + ] + }, + { + "id": 102, + "title": "Legacy completed", + "description": "", + "progressNotes": "", + "status": "completed", + "progress": 100, + "assignee": "", + "collaborators": [], + "dueDate": "", + "createdDate": "2025-01-02T08:00:00.000Z", + "language": "zh", + "priority": "low", + "isPinned": false, + "isHidden": true, + "timeline": [] + } + ], + "sortOrders": { + "todo": "priority-desc", + "inProgress": "created-asc", + "completed": "title-asc" + } + } +} diff --git a/test/core.test.js b/test/core.test.js new file mode 100644 index 0000000..bff40f0 --- /dev/null +++ b/test/core.test.js @@ -0,0 +1,505 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; +import legacyBackup from './backup-tasks-v1.json' with { type: 'json' }; +import { HIDEABLE_STATUSES, STATUSES } from '../src/js/config.js'; +import { t } from '../src/js/i18n/index.js'; +import { normalizeTask, normalizeTasks } from '../src/js/task-model.js'; +import { sortTasks } from '../src/js/sort.js'; +import { getDueDateState } from '../src/js/utils.js'; +import { applySearchFilter, renderColumn, updateTaskCounts } from '../src/js/render.js'; +import { addSystemEntries, addTimelineEntry, updateTimelineEntry } from '../src/js/timeline.js'; +import { + executeImport, + exportData, + mergeTasks, + readImportFile, + validateImportData, +} from '../src/js/import-export.js'; +import { + consumeStorageWriteFailure, + isInitialized, + loadShowHiddenData, + loadSortOrdersData, + loadTasksData, +} from '../src/js/storage.js'; +import { + addTask, + appendTasks, + getShowHidden, + getSortOrders, + getTasks, + initializeStore, + setShowHidden, + setSortOrder, + updateTask, +} from '../src/js/store.js'; +import { createSeedTasks } from '../src/js/seed-data.js'; + +const base = (id, title, extra = {}) => ({ + id, + title, + status: 'todo', + priority: 'medium', + progress: 20, + createdDate: `2025-01-${String(id).padStart(2, '0')}T00:00:00Z`, + dueDate: `2025-01-${String(id).padStart(2, '0')}`, + timeline: [], + ...extra, +}); + +beforeEach(() => localStorage.clear()); +afterEach(() => vi.restoreAllMocks()); + +describe('task model', () => { + it('normalizes fields and language whitelist', () => { + const task = normalizeTask({ + id: 1, + title: ' x ', + language: 'fr', + progress: 999, + status: 'bad', + }); + expect(task).toMatchObject({ title: 'x', language: 'zh', progress: 100, status: 'todo' }); + expect(normalizeTask({ id: 2, title: 'English', language: 'en' }).language).toBe('en'); + }); + it('deduplicates task and timeline ids', () => { + const tasks = normalizeTasks([ + base(1, 'a', { timeline: [{ id: 5 }, { id: 5 }] }), + base(1, 'b'), + ]); + expect(new Set(tasks.map((task) => task.id)).size).toBe(2); + expect(new Set(tasks[0].timeline.map((item) => item.id)).size).toBe(2); + }); + it('drops non-object timeline entries from legacy data', () => { + const task = normalizeTask({ + ...base(1, 'dirty timeline'), + timeline: ['oops', 42, true, null, [], { id: 7, content: 'valid' }], + }); + expect(task.timeline).toEqual([{ id: 7, content: 'valid', type: 'manual-entry' }]); + }); +}); + +describe('all 13 sorts', () => { + const tasks = [ + base(1, 'Beta 10', { + priority: 'medium', + progress: 50, + dueDate: '2025-03-02', + createdDate: '2025-01-02T00:00:00Z', + }), + base(2, 'Alpha 2', { + priority: 'high', + progress: 90, + dueDate: '2025-03-03', + createdDate: '2025-01-03T00:00:00Z', + }), + base(3, 'Gamma 1', { + priority: 'low', + progress: 10, + dueDate: '2025-03-01', + createdDate: '2025-01-01T00:00:00Z', + }), + ]; + const expected = { + default: [2, 1, 3], + 'title-asc': [2, 1, 3], + 'title-desc': [3, 1, 2], + 'number-asc': [3, 2, 1], + 'number-desc': [1, 2, 3], + 'dueDate-asc': [3, 1, 2], + 'dueDate-desc': [2, 1, 3], + 'priority-asc': [3, 1, 2], + 'priority-desc': [2, 1, 3], + 'progress-asc': [3, 1, 2], + 'progress-desc': [2, 1, 3], + 'created-asc': [3, 1, 2], + 'created-desc': [2, 1, 3], + }; + it.each(Object.entries(expected))('sorts %s in the expected order', (order, ids) => { + const copy = structuredClone(tasks); + expect(sortTasks(tasks, order).map((task) => task.id)).toEqual(ids); + expect(tasks).toEqual(copy); + }); + it('keeps pinned first for every sort', () => { + const pinned = { ...tasks[2], isPinned: true }; + Object.keys(expected).forEach((order) => + expect(sortTasks([tasks[0], tasks[1], pinned], order)[0].id).toBe(3), + ); + }); + it('keeps hidden todo tasks last but leaves hidden in-progress tasks in place', () => { + const hiddenTodo = { ...tasks[1], isHidden: true }; + expect(sortTasks([hiddenTodo, tasks[0]], 'title-asc').map((task) => task.id)).toEqual([1, 2]); + const hiddenWork = { ...tasks[1], status: 'inProgress', isHidden: true }; + expect(sortTasks([hiddenWork, tasks[0]], 'title-asc').map((task) => task.id)).toEqual([2, 1]); + }); + it('keeps tasks without due dates last in both directions', () => { + const dated = base(1, 'Dated'); + const undated = { ...base(2, 'Undated'), dueDate: '' }; + expect(sortTasks([undated, dated], 'dueDate-asc').map((task) => task.id)).toEqual([1, 2]); + expect(sortTasks([undated, dated], 'dueDate-desc').map((task) => task.id)).toEqual([1, 2]); + }); +}); + +describe('due dates', () => { + const now = new Date(2025, 0, 10, 12); + it('detects overdue and due-soon boundaries', () => { + expect(getDueDateState('2025-01-09', now)).toEqual({ isOverdue: true, isDueSoon: false }); + expect(getDueDateState('2025-01-10', now)).toEqual({ isOverdue: false, isDueSoon: true }); + expect(getDueDateState('2025-01-13', now).isDueSoon).toBe(true); + expect(getDueDateState('2025-01-14', now).isDueSoon).toBe(false); + }); + it('ignores empty and invalid dates', () => { + expect(getDueDateState('', now)).toEqual({ isOverdue: false, isDueSoon: false }); + expect(getDueDateState('2025-02-30', now)).toEqual({ isOverdue: false, isDueSoon: false }); + }); +}); + +describe('seed data', () => { + it('creates five unique tasks at the initialization time', () => { + const now = new Date('2026-07-19T02:54:00.000Z'); + const tasks = createSeedTasks(now); + expect(tasks).toHaveLength(5); + expect(new Set(tasks.map((task) => task.id)).size).toBe(5); + expect(tasks.every((task) => task.createdDate === now.toISOString())).toBe(true); + }); +}); + +describe('storage and store compatibility', () => { + it('distinguishes a missing task key from an intentionally empty task list', () => { + expect(loadTasksData()).toMatchObject({ tasks: [], corrupted: false, exists: false }); + localStorage.setItem('tasks', '[]'); + expect(loadTasksData()).toMatchObject({ tasks: [], corrupted: false, exists: true }); + }); + it('preserves corrupted raw data without replacing it with seed tasks', () => { + localStorage.setItem('tasks', '{broken'); + initializeStore([base(1, 'Seed')], false); + expect(getTasks()).toEqual([]); + expect(localStorage.getItem('tasks')).toBe('{broken'); + expect(localStorage.getItem('tasksCorruptedBackup')).toBe('{broken'); + }); + it('restores all legacy preferences and task fields', () => { + localStorage.setItem('tasks', JSON.stringify(legacyBackup.data.tasks)); + localStorage.setItem('taskSortOrders', JSON.stringify(legacyBackup.data.sortOrders)); + localStorage.setItem('showHiddenCompletedTasks', 'true'); + localStorage.setItem('showHiddenTodoTasks', 'false'); + localStorage.setItem('tasksInitialized', 'true'); + localStorage.setItem('tasksCorruptedBackup', '{historical backup'); + initializeStore([], isInitialized()); + expect(getTasks()).toMatchObject(legacyBackup.data.tasks); + expect(getSortOrders()).toEqual(legacyBackup.data.sortOrders); + expect(getShowHidden('completed')).toBe(true); + expect(getShowHidden('todo')).toBe(false); + expect(loadSortOrdersData()).toEqual(legacyBackup.data.sortOrders); + expect(loadShowHiddenData('completed')).toBe(true); + expect(localStorage.getItem('tasksCorruptedBackup')).toBe('{historical backup'); + }); + it('maintains store ids and rejects invalid preference mutations', () => { + initializeStore([], true); + const first = addTask(base(1, 'First')); + const duplicate = addTask(base(1, 'Second')); + const [appended] = appendTasks([base(first.id, 'Third')]); + expect(new Set([first.id, duplicate.id, appended.id]).size).toBe(3); + expect(setSortOrder('invalid', 'title-asc')).toBe(false); + expect(setSortOrder('todo', 'invalid')).toBe(false); + expect(setShowHidden('inProgress', true)).toBe(false); + expect(localStorage.getItem('undefined')).toBeNull(); + }); + it('degrades safely when storage access throws', () => { + const getItem = vi.spyOn(Storage.prototype, 'getItem').mockImplementation(() => { + throw new DOMException('blocked', 'SecurityError'); + }); + expect(loadTasksData()).toMatchObject({ tasks: [], exists: false, available: false }); + expect(isInitialized()).toBe(false); + getItem.mockRestore(); + vi.spyOn(Storage.prototype, 'setItem').mockImplementation(() => { + throw new DOMException('full', 'QuotaExceededError'); + }); + expect(() => initializeStore([base(1, 'Seed')], false)).not.toThrow(); + expect(consumeStorageWriteFailure()).toBe(true); + expect(consumeStorageWriteFailure()).toBe(false); + }); +}); + +describe('imports', () => { + function loadPending(data) { + const original = globalThis.FileReader; + class FakeReader { + readAsText() { + this.result = JSON.stringify(data); + this.onload(); + } + } + globalThis.FileReader = FakeReader; + readImportFile({}, vi.fn(), vi.fn(), vi.fn()); + globalThis.FileReader = original; + } + beforeEach(() => { + globalThis.bootstrap = { + Modal: { getOrCreateInstance: () => ({ hide: vi.fn(), show: vi.fn() }) }, + }; + document.body.innerHTML = '
'; + initializeStore([], true); + }); + it('validates the legacy export fixture', () => { + expect(validateImportData(legacyBackup)).toBe(true); + expect(validateImportData({ version: '1.0', data: { tasks: [] } })).toBe(true); + }); + it('rejects unsupported versions, invalid task shapes, and dates', () => { + expect(validateImportData(null)).toBe(false); + expect(validateImportData({ version: '2.0', data: { tasks: [] } })).toBe(false); + expect(validateImportData({ version: '1.0', data: { tasks: 'bad' } })).toBe(false); + expect( + validateImportData({ + version: '1.0', + data: { tasks: [{ id: 1, title: '', status: 'todo' }] }, + }), + ).toBe(false); + expect( + validateImportData({ + version: '1.0', + data: { tasks: [{ ...base(1, 'Bad date'), dueDate: '2025-02-30' }] }, + }), + ).toBe(false); + }); + it('deduplicates merge key and preserves missing dates', () => { + const existing = [base(1, 'Same')]; + const result = mergeTasks(existing, [ + base(2, 'Same', { createdDate: existing[0].createdDate }), + { ...base(3, 'Same'), createdDate: undefined }, + ]); + expect(result.skipped).toBe(1); + expect(result.tasks).toHaveLength(2); + expect(new Set(result.tasks.map((task) => task.id)).size).toBe(2); + }); + it('executes replacement import with legacy tasks and sort orders', () => { + addTask(base(1, 'Existing')); + loadPending(legacyBackup); + expect(executeImport('replace')).toBe(0); + expect(getTasks()).toMatchObject(legacyBackup.data.tasks); + expect(getSortOrders()).toEqual(legacyBackup.data.sortOrders); + }); + it('executes merge import and reports duplicates', () => { + const existing = legacyBackup.data.tasks[0]; + addTask(existing); + loadPending(legacyBackup); + expect(executeImport('merge')).toBe(1); + expect(getTasks()).toHaveLength(2); + }); + it('rejects an invalid import mode', () => { + loadPending(legacyBackup); + expect(() => executeImport('unexpected')).toThrow(TypeError); + }); + it('clears a previous pending import when a newly selected file is invalid', () => { + loadPending(legacyBackup); + loadPending({ version: '1.0', data: { tasks: 'invalid' } }); + expect(() => executeImport('replace')).toThrow('No import pending'); + }); +}); + +describe('timeline entries', () => { + beforeEach(() => initializeStore([], true)); + + it('ignores fields that the change set omits', () => { + const task = addTask(base(1, 'Untouched', { assignee: 'Ada', dueDate: '2025-01-01' })); + expect(addSystemEntries(task, { progress: task.progress })).toEqual([]); + }); + + it('records only the fields that actually changed', () => { + const task = addTask(base(1, 'Changed')); + const timeline = addSystemEntries(task, { progress: 80, status: task.status }); + expect(timeline).toHaveLength(1); + expect(timeline[0].event).toEqual({ key: 'progressUpdated', values: { from: 20, to: 80 } }); + }); + + it('turns an edited system entry into a manual entry owned by the assignee', () => { + const task = addTask(base(1, 'Edited', { assignee: 'Ada' })); + const timeline = addSystemEntries(task, { progress: 80 }); + updateTask(task.id, { timeline }); + expect(updateTimelineEntry(task.id, timeline[0].id, ' my own words ')).toBe(true); + const [stored] = getTasks(); + expect(stored.timeline[0]).toMatchObject({ + type: 'manual-entry', + content: 'my own words', + user: 'Ada', + }); + expect(stored.timeline[0].userType).toBeUndefined(); + expect(stored.timeline[0].event).toBeUndefined(); + }); + + it('keeps the original author when an existing manual entry is edited', () => { + const task = addTask(base(1, 'Reassigned', { assignee: 'Ada' })); + updateTask(task.id, { + timeline: [ + { id: 11, date: '2025-01-01', content: 'by bob', type: 'manual-entry', user: 'Bob' }, + ], + }); + expect(updateTimelineEntry(task.id, 11, ' typo fixed ')).toBe(true); + expect(getTasks()[0].timeline[0]).toMatchObject({ user: 'Bob', content: 'typo fixed' }); + }); +}); + +describe('board rendering', () => { + const fixture = () => + '
' + + STATUSES.map( + (status) => + `` + + `` + + `
`, + ).join('') + + HIDEABLE_STATUSES.map( + (status) => + ``, + ).join('') + + '
'; + const visibleTitles = (status) => + [...document.querySelectorAll(`#${status}Tasks .task-card`)] + .filter((card) => !card.hidden) + .map((card) => card.querySelector('h6').textContent); + const entryTexts = (status) => + [...document.querySelectorAll(`#${status}Tasks .timeline-content`)].map((node) => + node.lastElementChild.textContent.trim(), + ); + const search = (term) => { + document.querySelector('#searchInput').value = term; + applySearchFilter(); + updateTaskCounts(); + }; + + beforeEach(() => { + document.body.innerHTML = fixture(); + initializeStore([], true); + }); + + it('lists timeline entries newest first within a single day', () => { + const task = addTask(base(1, 'Ordered')); + ['first', 'second', 'third'].forEach((text) => addTimelineEntry(task.id, text)); + renderColumn('todo'); + expect(entryTexts('todo')).toEqual(['third', 'second', 'first']); + }); + + it('lists timeline entries newest first across days', () => { + const task = addTask(base(1, 'Ordered')); + updateTask(task.id, { + timeline: [ + { id: 11, date: '2025-01-01', content: 'older', type: 'manual-entry', user: 'Ada' }, + { id: 12, date: '2025-06-01', content: 'newer', type: 'manual-entry', user: 'Ada' }, + ], + }); + renderColumn('todo'); + expect(entryTexts('todo')).toEqual(['newer', 'older']); + }); + + it('lists entries carrying an unusable date last', () => { + const task = addTask(base(1, 'Ordered')); + updateTask(task.id, { + timeline: [ + { id: 11, date: 'sometime', content: 'undated', type: 'manual-entry', user: 'Ada' }, + { id: 12, date: '2025-01-01', content: 'dated', type: 'manual-entry', user: 'Ada' }, + ], + }); + renderColumn('todo'); + expect(entryTexts('todo')).toEqual(['dated', 'undated']); + }); + + it('searches task content without matching card boilerplate', () => { + addTask(base(1, 'Apple', { description: 'crisp fruit', assignee: 'Ada' })); + addTask(base(2, 'Banana', { progressNotes: 'still ripening' })); + renderColumn('todo'); + + search('apple'); + expect(visibleTitles('todo')).toEqual(['Apple']); + search('crisp'); + expect(visibleTitles('todo')).toEqual(['Apple']); + search('ripening'); + expect(visibleTitles('todo')).toEqual(['Banana']); + search('ada'); + expect(visibleTitles('todo')).toEqual(['Apple']); + + [t('timeline'), t('addEntry'), t('edit'), t('progress')].forEach((label) => { + search(label); + expect(visibleTitles('todo')).toEqual([]); + }); + }); + + it('keeps the header count in step with the active search', () => { + addTask(base(1, 'Apple')); + addTask(base(2, 'Banana')); + renderColumn('todo'); + expect(document.querySelector('#todoCount').textContent).toBe('2'); + search('apple'); + expect(document.querySelector('#todoCount').textContent).toBe('1'); + search(''); + expect(document.querySelector('#todoCount').textContent).toBe('2'); + }); + + it('leaves hidden tasks out of the header count while they stay collapsed', () => { + addTask(base(1, 'Visible')); + addTask(base(2, 'Quiet', { isHidden: true })); + renderColumn('todo'); + expect(document.querySelector('#todoCount').textContent).toBe('1'); + setShowHidden('todo', true); + renderColumn('todo'); + expect(document.querySelector('#todoCount').textContent).toBe('2'); + }); + + it('explains a column emptied by the search instead of leaving it blank', () => { + addTask(base(1, 'Apple')); + renderColumn('todo'); + search('kiwi'); + expect(document.querySelector('#todoTasks .search-empty-state').textContent).toBe( + t('noSearchResults'), + ); + search('apple'); + expect(document.querySelector('#todoTasks .search-empty-state')).toBeNull(); + }); + + it('dims hidden todo tasks but leaves in-progress tasks alone', () => { + addTask(base(1, 'Hidden todo', { isHidden: true })); + addTask(base(2, 'Hidden work', { status: 'inProgress', isHidden: true })); + setShowHidden('todo', true); + renderColumn('todo'); + renderColumn('inProgress'); + expect(document.querySelector('#todoTasks .task-card').className).toContain('hidden-task'); + expect(document.querySelector('#inProgressTasks .task-card').className).not.toContain( + 'hidden-task', + ); + expect(document.querySelector('#inProgressCount').textContent).toBe('1'); + }); +}); + +describe('export', () => { + const original = {}; + + beforeEach(() => { + original.createObjectURL = URL.createObjectURL; + original.revokeObjectURL = URL.revokeObjectURL; + document.body.innerHTML = ''; + initializeStore([], true); + }); + afterEach(() => { + URL.createObjectURL = original.createObjectURL; + URL.revokeObjectURL = original.revokeObjectURL; + vi.useRealTimers(); + }); + + it('clicks an attached anchor and revokes the blob url only afterwards', () => { + vi.useFakeTimers(); + addTask(base(1, 'Exported')); + URL.createObjectURL = vi.fn(() => 'blob:fake'); + URL.revokeObjectURL = vi.fn(); + let connectedAtClick = null; + const createElement = document.createElement.bind(document); + vi.spyOn(document, 'createElement').mockImplementation((tag) => { + const element = createElement(tag); + if (tag === 'a') element.click = () => (connectedAtClick = element.isConnected); + return element; + }); + + expect(exportData()).toMatch(/^backup-tasks-\d{4}-\d{2}-\d{2}\.json$/); + expect(connectedAtClick).toBe(true); + expect(document.querySelector('a')).toBeNull(); + expect(URL.revokeObjectURL).not.toHaveBeenCalled(); + vi.runAllTimers(); + expect(URL.revokeObjectURL).toHaveBeenCalledWith('blob:fake'); + }); +}); diff --git a/todo.md b/todo.md new file mode 100644 index 0000000..1a62ca4 --- /dev/null +++ b/todo.md @@ -0,0 +1,191 @@ +# 任务管理系统重构 TODO + +> 分支:`dev-refactor` 目标:在**不改变任何现有功能与数据格式**的前提下,把单文件架构重构为模块化、可维护的结构。 +> 原则:小步提交,每个阶段完成后跑一遍「回归测试清单」再进入下一阶段。 + +--- + +## 一、现状分析 + +| 文件 | 行数 | 问题概述 | +|------|------|----------| +| `src/index.html` | 311 | 内联 onclick、3 份重复的排序下拉、favicon 重复 4 次 | +| `src/script.js` | 1788 | 单文件巨石:状态、i18n、渲染、存储、导入导出全部混在一起,约 30 个全局函数 | +| `src/styles.css` | 432 | 颜色硬编码、选择器重复定义、存在疑似死代码 | + +### 主要问题清单 + +1. **全局可变状态散落**:`tasks`、`currentLang`、`sortOrders`、`showHiddenCompletedTasks`、`showHiddenTodoTasks`、`currentEditingTaskId`、`pendingImportData`、`idCounter` 共 8 个全局变量,任何函数都能直接改。 +2. **内联事件处理**:HTML 和 JS 模板字符串里大量 `onclick="xxx()"`,迫使所有函数必须挂在全局作用域。 +3. **成对重复代码**(completed / todo 两套几乎相同的逻辑): + - `toggleShowHiddenCompletedTasks` / `toggleShowHiddenTodoTasks`(script.js:1665 / 1728) + - `updateShowHiddenCompletedTasksUI` / `updateShowHiddenTodoTasksUI`(script.js:1682 / 1745) + - `save/loadShowHiddenCompletedTasks` / `save/loadShowHiddenTodoTasks`(script.js:1708 / 1771) + - `renderTasks` 内部对 completed 和 todo 的隐藏任务处理两段几乎一样(script.js:630-667) +4. **HTML 重复**:三列的排序 `