diff --git a/.gitignore b/.gitignore index 274dc5b..2561b5b 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,9 @@ build/ .env .env.* !.env.example +!apps/web/.env.production +!apps/web/.env.staging +!apps/web/.env.test .DS_Store .vscode/ diff --git a/apps/web/.browserslistrc b/apps/web/.browserslistrc new file mode 100644 index 0000000..21e47a5 --- /dev/null +++ b/apps/web/.browserslistrc @@ -0,0 +1 @@ +chrome >= 90 diff --git a/apps/web/.env.production b/apps/web/.env.production new file mode 100644 index 0000000..c19eedf --- /dev/null +++ b/apps/web/.env.production @@ -0,0 +1,8 @@ +PUBLIC_ENV=xuyue.cc +PUBLIC_MAXKB_URL=https://maxkb.xuyue.cc/chat/api/embed?protocol=https&host=maxkb.xuyue.cc&token=dd37457027c40b39 +PUBLIC_OJ_URL=https://oj.xuyue.cc +PUBLIC_CODE_URL=https://code.xuyue.cc +PUBLIC_JUDGE0_URL=https://judge0api.xuyue.cc +PUBLIC_SIGNALING_URL=wss://signaling.xuyue.cc +PUBLIC_WS_URL=wss://oj.xuyue.cc/ws +PUBLIC_ICONIFY_URL=https://icon.xuyue.cc \ No newline at end of file diff --git a/apps/web/.env.staging b/apps/web/.env.staging new file mode 100644 index 0000000..7916890 --- /dev/null +++ b/apps/web/.env.staging @@ -0,0 +1,8 @@ +PUBLIC_ENV=school +PUBLIC_MAXKB_URL=http://10.13.114.114:92/chat/api/embed?protocol=http&host=10.13.114.114:92&token=dd37457027c40b39 +PUBLIC_OJ_URL=http://10.13.114.114:81 +PUBLIC_CODE_URL=http://10.13.114.114:82 +PUBLIC_JUDGE0_URL=http://10.13.114.114:8082 +PUBLIC_ICONIFY_URL=http://10.13.114.114:8098 +PUBLIC_SIGNALING_URL=ws://10.13.114.114:8085 +PUBLIC_WS_URL=ws://10.13.114.114:81/ws \ No newline at end of file diff --git a/apps/web/.env.test b/apps/web/.env.test new file mode 100644 index 0000000..cf7dc87 --- /dev/null +++ b/apps/web/.env.test @@ -0,0 +1,8 @@ +PUBLIC_ENV=test +PUBLIC_MAXKB_URL=http://10.13.114.114:92/chat/api/embed?protocol=http&host=10.13.114.114:92&token=dd37457027c40b39 +PUBLIC_OJ_URL=http://10.13.114.114:93 +PUBLIC_CODE_URL=http://10.13.114.114:82 +PUBLIC_JUDGE0_URL=http://10.13.114.114:8082 +PUBLIC_ICONIFY_URL=http://10.13.114.114:8098 +PUBLIC_SIGNALING_URL=ws://10.13.114.114:8085 +PUBLIC_WS_URL=ws://10.13.114.114:93/ws \ No newline at end of file diff --git a/apps/web/.github/workflows/deploy.yml b/apps/web/.github/workflows/deploy.yml new file mode 100644 index 0000000..79cfa2c --- /dev/null +++ b/apps/web/.github/workflows/deploy.yml @@ -0,0 +1,46 @@ +name: Deploy + +on: + push: + branches: + - main + +permissions: + contents: read + +jobs: + deploy: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + include: + - name: debian + build_command: build + remote_port: 22 + target: /root/OJDeploy/data/clientnext + - name: school + build_command: build:staging + remote_port: 8822 + target: /root/OJ/data/dist + steps: + - uses: actions/checkout@v7 + + - uses: actions/setup-node@v7 + with: + node-version: 24 + cache: npm + - run: npm install + - run: npm run ${{ matrix.build_command }} + env: + CI: false + + - uses: easingthemes/ssh-deploy@main + with: + SSH_PRIVATE_KEY: ${{ secrets.KEY }} + REMOTE_HOST: ${{ secrets.HOST }} + REMOTE_PORT: ${{ matrix.remote_port }} + ARGS: "-avzr --delete" + SOURCE: dist/ + REMOTE_USER: root + TARGET: ${{ matrix.target }} \ No newline at end of file diff --git a/apps/web/.gitignore b/apps/web/.gitignore new file mode 100644 index 0000000..e0280c2 --- /dev/null +++ b/apps/web/.gitignore @@ -0,0 +1,30 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? + +src/components.d.ts +src/auto-imports.d.ts + +.claude +.worktrees \ No newline at end of file diff --git a/apps/web/.prettierrc.toml b/apps/web/.prettierrc.toml new file mode 100644 index 0000000..d165b8d --- /dev/null +++ b/apps/web/.prettierrc.toml @@ -0,0 +1 @@ +semi=false \ No newline at end of file diff --git a/apps/web/CLAUDE.md b/apps/web/CLAUDE.md new file mode 100644 index 0000000..7efe1f5 --- /dev/null +++ b/apps/web/CLAUDE.md @@ -0,0 +1,114 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +**ojnext** is the frontend for an Online Judge platform. Built with Vue 3 + TypeScript using Vite (Rolldown-based bundler), Naive UI component library, Pinia for state management, and Vue Router. + +## Commands + +```bash +npm start # Start dev server on port 5173 +npm run build # Production build +npm run build:staging # Staging build +npm run build:test # Test build +npm fmt # Format with Prettier +``` + +No test suite is configured. Linting is via Prettier only. + +## Architecture + +### Directory Structure + +``` +src/ +├── shared/ # Cross-cutting concerns: layout, stores, composables, API +├── oj/ # User-facing features (problems, submissions, contests, etc.) +├── admin/ # Admin panel features +├── utils/ # Constants, types, HTTP client, helpers +├── routes.ts # Route definitions (two top-level: ojs, admins) +├── main.ts # App entry point +└── App.vue # Root component with Naive UI theme setup +``` + +### Module Pattern + +Each feature module (under `oj/` or `admin/`) typically has: +- `views/` — page-level Vue components +- `components/` — feature-specific components +- `api.ts` — API calls specific to the feature + +Shared logic lives in `shared/`: +- `store/` — Pinia stores: `user` (auth/roles), `config` (site-wide settings), `authModal` (login/signup form state), `screenMode` (problem split-screen layout), `loginSummary` (AI activity summary) +- `composables/` — `pagination` (URL-synced), `websocket` (reconnect + heartbeat), `sync` (Yjs/y-webrtc for collaborative editing), `configUpdate` (WS-pushed config sync), `useMermaid` (lazy Mermaid render), `breakpoints`, `maxkb` +- `layout/` — `default.vue` and `admin.vue` layout wrappers +- `api.ts` — shared API calls (auth, profile, tags, captcha) + +### Auto-Imports + +Configured via `unplugin-auto-import` and `unplugin-vue-components`. You do **not** need to manually import: +- Vue APIs (`ref`, `computed`, `watch`, etc.) +- Vue Router (`useRouter`, `useRoute`) +- Pinia (`defineStore`, `storeToRefs`) +- VueUse composables +- Naive UI composables (`useDialog`, `useMessage`, `useNotification`, `useLoadingBar`) +- Naive UI components (all `N*` components) +- Naive UI types (`DataTableColumn`, `FormRules`, `FormItemRule`, `SelectOption`, `UploadCustomRequestOptions`, `UploadFileInfo`, `MenuOption`, `DropdownOption`) + +Generated type declaration files: `src/auto-imports.d.ts`, `src/components.d.ts`. + +### Path Aliases + +``` +utils → ./src/utils +oj → ./src/oj +admin → ./src/admin +shared → ./src/shared +``` + +### HTTP Client + +`utils/http.ts` — Axios instance with interceptors. All API calls proxy through the dev server: +- `/api` and `/public` → `PUBLIC_OJ_URL` (backend) +- `/ws` → `PUBLIC_WS_URL` (WebSocket backend) + +### Key Utilities + +- `utils/constants.ts` — Judge status codes, language IDs, difficulty levels, contest types +- `utils/types.ts` — TypeScript interfaces for all domain models +- `utils/permissions.ts` — Permission check helpers +- `utils/judge.ts` — Judge-related utilities +- `utils/renders.ts` — Table column render helpers for Naive UI DataTable + +### Environment Variables + +Variables prefixed with `PUBLIC_` are injected at build time. Env files: `.env`, `.env.staging`, `.env.test`. + +| Variable | Purpose | +|---|---| +| `PUBLIC_OJ_URL` | Backend REST API base URL | +| `PUBLIC_WS_URL` | WebSocket server URL | +| `PUBLIC_ENV` | Environment name (dev/staging/production) | +| `PUBLIC_CODE_URL` | Code execution service | +| `PUBLIC_JUDGE0_URL` | Judge0 API | +| `PUBLIC_MAXKB_URL` | Knowledge base service | +| `PUBLIC_SIGNALING_URL` | WebRTC signaling server | +| `PUBLIC_ICONIFY_URL` | Iconify icon CDN | + +### Routing + +Routes are defined in `src/routes.ts` with two root routes: `ojs` (user-facing) and `admins` (admin panel). Route meta fields used: +- `requiresAuth` — redirect to login if not authenticated +- `requiresSuperAdmin` — super admin only +- `requiresProblemPermission` — problem management access + +### Real-time Features + +- WebSocket via composable in `shared/composables/` for submission status updates +- Yjs + y-webrtc for collaborative editing in the flowchart editor + +## Related Repository + +The backend is at `../OnlineJudge` — a Django 5 + DRF project. See its CLAUDE.md for backend details. diff --git a/apps/web/README.md b/apps/web/README.md new file mode 100644 index 0000000..b18748c --- /dev/null +++ b/apps/web/README.md @@ -0,0 +1 @@ +oj.xuyue.cc \ No newline at end of file diff --git a/apps/web/docs/API接口对比分析.md b/apps/web/docs/API接口对比分析.md new file mode 100644 index 0000000..2a0cb71 --- /dev/null +++ b/apps/web/docs/API接口对比分析.md @@ -0,0 +1,548 @@ +# API接口对比分析 + +## 更新日志 + +### 最近更新(2025-01-15) +- ✨ **FlowchartEditor 流程图编辑器**:新增完整的流程图编辑功能 + - 基于 Vue Flow 构建的流程图编辑器组件 + - 支持7种节点类型:开始、输入、处理、判断、循环、输出、结束 + - 完整的拖拽创建、节点连接、编辑功能 + - 撤销重做、自动保存、键盘快捷键支持 + - 模块化设计,包含9个独立的功能模块 +- 🎨 **前端组件优化**:完善了组件文档和项目结构说明 + - 更新了 README.md 中的技术栈和项目结构 + - 创建了详细的 FlowchartEditor 使用文档 + - 优化了开发指南和构建流程说明 +- 🔧 **构建工具升级**:从 Vite 迁移到 Rsbuild + - 使用 Rsbuild 作为新的构建工具 + - 支持多环境构建(test、staging、production) + - 优化了构建性能和开发体验 + +### 历史更新(2025-10) +- ✨ **AI分析功能增强**:完善了AI智能分析模块的文档说明 + - 详细说明了4个AI相关接口的功能和参数 + - 新增等级系统说明(S/A/B/C),包含特殊规则 + - 补充了时间范围选择功能 + - 说明了流式响应的实现方式 + - 前端组件从 `WeeklyChart.vue` 升级为 `DurationChart.vue`(混合图表) +- 🔧 **数据缓存优化**:后端AI接口增加了缓存机制,提升性能 +- 🐛 **修正等级系统说明**:更正了等级阈值(A级:前35%,B级:前75%),并补充了小规模参与惩罚规则 + +--- + +## 一、前端已使用的API接口 + +### 1. 用户认证相关(shared/api.ts) +- `POST /api/login` - 用户登录 +- `POST /api/register` - 用户注册 +- `GET /api/logout` - 用户登出 +- `GET /api/profile` - 获取用户资料 +- `GET /api/captcha` - 获取验证码 + +### 2. OJ普通用户API(oj/api.ts) +#### 2.1 网站配置 +- `GET /api/website` - 获取网站配置 +- `GET /api/hitokoto` - 获取一言 + +#### 2.2 题目相关 +- `GET /api/problem` - 获取题目列表 +- `GET /api/problem` - 获取单个题目 +- `GET /api/problem/tags` - 获取题目标签列表 +- `GET /api/problem/author` - 获取题目作者列表 +- `GET /api/problem/beat_count` - 获取题目击败率 +- `GET /api/pickone` - 随机获取题目 +- `GET /api/contest/problem` - 获取竞赛题目 + +#### 2.3 提交相关 +- `GET /api/submission` - 获取单个提交 +- `POST /api/submission` - 提交代码 +- `GET /api/submissions` - 获取提交列表 +- `GET /api/submissions/today_count` - 获取今日提交数 +- `GET /api/contest_submissions` - 获取竞赛提交列表 + +#### 2.4 排名相关 +- `GET /api/user_rank` - 获取用户排名 +- `GET /api/user_activity_rank` - 获取活跃度排名 +- `GET /api/user_problem_rank` - 获取题目排名 +- `GET /api/contest_rank` - 获取竞赛排名 + +#### 2.5 竞赛相关 +- `GET /api/contests` - 获取竞赛列表 +- `GET /api/contest` - 获取单个竞赛 +- `GET /api/contest/access` - 获取竞赛访问权限 +- `POST /api/contest/password` - 验证竞赛密码 + +#### 2.6 公告相关 +- `GET /api/announcement` - 获取公告列表/单个公告 + +#### 2.7 消息相关 +- `POST /api/message` - 创建消息 +- `GET /api/message` - 获取消息列表 + +#### 2.8 评论相关 +- `POST /api/comment` - 创建评论 +- `GET /api/comment` - 获取评论 +- `GET /api/comment/statistics` - 获取评论统计 + +#### 2.9 用户相关 +- `POST /api/upload_avatar` - 上传头像 +- `PUT /api/profile` - 更新用户资料 +- `GET /api/profile/fresh_display_id` - 刷新用户题目显示ID +- `GET /api/metrics` - 获取用户统计数据 + +#### 2.10 教程相关 +- `GET /api/tutorial` - 获取单个教程 +- `GET /api/tutorials` - 获取教程列表 + +#### 2.11 AI分析相关 +- `GET /api/ai/detail` - 获取用户详细数据 + - **参数**: start, end(时间范围) + - **返回**: 用户等级(S/A/B/C)、已解决题目列表、标签统计、难度统计、参赛次数等 + - **特点**: 包含班级排名对比,计算每道题的解题排名和等级 +- `GET /api/ai/duration` - 获取时段数据 + - **参数**: end(结束时间), duration(时间单位,如 "months:6", "weeks:1") + - **返回**: 每周/每月的综合情况(题目数、提交数、等级) + - **用途**: 用于绘制时间趋势图,展示学习进度变化 +- `GET /api/ai/heatmap` - 获取热力图数据 + - **返回**: 用户的提交热力图数据(按日期统计提交数) + - **用途**: 可视化用户活跃度分布 +- `POST /api/ai/analysis` - AI智能分析生成 + - **请求体**: details(详细数据), duration(时段数据) + - **响应方式**: 流式响应(Server-Sent Events) + - **AI提供商**: DeepSeek + - **功能**: 根据用户学习数据生成个性化学习建议和鼓励 + - **实现**: 使用fetch直接调用,在 `oj/store/ai.ts` 中处理流式输出 + +### 3. 管理员API(admin/api.ts) +#### 3.1 仪表板 +- `GET /api/admin/dashboard_info` - 获取仪表板信息 +- `GET /api/admin/random_user` - 随机获取用户 + +#### 3.2 题目管理 +- `GET /api/admin/problem` - 获取题目列表/单个题目 +- `POST /api/admin/problem` - 创建题目 +- `PUT /api/admin/problem` - 编辑题目 +- `PUT /api/admin/problem/visible` - 切换题目可见性 +- `DELETE /api/admin/problem` - 删除题目 +- `GET /api/admin/contest/problem` - 获取竞赛题目 +- `POST /api/admin/contest/problem` - 创建竞赛题目 +- `PUT /api/admin/contest/problem` - 编辑竞赛题目 +- `DELETE /api/admin/contest/problem` - 删除竞赛题目 +- `POST /api/admin/contest/add_problem_from_public` - 从公开题库添加题目到竞赛 + +#### 3.3 用户管理 +- `GET /api/admin/user` - 获取用户列表 +- `POST /api/admin/user` - 导入用户 +- `PUT /api/admin/user` - 编辑用户 +- `DELETE /api/admin/user` - 删除用户 +- `POST /api/admin/reset_password` - 重置用户密码 + +#### 3.4 竞赛管理 +- `GET /api/admin/contest` - 获取竞赛列表/单个竞赛 +- `POST /api/admin/contest` - 创建竞赛 +- `PUT /api/admin/contest` - 编辑竞赛 +- `GET /api/admin/contest/acm_helper` - 获取ACM比赛辅助检查列表 +- `PUT /api/admin/contest/acm_helper` - 更新ACM比赛辅助检查状态 + +#### 3.5 测试用例管理 +- `POST /api/admin/test_case` - 上传测试用例 +- `GET /api/admin/prune_test_case` - 列出无效测试用例 +- `DELETE /api/admin/prune_test_case` - 清理无效测试用例 + +#### 3.6 题目管理扩展 +- `POST /api/admin/contest_problem/make_public` - 将竞赛题目转为公开题目 + +#### 3.7 判题服务器管理 +- `GET /api/admin/judge_server` - 获取判题服务器列表 +- `DELETE /api/admin/judge_server` - 删除判题服务器 + +#### 3.8 公告管理 +- `GET /api/admin/announcement` - 获取公告列表/单个公告 +- `POST /api/admin/announcement` - 创建公告 +- `PUT /api/admin/announcement` - 编辑公告 +- `DELETE /api/admin/announcement` - 删除公告 + +#### 3.9 评论管理 +- `GET /api/admin/comment` - 获取评论列表 +- `DELETE /api/admin/comment` - 删除评论 + +#### 3.10 网站配置 +- `GET /api/admin/website` - 获取网站配置 +- `POST /api/admin/website` - 更新网站配置 + +#### 3.11 文件上传 +- `POST /api/admin/upload_image` - 上传图片(富文本编辑器、Markdown编辑器使用) + +#### 3.12 提交管理 +- `GET /api/admin/submission/rejudge` - 重新判题 +- `GET /api/admin/submission/statistics` - 获取提交统计 + +#### 3.13 教程管理 +- `GET /api/admin/tutorial` - 获取教程列表/单个教程 +- `POST /api/admin/tutorial` - 创建教程 +- `PUT /api/admin/tutorial` - 更新教程 +- `DELETE /api/admin/tutorial` - 删除教程 +- `PUT /api/admin/tutorial/visibility` - 设置教程可见性 + +--- + +## 二、后端提供但前端未使用的API接口 + +### 1. 用户认证相关(account) +- `POST /api/change_password` - 修改密码 +- `POST /api/change_email` - 修改邮箱 +- `POST /api/apply_reset_password` - 申请重置密码 +- `POST /api/reset_password` - 重置密码 +- `GET /api/check_username_or_email` - 检查用户名或邮箱是否存在 +- `GET /api/tfa_required` - 检查是否需要双因素认证 +- `POST /api/two_factor_auth` - 双因素认证 +- `GET /api/sessions` - 会话管理 +- `GET /api/open_api_appkey` - OpenAPI密钥管理 +- `GET /api/sso` - 单点登录 + +### 2. 用户管理(admin) +- `GET /api/admin/generate_user` - 生成用户 + +### 3. 网站配置相关 +- `GET /api/languages` - 获取支持的编程语言列表 + +### 4. 判题服务器内部接口(不需要前端实现) +- ❌ `POST /api/judge_server_heartbeat/` - 判题服务器心跳 + - **标记为不需要** + - **原因**: 此接口由 JudgeServer Docker 容器调用,用于向后端报告服务器状态 + - **使用方**: 判题服务器(非前端) + - **数据内容**: hostname, judger_version, CPU/内存使用率等 + - **认证方式**: 使用特殊的 judge_server_token,非用户认证 + +### 5. 管理员配置相关 +- `GET /api/admin/smtp` - SMTP配置 +- `POST /api/admin/smtp_test` - SMTP测试 +- `GET /api/admin/versions` - 版本信息 + +### 6. 题目管理相关(admin) +- `POST /api/admin/export_problem` - 导出题目 +- `POST /api/admin/import_problem` - 导入题目 +- `POST /api/admin/import_fps` - 导入FPS格式题目 + +### 7. 竞赛相关(admin) +- `GET /api/contest/announcement` - 获取竞赛公告列表(OJ端) +- `GET /api/admin/contest/announcement` - 获取竞赛公告(管理端) +- `GET /api/admin/download_submissions` - 下载竞赛提交 + +### 8. 提交相关(不必要的接口) +- ❌ `GET /api/submission_exists` - 检查提交是否存在 + - **标记为不需要** + - **原因**: 题目接口返回的 `my_status` 字段已完整包含此信息 + - **替代方案**: 直接判断 `my_status` 的值(0=已通过,非零=已尝试未通过,null=未尝试) + - **优势**: 零额外请求,性能更优 + +### 9. 文件上传相关(admin) +- `POST /api/admin/upload_file` - 上传文件(任意格式) + - **说明**: 前端已使用 `upload_image`(仅图片),`upload_file` 可上传任意文件 + - **当前状态**: 未使用(前端暂无上传非图片文件的需求) + - **潜在场景**: 题目附件、教程资料、作业提交等 + +--- + +## 三、统计总结 + +### 前端已使用接口统计 +- **OJ普通用户接口**: 38个(包括1个直接用fetch调用的流式接口) +- **管理员接口**: 35个 +- **共享接口**: 5个 +- **总计**: 78个API调用 + +### 后端未被使用接口统计 +- **用户认证相关**: 10个 +- **网站配置相关**: 2个(languages) +- **题目管理相关**: 3个 +- **竞赛管理相关**: 3个 +- **其他**: 1个 +- **标记为不需要**: 2个 + - submission_exists(数据冗余) + - judge_server_heartbeat(内部接口) +- **总计**: 21个API端点(其中2个不需要前端实现) + +### 未使用接口占比 +约 **21%** 的后端API接口前端尚未使用 +- **需要考虑实现**: 19个接口 +- **不必要实现**: 2个接口(submission_exists, judge_server_heartbeat) + +**备注**: 本次更新新增了 ACM 比赛辅助检查功能(2个接口),用于赛后人工审核代码。 + +--- + +## 四、建议 + +### 1. 高优先级需要实现的功能 +- **密码管理**: change_password, apply_reset_password, reset_password +- **邮箱管理**: change_email +- **用户名/邮箱检查**: check_username_or_email(注册时实时验证) +- **编程语言列表**: languages(显示支持的编程语言) +- **题目导入导出**: export_problem, import_problem(方便题库管理) + +### 2. 中等优先级功能 +- **双因素认证**: tfa_required, two_factor_auth(增强安全性) +- **会话管理**: sessions(多设备登录管理) +- **竞赛公告**: contest/announcement(OJ端,增强竞赛体验) + +### 3. 低优先级功能 +- **SSO单点登录**: sso(如需要集成其他系统) +- **OpenAPI**: open_api_appkey(如需要开放API) +- **SMTP配置**: smtp, smtp_test(管理员配置) +- **版本信息**: versions(显示系统版本) +- **FPS导入**: import_fps(特定格式题目导入) +- **文件上传**: upload_file(目前只有图片上传) + +### 4. 可选功能 +- **生成用户**: generate_user(批量生成测试用户) +- **下载提交**: download_submissions(下载竞赛所有提交) + +--- + +## 五、接口使用率分析 + +| 模块 | 后端提供 | 前端使用 | 使用率 | +|------|---------|---------|--------| +| 用户认证 | 17 | 7 | 41% | +| 题目管理 | 14 | 11 | 79% | +| 提交管理 | 7 | 6 | 86% | +| 竞赛管理 | 12 | 9 | 75% | +| 公告管理 | 4 | 4 | 100% | +| 评论管理 | 4 | 4 | 100% | +| 消息管理 | 1 | 1 | 100% | +| 教程管理 | 4 | 4 | 100% | +| AI分析 | 4 | 4 | 100% ✅ | +| 配置管理 | 9 | 3 | 33% ⚠️(1个为内部接口) | + +**总体使用率约为 79%**(已实现ACM比赛辅助检查功能,竞赛管理使用率提升至75%) + +### 特殊说明 + +#### 1. AI智能分析功能 ✨ + +**功能概述**: 基于用户的学习数据,使用DeepSeek AI生成个性化的学习分析报告和建议。 + +**涉及接口**: +- `GET /api/ai/detail` - 获取详细学习数据 +- `GET /api/ai/duration` - 获取时段趋势数据 +- `GET /api/ai/heatmap` - 获取活跃度热力图 +- `POST /api/ai/analysis` - 生成AI分析(流式响应) + +**前端实现**: +- **页面**: `src/oj/ai/analysis.vue` +- **Store**: `src/oj/store/ai.ts` +- **组件**: + - `DurationChart.vue` - 混合图表(柱状图+折线图),展示题目数、提交数、等级变化 + - `Heatmap.vue` - 提交热力图 + - `Details.vue` - 详细数据展示 + - `AI.vue` - AI分析结果展示(Markdown格式) + +**时间范围选择**: +支持多种时间范围:一节课(1小时)、两节课(2小时)、一天、一周、一个月、两个月、半年、一年 + +**等级系统**: +- **S级**: 排名前10%(卓越水平,约10%的人) +- **A级**: 排名前35%(优秀水平,约25%的人) +- **B级**: 排名前75%(良好水平,约40%的人) +- **C级**: 75%之后(及格水平,约25%的人) +- **特殊规则**: 参与人数少于10人时,S级降为A级,A级降为B级(避免因人少而评级虚高) + +**流式接口实现**: +`POST /api/ai/analysis` 使用了**流式响应(Server-Sent Events)**,在 `oj/store/ai.ts` 中直接使用 `fetch` API调用,配合 `consumeJSONEventStream` 工具函数处理流式数据,实现AI内容的实时流式输出。 + +**数据缓存**: +为提升性能,后端对 `ai/detail` 和 `ai/duration` 接口的返回数据进行了缓存,相同参数的请求会直接返回缓存结果。 + +#### 2. ACM 比赛辅助检查功能 ✨ + +**功能说明**: 用于赛后人工审核 ACM 模式比赛的代码,检查是否存在抄袭、作弊等行为。 + +**涉及接口**: +- `GET /api/admin/contest/acm_helper` - 获取比赛中所有 AC 的提交记录 + - **返回数据**: 用户名、题目ID、AC时间、错误次数、检查状态等 +- `PUT /api/admin/contest/acm_helper` - 更新提交的检查状态 + - **参数**: contest_id, rank_id, problem_id, checked + +**使用场景**: +1. 管理员进入比赛详情页,点击"审核"按钮进入辅助检查页面 +2. 系统展示所有 AC 提交的列表(按用户和题目分组) +3. 管理员可以: + - 查看每个提交的代码详情 + - 标记已检查的提交 + - 批量标记所有提交为已检查 + - 按用户名、题目、检查状态筛选 +4. 实时显示检查进度统计(总计/已检查/未检查) + +**实现位置**: +- 页面: `src/admin/contest/helper.vue` +- 路由: `/admin/contest/:contestID/helper` +- API: `src/admin/api.ts` (getACMHelperList, updateACMHelperChecked) + +#### 3. 前端不需要的接口 ❌ + +##### 3.1 数据冗余接口 +**`GET /api/submission_exists`** - 此接口**无需实现** + +**分析结论**: +- 后端题目接口(`GET /api/problem`)已返回 `my_status` 字段 +- `my_status` 完整记录了用户的做题状态: + - `0` (JudgeStatus.ACCEPTED) = 已通过 ✅ + - `-1` (JudgeStatus.WRONG_ANSWER) = 答案错误 ❌ + - `-2` (JudgeStatus.COMPILE_ERROR) = 编译错误 ❌ + - `1` (JudgeStatus.TIME_LIMIT_EXCEEDED) = 超时 ❌ + - 其他非零值 = 其他失败原因 ❌ + - `null/undefined` = 从未提交 ⭕ + +**前端实现**: +```typescript +// 仅需一个计算属性即可判断 +const hasTriedButNotPassed = computed(() => { + return problem.value?.my_status !== undefined && + problem.value?.my_status !== null && + problem.value?.my_status !== 0 +}) +``` + +**优势对比**: +| 方案 | API请求 | 代码复杂度 | 性能 | +|------|---------|-----------|------| +| ❌ 使用 submission_exists | +1 | 高(需异步+状态管理) | 慢(额外网络请求) | +| ✅ 使用 my_status | 0 | 低(3行计算属性) | 快(本地计算) | + +**经验教训**: +- 实现新功能前应充分了解后端现有数据结构 +- 避免创建冗余接口,优先利用现有数据 +- 简单的解决方案往往是最好的 + +--- + +##### 3.2 内部系统接口 +**`POST /api/judge_server_heartbeat/`** - 此接口**无需前端实现** + +**接口说明**: +- **用途**: 判题服务器(JudgeServer)向后端报告健康状态 +- **调用方**: JudgeServer Docker 容器(非前端) +- **调用频率**: 每隔几秒自动调用一次 +- **认证方式**: 使用 `judge_server_token`(特殊令牌,非用户认证) + +**报告的数据**: +```python +{ + "hostname": "judge-server-1", + "judger_version": "2.0.4", + "cpu_core": 4, + "cpu": 45.2, # CPU使用率 + "memory": 60.5, # 内存使用率 + "service_url": "http://judger:8080" +} +``` + +**后端使用场景**: +- 监控判题服务器在线状态 +- 显示服务器资源使用情况(仅在管理后台显示) +- 负载均衡分配判题任务 + +**前端已有接口**: +前端查看判题服务器状态使用的是: +- `GET /api/admin/judge_server` - 获取所有判题服务器列表及状态 + +**结论**: +此接口是系统内部通信接口,前端完全不需要调用。类似的内部接口还可能存在于分布式系统的其他服务间通信中。 + +--- + +## 六、新增功能模块 + +### FlowchartEditor 流程图编辑器 ✨ + +**功能概述**: 基于 Vue Flow 构建的完整流程图编辑器,支持拖拽创建、节点连接、编辑等核心功能。 + +**技术实现**: +- **核心库**: Vue Flow (@vue-flow/core) +- **组件架构**: 模块化设计,9个独立功能模块 +- **状态管理**: 基于 Vue 3 Composition API +- **数据持久化**: localStorage 自动缓存 +- **交互体验**: 拖拽、键盘快捷键、撤销重做 + +**组件结构**: +``` +FlowchartEditor/ +├── index.vue # 主组件 - 整合所有功能 +├── CustomNode.vue # 自定义节点 - 7种节点类型 +├── Toolbar.vue # 工具栏 - 节点创建和操作 +├── NodeHandles.vue # 节点操作手柄 - 连接点管理 +├── NodeActions.vue # 节点动作 - 删除、编辑按钮 +├── useCache.ts # 缓存管理 - 自动保存/恢复 +├── useDnD.ts # 拖拽处理 - 节点创建逻辑 +├── useFlowOperations.ts # 流程操作 - 增删改查 +├── useHistory.ts # 历史记录 - 撤销重做 +└── useNodeStyles.ts # 节点样式 - 类型配置 +``` + +**节点类型支持**: +- **开始节点** (start) - 流程开始,绿色主题 +- **输入节点** (input) - 数据输入,蓝色主题 +- **处理节点** (default) - 数据处理,紫色主题 +- **判断节点** (decision) - 条件判断,橙色主题 +- **循环节点** (loop) - 循环处理,青色主题 +- **输出节点** (output) - 数据输出,粉色主题 +- **结束节点** (end) - 流程结束,红色主题 + +**核心功能**: +1. **拖拽创建**: 从工具栏拖拽节点到画布 +2. **节点连接**: 支持节点间的连线操作 +3. **节点编辑**: 双击节点进行文本编辑 +4. **撤销重做**: 完整的历史记录管理 +5. **自动保存**: 本地缓存,防止数据丢失 +6. **键盘快捷键**: Ctrl+Z/Ctrl+Y 撤销重做,Delete 删除 +7. **批量操作**: 支持多选和批量删除 + +**数据格式**: +```typescript +// 节点数据 +interface Node { + id: string + type: string + position: { x: number, y: number } + data: { + customLabel: string + [key: string]: any + } +} + +// 边数据 +interface Edge { + id: string + source: string + target: string + sourceHandle?: string + targetHandle?: string + type?: string +} +``` + +**使用场景**: +- 算法流程图绘制 +- 业务流程设计 +- 教学演示工具 +- 问题分析图表 + +**性能优化**: +- 基于 Vue Flow 的高性能渲染 +- 模块化设计,按需加载 +- 本地缓存减少重复计算 +- 响应式状态管理 + +**扩展性**: +- 支持自定义节点类型 +- 可扩展工具栏功能 +- 支持主题定制 +- 支持数据导入导出 + +**文档支持**: +- 详细的使用文档: `docs/FlowchartEditor.md` +- 完整的 API 说明 +- 最佳实践指南 +- 故障排除手册 + diff --git a/apps/web/docs/FlowchartEditor.md b/apps/web/docs/FlowchartEditor.md new file mode 100644 index 0000000..e026d3a --- /dev/null +++ b/apps/web/docs/FlowchartEditor.md @@ -0,0 +1,311 @@ +# FlowchartEditor 流程图编辑器 + +一个基于 Vue 3 + Vue Flow 构建的功能完整的流程图编辑器组件。 + +## 功能特性 + +### 🎯 核心功能 +- **拖拽创建节点** - 从工具栏拖拽节点到画布 +- **节点连接** - 支持节点间的连线操作 +- **节点编辑** - 双击节点进行文本编辑 +- **撤销重做** - 完整的历史记录管理 +- **自动保存** - 本地缓存,防止数据丢失 +- **键盘快捷键** - 支持常用快捷键操作 + +### 🎨 节点类型 +- **开始节点** (start) - 流程开始 +- **输入节点** (input) - 数据输入 +- **处理节点** (default) - 数据处理 +- **判断节点** (decision) - 条件判断 +- **循环节点** (loop) - 循环处理 +- **输出节点** (output) - 数据输出 +- **结束节点** (end) - 流程结束 + +### ⌨️ 快捷键 +- `Ctrl+Z` / `Cmd+Z` - 撤销 +- `Ctrl+Y` / `Cmd+Shift+Z` - 重做 +- `Delete` / `Backspace` - 删除选中节点 + +## 组件结构 + +``` +FlowchartEditor/ +├── index.vue # 主组件 +├── CustomNode.vue # 自定义节点组件 +├── Toolbar.vue # 工具栏组件 +├── NodeHandles.vue # 节点操作手柄 +├── NodeActions.vue # 节点动作按钮 +├── useCache.ts # 缓存管理 +├── useDnD.ts # 拖拽处理 +├── useFlowOperations.ts # 流程操作 +├── useHistory.ts # 历史记录 +└── useNodeStyles.ts # 节点样式 +``` + +## 使用方法 + +### 基本用法 + +```vue + + + +``` + +### 获取流程图数据 + +```javascript +// 获取当前流程图数据 +const data = flowchartRef.value.getFlowchartData() +console.log(data.nodes) // 节点数组 +console.log(data.edges) // 边数组 +``` + +## 组件详解 + +### 主组件 (index.vue) + +主组件负责整合所有功能模块,提供完整的流程图编辑体验。 + +**主要功能**: +- 整合 Vue Flow 核心功能 +- 管理节点和边的状态 +- 处理用户交互事件 +- 提供数据暴露接口 + +**暴露的方法**: +- `getFlowchartData()` - 获取当前流程图数据 + +### 自定义节点 (CustomNode.vue) + +基于 Vue Flow 的自定义节点组件,支持多种节点类型。 + +**节点类型配置**: +- 每种节点类型都有独特的样式和图标 +- 支持自定义标签文本 +- 提供删除和编辑功能 + +### 工具栏 (Toolbar.vue) + +提供节点创建和操作的工具集合。 + +**功能**: +- 节点类型选择 +- 撤销/重做操作 +- 清空画布 +- 保存状态显示 + +### 缓存管理 (useCache.ts) + +提供本地存储功能,防止数据丢失。 + +**功能**: +- 自动保存到 localStorage +- 页面刷新后恢复数据 +- 保存状态提示 +- 清空缓存功能 + +### 拖拽处理 (useDnD.ts) + +处理节点拖拽创建的逻辑。 + +**功能**: +- 拖拽开始处理 +- 拖拽悬停效果 +- 拖拽放置处理 +- 节点位置计算 + +### 流程操作 (useFlowOperations.ts) + +处理流程图的增删改操作。 + +**功能**: +- 节点连接处理 +- 边删除处理 +- 节点删除处理 +- 节点更新处理 +- 清空画布 + +### 历史记录 (useHistory.ts) + +提供撤销重做功能。 + +**功能**: +- 状态快照保存 +- 撤销操作 +- 重做操作 +- 历史记录管理 + +### 节点样式 (useNodeStyles.ts) + +定义各种节点类型的样式配置。 + +**功能**: +- 节点类型配置 +- 样式定义 +- 图标配置 +- 颜色主题 + +## 样式定制 + +### 节点样式 + +可以通过修改 `useNodeStyles.ts` 来自定义节点样式: + +```typescript +export function getNodeTypeConfig(type: string) { + const configs = { + start: { + label: '开始', + icon: 'play-circle', + color: '#10b981', + // 自定义样式 + }, + // 其他节点类型... + } + + return configs[type] || configs.default +} +``` + +### 边样式 + +边的样式在主组件中定义: + +```vue +:default-edge-options="{ + type: 'step', + style: { + stroke: '#6366f1', + strokeWidth: 2.5, + cursor: 'pointer', + filter: 'drop-shadow(0 2px 4px rgba(0,0,0,0.1))' + }, + markerEnd: { + type: MarkerType.ArrowClosed, + color: '#6366f1', + width: 16, + height: 16, + }, +}" +``` + +## 数据格式 + +### 节点数据格式 + +```typescript +interface Node { + id: string + type: string + position: { x: number, y: number } + data: { + customLabel: string + [key: string]: any + } +} +``` + +### 边数据格式 + +```typescript +interface Edge { + id: string + source: string + target: string + sourceHandle?: string + targetHandle?: string + type?: string +} +``` + +## 最佳实践 + +### 1. 性能优化 +- 大量节点时考虑虚拟化 +- 合理使用缓存机制 +- 避免频繁的状态更新 + +### 2. 用户体验 +- 提供清晰的操作反馈 +- 支持键盘快捷键 +- 保持操作的直观性 + +### 3. 数据管理 +- 定期保存到服务器 +- 提供数据导入导出功能 +- 支持版本控制机制 + +## 扩展开发 + +### 添加新节点类型 + +1. 在 `useNodeStyles.ts` 中添加新类型配置 +2. 在 `Toolbar.vue` 中添加新节点按钮 +3. 在 `CustomNode.vue` 中添加新节点渲染逻辑 + +### 添加新功能 + +1. 创建新的 composable 函数 +2. 在主组件中集成新功能 +3. 更新工具栏和用户界面 + +## 故障排除 + +### 常见问题 + +1. **节点无法拖拽** + - 检查拖拽事件处理 + - 确认节点类型配置正确 + +2. **撤销重做不工作** + - 检查历史记录状态 + - 确认状态保存时机 + +3. **数据丢失** + - 检查缓存配置 + - 确认 localStorage 可用 + +### 调试技巧 + +1. 使用 Vue DevTools 查看组件状态 +2. 检查浏览器控制台错误 +3. 验证数据格式正确性 + +## 更新日志 + +### v1.0.0 (2024-01-15) +- ✨ 初始版本发布 +- 🎯 基础流程图编辑功能 +- 🔧 拖拽创建节点 +- 🔗 节点连接功能 +- ↩️ 撤销重做支持 +- 💾 本地缓存功能 + +### v1.1.0 (2024-01-20) +- 🎨 新增多种节点类型 +- ⌨️ 键盘快捷键支持 +- 🖱️ 优化拖拽体验 +- 📱 响应式布局改进 + +### v1.2.0 (2024-01-25) +- 🔧 模块化重构 +- 📦 组合式函数优化 +- 🎯 性能提升 +- 🐛 修复已知问题 + +## 许可证 + +MIT License diff --git a/apps/web/docs/superpowers/plans/2026-06-14-submit-formatting-button-state.md b/apps/web/docs/superpowers/plans/2026-06-14-submit-formatting-button-state.md new file mode 100644 index 0000000..13ba1f7 --- /dev/null +++ b/apps/web/docs/superpowers/plans/2026-06-14-submit-formatting-button-state.md @@ -0,0 +1,259 @@ +# Submit Formatting Button State Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Show `格式化中` on the submit button during automatic formatting, then show `正在提交` continuously while the submission request is pending. + +**Architecture:** Extract the button presentation rules into a small pure TypeScript function so the state priority can be tested without adding a frontend test framework. Keep formatter and submission-request flags local to `SubmitCode.vue`, with `finally` blocks ensuring both flags clear on every outcome. + +**Tech Stack:** Vue 3 Composition API, TypeScript, Node.js built-in test runner, Rsbuild + +--- + +### Task 1: Define and test submit button presentation rules + +**Files:** +- Create: `tests/submitButtonState.test.ts` +- Create: `src/oj/problem/components/submitButtonState.ts` + +- [ ] **Step 1: Write the failing test** + +Create `tests/submitButtonState.test.ts`: + +```ts +import assert from "node:assert/strict" +import test from "node:test" +import { getSubmitButtonState } from "../src/oj/problem/components/submitButtonState.ts" + +const idleInput = { + isAuthed: true, + hasCode: true, + isFormatting: false, + isSubmitting: false, + isJudging: false, + isCooldown: false, +} + +test("shows a disabled loading state while formatting", () => { + assert.deepEqual( + getSubmitButtonState({ ...idleInput, isFormatting: true }), + { + disabled: true, + label: "格式化中", + icon: "eos-icons:loading", + }, + ) +}) + +test("shows submitting immediately after formatting", () => { + assert.deepEqual( + getSubmitButtonState({ ...idleInput, isSubmitting: true }), + { + disabled: true, + label: "正在提交", + icon: "eos-icons:loading", + }, + ) +}) + +test("preserves existing login, judging, cooldown, and idle states", () => { + assert.deepEqual( + getSubmitButtonState({ ...idleInput, isAuthed: false }), + { + disabled: true, + label: "请先登录", + icon: "ph:play-fill", + }, + ) + assert.deepEqual(getSubmitButtonState({ ...idleInput, isJudging: true }), { + disabled: true, + label: "正在评分", + icon: "eos-icons:loading", + }) + assert.deepEqual(getSubmitButtonState({ ...idleInput, isCooldown: true }), { + disabled: true, + label: "正在冷却", + icon: "ph:lightbulb-fill", + }) + assert.deepEqual(getSubmitButtonState(idleInput), { + disabled: false, + label: "提交代码", + icon: "ph:play-fill", + }) +}) +``` + +- [ ] **Step 2: Run the test to verify it fails** + +Run: + +```bash +node --test tests/submitButtonState.test.ts +``` + +Expected: FAIL with `ERR_MODULE_NOT_FOUND` for `submitButtonState.ts`. + +- [ ] **Step 3: Implement the pure state function** + +Create `src/oj/problem/components/submitButtonState.ts`: + +```ts +export interface SubmitButtonStateInput { + isAuthed: boolean + hasCode: boolean + isFormatting: boolean + isSubmitting: boolean + isJudging: boolean + isCooldown: boolean +} + +export interface SubmitButtonState { + disabled: boolean + label: string + icon: string +} + +export function getSubmitButtonState({ + isAuthed, + hasCode, + isFormatting, + isSubmitting, + isJudging, + isCooldown, +}: SubmitButtonStateInput): SubmitButtonState { + const disabled = + !isAuthed || + !hasCode || + isFormatting || + isSubmitting || + isJudging || + isCooldown + + let label = "提交代码" + if (!isAuthed) { + label = "请先登录" + } else if (isFormatting) { + label = "格式化中" + } else if (isSubmitting) { + label = "正在提交" + } else if (isJudging) { + label = "正在评分" + } else if (isCooldown) { + label = "正在冷却" + } + + const icon = + isFormatting || isSubmitting || isJudging + ? "eos-icons:loading" + : isCooldown + ? "ph:lightbulb-fill" + : "ph:play-fill" + + return { disabled, label, icon } +} +``` + +- [ ] **Step 4: Run the test to verify it passes** + +Run: + +```bash +node --test tests/submitButtonState.test.ts +``` + +Expected: 3 tests pass. + +### Task 2: Connect formatting and submission request lifecycle to the button + +**Files:** +- Modify: `src/oj/problem/components/SubmitCode.vue` + +- [ ] **Step 1: Add local request states and computed presentation** + +Import `getSubmitButtonState`, add `isFormatting` and `isSubmittingRequest` refs, and replace the three existing button computed properties with: + +```ts +const buttonState = computed(() => + getSubmitButtonState({ + isAuthed: userStore.isAuthed, + hasCode: codeStore.code.value.trim() !== "", + isFormatting: isFormatting.value, + isSubmitting: isSubmittingRequest.value || submitting.value, + isJudging: judging.value || pending.value, + isCooldown: isCooldown.value, + }), +) +``` + +Use `buttonState.disabled`, `buttonState.icon`, and `buttonState.label` in the template. + +- [ ] **Step 2: Guard and track the formatting request** + +At the start of `submit`, return when `buttonState.value.disabled` is true. Around `formatCode`, set `isFormatting.value = true` before the request and clear it in `finally`: + +```ts +isFormatting.value = true +try { + const res = await formatCode({ + code: codeStore.code.value, + language: formatLang, + }) + codeStore.setCode(res.data.code) +} catch (e: any) { + if (e?.error === "format-error") { + message.warning(`代码格式化失败:${e.data},请检查代码后重试`) + return + } +} finally { + isFormatting.value = false +} +``` + +- [ ] **Step 3: Track the submission API request** + +Set `isSubmittingRequest.value = true` immediately before `submitCode`, keep the existing success flow inside the `try`, and clear the request state in `finally`: + +```ts +isSubmittingRequest.value = true +try { + const res = await submitCode(data) + console.log(`[Submit] 代码已提交: ID=${res.data.submission_id}`) + + startCooldown() + startMonitoring(res.data.submission_id) + showResult.value = true +} finally { + isSubmittingRequest.value = false +} +``` + +- [ ] **Step 4: Run focused tests** + +Run: + +```bash +node --test tests/submitButtonState.test.ts +``` + +Expected: 3 tests pass. + +- [ ] **Step 5: Run the production build** + +Run: + +```bash +npm run build +``` + +Expected: Rsbuild exits with status 0. + +- [ ] **Step 6: Check the final diff** + +Run: + +```bash +git diff --check +git diff -- src/oj/problem/components/SubmitCode.vue src/oj/problem/components/submitButtonState.ts tests/submitButtonState.test.ts +``` + +Expected: no whitespace errors; diff is limited to the button state feature and its test. diff --git a/apps/web/docs/superpowers/plans/2026-07-26-demo-student-view.md b/apps/web/docs/superpowers/plans/2026-07-26-demo-student-view.md new file mode 100644 index 0000000..d035447 --- /dev/null +++ b/apps/web/docs/superpowers/plans/2026-07-26-demo-student-view.md @@ -0,0 +1,296 @@ +# 学生视角(演示模式)Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** 超级管理员在右上角下拉菜单点一下,整站界面变成普通学生看到的样子,再点一下恢复。 + +**Architecture:** 纯前端伪装。全站 40 多处权限判断读的都是 `shared/store/user.ts` 里的 6 个角色 getter,没有一处直接读 `user.admin_type`。在 store 里加一个 `demoMode` 开关,给每个 getter 加 `!demoMode.value &&` 前缀,全站自动跟随。路由守卫(`src/main.ts`)、权限工具(`src/utils/permissions.ts`)、各页面 `v-if` 零改动。 + +**Tech Stack:** Vue 3 ` +``` + +### 图表样式定制 + +```typescript +// 主题配置 +const theme = { + colors: { + primary: '#6366f1', + secondary: '#8b5cf6', + success: '#10b981', + warning: '#f59e0b', + error: '#ef4444' + }, + gradients: { + primary: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)', + success: 'linear-gradient(135deg, #11998e 0%, #38ef7d 100%)' + } +} +``` + +## 性能优化 + +### 1. 数据懒加载 +- 按需加载图表数据 +- 使用虚拟滚动处理大量数据 + +### 2. 图表缓存 +- 缓存计算结果 +- 避免重复渲染 + +### 3. 响应式设计 +- 自适应容器大小 +- 移动端优化 + +## 最佳实践 + +### 1. 数据预处理 +- 在组件外部处理数据 +- 使用 computed 属性缓存计算结果 + +### 2. 错误处理 +- 添加数据验证 +- 提供降级方案 + +### 3. 用户体验 +- 添加加载状态 +- 提供交互反馈 + +## 更新日志 + +### v1.0.0 (2024-01-15) +- ✨ 初始版本发布 +- 📊 基础图表组件 +- 🎨 主题样式支持 +- 📱 响应式设计 + +### v1.1.0 (2024-01-20) +- 🔧 性能优化 +- 📈 新增混合图表 +- 🎯 交互体验改进 + +## 许可证 + +MIT License \ No newline at end of file diff --git a/apps/web/index.html b/apps/web/index.html new file mode 100644 index 0000000..3737e63 --- /dev/null +++ b/apps/web/index.html @@ -0,0 +1,17 @@ + + + + + + + 判题狗 + + + + +
+ + + diff --git a/apps/web/package.json b/apps/web/package.json new file mode 100644 index 0000000..1776ccb --- /dev/null +++ b/apps/web/package.json @@ -0,0 +1,67 @@ +{ + "name": "@oj2/web", + "version": "1.9.0", + "type": "module", + "scripts": { + "dev": "vite", + "start": "vite", + "build": "vite build", + "build:staging": "vite build --mode staging", + "build:test": "vite build --mode test", + "fmt": "prettier --write src *.ts" + }, + "dependencies": { + "@oj2/contract": "workspace:*", + "@codemirror/autocomplete": "^6.20.3", + "@codemirror/lang-cpp": "^6.0.3", + "@codemirror/lang-python": "^6.2.1", + "@codemirror/lang-sql": "^6.10.0", + "@vue-flow/background": "^1.3.2", + "@vue-flow/controls": "^1.1.3", + "@vue-flow/core": "^1.48.2", + "@vue-flow/minimap": "^1.5.4", + "@vue-flow/node-resizer": "^1.5.1", + "@vue-flow/node-toolbar": "^1.1.1", + "@vueuse/core": "^14.4.0", + "@vueuse/router": "^14.4.0", + "@wangeditor-next/editor": "^6.2.0", + "@wangeditor-next/editor-for-vue": "^6.2.0", + "axios": "^1.19.0", + "canvas-confetti": "^1.9.4", + "chart.js": "^4.5.1", + "chartjs-chart-wordcloud": "^4.4.5", + "codemirror": "^6.0.2", + "copy-text-to-clipboard": "^3.2.2", + "date-fns": "^4.4.0", + "fflate": "^0.8.3", + "highlight.js": "^11.11.1", + "md-editor-v3": "^6.5.6", + "mermaid": "^11.16.1", + "mermaid-legacy": "npm:mermaid@^9.4.3", + "naive-ui": "^2.44.1", + "nanoid": "^6.0.1", + "normalize.css": "^8.0.1", + "pinia": "^4.0.2", + "skulpt": "^1.2.0", + "vue": "^3.5.41", + "vue-chartjs": "^5.3.4", + "vue-codemirror": "^6.1.1", + "vue-router": "^5.2.0", + "y-codemirror.next": "^0.3.5", + "y-webrtc": "^10.3.0", + "yjs": "^13.6.32" + }, + "devDependencies": { + "@iconify/vue": "^5.0.1", + "@types/canvas-confetti": "^1.9.0", + "@types/node": "^26.1.2", + "@vitejs/plugin-legacy": "^8.2.3", + "@vitejs/plugin-vue": "^6.0.8", + "@vue/tsconfig": "^0.9.1", + "prettier": "^3.9.6", + "typescript": "^7.0.2", + "unplugin-auto-import": "^21.1.0", + "unplugin-vue-components": "^32.1.0", + "vite": "^8.2.1" + } +} diff --git a/apps/web/public/A.png b/apps/web/public/A.png new file mode 100644 index 0000000..01c828e Binary files /dev/null and b/apps/web/public/A.png differ diff --git a/apps/web/public/B.png b/apps/web/public/B.png new file mode 100644 index 0000000..57d6638 Binary files /dev/null and b/apps/web/public/B.png differ diff --git a/apps/web/public/C.png b/apps/web/public/C.png new file mode 100644 index 0000000..7be3add Binary files /dev/null and b/apps/web/public/C.png differ diff --git a/apps/web/public/Monaco.ttf b/apps/web/public/Monaco.ttf new file mode 100644 index 0000000..c19946c Binary files /dev/null and b/apps/web/public/Monaco.ttf differ diff --git a/apps/web/public/S.png b/apps/web/public/S.png new file mode 100644 index 0000000..1e2daa0 Binary files /dev/null and b/apps/web/public/S.png differ diff --git a/apps/web/public/badge-1.png b/apps/web/public/badge-1.png new file mode 100644 index 0000000..5bb14aa Binary files /dev/null and b/apps/web/public/badge-1.png differ diff --git a/apps/web/public/badge-2.png b/apps/web/public/badge-2.png new file mode 100644 index 0000000..7610c6b Binary files /dev/null and b/apps/web/public/badge-2.png differ diff --git a/apps/web/public/badge-3.png b/apps/web/public/badge-3.png new file mode 100644 index 0000000..73247e2 Binary files /dev/null and b/apps/web/public/badge-3.png differ diff --git a/apps/web/public/badge-4.png b/apps/web/public/badge-4.png new file mode 100644 index 0000000..dae57cc Binary files /dev/null and b/apps/web/public/badge-4.png differ diff --git a/apps/web/public/badge-5.png b/apps/web/public/badge-5.png new file mode 100644 index 0000000..a9d5a82 Binary files /dev/null and b/apps/web/public/badge-5.png differ diff --git a/apps/web/public/badge-6.png b/apps/web/public/badge-6.png new file mode 100644 index 0000000..06d4b4d Binary files /dev/null and b/apps/web/public/badge-6.png differ diff --git a/apps/web/public/favicon.ico b/apps/web/public/favicon.ico new file mode 100644 index 0000000..1279223 Binary files /dev/null and b/apps/web/public/favicon.ico differ diff --git a/apps/web/public/noto--dog-face.ico b/apps/web/public/noto--dog-face.ico new file mode 100644 index 0000000..a49602a Binary files /dev/null and b/apps/web/public/noto--dog-face.ico differ diff --git a/apps/web/public/style.css b/apps/web/public/style.css new file mode 100644 index 0000000..b7d5a13 --- /dev/null +++ b/apps/web/public/style.css @@ -0,0 +1,20 @@ +@font-face { + font-family: "Monaco"; + src: url(/Monaco.ttf); +} + +.md-editor-preview .md-editor-code .md-editor-code-head { + z-index: 100 !important; +} + +.md-editor-preview img { + height: auto !important; +} + +.md-editor-preview h1 { + font-size: 1.6rem !important; +} + +.md-editor-preview h2 { + font-size: 1.4rem !important; +} diff --git a/apps/web/src/App.vue b/apps/web/src/App.vue new file mode 100644 index 0000000..35d2644 --- /dev/null +++ b/apps/web/src/App.vue @@ -0,0 +1,77 @@ + + + diff --git a/apps/web/src/admin/achievement/components/AchievementModal.vue b/apps/web/src/admin/achievement/components/AchievementModal.vue new file mode 100644 index 0000000..676ba19 --- /dev/null +++ b/apps/web/src/admin/achievement/components/AchievementModal.vue @@ -0,0 +1,185 @@ + + + + + diff --git a/apps/web/src/admin/achievement/list.vue b/apps/web/src/admin/achievement/list.vue new file mode 100644 index 0000000..db6d265 --- /dev/null +++ b/apps/web/src/admin/achievement/list.vue @@ -0,0 +1,144 @@ + + + diff --git a/apps/web/src/admin/ai/list.vue b/apps/web/src/admin/ai/list.vue new file mode 100644 index 0000000..c0c7e5e --- /dev/null +++ b/apps/web/src/admin/ai/list.vue @@ -0,0 +1,206 @@ + + + + + diff --git a/apps/web/src/admin/announcement/components/Actions.vue b/apps/web/src/admin/announcement/components/Actions.vue new file mode 100644 index 0000000..bd0856a --- /dev/null +++ b/apps/web/src/admin/announcement/components/Actions.vue @@ -0,0 +1,39 @@ + + + diff --git a/apps/web/src/admin/announcement/detail.vue b/apps/web/src/admin/announcement/detail.vue new file mode 100644 index 0000000..258f594 --- /dev/null +++ b/apps/web/src/admin/announcement/detail.vue @@ -0,0 +1,117 @@ + + + diff --git a/apps/web/src/admin/announcement/list.vue b/apps/web/src/admin/announcement/list.vue new file mode 100644 index 0000000..a4ca45f --- /dev/null +++ b/apps/web/src/admin/announcement/list.vue @@ -0,0 +1,113 @@ + + + + + diff --git a/apps/web/src/admin/api.ts b/apps/web/src/admin/api.ts new file mode 100644 index 0000000..9a32961 --- /dev/null +++ b/apps/web/src/admin/api.ts @@ -0,0 +1,600 @@ +import http from "utils/http" +import { toProblemListItem } from "admin/transforms" +import type { + AdminProblem, + AdminTag, + Announcement, + AnnouncementEdit, + BlankContest, + BlankProblem, + Contest, + Exercise, + ExerciseType, + Server, + SQLDisplay, + TestcaseUploadedReturns, + Tutorial, + User, + WebsiteConfig, +} from "utils/types" + +export function getBaseInfo() { + return http.get("admin/dashboard_info") +} + +export function randomUser10(classroom: string) { + return http.get("admin/random_user", { params: { classroom } }) +} + +export async function getProblemList( + offset = 0, + limit = 10, + keyword: string, + author?: string, + contestID?: string, + tagId?: number, +) { + const endpoint = !!contestID ? "admin/contest/problem" : "admin/problem" + const res = await http.get<{ results: AdminProblem[]; total: number }>( + endpoint, + { + params: { + paging: true, + offset, + limit, + keyword, + author, + contest_id: contestID, + tag_id: tagId, + }, + }, + ) + return { + results: res.data.results.map(toProblemListItem), + total: res.data.total, + } +} + +export function deleteProblem(id: number) { + return http.delete("admin/problem", { params: { id } }) +} + +export function deleteContestProblem(id: number) { + return http.delete("admin/contest/problem", { params: { id } }) +} + +export function editProblem(problem: AdminProblem | BlankProblem) { + return http.put("admin/problem", problem) +} + +export function toggleProblemVisible(problemID: number) { + return http.put("admin/problem/visible", { id: problemID }) +} + +export function generateFlowchartFromPythonCode(python: string) { + return http.post("admin/problem/flowchart", { python }) +} + +export function editContestProblem(problem: AdminProblem | BlankProblem) { + return http.put("admin/contest/problem", problem) +} + +export function getProblem(id: string | number) { + return http.get("admin/problem", { params: { id } }) +} + +export function getContestProblem(id: number) { + return http.get("admin/contest/problem", { params: { id } }) +} + +// 标签管理 +export function getTagAdminList(keyword = "") { + return http.get("admin/problem/tag", { params: { keyword } }) +} + +export function renameTag(id: number, name: string) { + return http.put<{ + merged: boolean + id: number + name: string + affected_count: number + }>("admin/problem/tag", { id, name }) +} + +export function deleteTag(id: number) { + return http.delete("admin/problem/tag", { params: { id } }) +} + +export function batchTagProblems( + problemIds: number[], + tagNames: string[], + action: "add" | "remove", +) { + return http.post<{ problem_count: number; tag_count: number }>( + "admin/problem/batch_tag", + { problem_ids: problemIds, tag_names: tagNames, action }, + ) +} + +// 用户列表 +export function getUserList( + offset = 0, + limit = 10, + type = "", + keyword: string, + orderBy = "", +) { + return http.get("admin/user", { + params: { paging: true, offset, limit, keyword, type, order_by: orderBy }, + }) +} + +// 编辑用户 +export function editUser(user: User) { + return http.put("admin/user", user) +} + +// 重置用户密码 +export function resetPassword(userID: number) { + return http.post("admin/reset_password", { id: userID }) +} + +// 导入用户 +export function importUsers(users: string[][]) { + return http.post("admin/user", { users }) +} + +// 批量删除用户 +export function deleteUsers(userIDs: number[]) { + return http.delete("admin/user", { params: { id: userIDs.join(",") } }) +} + +export function getContestList(offset = 0, limit = 10, keyword: string) { + return http.get("admin/contest", { + params: { paging: true, offset, limit, keyword }, + }) +} + +// 上传图片 +export async function uploadImage(file: File): Promise { + const form = new window.FormData() + form.append("image", file) + // 该端点不走 { error, data } 信封,直接返回上传结果 + const res = (await http.post("admin/upload_image", form, { + headers: { "content-type": "multipart/form-data" }, + })) as unknown as { success: boolean; file_path: string; msg: "Success" } + return res.success ? res.file_path : "" +} + +// 上传测试用例;SQL 题的压缩包是 1.sql..N.sql(每个文件一个测试点的建表+数据脚本) +export function uploadTestcases(file: File, options: { sql?: boolean } = {}) { + const form = new window.FormData() + form.append("file", file) + if (options.sql) { + form.append("sql", "1") + } + return http.post("admin/test_case", form, { + headers: { "content-type": "multipart/form-data" }, + }) +} + +// SQL 题测试点预览:后端跑一遍初始化脚本+标准答案,返回数据表和期望结果展示数据 +export function previewSQLTestcase(data: { + init_sql: string + ref_sql: string + mode: "query" | "modify" +}) { + return http.post("admin/sql_test_case_preview", data) +} + +// 回显已上传的 SQL 测试点脚本内容(按 1.sql, 2.sql... 排序) +export function getSQLTestcaseScripts(problemId: number) { + return http.get<{ name: string; content: string }[]>( + "admin/sql_test_case_scripts", + { params: { problem_id: problemId } }, + ) +} + +// AI 根据标准答案生成一个 SQL 测试点初始化脚本 +export function generateSQLTestcase(data: { + ref_sql: string + mode: "query" | "modify" +}) { + return http.post<{ sql: string }>("admin/sql_test_case_ai_gen", data) +} + +export function createProblem(problem: BlankProblem) { + return http.post("admin/problem", problem) +} + +export function createContestProblem(problem: BlankProblem) { + return http.post("admin/contest/problem", problem) +} + +export function createContest(contest: BlankContest) { + return http.post("admin/contest", contest) +} + +export function editContest(contest: Contest | BlankContest) { + return http.put("admin/contest", contest) +} + +export function cloneContest(contest_id: number) { + return http.post("admin/contest/clone", { contest_id }) +} + +export function getContest(id: string) { + return http.get("admin/contest", { + params: { id }, + }) +} + +export function addProblemForContest( + contestID: string, + problemID: number, + displayID: string, +) { + return http.post("admin/contest/add_problem_from_public", { + contest_id: contestID, + problem_id: problemID, + display_id: displayID, + }) +} + +export function getWebsite() { + return http.get("admin/website") +} + +export function editWebsite(data: WebsiteConfig) { + return http.post("admin/website", data) +} + +export function listInvalidTestcases() { + return http.get("admin/prune_test_case") +} + +export function pruneInvalidTestcases(id?: string) { + return http.delete("admin/prune_test_case", { params: { id } }) +} + +export function getJudgeServer() { + return http.get<{ token: string; servers: Server[] }>("admin/judge_server") +} + +export function deleteJudgeServer(hostname: string) { + return http.delete("admin/judge_server", { params: { hostname } }) +} + +export function getAnnouncementList(offset = 0, limit = 10) { + return http.get("admin/announcement", { + params: { paging: true, offset, limit }, + }) +} + +export function getAnnouncement(id: number) { + return http.get("admin/announcement", { params: { id } }) +} + +export function deleteAnnouncement(id: number) { + return http.delete("admin/announcement", { params: { id } }) +} + +export function editAnnouncement(announcement: AnnouncementEdit) { + return http.put("admin/announcement", announcement) +} + +export function createAnnouncement(announcement: AnnouncementEdit) { + return http.post("admin/announcement", announcement) +} + +export async function getTutorialList() { + const res = await http.get("admin/tutorial") + return res.data +} + +export async function getTutorial(id: number) { + const res = await http.get("admin/tutorial", { params: { id } }) + return res.data +} + +export async function createTutorial(data: Partial) { + const res = await http.post("admin/tutorial", data) + return res.data +} + +export async function updateTutorial(data: Partial) { + const res = await http.put("admin/tutorial", data) + return res.data +} + +export function deleteTutorial(id: number) { + return http.delete("admin/tutorial", { params: { id } }) +} + +export function setTutorialVisibility(id: number, is_public: boolean) { + return http.put("admin/tutorial/visibility", { id, is_public }) +} + +export async function getAdminExercises(tutorialId: number) { + const res = await http.get("admin/exercise", { + params: { tutorial_id: tutorialId }, + }) + return res.data +} + +export async function createExercise(data: { + tutorial_id: number + type: ExerciseType + data: object + order: number +}) { + const res = await http.post("admin/exercise", data) + return res.data +} + +export async function updateExercise(data: { + id: number + type: ExerciseType + data: object + order: number +}) { + const res = await http.put("admin/exercise", data) + return res.data as Exercise +} + +export function deleteExercise(id: number) { + return http.delete("admin/exercise", { params: { id } }) +} + +// 将竞赛题目转为公开题目 +export function makeProblemPublic(id: number, display_id: string) { + return http.post("admin/contest_problem/make_public", { + id, + display_id, + }) +} + +// 比赛辅助检查 +export function getACMHelperList(contest_id: number) { + return http.get("admin/contest/acm_helper", { + params: { contest_id }, + }) +} + +export function updateACMHelperChecked( + contest_id: number, + rank_id: number, + problem_id: string, + checked: boolean, +) { + return http.put("admin/contest/acm_helper", { + contest_id, + rank_id, + problem_id, + checked, + }) +} + +// 题单管理 API +export function getProblemSetList( + offset = 0, + limit = 10, + keyword = "", + difficulty = "", + status = "", +) { + return http.get("admin/problemset", { + params: { + offset, + limit, + keyword, + difficulty, + status, + }, + }) +} + +export function getProblemSetDetail(id: number) { + return http.get(`admin/problemset/${id}`) +} + +export function createProblemSet(data: { + title: string + description: string + difficulty: string + status: string + end_time?: Date | null +}) { + return http.post("admin/problemset", data) +} + +export function editProblemSet(data: { + id: number + title?: string + description?: string + difficulty?: string + status?: string + end_time?: Date | null + visible?: boolean +}) { + return http.put("admin/problemset", data) +} + +export function deleteProblemSet(id: number) { + return http.delete("admin/problemset", { params: { id } }) +} + +export function toggleProblemSetVisible(id: number) { + return http.put("admin/problemset/visible", { id }) +} + +export function updateProblemSetStatus(id: number, status: string) { + return http.put("admin/problemset/status", { id, status }) +} + +// 题单题目管理 API +export function getProblemSetProblems(problemSetId: number) { + return http.get(`admin/problemset/${problemSetId}/problems`) +} + +export function addProblemToSet( + problemSetId: number, + data: { + problem_id: string + order?: number + is_required?: boolean + score?: number + hint?: string + }, +) { + return http.post(`admin/problemset/${problemSetId}/problems`, data) +} + +export function editProblemInSet( + problemSetId: number, + problemSetProblemId: number, + data: { + order?: number + is_required?: boolean + score?: number + hint?: string + }, +) { + return http.put( + `admin/problemset/${problemSetId}/problems/${problemSetProblemId}`, + data, + ) +} + +export function removeProblemFromSet( + problemSetId: number, + problemSetProblemId: number, +) { + return http.delete( + `admin/problemset/${problemSetId}/problems/${problemSetProblemId}`, + ) +} + +// 题单奖章管理 API +export function getProblemSetBadges(problemSetId: number) { + return http.get(`admin/problemset/${problemSetId}/badges`) +} + +export function createProblemSetBadge( + problemSetId: number, + data: { + name: string + description: string + icon: string + condition_type: string + condition_value: number + level?: number + }, +) { + return http.post(`admin/problemset/${problemSetId}/badges`, data) +} + +export function editProblemSetBadge( + problemSetId: number, + badgeId: number, + data: { + name?: string + description?: string + icon?: string + condition_type?: string + condition_value?: number + level?: number + }, +) { + return http.put(`admin/problemset/${problemSetId}/badges/${badgeId}`, data) +} + +export function deleteProblemSetBadge(problemSetId: number, badgeId: number) { + return http.delete(`admin/problemset/${problemSetId}/badges/${badgeId}`) +} + +// 题单进度管理 API +export function getProblemSetProgress(problemSetId: number) { + return http.get(`admin/problemset/${problemSetId}/progress`) +} + +export function removeUserFromProblemSet(problemSetId: number, userId: number) { + return http.delete(`admin/problemset/${problemSetId}/progress/${userId}`) +} + +// 学生卡点分析 +export function getStuckProblems() { + return http.get("admin/problem/stuck") +} + +export function getTopACTrend(params: { + since_year: number + until_year: number + min_per_year: number +}) { + return http.get("admin/problem/top_ac_trend", { params }) +} + +// AI 学习分析报告 +export function getAIReportList(offset = 0, limit = 10, username = "") { + return http.get("admin/ai/reports", { + params: { paging: true, offset, limit, username: username || undefined }, + }) +} + +export function getAIReportDetail(id: number) { + return http.get("admin/ai/reports", { params: { id } }) +} + +export function pinAIReport(id: number) { + return http.post("admin/ai/reports", { id }) +} + +export function getPinnedAIReports() { + return http.get("admin/ai/reports", { params: { pinned_only: "true" } }) +} + +// ==================== 成就 ==================== + +export interface AdminAchievement { + id: number + name: string + description: string + icon: string + rarity: string + hidden: boolean + metric: string + metric_name: string + operator: "gte" | "lte" + threshold: number + visible: boolean + unlock_count: number + order: number + create_time: string +} + +export interface MetricOption { + key: string + name: string + help_text: string +} + +export function getAdminAchievements() { + return http.get("admin/achievement") +} + +export function getMetricOptions() { + return http.get("admin/achievement/metrics") +} + +export function createAchievement(data: Partial) { + return http.post("admin/achievement", data) +} + +export function updateAchievement(data: Partial) { + return http.put("admin/achievement", data) +} + +export function deleteAchievement(id: number) { + return http.delete("admin/achievement", { params: { id } }) +} diff --git a/apps/web/src/admin/communication/messages.vue b/apps/web/src/admin/communication/messages.vue new file mode 100644 index 0000000..84e81e4 --- /dev/null +++ b/apps/web/src/admin/communication/messages.vue @@ -0,0 +1,2 @@ + + diff --git a/apps/web/src/admin/contest/components/Actions.vue b/apps/web/src/admin/contest/components/Actions.vue new file mode 100644 index 0000000..5f1a141 --- /dev/null +++ b/apps/web/src/admin/contest/components/Actions.vue @@ -0,0 +1,68 @@ + + + diff --git a/apps/web/src/admin/contest/detail.vue b/apps/web/src/admin/contest/detail.vue new file mode 100644 index 0000000..4aa66f2 --- /dev/null +++ b/apps/web/src/admin/contest/detail.vue @@ -0,0 +1,196 @@ + + + + + diff --git a/apps/web/src/admin/contest/helper.vue b/apps/web/src/admin/contest/helper.vue new file mode 100644 index 0000000..0dbd966 --- /dev/null +++ b/apps/web/src/admin/contest/helper.vue @@ -0,0 +1,326 @@ + + + + + diff --git a/apps/web/src/admin/contest/list.vue b/apps/web/src/admin/contest/list.vue new file mode 100644 index 0000000..4d16ae0 --- /dev/null +++ b/apps/web/src/admin/contest/list.vue @@ -0,0 +1,132 @@ + + + + + diff --git a/apps/web/src/admin/problem/Stuck.vue b/apps/web/src/admin/problem/Stuck.vue new file mode 100644 index 0000000..37824f2 --- /dev/null +++ b/apps/web/src/admin/problem/Stuck.vue @@ -0,0 +1,56 @@ + + + diff --git a/apps/web/src/admin/problem/TopACTrend.vue b/apps/web/src/admin/problem/TopACTrend.vue new file mode 100644 index 0000000..d9f3046 --- /dev/null +++ b/apps/web/src/admin/problem/TopACTrend.vue @@ -0,0 +1,202 @@ + + + + + diff --git a/apps/web/src/admin/problem/components/Actions.vue b/apps/web/src/admin/problem/components/Actions.vue new file mode 100644 index 0000000..d958a82 --- /dev/null +++ b/apps/web/src/admin/problem/components/Actions.vue @@ -0,0 +1,160 @@ + + diff --git a/apps/web/src/admin/problem/components/AddButton.vue b/apps/web/src/admin/problem/components/AddButton.vue new file mode 100644 index 0000000..f80e089 --- /dev/null +++ b/apps/web/src/admin/problem/components/AddButton.vue @@ -0,0 +1,47 @@ + + + diff --git a/apps/web/src/admin/problem/components/AstRulesEditor.vue b/apps/web/src/admin/problem/components/AstRulesEditor.vue new file mode 100644 index 0000000..3c5aedf --- /dev/null +++ b/apps/web/src/admin/problem/components/AstRulesEditor.vue @@ -0,0 +1,397 @@ + + + diff --git a/apps/web/src/admin/problem/components/BatchTagModal.vue b/apps/web/src/admin/problem/components/BatchTagModal.vue new file mode 100644 index 0000000..1e780db --- /dev/null +++ b/apps/web/src/admin/problem/components/BatchTagModal.vue @@ -0,0 +1,109 @@ + + + diff --git a/apps/web/src/admin/problem/components/Modal.vue b/apps/web/src/admin/problem/components/Modal.vue new file mode 100644 index 0000000..7475af7 --- /dev/null +++ b/apps/web/src/admin/problem/components/Modal.vue @@ -0,0 +1,94 @@ + + + diff --git a/apps/web/src/admin/problem/components/SQLTestcaseEditor.vue b/apps/web/src/admin/problem/components/SQLTestcaseEditor.vue new file mode 100644 index 0000000..f1aa2ba --- /dev/null +++ b/apps/web/src/admin/problem/components/SQLTestcaseEditor.vue @@ -0,0 +1,345 @@ + + + + + diff --git a/apps/web/src/admin/problem/components/TagProblemsModal.vue b/apps/web/src/admin/problem/components/TagProblemsModal.vue new file mode 100644 index 0000000..d5558ea --- /dev/null +++ b/apps/web/src/admin/problem/components/TagProblemsModal.vue @@ -0,0 +1,147 @@ + + + diff --git a/apps/web/src/admin/problem/components/TestcaseGenerator.vue b/apps/web/src/admin/problem/components/TestcaseGenerator.vue new file mode 100644 index 0000000..589ce29 --- /dev/null +++ b/apps/web/src/admin/problem/components/TestcaseGenerator.vue @@ -0,0 +1,263 @@ + + + diff --git a/apps/web/src/admin/problem/detail.vue b/apps/web/src/admin/problem/detail.vue new file mode 100644 index 0000000..568e016 --- /dev/null +++ b/apps/web/src/admin/problem/detail.vue @@ -0,0 +1,920 @@ + + + + + diff --git a/apps/web/src/admin/problem/list.vue b/apps/web/src/admin/problem/list.vue new file mode 100644 index 0000000..94161a8 --- /dev/null +++ b/apps/web/src/admin/problem/list.vue @@ -0,0 +1,335 @@ + + + + + diff --git a/apps/web/src/admin/problem/tags.vue b/apps/web/src/admin/problem/tags.vue new file mode 100644 index 0000000..9b0befe --- /dev/null +++ b/apps/web/src/admin/problem/tags.vue @@ -0,0 +1,186 @@ + + + + + diff --git a/apps/web/src/admin/problemset/components/Actions.vue b/apps/web/src/admin/problemset/components/Actions.vue new file mode 100644 index 0000000..97d1b7b --- /dev/null +++ b/apps/web/src/admin/problemset/components/Actions.vue @@ -0,0 +1,105 @@ + + + diff --git a/apps/web/src/admin/problemset/components/AddBadgeModal.vue b/apps/web/src/admin/problemset/components/AddBadgeModal.vue new file mode 100644 index 0000000..a7a0f60 --- /dev/null +++ b/apps/web/src/admin/problemset/components/AddBadgeModal.vue @@ -0,0 +1,162 @@ + + + diff --git a/apps/web/src/admin/problemset/components/AddProblemModal.vue b/apps/web/src/admin/problemset/components/AddProblemModal.vue new file mode 100644 index 0000000..90cfece --- /dev/null +++ b/apps/web/src/admin/problemset/components/AddProblemModal.vue @@ -0,0 +1,103 @@ + + + diff --git a/apps/web/src/admin/problemset/components/BadgeManagement.vue b/apps/web/src/admin/problemset/components/BadgeManagement.vue new file mode 100644 index 0000000..91e4677 --- /dev/null +++ b/apps/web/src/admin/problemset/components/BadgeManagement.vue @@ -0,0 +1,96 @@ + + + diff --git a/apps/web/src/admin/problemset/components/EditBadgeModal.vue b/apps/web/src/admin/problemset/components/EditBadgeModal.vue new file mode 100644 index 0000000..7a0a938 --- /dev/null +++ b/apps/web/src/admin/problemset/components/EditBadgeModal.vue @@ -0,0 +1,167 @@ + + + diff --git a/apps/web/src/admin/problemset/components/EditProblemModal.vue b/apps/web/src/admin/problemset/components/EditProblemModal.vue new file mode 100644 index 0000000..6709334 --- /dev/null +++ b/apps/web/src/admin/problemset/components/EditProblemModal.vue @@ -0,0 +1,100 @@ + + + diff --git a/apps/web/src/admin/problemset/components/ProblemManagement.vue b/apps/web/src/admin/problemset/components/ProblemManagement.vue new file mode 100644 index 0000000..2c337a8 --- /dev/null +++ b/apps/web/src/admin/problemset/components/ProblemManagement.vue @@ -0,0 +1,73 @@ + + + diff --git a/apps/web/src/admin/problemset/components/ProblemSetInfo.vue b/apps/web/src/admin/problemset/components/ProblemSetInfo.vue new file mode 100644 index 0000000..ccbc88a --- /dev/null +++ b/apps/web/src/admin/problemset/components/ProblemSetInfo.vue @@ -0,0 +1,70 @@ + + + diff --git a/apps/web/src/admin/problemset/components/ProgressManagement.vue b/apps/web/src/admin/problemset/components/ProgressManagement.vue new file mode 100644 index 0000000..4db070a --- /dev/null +++ b/apps/web/src/admin/problemset/components/ProgressManagement.vue @@ -0,0 +1,69 @@ + + + diff --git a/apps/web/src/admin/problemset/detail.vue b/apps/web/src/admin/problemset/detail.vue new file mode 100644 index 0000000..7b2c883 --- /dev/null +++ b/apps/web/src/admin/problemset/detail.vue @@ -0,0 +1,270 @@ + + + + + diff --git a/apps/web/src/admin/problemset/edit.vue b/apps/web/src/admin/problemset/edit.vue new file mode 100644 index 0000000..157046b --- /dev/null +++ b/apps/web/src/admin/problemset/edit.vue @@ -0,0 +1,169 @@ + + + + + diff --git a/apps/web/src/admin/problemset/list.vue b/apps/web/src/admin/problemset/list.vue new file mode 100644 index 0000000..736ed6b --- /dev/null +++ b/apps/web/src/admin/problemset/list.vue @@ -0,0 +1,212 @@ + + + + + diff --git a/apps/web/src/admin/setting/config.vue b/apps/web/src/admin/setting/config.vue new file mode 100644 index 0000000..23fdc42 --- /dev/null +++ b/apps/web/src/admin/setting/config.vue @@ -0,0 +1,314 @@ + + + + + diff --git a/apps/web/src/admin/setting/home.vue b/apps/web/src/admin/setting/home.vue new file mode 100644 index 0000000..5a8dbc6 --- /dev/null +++ b/apps/web/src/admin/setting/home.vue @@ -0,0 +1,244 @@ + + + + + diff --git a/apps/web/src/admin/transforms.ts b/apps/web/src/admin/transforms.ts new file mode 100644 index 0000000..63e99b6 --- /dev/null +++ b/apps/web/src/admin/transforms.ts @@ -0,0 +1,20 @@ +import type { AdminProblem } from "utils/types" + +// 把后端的 AdminProblem 塑形成管理端列表项,与请求逻辑解耦。 +export function toProblemListItem(result: AdminProblem) { + return { + id: result.id, + _id: result._id, + title: result.title, + username: result.created_by.username, + create_time: result.create_time, + visible: result.visible, + difficulty: result.difficulty, + tags: result.tags, + has_ast_rules: result.has_ast_rules, + allow_flowchart: result.allow_flowchart, + show_flowchart: result.show_flowchart, + // 比赛题目列表接口不返回这个字段 + top_reaction: result.top_reaction ?? null, + } +} diff --git a/apps/web/src/admin/tutorial/components/Actions.vue b/apps/web/src/admin/tutorial/components/Actions.vue new file mode 100644 index 0000000..e2e535b --- /dev/null +++ b/apps/web/src/admin/tutorial/components/Actions.vue @@ -0,0 +1,43 @@ + + + diff --git a/apps/web/src/admin/tutorial/components/ExerciseManager.vue b/apps/web/src/admin/tutorial/components/ExerciseManager.vue new file mode 100644 index 0000000..9c572bd --- /dev/null +++ b/apps/web/src/admin/tutorial/components/ExerciseManager.vue @@ -0,0 +1,652 @@ + + + diff --git a/apps/web/src/admin/tutorial/detail.vue b/apps/web/src/admin/tutorial/detail.vue new file mode 100644 index 0000000..a60b058 --- /dev/null +++ b/apps/web/src/admin/tutorial/detail.vue @@ -0,0 +1,133 @@ + + + diff --git a/apps/web/src/admin/tutorial/list.vue b/apps/web/src/admin/tutorial/list.vue new file mode 100644 index 0000000..1b1581a --- /dev/null +++ b/apps/web/src/admin/tutorial/list.vue @@ -0,0 +1,107 @@ + + + + + diff --git a/apps/web/src/admin/user/components/Actions.vue b/apps/web/src/admin/user/components/Actions.vue new file mode 100644 index 0000000..e64fe7d --- /dev/null +++ b/apps/web/src/admin/user/components/Actions.vue @@ -0,0 +1,55 @@ + + diff --git a/apps/web/src/admin/user/components/Name.vue b/apps/web/src/admin/user/components/Name.vue new file mode 100644 index 0000000..72462ac --- /dev/null +++ b/apps/web/src/admin/user/components/Name.vue @@ -0,0 +1,42 @@ + + diff --git a/apps/web/src/admin/user/generate.vue b/apps/web/src/admin/user/generate.vue new file mode 100644 index 0000000..d613f3b --- /dev/null +++ b/apps/web/src/admin/user/generate.vue @@ -0,0 +1,107 @@ + + + + + diff --git a/apps/web/src/admin/user/list.vue b/apps/web/src/admin/user/list.vue new file mode 100644 index 0000000..bc49bae --- /dev/null +++ b/apps/web/src/admin/user/list.vue @@ -0,0 +1,359 @@ + + + + + diff --git a/apps/web/src/env.d.ts b/apps/web/src/env.d.ts new file mode 100644 index 0000000..671f9d4 --- /dev/null +++ b/apps/web/src/env.d.ts @@ -0,0 +1,16 @@ +/// + +interface ImportMetaEnv { + readonly PUBLIC_ENV: string + readonly PUBLIC_MAXKB_URL: string + readonly PUBLIC_OJ_URL: string + readonly PUBLIC_CODE_URL: string + readonly PUBLIC_JUDGE0_URL: string + readonly PUBLIC_ICONIFY_URL: string + readonly PUBLIC_SIGNALING_URL: string + readonly PUBLIC_WS_URL: string +} + +interface ImportMeta { + readonly env: ImportMetaEnv +} diff --git a/apps/web/src/index.css b/apps/web/src/index.css new file mode 100644 index 0000000..fcb8334 --- /dev/null +++ b/apps/web/src/index.css @@ -0,0 +1,29 @@ +body { + height: 100vh; +} + +.md-editor-dark { + --md-bk-color: var(--n-body-color) !important; +} + +.md-editor-dark div.vuepress-theme { + --md-theme-color: var(--n-text-color) !important; +} + +.oj-mermaid-surface { + box-sizing: border-box; + padding: 18px; + overflow: auto; + border: 1px solid rgba(148, 163, 184, 0.24); + border-radius: 8px; +} + +.oj-mermaid-surface > svg { + max-width: 100%; +} + +::view-transition-old(root), +::view-transition-new(root) { + animation: none; + mix-blend-mode: normal; +} diff --git a/apps/web/src/main.ts b/apps/web/src/main.ts new file mode 100644 index 0000000..e84d4f2 --- /dev/null +++ b/apps/web/src/main.ts @@ -0,0 +1,92 @@ +import { addAPIProvider } from "@iconify/vue" +import { createPinia } from "pinia" +import { createRouter, createWebHistory } from "vue-router" + +import { STORAGE_KEY } from "utils/constants" +import storage from "utils/storage" + +import App from "./App.vue" +import { admins, ojs } from "./routes" + +const router = createRouter({ + history: createWebHistory(), + routes: [ojs, admins], +}) + +const pinia = createPinia() + +// 创建 app 并安装插件 +const app = createApp(App) +app.use(pinia) +app.use(router) + +// 现在可以安全地使用 Store +import { useAuthModalStore } from "./shared/store/authModal" +import { useUserStore } from "./shared/store/user" + +const authStore = useAuthModalStore() + +router.beforeEach(async (to, from, next) => { + // 检查是否需要认证 + if (to.matched.some((record) => record.meta.requiresAuth)) { + if (!storage.get(STORAGE_KEY.AUTHED)) { + authStore.openLoginModal() + next("/") + return + } + } + + // 检查权限 + if ( + to.matched.some( + (record) => + record.meta.requiresSuperAdmin || + record.meta.requiresTeacherAdmin || + record.meta.requiresProblemPermission, + ) + ) { + if (!storage.get(STORAGE_KEY.AUTHED)) { + authStore.openLoginModal() + next("/") + return + } + + const userStore = useUserStore() + if (!userStore.user) { + try { + await userStore.getMyProfile() + } catch (error) { + next("/") + return + } + } + if (to.matched.some((record) => record.meta.requiresSuperAdmin)) { + if (!userStore.isSuperAdmin) { + next("/") + return + } + } else if (to.matched.some((record) => record.meta.requiresTeacherAdmin)) { + if (!userStore.isTeacherOrAbove) { + next("/") + return + } + } else if ( + to.matched.some((record) => record.meta.requiresProblemPermission) + ) { + if (!userStore.hasProblemPermission) { + next("/") + return + } + } + } + + next() +}) + +app.mount("#app") + +if (!!import.meta.env.PUBLIC_ICONIFY_URL) { + addAPIProvider("", { + resources: [import.meta.env.PUBLIC_ICONIFY_URL], + }) +} diff --git a/apps/web/src/mermaid-legacy.d.ts b/apps/web/src/mermaid-legacy.d.ts new file mode 100644 index 0000000..a0c9382 --- /dev/null +++ b/apps/web/src/mermaid-legacy.d.ts @@ -0,0 +1 @@ +declare module "mermaid-legacy" diff --git a/apps/web/src/oj/achievement/api.ts b/apps/web/src/oj/achievement/api.ts new file mode 100644 index 0000000..8d682f5 --- /dev/null +++ b/apps/web/src/oj/achievement/api.ts @@ -0,0 +1,27 @@ +import http from "utils/http" +import type { + Achievement, + AchievementSummary, + PendingAchievement, +} from "utils/types" + +export function getAchievements(name?: string) { + return http.get<{ username: string; achievements: Achievement[] }>( + "achievements", + { params: name ? { name } : {} }, + ) +} + +export function getAchievementSummary(name?: string) { + return http.get("achievements/summary", { + params: name ? { name } : {}, + }) +} + +export function getPendingAchievements() { + return http.get("achievements/pending") +} + +export function markAchievementsRead(ids: number[]) { + return http.post("achievements/pending", { ids }) +} diff --git a/apps/web/src/oj/achievement/components/AchievementCard.vue b/apps/web/src/oj/achievement/components/AchievementCard.vue new file mode 100644 index 0000000..05b846a --- /dev/null +++ b/apps/web/src/oj/achievement/components/AchievementCard.vue @@ -0,0 +1,134 @@ + + + + + diff --git a/apps/web/src/oj/achievement/index.vue b/apps/web/src/oj/achievement/index.vue new file mode 100644 index 0000000..4b54628 --- /dev/null +++ b/apps/web/src/oj/achievement/index.vue @@ -0,0 +1,227 @@ + + + + + diff --git a/apps/web/src/oj/ai/analysis.vue b/apps/web/src/oj/ai/analysis.vue new file mode 100644 index 0000000..4eee1c7 --- /dev/null +++ b/apps/web/src/oj/ai/analysis.vue @@ -0,0 +1,122 @@ + + diff --git a/apps/web/src/oj/ai/components/AI.vue b/apps/web/src/oj/ai/components/AI.vue new file mode 100644 index 0000000..a6865fa --- /dev/null +++ b/apps/web/src/oj/ai/components/AI.vue @@ -0,0 +1,91 @@ + + + diff --git a/apps/web/src/oj/ai/components/DifficultyGradeChart.vue b/apps/web/src/oj/ai/components/DifficultyGradeChart.vue new file mode 100644 index 0000000..6add8be --- /dev/null +++ b/apps/web/src/oj/ai/components/DifficultyGradeChart.vue @@ -0,0 +1,144 @@ + + + diff --git a/apps/web/src/oj/ai/components/DurationChart.vue b/apps/web/src/oj/ai/components/DurationChart.vue new file mode 100644 index 0000000..502d7ca --- /dev/null +++ b/apps/web/src/oj/ai/components/DurationChart.vue @@ -0,0 +1,204 @@ + + + diff --git a/apps/web/src/oj/ai/components/EfficiencyChart.vue b/apps/web/src/oj/ai/components/EfficiencyChart.vue new file mode 100644 index 0000000..8b74bf4 --- /dev/null +++ b/apps/web/src/oj/ai/components/EfficiencyChart.vue @@ -0,0 +1,234 @@ + + + diff --git a/apps/web/src/oj/ai/components/Grade.vue b/apps/web/src/oj/ai/components/Grade.vue new file mode 100644 index 0000000..b5096d1 --- /dev/null +++ b/apps/web/src/oj/ai/components/Grade.vue @@ -0,0 +1,53 @@ + + + diff --git a/apps/web/src/oj/ai/components/Heatmap.vue b/apps/web/src/oj/ai/components/Heatmap.vue new file mode 100644 index 0000000..2c4f4a8 --- /dev/null +++ b/apps/web/src/oj/ai/components/Heatmap.vue @@ -0,0 +1,251 @@ + + + + + diff --git a/apps/web/src/oj/ai/components/Overview.vue b/apps/web/src/oj/ai/components/Overview.vue new file mode 100644 index 0000000..4355f4d --- /dev/null +++ b/apps/web/src/oj/ai/components/Overview.vue @@ -0,0 +1,63 @@ + + + diff --git a/apps/web/src/oj/ai/components/ProgressChart.vue b/apps/web/src/oj/ai/components/ProgressChart.vue new file mode 100644 index 0000000..3f55eed --- /dev/null +++ b/apps/web/src/oj/ai/components/ProgressChart.vue @@ -0,0 +1,271 @@ + + + diff --git a/apps/web/src/oj/ai/components/RankDistributionChart.vue b/apps/web/src/oj/ai/components/RankDistributionChart.vue new file mode 100644 index 0000000..db51802 --- /dev/null +++ b/apps/web/src/oj/ai/components/RankDistributionChart.vue @@ -0,0 +1,132 @@ + + + diff --git a/apps/web/src/oj/ai/components/SolvedTable.vue b/apps/web/src/oj/ai/components/SolvedTable.vue new file mode 100644 index 0000000..60ef450 --- /dev/null +++ b/apps/web/src/oj/ai/components/SolvedTable.vue @@ -0,0 +1,154 @@ + + + diff --git a/apps/web/src/oj/ai/components/StreakStats.vue b/apps/web/src/oj/ai/components/StreakStats.vue new file mode 100644 index 0000000..4e05a32 --- /dev/null +++ b/apps/web/src/oj/ai/components/StreakStats.vue @@ -0,0 +1,177 @@ + + + diff --git a/apps/web/src/oj/ai/components/TagTitle.vue b/apps/web/src/oj/ai/components/TagTitle.vue new file mode 100644 index 0000000..48d91e3 --- /dev/null +++ b/apps/web/src/oj/ai/components/TagTitle.vue @@ -0,0 +1,21 @@ + + diff --git a/apps/web/src/oj/ai/components/TagsRadarChart.vue b/apps/web/src/oj/ai/components/TagsRadarChart.vue new file mode 100644 index 0000000..42b6e37 --- /dev/null +++ b/apps/web/src/oj/ai/components/TagsRadarChart.vue @@ -0,0 +1,154 @@ + + + diff --git a/apps/web/src/oj/ai/components/TimeActivityHeatmap.vue b/apps/web/src/oj/ai/components/TimeActivityHeatmap.vue new file mode 100644 index 0000000..9711864 --- /dev/null +++ b/apps/web/src/oj/ai/components/TimeActivityHeatmap.vue @@ -0,0 +1,154 @@ + + + diff --git a/apps/web/src/oj/announcement/components/TitleWithTag.vue b/apps/web/src/oj/announcement/components/TitleWithTag.vue new file mode 100644 index 0000000..dbb7ec0 --- /dev/null +++ b/apps/web/src/oj/announcement/components/TitleWithTag.vue @@ -0,0 +1,14 @@ + + diff --git a/apps/web/src/oj/announcement/list.vue b/apps/web/src/oj/announcement/list.vue new file mode 100644 index 0000000..a849691 --- /dev/null +++ b/apps/web/src/oj/announcement/list.vue @@ -0,0 +1,94 @@ + + diff --git a/apps/web/src/oj/api.ts b/apps/web/src/oj/api.ts new file mode 100644 index 0000000..efe618d --- /dev/null +++ b/apps/web/src/oj/api.ts @@ -0,0 +1,435 @@ +import http from "utils/http" +import { filterResult } from "oj/transforms" +import type { + Exercise, + Problem, + ReactionKey, + ReactionState, + Submission, + SubmissionListPayload, + SubmitCodePayload, +} from "utils/types" + +export function getWebsiteConfig() { + return http.get("website") +} + +export async function getProblemList( + offset = 0, + limit = 10, + searchParams: any = {}, +) { + const res = await http.get<{ results: Problem[]; total: number }>("problem", { + params: { paging: true, offset, limit, ...searchParams }, + }) + return { + results: res.data.results.map(filterResult), + total: res.data.total, + } +} + +export function getAuthors(all = false) { + return http.get("problem/author", { + params: { + all: all ? "1" : "0", + }, + }) +} + +export function getRandomProblemID() { + return http.get("pickone") +} + +export function getProblem(problemID: string, contestID: string) { + const endpoint = !!contestID ? "contest/problem" : "problem" + return http.get(endpoint, { + params: { + problem_id: problemID, + contest_id: contestID, + }, + }) +} + +export function getProblemBeatRate(problemID: number) { + return http.get("problem/beat_count", { params: { problem_id: problemID } }) +} + +export function getSubmission(id: string) { + return http.get("submission", { + params: { id }, + }) +} + +export function submitCode(data: SubmitCodePayload) { + return http.post("submission", data) +} + +export function formatCode(data: { code: string; language: string }) { + return http.post<{ code: string }>("format_code", data) +} + +export function getSubmissions(params: Partial) { + const endpoint = !!params.contest_id ? "contest_submissions" : "submissions" + return http.get(endpoint, { params }) +} + +export function getRankOfProblem(problem_id: string) { + return http.get("user_problem_rank", { params: { problem_id: problem_id } }) +} + +export function getTodaySubmissionCount(language?: string) { + return http.get("submissions/today_count", { params: { language } }) +} + +export function adminRejudge(id: string) { + return http.get("admin/submission/rejudge", { + params: { id }, + }) +} + +export function getSubmissionStatistics( + duration: { start?: string; end: string }, + problemID?: string, + username?: string, +) { + return http.get("admin/submission/statistics", { + params: { + ...duration, + problem_id: problemID, + username, + }, + }) +} + +export function getRank( + offset: number, + limit: number, + n: number, + username?: string, +) { + return http.get("user_rank", { + params: { offset, limit, rule: "acm", username, n }, + }) +} + +export function getActivityRank(start: string) { + return http.get("user_activity_rank", { + params: { start }, + }) +} + +export function getClassRank(grade?: number | null) { + return http.get("class_rank", { + params: { grade }, + }) +} + +export function getUserClassRank( + scope?: "all" | "window", + offset?: number, + limit?: number, +) { + return http.get("user_class_rank", { params: { scope, offset, limit } }) +} + +export function getClassPK( + classNames: string[], + startTime?: string, + endTime?: string, +) { + const payload: any = { + class_name: classNames, + } + if (startTime) { + payload.start_time = startTime + } + if (endTime) { + payload.end_time = endTime + } + return http.post("class_pk", payload) +} + +export function getContestList(query: { + offset: number + limit: number + keyword: string + status: string + tag: string +}) { + return http.get("contests", { params: query }) +} + +export function getContest(id: string) { + return http.get("contest", { params: { id } }) +} + +export function getContestAccess(id: string) { + return http.get("contest/access", { params: { contest_id: id } }) +} + +export function checkContestPassword(contestID: string, password: string) { + return http.post("contest/password", { + contest_id: contestID, + password, + }) +} + +export async function getContestProblems(contestID: string) { + const res = await http.get("contest/problem", { + params: { contest_id: contestID }, + }) + return res.data.map(filterResult) +} + +export function getContestRank( + contestID: string, + query: { limit: number; offset: number }, +) { + return http.get("contest_rank", { + params: { + contest_id: contestID, + ...query, + }, + }) +} + +export function uploadAvatar(file: File) { + const form = new window.FormData() + form.append("image", file) + return http.post("upload_avatar", form, { + headers: { "content-type": "multipart/form-data" }, + }) +} + +export function updateProfile(data: { real_name: string; mood: string }) { + return http.put("profile", data) +} + +export function getAnnouncementList(offset = 0, limit = 10) { + return http.get("announcement", { params: { limit, offset } }) +} + +export function getAnnouncement(id: number) { + return http.get("announcement", { params: { id } }) +} + +export function createMessage(data: { + recipient: number + message: string + submission: string +}) { + return http.post("message", data) +} + +export function getMessageList(offset = 0, limit = 10) { + return http.get("message", { params: { limit, offset } }) +} + +export function getReaction(problemID: number) { + return http.get("reaction", { + params: { problem_id: problemID }, + }) +} + +export function setReaction(problemID: number, type: ReactionKey) { + return http.post("reaction", { + problem_id: problemID, + type, + }) +} + +// TODO: 这个API有问题 +export function refreshUserProblemDisplayIds() { + return http.get("profile/fresh_display_id") +} + +export function getMetrics(userid: number) { + return http.get("metrics", { params: { userid } }) +} + +export function getTutorial(id: number) { + return http.get("tutorial", { params: { id } }) +} + +export function getTutorials(type: "python" | "c") { + return http.get("tutorials", { params: { type } }) +} + +export function getAIDetailData(start: string, end: string, username?: string) { + return http.get("ai/detail", { params: { start, end, username } }) +} + +export function getAIDurationData( + end: string, + duration: string, + username?: string, +) { + return http.get("ai/duration", { params: { end, duration, username } }) +} + +export function getAIHeatmapData(username?: string) { + return http.get("ai/heatmap", { params: username ? { username } : {} }) +} + +export function getAILoginSummary() { + return http.get("ai/login_summary") +} + +export function getAIPinnedReport() { + return http.get("ai/pinned") +} + +// ==================== 相似题目推荐 ==================== + +export function getSimilarProblems(problemId: string) { + return http.get("problem/similar", { params: { problem_id: problemId } }) +} + +export interface YearlyACData { + year: number + total: number + accepted: number + ac_rate: number +} + +export function getProblemYearlyAC(problemId: string) { + return http.get("problem/yearly_ac", { + params: { problem_id: problemId }, + }) +} + +// ==================== 流程图相关API ==================== + +export function submitFlowchart(data: { + problem_id: number + mermaid_code: string + flowchart_data: any // 这个是压缩之后的,元数据太长了 +}) { + return http.post("flowchart/submission", data) +} + +export function getFlowchartSubmission(id: string) { + return http.get("flowchart/submission", { + params: { id }, + }) +} + +export function getFlowchartSubmissions(params: { + username?: string + problem_id?: string + myself?: string + offset?: number + limit?: number + today?: string + grade?: string +}) { + return http.get("flowchart/submissions", { params }) +} + +export function getFlowchartStatistics( + duration: { start?: string; end: string }, + problemID?: string, + username?: string, +) { + return http.get("admin/flowchart/statistics", { + params: { + ...duration, + problem_id: problemID, + username, + }, + }) +} + +export function retryFlowchartSubmission(submissionId: string) { + return http.post("flowchart/submission/retry", { + submission_id: submissionId, + }) +} + +export function getCurrentProblemFlowchartSubmission(problemId: number) { + return http.get("flowchart/submission/current", { + params: { problem_id: problemId }, + }) +} + +export function getFlowchartSubmissionDetail(problemId: number, page = 0) { + return http.get("flowchart/submission/detail", { + params: { problem_id: problemId, page }, + }) +} + +// ==================== 题单相关API ==================== + +export function getProblemSetList( + offset = 0, + limit = 10, + keyword = "", + difficulty = "", + status = "", +) { + return http.get("problemset", { + params: { + offset, + limit, + keyword, + difficulty, + status, + }, + }) +} + +export function getProblemSetDetail(id: number) { + return http.get(`problemset/${id}`) +} + +export function getProblemSetProblems(problemSetId: number) { + return http.get(`problemset/${problemSetId}/problems`) +} + +export function joinProblemSet(problemSetId: number) { + return http.post("problemset/progress", { + problemset_id: problemSetId, + }) +} + +export function updateProblemSetProgress( + problemSetId: number, + problemId: number, + submissionId: string, +) { + return http.put("problemset/progress", { + problemset_id: problemSetId, + problem_id: problemId, + submission_id: submissionId, + }) +} + +// 获取用户徽章列表 +export function getUserBadges(username?: string) { + return http.get("user/badges", { params: username ? { username } : {} }) +} + +// 获取题单徽章列表 +export function getProblemSetBadges(problemSetId: number) { + return http.get(`problemset/${problemSetId}/badges`) +} + +// 获取题单用户进度列表 +export function getProblemSetUserProgress( + problemSetId: number, + params?: { + limit?: number + offset?: number + class_name?: string + completion_status?: "" | "completed" | "in_progress" | "not_started" + }, +) { + return http.get(`problemset/${problemSetId}/users_progress`, { params }) +} + +export async function getExercises(tutorialId: number): Promise { + const res = await http.get("exercises", { + params: { tutorial_id: tutorialId }, + }) + return res.data +} diff --git a/apps/web/src/oj/class/pk.vue b/apps/web/src/oj/class/pk.vue new file mode 100644 index 0000000..264608b --- /dev/null +++ b/apps/web/src/oj/class/pk.vue @@ -0,0 +1,1260 @@ + + + + + diff --git a/apps/web/src/oj/composables/syncStatus.ts b/apps/web/src/oj/composables/syncStatus.ts new file mode 100644 index 0000000..fcddcb2 --- /dev/null +++ b/apps/web/src/oj/composables/syncStatus.ts @@ -0,0 +1,73 @@ +import { ref, provide, inject } from "vue" + +/** + * 同步状态管理 composable + * 使用 provide/inject 模式在组件树中共享状态 + */ + +export interface SyncStatusState { + hadConnection: boolean + otherUser?: { name: string; isSuperAdmin: boolean } + lastLeftUser?: { name: string; isSuperAdmin: boolean } // 保存离开之人的信息 +} + +// 提供/注入的 key +export const SYNC_STATUS_KEY = Symbol("syncStatus") + +/** + * 创建同步状态实例 + * 每次调用创建新的状态实例 + */ +export function createSyncStatus() { + const otherUser = ref<{ name: string; isSuperAdmin: boolean }>() + const hadConnection = ref(false) + const lastLeftUser = ref<{ name: string; isSuperAdmin: boolean }>() + + const setOtherUser = (user?: { name: string; isSuperAdmin: boolean }) => { + // 如果之前有其他用户,现在没有了,说明用户离开了 + if (otherUser.value && !user) { + lastLeftUser.value = otherUser.value + } + otherUser.value = user + if (user) { + hadConnection.value = true + } + } + + const reset = () => { + otherUser.value = undefined + hadConnection.value = false + lastLeftUser.value = undefined + } + + return { + otherUser, + hadConnection, + lastLeftUser, + setOtherUser, + reset, + } +} + +/** + * 提供同步状态到子组件 + * 在父组件中调用 + */ +export function provideSyncStatus() { + const syncStatus = createSyncStatus() + provide(SYNC_STATUS_KEY, syncStatus) + return syncStatus +} + +/** + * 注入同步状态 + * 在子组件中调用,获取父组件提供的状态 + */ +export function injectSyncStatus() { + const syncStatus = + inject>(SYNC_STATUS_KEY) + if (!syncStatus) { + throw new Error("syncStatus must be provided by a parent component") + } + return syncStatus +} diff --git a/apps/web/src/oj/contest/components/AcAndSubmission.vue b/apps/web/src/oj/contest/components/AcAndSubmission.vue new file mode 100644 index 0000000..4f99a36 --- /dev/null +++ b/apps/web/src/oj/contest/components/AcAndSubmission.vue @@ -0,0 +1,25 @@ + + + + + diff --git a/apps/web/src/oj/contest/components/ContestInfo.vue b/apps/web/src/oj/contest/components/ContestInfo.vue new file mode 100644 index 0000000..4f85d38 --- /dev/null +++ b/apps/web/src/oj/contest/components/ContestInfo.vue @@ -0,0 +1,42 @@ + + + diff --git a/apps/web/src/oj/contest/components/ContestMenu.vue b/apps/web/src/oj/contest/components/ContestMenu.vue new file mode 100644 index 0000000..3eece0b --- /dev/null +++ b/apps/web/src/oj/contest/components/ContestMenu.vue @@ -0,0 +1,56 @@ + + + diff --git a/apps/web/src/oj/contest/components/LineChart.vue b/apps/web/src/oj/contest/components/LineChart.vue new file mode 100644 index 0000000..ca65dd9 --- /dev/null +++ b/apps/web/src/oj/contest/components/LineChart.vue @@ -0,0 +1,231 @@ + + + + diff --git a/apps/web/src/oj/contest/detail.vue b/apps/web/src/oj/contest/detail.vue new file mode 100644 index 0000000..15ae6de --- /dev/null +++ b/apps/web/src/oj/contest/detail.vue @@ -0,0 +1,93 @@ + + + + + diff --git a/apps/web/src/oj/contest/list.vue b/apps/web/src/oj/contest/list.vue new file mode 100644 index 0000000..f71a0ef --- /dev/null +++ b/apps/web/src/oj/contest/list.vue @@ -0,0 +1,180 @@ + + diff --git a/apps/web/src/oj/contest/pages/problems.vue b/apps/web/src/oj/contest/pages/problems.vue new file mode 100644 index 0000000..c3bd47f --- /dev/null +++ b/apps/web/src/oj/contest/pages/problems.vue @@ -0,0 +1,61 @@ + + + + + diff --git a/apps/web/src/oj/contest/pages/rank.vue b/apps/web/src/oj/contest/pages/rank.vue new file mode 100644 index 0000000..46d0748 --- /dev/null +++ b/apps/web/src/oj/contest/pages/rank.vue @@ -0,0 +1,341 @@ + + + + diff --git a/apps/web/src/oj/flowchart/index.vue b/apps/web/src/oj/flowchart/index.vue new file mode 100644 index 0000000..cd8329d --- /dev/null +++ b/apps/web/src/oj/flowchart/index.vue @@ -0,0 +1,7 @@ + + + diff --git a/apps/web/src/oj/learn/components/ExerciseDebug.vue b/apps/web/src/oj/learn/components/ExerciseDebug.vue new file mode 100644 index 0000000..b1ee170 --- /dev/null +++ b/apps/web/src/oj/learn/components/ExerciseDebug.vue @@ -0,0 +1,121 @@ + + + diff --git a/apps/web/src/oj/learn/components/ExerciseFill.vue b/apps/web/src/oj/learn/components/ExerciseFill.vue new file mode 100644 index 0000000..9979ec3 --- /dev/null +++ b/apps/web/src/oj/learn/components/ExerciseFill.vue @@ -0,0 +1,135 @@ + + + diff --git a/apps/web/src/oj/learn/components/ExerciseGroup.vue b/apps/web/src/oj/learn/components/ExerciseGroup.vue new file mode 100644 index 0000000..70c4cc0 --- /dev/null +++ b/apps/web/src/oj/learn/components/ExerciseGroup.vue @@ -0,0 +1,183 @@ + + + diff --git a/apps/web/src/oj/learn/components/ExerciseMatch.vue b/apps/web/src/oj/learn/components/ExerciseMatch.vue new file mode 100644 index 0000000..6dbc673 --- /dev/null +++ b/apps/web/src/oj/learn/components/ExerciseMatch.vue @@ -0,0 +1,184 @@ + + + diff --git a/apps/web/src/oj/learn/components/ExerciseMcq.vue b/apps/web/src/oj/learn/components/ExerciseMcq.vue new file mode 100644 index 0000000..71925c1 --- /dev/null +++ b/apps/web/src/oj/learn/components/ExerciseMcq.vue @@ -0,0 +1,123 @@ + + + diff --git a/apps/web/src/oj/learn/components/ExercisePredict.vue b/apps/web/src/oj/learn/components/ExercisePredict.vue new file mode 100644 index 0000000..48d4c29 --- /dev/null +++ b/apps/web/src/oj/learn/components/ExercisePredict.vue @@ -0,0 +1,92 @@ + + + diff --git a/apps/web/src/oj/learn/components/ExerciseSort.vue b/apps/web/src/oj/learn/components/ExerciseSort.vue new file mode 100644 index 0000000..cfa1d88 --- /dev/null +++ b/apps/web/src/oj/learn/components/ExerciseSort.vue @@ -0,0 +1,130 @@ + + + diff --git a/apps/web/src/oj/learn/components/ExerciseWidget.vue b/apps/web/src/oj/learn/components/ExerciseWidget.vue new file mode 100644 index 0000000..ab479ff --- /dev/null +++ b/apps/web/src/oj/learn/components/ExerciseWidget.vue @@ -0,0 +1,41 @@ + + + diff --git a/apps/web/src/oj/learn/components/exercise-highlight.css b/apps/web/src/oj/learn/components/exercise-highlight.css new file mode 100644 index 0000000..5493423 --- /dev/null +++ b/apps/web/src/oj/learn/components/exercise-highlight.css @@ -0,0 +1,50 @@ +/* 练一练代码高亮配色(明 / 暗),由涉及代码高亮的题型组件统一引入 */ +.hljs-keyword, +.hljs-operator, +.hljs-selector-tag { + color: #d73a49; +} +.hljs-string, +.hljs-regexp, +.hljs-template-literal { + color: #032f62; +} +.hljs-comment, +.hljs-quote { + color: #6a737d; + font-style: italic; +} +.hljs-number, +.hljs-literal { + color: #005cc5; +} +.hljs-built_in, +.hljs-title.function_, +.hljs-class .hljs-title { + color: #6f42c1; +} + +.dark .hljs-keyword, +.dark .hljs-operator, +.dark .hljs-selector-tag { + color: #c678dd; +} +.dark .hljs-string, +.dark .hljs-regexp, +.dark .hljs-template-literal { + color: #98c379; +} +.dark .hljs-comment, +.dark .hljs-quote { + color: #7f848e; + font-style: italic; +} +.dark .hljs-number, +.dark .hljs-literal { + color: #e5c07b; +} +.dark .hljs-built_in, +.dark .hljs-title.function_, +.dark .hljs-class .hljs-title { + color: #61afef; +} diff --git a/apps/web/src/oj/learn/composables/useCodeHighlight.ts b/apps/web/src/oj/learn/composables/useCodeHighlight.ts new file mode 100644 index 0000000..3ac8261 --- /dev/null +++ b/apps/web/src/oj/learn/composables/useCodeHighlight.ts @@ -0,0 +1,41 @@ +import hljs from "highlight.js/lib/core" +import python from "highlight.js/lib/languages/python" +import c from "highlight.js/lib/languages/c" + +hljs.registerLanguage("python", python) +hljs.registerLanguage("c", c) + +export function escapeHtml(text: string): string { + return text.replace(/&/g, "&").replace(//g, ">") +} + +function normalizeLang(lang?: string): "python" | "c" | null { + return lang === "python" ? "python" : lang === "c" ? "c" : null +} + +// 把整段代码高亮为 HTML;不支持的语言或异常时回退为转义文本 +export function highlight(code: string, lang?: string): string { + const language = normalizeLang(lang) + if (language) { + try { + return hljs.highlight(code, { language }).value + } catch { + // fall through + } + } + return escapeHtml(code) +} + +// 按行高亮:整体高亮后再按行切分,保证跨行 token 着色正确,返回逐行 HTML 数组 +export function highlightLines(lines: string[], lang?: string): string[] { + const language = normalizeLang(lang) + if (language) { + try { + const html = hljs.highlight(lines.join("\n"), { language }).value + return html.split("\n") + } catch { + // fall through + } + } + return lines.map((line) => escapeHtml(line)) +} diff --git a/apps/web/src/oj/learn/composables/useExerciseParse.ts b/apps/web/src/oj/learn/composables/useExerciseParse.ts new file mode 100644 index 0000000..aa483ba --- /dev/null +++ b/apps/web/src/oj/learn/composables/useExerciseParse.ts @@ -0,0 +1,36 @@ +import type { Exercise } from "utils/types" + +type Segment = + { type: "md"; content: string } | { type: "exercise"; exercise: Exercise } + +export function parseExercises( + content: string, + exercises: Exercise[], +): Segment[] { + const exerciseMap = new Map(exercises.map((e) => [e.id, e])) + const segments: Segment[] = [] + const regex = /\[\[exercise:(\d+)\]\]/g + let lastIndex = 0 + let match: RegExpExecArray | null + + while ((match = regex.exec(content)) !== null) { + if (match.index > lastIndex) { + segments.push({ + type: "md", + content: content.slice(lastIndex, match.index), + }) + } + const id = parseInt(match[1]) + const exercise = exerciseMap.get(id) + if (exercise) { + segments.push({ type: "exercise", exercise }) + } + lastIndex = regex.lastIndex + } + + if (lastIndex < content.length) { + segments.push({ type: "md", content: content.slice(lastIndex) }) + } + + return segments +} diff --git a/apps/web/src/oj/learn/composables/useShuffle.ts b/apps/web/src/oj/learn/composables/useShuffle.ts new file mode 100644 index 0000000..9b77a45 --- /dev/null +++ b/apps/web/src/oj/learn/composables/useShuffle.ts @@ -0,0 +1,9 @@ +// Fisher–Yates 洗牌,返回新数组,不修改原数组 +export function shuffle(arr: T[]): T[] { + const a = [...arr] + for (let i = a.length - 1; i > 0; i--) { + const j = Math.floor(Math.random() * (i + 1)) + ;[a[i], a[j]] = [a[j], a[i]] + } + return a +} diff --git a/apps/web/src/oj/learn/index.vue b/apps/web/src/oj/learn/index.vue new file mode 100644 index 0000000..6dc880f --- /dev/null +++ b/apps/web/src/oj/learn/index.vue @@ -0,0 +1,255 @@ + + + + + diff --git a/apps/web/src/oj/problem/components/ContestEditor.vue b/apps/web/src/oj/problem/components/ContestEditor.vue new file mode 100644 index 0000000..316b250 --- /dev/null +++ b/apps/web/src/oj/problem/components/ContestEditor.vue @@ -0,0 +1,69 @@ + + + diff --git a/apps/web/src/oj/problem/components/EditorForTest.vue b/apps/web/src/oj/problem/components/EditorForTest.vue new file mode 100644 index 0000000..62ac570 --- /dev/null +++ b/apps/web/src/oj/problem/components/EditorForTest.vue @@ -0,0 +1,161 @@ + + + + + diff --git a/apps/web/src/oj/problem/components/Form.vue b/apps/web/src/oj/problem/components/Form.vue new file mode 100644 index 0000000..7cecaef --- /dev/null +++ b/apps/web/src/oj/problem/components/Form.vue @@ -0,0 +1,291 @@ + + + diff --git a/apps/web/src/oj/problem/components/MyFlowchartTab.vue b/apps/web/src/oj/problem/components/MyFlowchartTab.vue new file mode 100644 index 0000000..10987f4 --- /dev/null +++ b/apps/web/src/oj/problem/components/MyFlowchartTab.vue @@ -0,0 +1,42 @@ + + + + + diff --git a/apps/web/src/oj/problem/components/ProblemContent.vue b/apps/web/src/oj/problem/components/ProblemContent.vue new file mode 100644 index 0000000..afcafe7 --- /dev/null +++ b/apps/web/src/oj/problem/components/ProblemContent.vue @@ -0,0 +1,550 @@ + + + + + diff --git a/apps/web/src/oj/problem/components/ProblemEditor.vue b/apps/web/src/oj/problem/components/ProblemEditor.vue new file mode 100644 index 0000000..ed6d7f1 --- /dev/null +++ b/apps/web/src/oj/problem/components/ProblemEditor.vue @@ -0,0 +1,123 @@ + + + diff --git a/apps/web/src/oj/problem/components/ProblemFlowchart.vue b/apps/web/src/oj/problem/components/ProblemFlowchart.vue new file mode 100644 index 0000000..0ec970d --- /dev/null +++ b/apps/web/src/oj/problem/components/ProblemFlowchart.vue @@ -0,0 +1,43 @@ + + + + + diff --git a/apps/web/src/oj/problem/components/ProblemInfo.vue b/apps/web/src/oj/problem/components/ProblemInfo.vue new file mode 100644 index 0000000..dc7d3b2 --- /dev/null +++ b/apps/web/src/oj/problem/components/ProblemInfo.vue @@ -0,0 +1,177 @@ + + + + diff --git a/apps/web/src/oj/problem/components/ProblemListTitle.vue b/apps/web/src/oj/problem/components/ProblemListTitle.vue new file mode 100644 index 0000000..345b7c4 --- /dev/null +++ b/apps/web/src/oj/problem/components/ProblemListTitle.vue @@ -0,0 +1,28 @@ + + diff --git a/apps/web/src/oj/problem/components/ProblemReaction.vue b/apps/web/src/oj/problem/components/ProblemReaction.vue new file mode 100644 index 0000000..23a4344 --- /dev/null +++ b/apps/web/src/oj/problem/components/ProblemReaction.vue @@ -0,0 +1,698 @@ + + + + + diff --git a/apps/web/src/oj/problem/components/ProblemStatus.vue b/apps/web/src/oj/problem/components/ProblemStatus.vue new file mode 100644 index 0000000..2440b9f --- /dev/null +++ b/apps/web/src/oj/problem/components/ProblemStatus.vue @@ -0,0 +1,24 @@ + + + + + diff --git a/apps/web/src/oj/problem/components/ProblemSubmission.vue b/apps/web/src/oj/problem/components/ProblemSubmission.vue new file mode 100644 index 0000000..1a70976 --- /dev/null +++ b/apps/web/src/oj/problem/components/ProblemSubmission.vue @@ -0,0 +1,343 @@ + + + + diff --git a/apps/web/src/oj/problem/components/ProblemYearlyChart.vue b/apps/web/src/oj/problem/components/ProblemYearlyChart.vue new file mode 100644 index 0000000..240f49f --- /dev/null +++ b/apps/web/src/oj/problem/components/ProblemYearlyChart.vue @@ -0,0 +1,85 @@ + + + + + diff --git a/apps/web/src/oj/problem/components/SQLDataTable.vue b/apps/web/src/oj/problem/components/SQLDataTable.vue new file mode 100644 index 0000000..8330664 --- /dev/null +++ b/apps/web/src/oj/problem/components/SQLDataTable.vue @@ -0,0 +1,60 @@ + + + + + diff --git a/apps/web/src/oj/problem/components/SubmissionResult.vue b/apps/web/src/oj/problem/components/SubmissionResult.vue new file mode 100644 index 0000000..cce77ba --- /dev/null +++ b/apps/web/src/oj/problem/components/SubmissionResult.vue @@ -0,0 +1,237 @@ + + + + + diff --git a/apps/web/src/oj/problem/components/SubmitCode.vue b/apps/web/src/oj/problem/components/SubmitCode.vue new file mode 100644 index 0000000..0140d81 --- /dev/null +++ b/apps/web/src/oj/problem/components/SubmitCode.vue @@ -0,0 +1,275 @@ + + + diff --git a/apps/web/src/oj/problem/components/SubmitFlowchart.vue b/apps/web/src/oj/problem/components/SubmitFlowchart.vue new file mode 100644 index 0000000..94910db --- /dev/null +++ b/apps/web/src/oj/problem/components/SubmitFlowchart.vue @@ -0,0 +1,422 @@ + + + + diff --git a/apps/web/src/oj/problem/components/submitButtonState.ts b/apps/web/src/oj/problem/components/submitButtonState.ts new file mode 100644 index 0000000..5d843cd --- /dev/null +++ b/apps/web/src/oj/problem/components/submitButtonState.ts @@ -0,0 +1,53 @@ +export interface SubmitButtonStateInput { + isAuthed: boolean + hasCode: boolean + isFormatting: boolean + isSubmitting: boolean + isJudging: boolean + isCooldown: boolean +} + +export interface SubmitButtonState { + disabled: boolean + label: string + icon: string +} + +export function getSubmitButtonState({ + isAuthed, + hasCode, + isFormatting, + isSubmitting, + isJudging, + isCooldown, +}: SubmitButtonStateInput): SubmitButtonState { + const disabled = + !isAuthed || + !hasCode || + isFormatting || + isSubmitting || + isJudging || + isCooldown + + let label = "提交代码" + if (!isAuthed) { + label = "请先登录" + } else if (isFormatting) { + label = "格式化中" + } else if (isSubmitting) { + label = "正在提交" + } else if (isJudging) { + label = "正在评分" + } else if (isCooldown) { + label = "正在冷却" + } + + const icon = + isFormatting || isSubmitting || isJudging + ? "eos-icons:loading" + : isCooldown + ? "ph:lightbulb-fill" + : "ph:play-fill" + + return { disabled, label, icon } +} diff --git a/apps/web/src/oj/problem/composables/useFireworks.ts b/apps/web/src/oj/problem/composables/useFireworks.ts new file mode 100644 index 0000000..8da2e86 --- /dev/null +++ b/apps/web/src/oj/problem/composables/useFireworks.ts @@ -0,0 +1,391 @@ +import confetti from "canvas-confetti" +import { getCurrentScope, onScopeDispose } from "vue" + +// 表情图标取自 @iconify 的 streamline-emojis 图标集,与项目内 用法保持一致 +const EMOJI_SVGS = [ + // 星星眼 + '', + // 点赞 + '', + // 火箭 + '', +] + +// 将 emoji SVG 转成 confetti 可用的彩色位图 Shape,缓存后供重复调用复用 +let emojiShapesPromise: Promise | null = null + +function svgToBitmapShape(svg: string): Promise { + return new Promise((resolve, reject) => { + const blob = new Blob([svg], { type: "image/svg+xml" }) + const url = URL.createObjectURL(blob) + const img = new Image() + img.onload = async () => { + const renderSize = 64 + const baseUnit = 10 + const canvas = document.createElement("canvas") + canvas.width = renderSize + canvas.height = renderSize + const ctx = canvas.getContext("2d")! + ctx.drawImage(img, 0, 0, renderSize, renderSize) + URL.revokeObjectURL(url) + + try { + const bitmap = await createImageBitmap(canvas) + const scale = baseUnit / renderSize + resolve({ + type: "bitmap", + bitmap, + matrix: [ + scale, + 0, + 0, + scale, + (-renderSize * scale) / 2, + (-renderSize * scale) / 2, + ], + } as unknown as confetti.Shape) + } catch (e) { + reject(e) + } + } + img.onerror = reject + img.src = url + }) +} + +function loadEmojiShapes(): Promise { + if (!emojiShapesPromise) { + emojiShapesPromise = Promise.all(EMOJI_SVGS.map(svgToBitmapShape)) + } + return emojiShapesPromise +} + +/** + * 随机烟花效果 Composable + * 提供10种不同风格的烟花庆祝效果 + */ +export function useFireworks() { + // 提前预加载表情包位图,避免首次触发效果8时出现解码延迟 + loadEmojiShapes() + + // 动画期间创建的定时器,组件卸载时统一清理,避免离开页面后动画继续 + const timers = new Set>() + let disposed = false + + function stopTimer(id: ReturnType) { + clearInterval(id) + timers.delete(id) + } + + function runLater(fn: () => void, ms: number) { + const id = setTimeout(() => { + timers.delete(id) + fn() + }, ms) + timers.add(id) + } + + if (getCurrentScope()) { + onScopeDispose(() => { + disposed = true + timers.forEach((id) => clearInterval(id)) + timers.clear() + }) + } + + /** + * 触发随机烟花效果 + */ + function celebrate() { + const fireworkTypes = [ + // 效果1: 经典烟花秀 + () => { + const duration = 3000 + const animationEnd = Date.now() + duration + const defaults = { + startVelocity: 30, + spread: 360, + ticks: 60, + } + + const interval = setInterval(() => { + const timeLeft = animationEnd - Date.now() + if (timeLeft <= 0) return stopTimer(interval) + + const particleCount = 50 * (timeLeft / duration) + confetti({ + ...defaults, + particleCount, + origin: { x: Math.random() * 0.3 + 0.1, y: Math.random() - 0.2 }, + colors: ["#ff6b6b", "#ffd93d", "#6bcf7f", "#4ecdc4", "#a29bfe"], + }) + confetti({ + ...defaults, + particleCount, + origin: { x: Math.random() * 0.3 + 0.7, y: Math.random() - 0.2 }, + colors: ["#ff6b6b", "#ffd93d", "#6bcf7f", "#4ecdc4", "#a29bfe"], + }) + }, 250) + timers.add(interval) + }, + + // 效果2: 星星雨 + () => { + const animationEnd = Date.now() + 2000 + + const interval = setInterval(() => { + if (Date.now() >= animationEnd) return stopTimer(interval) + + confetti({ + particleCount: 12, + angle: 270, + spread: 100, + startVelocity: 12, + gravity: 0.6, + decay: 0.96, + ticks: 200, + scalar: 1.2, + shapes: ["star"], + origin: { x: Math.random(), y: 0 }, + colors: ["#FFD700", "#FFA500", "#FFFF00", "#FF69B4", "#00CED1"], + }) + }, 150) + timers.add(interval) + }, + + // 效果3: 爆炸波浪 + () => { + function randomInRange(min: number, max: number) { + return Math.random() * (max - min) + min + } + + for (let i = 0; i < 5; i++) { + runLater(() => { + confetti({ + angle: randomInRange(55, 125), + spread: randomInRange(50, 70), + particleCount: randomInRange(50, 100), + origin: { y: 0.6 }, + colors: ["#26ccff", "#a25afd", "#ff5e7e", "#88ff5a", "#fcff42"], + }) + }, i * 200) + } + }, + + // 效果4: 彩虹喷泉 + () => { + const end = Date.now() + 2000 + + const colors = [ + "#ff595e", + "#ff924c", + "#ffca3a", + "#8ac926", + "#1982c4", + "#6a4c93", + ] + + const frame = () => { + confetti({ + particleCount: 2, + angle: 60, + spread: 55, + origin: { x: 0 }, + colors: colors, + }) + confetti({ + particleCount: 2, + angle: 120, + spread: 55, + origin: { x: 1 }, + colors: colors, + }) + + if (Date.now() < end && !disposed) { + requestAnimationFrame(frame) + } + } + + frame() + }, + + // 效果5: 烟花雨 + () => { + const duration = 2500 + const animationEnd = Date.now() + duration + + const interval = setInterval(() => { + const timeLeft = animationEnd - Date.now() + if (timeLeft <= 0) return stopTimer(interval) + + const particleCount = 50 + confetti({ + particleCount, + startVelocity: 30, + spread: 360, + ticks: 60, + origin: { + x: Math.random(), + y: Math.random() - 0.2, + }, + colors: [ + "#ff0000", + "#00ff00", + "#0000ff", + "#ffff00", + "#ff00ff", + "#00ffff", + ], + }) + }, 200) + timers.add(interval) + }, + + // 效果6: 炮竹齐鸣 + () => { + const count = 200 + const defaults = { + origin: { y: 0.7 }, + } + + function fire(particleRatio: number, opts: any) { + confetti({ + ...defaults, + ...opts, + particleCount: Math.floor(count * particleRatio), + }) + } + + fire(0.25, { + spread: 26, + startVelocity: 55, + }) + + fire(0.2, { + spread: 60, + }) + + fire(0.35, { + spread: 100, + decay: 0.91, + scalar: 0.8, + }) + + fire(0.1, { + spread: 120, + startVelocity: 25, + decay: 0.92, + scalar: 1.2, + }) + + fire(0.1, { + spread: 120, + startVelocity: 45, + }) + }, + + // 效果7: 螺旋上升 + () => { + const steps = 16 + const colors = ["#a864fd", "#29cdff", "#78ff44", "#ff718d", "#fdff6a"] + + for (let step = 0; step < steps; step++) { + runLater(() => { + const progress = step / (steps - 1) + const phase = step * (Math.PI / 4) + + confetti({ + particleCount: 7, + angle: 90 + Math.cos(phase) * 18, + spread: 25, + startVelocity: 16, + gravity: 0.25, + decay: 0.94, + ticks: 100, + scalar: 0.9, + shapes: ["circle", "square"], + origin: { + x: 0.5 + Math.sin(phase) * 0.14, + y: 0.85 - progress * 0.55, + }, + colors, + }) + }, step * 65) + } + }, + + // 效果8: 表情包烟花 + () => { + loadEmojiShapes().then((emojiShapes) => { + confetti({ + shapes: emojiShapes, + flat: true, + scalar: 6, + particleCount: 18, + spread: 360, + startVelocity: 45, + gravity: 0.7, + ticks: 150, + origin: { y: 0.6 }, + }) + }) + }, + + // 效果9: 风车螺旋 + () => { + const colors = ["#ff9a9e", "#fad0c4", "#fbc2eb", "#a18cd1", "#fad961"] + + for (let angle = 0; angle < 360; angle += 30) { + runLater( + () => { + confetti({ + particleCount: 15, + angle, + spread: 20, + startVelocity: 40, + origin: { x: 0.5, y: 0.5 }, + colors, + }) + }, + (angle / 30) * 80, + ) + } + }, + + // 效果10: 礼花瀑布 + () => { + const animationEnd = Date.now() + 1800 + const colors = ["#f6d365", "#fda085", "#fbc2eb", "#a6c1ee", "#84fab0"] + + const interval = setInterval(() => { + if (Date.now() >= animationEnd) return stopTimer(interval) + + for (let column = 0; column < 6; column++) { + confetti({ + particleCount: 2, + angle: 270, + spread: 30, + startVelocity: 5, + gravity: 1, + decay: 0.96, + ticks: 240, + scalar: 1.1, + origin: { x: (column + Math.random()) / 6, y: 0 }, + colors, + }) + } + }, 120) + timers.add(interval) + }, + ] + + // 随机选择一种效果 + const randomEffect = + fireworkTypes[Math.floor(Math.random() * fireworkTypes.length)] + randomEffect() + } + + return { + celebrate, + } +} diff --git a/apps/web/src/oj/problem/composables/useMermaidConverter.ts b/apps/web/src/oj/problem/composables/useMermaidConverter.ts new file mode 100644 index 0000000..0cd2eb2 --- /dev/null +++ b/apps/web/src/oj/problem/composables/useMermaidConverter.ts @@ -0,0 +1,125 @@ +/** + * 将流程图JSON数据转换为Mermaid格式 + */ +export function useMermaidConverter() { + const convertToMermaid = (flowchartData: any) => { + const { nodes, edges } = flowchartData + + if (!nodes || nodes.length === 0) { + return "graph TD\n A[空流程图]" + } + + let mermaid = "graph TD\n" + + // Build safe ID mapping to prevent Mermaid syntax errors from special characters + const idMap = new Map() + nodes.forEach((node: any, index: number) => { + idMap.set(node.id, `node_${index}`) + }) + const safeId = (id: string) => + idMap.get(id) || id.replace(/[^a-zA-Z0-9_]/g, "_") + + // 处理节点 - 根据原始类型和自定义标签 + nodes.forEach((node: any) => { + const nodeId = safeId(node.id) + const label = node.data?.customLabel || node.data?.label || "节点" + const originalType = node.data?.originalType || node.type + + // 根据节点原始类型确定Mermaid语法 + switch (originalType) { + case "start": + mermaid += ` ${nodeId}(("${label}"))\n` + break + case "end": + mermaid += ` ${nodeId}(("${label}"))\n` + break + case "input": + // 输入框使用平行四边形 + mermaid += ` ${nodeId}[/"${label}"/]\n` + break + case "output": + // 输出框使用平行四边形 + mermaid += ` ${nodeId}[/"${label}"/]\n` + break + case "default": + mermaid += ` ${nodeId}["${label}"]\n` + break + case "decision": + mermaid += ` ${nodeId}{"${label}"}\n` + break + case "loop": + // 循环使用菱形 + mermaid += ` ${nodeId}{"${label}"}\n` + break + default: + mermaid += ` ${nodeId}["${label}"]\n` + } + }) + + // 处理边 + edges.forEach((edge: any) => { + const source = safeId(edge.source) + const target = safeId(edge.target) + const label = edge.label ?? "" + + if (label && label.trim() !== "") { + mermaid += ` ${source} -->|"${label}"| ${target}\n` + } else { + mermaid += ` ${source} --> ${target}\n` + } + }) + + // 添加样式定义来区分不同类型的节点 + mermaid += "\n" + mermaid += + " classDef startNode fill:#dcfce7,stroke:#16a34a,stroke-width:2px,color:#0f172a\n" + mermaid += + " classDef endNode fill:#fee2e2,stroke:#dc2626,stroke-width:2px,color:#0f172a\n" + mermaid += + " classDef input fill:#dbeafe,stroke:#2563eb,stroke-width:2px,color:#0f172a\n" + mermaid += + " classDef output fill:#ede9fe,stroke:#7c3aed,stroke-width:2px,color:#0f172a\n" + mermaid += + " classDef process fill:#f0f9ff,stroke:#0284c7,stroke-width:2px,color:#0f172a\n" + mermaid += + " classDef decision fill:#fef3c7,stroke:#d97706,stroke-width:2px,color:#0f172a\n" + mermaid += + " classDef loop fill:#fae8ff,stroke:#c026d3,stroke-width:2px,color:#0f172a\n" + mermaid += "\n" + + // 为节点应用样式 + nodes.forEach((node: any) => { + const nodeId = safeId(node.id) + const originalType = node.data?.originalType || node.type + + switch (originalType) { + case "start": + mermaid += ` class ${nodeId} startNode\n` + break + case "end": + mermaid += ` class ${nodeId} endNode\n` + break + case "input": + mermaid += ` class ${nodeId} input\n` + break + case "output": + mermaid += ` class ${nodeId} output\n` + break + case "decision": + mermaid += ` class ${nodeId} decision\n` + break + case "loop": + mermaid += ` class ${nodeId} loop\n` + break + default: + mermaid += ` class ${nodeId} process\n` + } + }) + + return mermaid + } + + return { + convertToMermaid, + } +} diff --git a/apps/web/src/oj/problem/composables/useSubmissionMonitor.ts b/apps/web/src/oj/problem/composables/useSubmissionMonitor.ts new file mode 100644 index 0000000..832b380 --- /dev/null +++ b/apps/web/src/oj/problem/composables/useSubmissionMonitor.ts @@ -0,0 +1,190 @@ +import { ref, computed, watch, onUnmounted } from "vue" +import { useIntervalFn, useTimeoutFn } from "@vueuse/core" +import { getSubmission } from "oj/api" +import { SubmissionStatus } from "utils/constants" +import type { PendingAchievement, Submission } from "utils/types" +import { useAchievementStore } from "shared/store/achievement" +import { + useSubmissionWebSocket, + type SubmissionUpdate, +} from "shared/composables/websocket" + +/** + * 判题监控 Composable + * 负责通过 WebSocket + 轮询双保险机制监控判题结果 + */ +export function useSubmissionMonitor() { + // ==================== 状态 ==================== + const submissionId = ref("") + const submission = ref() + + // ==================== 轮询机制 ==================== + const { pause: pausePolling, resume: resumePolling } = useIntervalFn( + async () => { + if (!submissionId.value) return + + try { + const res = await getSubmission(submissionId.value) + submission.value = res.data + + const result = res.data.result + // 判题完成,停止轮询 + if ( + result !== SubmissionStatus.judging && + result !== SubmissionStatus.pending + ) { + pausePolling() + } + } catch (error) { + console.error("[SubmissionMonitor] 轮询失败:", error) + pausePolling() + } + }, + 2000, + { immediate: false }, + ) + + // ==================== WebSocket 处理 ==================== + const handleSubmissionUpdate = (data: SubmissionUpdate) => { + // push_to_user 复用了 submission_update 这个 channel handler, + // 其他类型的消息会走同一条 WebSocket 帧进来,必须先分流 + const frame = data as unknown as { + type: string + achievements?: PendingAchievement[] + } + if (frame.type === "achievement_unlocked") { + useAchievementStore().enqueue(frame.achievements ?? []) + return + } + if (frame.type !== "submission_update") { + return + } + + console.log("[SubmissionMonitor] 收到WebSocket更新:", data) + + if (data.submission_id !== submissionId.value) { + console.log("[SubmissionMonitor] 提交ID不匹配,忽略") + return + } + + if (!submission.value) { + submission.value = {} as Submission + } + + submission.value.result = data.result as Submission["result"] + + // 判题完成或出错,获取完整详情 + if (data.status === "finished" || data.status === "error") { + console.log( + `[SubmissionMonitor] 判题${data.status === "finished" ? "完成" : "出错"}`, + ) + + // 停止轮询(WebSocket已成功) + pausePolling() + + getSubmission(submissionId.value).then((res) => { + submission.value = res.data + // 15分钟无新提交则断开WebSocket(节省资源) + scheduleDisconnect(15 * 60 * 1000) + }) + } + } + + // 初始化 WebSocket + const { + connect, + subscribe, + scheduleDisconnect, + cancelScheduledDisconnect, + status: wsStatus, + } = useSubmissionWebSocket(handleSubmissionUpdate) + + // ==================== 轮询保底启动 ==================== + const { start: startPollingFallback } = useTimeoutFn( + () => { + if ( + submission.value && + (submission.value.result === SubmissionStatus.judging || + submission.value.result === SubmissionStatus.pending || + submission.value.result === 9) // 9 = submitting + ) { + console.log("[SubmissionMonitor] WebSocket未及时响应,启动轮询保底") + resumePolling() + } + }, + 5000, + { immediate: false }, + ) + + // ==================== 启动监控 ==================== + const startMonitoring = (id: string) => { + submissionId.value = id + submission.value = { id, result: 9 } as Submission // 9 = submitting + + // 取消之前的断开计划 + cancelScheduledDisconnect() + + // 如果WebSocket未连接,先连接 + if (wsStatus.value !== "connected") { + console.log("[SubmissionMonitor] 启动WebSocket连接...") + connect() + } + + // 等待WebSocket连接并订阅 + let unwatch: (() => void) | null = null + unwatch = watch( + wsStatus, + (status) => { + if (status === "connected") { + console.log("[SubmissionMonitor] WebSocket已连接,订阅提交:", id) + subscribe(id) + if (unwatch) { + unwatch() // 订阅成功后停止监听 + } + } + }, + { immediate: true }, + ) + + // 5秒后启动轮询保底(防止WebSocket失败) + startPollingFallback() + } + + // ==================== 计算属性 ==================== + const judging = computed( + () => submission.value?.result === SubmissionStatus.judging, + ) + + const pending = computed( + () => submission.value?.result === SubmissionStatus.pending, + ) + + const submitting = computed( + () => submission.value?.result === SubmissionStatus.submitting, + ) + + const isProcessing = computed(() => { + return judging.value || pending.value || submitting.value + }) + + // ==================== 清理 ==================== + onUnmounted(() => { + pausePolling() + }) + + return { + // 状态 + submissionId, + submission, + + // 计算属性 + judging, + pending, + submitting, + isProcessing, + + // 方法 + startMonitoring, + pausePolling, + } +} diff --git a/apps/web/src/oj/problem/detail.vue b/apps/web/src/oj/problem/detail.vue new file mode 100644 index 0000000..3b1a667 --- /dev/null +++ b/apps/web/src/oj/problem/detail.vue @@ -0,0 +1,284 @@ + + + diff --git a/apps/web/src/oj/problem/list.vue b/apps/web/src/oj/problem/list.vue new file mode 100644 index 0000000..ff72393 --- /dev/null +++ b/apps/web/src/oj/problem/list.vue @@ -0,0 +1,342 @@ + + + + + diff --git a/apps/web/src/oj/problem/utils/pythonSyntaxCheck.ts b/apps/web/src/oj/problem/utils/pythonSyntaxCheck.ts new file mode 100644 index 0000000..3b5bf9d --- /dev/null +++ b/apps/web/src/oj/problem/utils/pythonSyntaxCheck.ts @@ -0,0 +1,53 @@ +export interface PythonSyntaxError { + line: number +} + +let skulptPromise: Promise | null = null + +/** + * 按需加载 Skulpt(约 233KB gzip),只在提交 Python3 代码时才下载。 + * 结果缓存,同一页面只加载一次。 + */ +function loadSkulpt(): Promise { + if (!skulptPromise) { + // @ts-ignore - skulpt has no type definitions + skulptPromise = import("skulpt").then((m) => m.default ?? m) + } + return skulptPromise +} + +/** + * 提前把 Skulpt 拉下来,避免点提交时才开始下载。 + * 失败不影响功能,提交时会再试一次。 + */ +export function prefetchPythonSyntaxChecker() { + loadSkulpt().catch(() => { + skulptPromise = null + }) +} + +/** + * 用 Skulpt 检测 Python 代码中的语法错误。 + * 只编译不执行,不受 input() 等 IO 调用影响。 + * 加载失败时返回 null(放行提交),交给后端判题兜底。 + */ +export async function checkPythonSyntax( + code: string, +): Promise { + let Sk: any + try { + Sk = await loadSkulpt() + } catch { + skulptPromise = null + return null + } + + Sk.configure({ output: () => {} }) + try { + Sk.compile(code, "prog.py", "exec") + return null + } catch (e: any) { + const line: number = e?.traceback?.[0]?.lineno ?? 1 + return { line } + } +} diff --git a/apps/web/src/oj/problemset/components/ProblemSetHeader.vue b/apps/web/src/oj/problemset/components/ProblemSetHeader.vue new file mode 100644 index 0000000..7d1762f --- /dev/null +++ b/apps/web/src/oj/problemset/components/ProblemSetHeader.vue @@ -0,0 +1,109 @@ + + + diff --git a/apps/web/src/oj/problemset/components/ProblemSetProblemsList.vue b/apps/web/src/oj/problemset/components/ProblemSetProblemsList.vue new file mode 100644 index 0000000..c720b2b --- /dev/null +++ b/apps/web/src/oj/problemset/components/ProblemSetProblemsList.vue @@ -0,0 +1,81 @@ + + + + + diff --git a/apps/web/src/oj/problemset/components/UserProgressView.vue b/apps/web/src/oj/problemset/components/UserProgressView.vue new file mode 100644 index 0000000..6acd4fb --- /dev/null +++ b/apps/web/src/oj/problemset/components/UserProgressView.vue @@ -0,0 +1,268 @@ + + + diff --git a/apps/web/src/oj/problemset/detail.vue b/apps/web/src/oj/problemset/detail.vue new file mode 100644 index 0000000..ab0d1c0 --- /dev/null +++ b/apps/web/src/oj/problemset/detail.vue @@ -0,0 +1,139 @@ + + + + + diff --git a/apps/web/src/oj/problemset/list.vue b/apps/web/src/oj/problemset/list.vue new file mode 100644 index 0000000..b1e21f7 --- /dev/null +++ b/apps/web/src/oj/problemset/list.vue @@ -0,0 +1,274 @@ + + + + + diff --git a/apps/web/src/oj/rank/components/Chart.vue b/apps/web/src/oj/rank/components/Chart.vue new file mode 100644 index 0000000..73f33cd --- /dev/null +++ b/apps/web/src/oj/rank/components/Chart.vue @@ -0,0 +1,114 @@ + + + diff --git a/apps/web/src/oj/rank/components/Index.vue b/apps/web/src/oj/rank/components/Index.vue new file mode 100644 index 0000000..477cf58 --- /dev/null +++ b/apps/web/src/oj/rank/components/Index.vue @@ -0,0 +1,31 @@ + + diff --git a/apps/web/src/oj/rank/list.vue b/apps/web/src/oj/rank/list.vue new file mode 100644 index 0000000..0b94a7e --- /dev/null +++ b/apps/web/src/oj/rank/list.vue @@ -0,0 +1,880 @@ + + + + + diff --git a/apps/web/src/oj/store/ai.ts b/apps/web/src/oj/store/ai.ts new file mode 100644 index 0000000..2e66c95 --- /dev/null +++ b/apps/web/src/oj/store/ai.ts @@ -0,0 +1,206 @@ +import type { DetailsData, DurationData } from "utils/types" +import { consumeJSONEventStream } from "utils/stream" +import { + getAIDetailData, + getAIDurationData, + getAIHeatmapData, + getAIPinnedReport, +} from "../api" +import { getCSRFToken } from "utils/functions" + +export const useAIStore = defineStore("ai", () => { + const duration = ref("months:6") + const targetUsername = ref("") + const durationData = ref([]) + const detailsData = reactive({ + start: "", + end: "", + grade: "B", + class_name: "", + tags: {}, + difficulty: {}, + contest_count: 0, + solved: [], + flowcharts: [], + }) + const heatmapData = ref<{ timestamp: number; value: number }[]>([]) + + const loading = reactive({ + fetching: false, // 合并 details 和 duration 的 loading + ai: false, + heatmap: false, + }) + + const mdContent = ref("") + const pinnedReport = ref<{ analysis: string } | null>(null) + + async function fetchDetailsData(start: string, end: string) { + const res = await getAIDetailData( + start, + end, + targetUsername.value || undefined, + ) + detailsData.start = res.data.start + detailsData.end = res.data.end + detailsData.solved = res.data.solved + detailsData.grade = res.data.grade + detailsData.class_name = res.data.class_name + detailsData.tags = res.data.tags + detailsData.difficulty = res.data.difficulty + detailsData.contest_count = res.data.contest_count + detailsData.flowcharts = res.data.flowcharts + } + + async function fetchDurationData(end: string, duration: string) { + const res = await getAIDurationData( + end, + duration, + targetUsername.value || undefined, + ) + durationData.value = res.data + } + + async function fetchHeatmapData() { + loading.heatmap = true + const res = await getAIHeatmapData(targetUsername.value || undefined) + heatmapData.value = res.data + loading.heatmap = false + } + + async function fetchAnalysisData( + start: string, + end: string, + duration: string, + ) { + loading.fetching = true + try { + await Promise.all([ + fetchDetailsData(start, end), + fetchDurationData(end, duration), + ]) + } finally { + loading.fetching = false + } + } + + let aiController: AbortController | null = null + + async function fetchAIAnalysis() { + if (aiController) { + aiController.abort() + } + const controller = new AbortController() + aiController = controller + + loading.ai = true + mdContent.value = "" + + const headers: Record = { + "Content-Type": "application/json", + } + const csrfToken = getCSRFToken() + if (csrfToken) { + headers["X-CSRFToken"] = csrfToken + } + + try { + const response = await fetch("/api/ai/analysis", { + method: "POST", + headers, + body: JSON.stringify({ + details: detailsData, + duration: durationData.value, + }), + signal: controller.signal, + }) + + if (!response.ok) { + throw new Error("AI 分析生成失败") + } + + let hasStarted = false + + await consumeJSONEventStream(response, { + signal: controller.signal, + onEvent(event) { + if (event === "end" && !hasStarted) { + loading.ai = false + } + }, + onMessage(payload) { + const parsed = payload as { + type?: string + content?: string + message?: string + } + + if (parsed.type === "delta" && parsed.content) { + if (!hasStarted) { + hasStarted = true + loading.ai = false + } + mdContent.value += parsed.content + } else if (parsed.type === "error") { + throw new Error(parsed.message || "AI 服务异常") + } else if (parsed.type === "done" && !hasStarted) { + loading.ai = false + } + }, + }) + } catch (error: any) { + if (controller.signal.aborted) { + return + } + console.error("生成 AI 分析失败", error) + const message = error?.message || "生成失败,请稍后再试" + mdContent.value = `生成失败:${message}` + } finally { + if (aiController === controller) { + aiController = null + loading.ai = false + } + } + } + + async function fetchPinnedReport() { + const res = await getAIPinnedReport() + pinnedReport.value = res.data + } + + async function simulatePinnedStream() { + if (!pinnedReport.value) return + const text = pinnedReport.value.analysis + mdContent.value = "" + const CHUNK = 6 + const DELAY = 18 + await new Promise((resolve) => { + let i = 0 + function step() { + if (i >= text.length) { + resolve() + return + } + mdContent.value += text.slice(i, i + CHUNK) + i += CHUNK + setTimeout(step, DELAY) + } + step() + }) + } + + return { + fetchAnalysisData, + fetchHeatmapData, + fetchAIAnalysis, + fetchPinnedReport, + simulatePinnedStream, + durationData, + detailsData, + heatmapData, + duration, + targetUsername, + loading, + mdContent, + pinnedReport, + } +}) diff --git a/apps/web/src/oj/store/code.ts b/apps/web/src/oj/store/code.ts new file mode 100644 index 0000000..1b24ac2 --- /dev/null +++ b/apps/web/src/oj/store/code.ts @@ -0,0 +1,94 @@ +import { defineStore } from "pinia" +import { STORAGE_KEY } from "utils/constants" +import storage from "utils/storage" +import type { Code, LANGUAGE } from "utils/types" + +/** + * 代码编辑器状态管理 Store + * 管理全局的代码、输入、输出状态 + */ +export const useCodeStore = defineStore("code", () => { + // ==================== 状态 ==================== + const code = reactive({ + value: "", + language: storage.get(STORAGE_KEY.LANGUAGE) || "Python3", + }) + + const input = ref("") + const output = ref("") + + // ==================== 计算属性 ==================== + const isEmpty = computed(() => code.value.trim() === "") + + // ==================== 操作 ==================== + /** + * 设置代码内容 + */ + function setCode(value: string) { + code.value = value + } + + /** + * 设置编程语言 + */ + function setLanguage(language: LANGUAGE) { + code.language = language + storage.set(STORAGE_KEY.LANGUAGE, language) + } + + /** + * 设置输入 + */ + function setInput(value: string) { + input.value = value + } + + /** + * 设置输出 + */ + function setOutput(value: string) { + output.value = value + } + + /** + * 重置所有状态 + */ + function reset() { + code.value = "" + input.value = "" + output.value = "" + } + + /** + * 清空输出 + */ + function clearOutput() { + output.value = "" + } + + // 监听语言变化,保存到本地存储 + watch( + () => code.language, + (newLanguage) => { + storage.set(STORAGE_KEY.LANGUAGE, newLanguage) + }, + ) + + return { + // 状态 + code, + input, + output, + + // 计算属性 + isEmpty, + + // 操作 + setCode, + setLanguage, + setInput, + setOutput, + reset, + clearOutput, + } +}) diff --git a/apps/web/src/oj/store/contest.ts b/apps/web/src/oj/store/contest.ts new file mode 100644 index 0000000..bd58561 --- /dev/null +++ b/apps/web/src/oj/store/contest.ts @@ -0,0 +1,116 @@ +import { formatISO, getTime, parseISO } from "date-fns" +import { useUserStore } from "shared/store/user" +import { ContestStatus, ContestType } from "utils/constants" +import { duration } from "utils/functions" +import type { Contest, Problem } from "utils/types" +import { + checkContestPassword, + getContest, + getContestAccess, + getContestProblems, +} from "../api" + +export const useContestStore = defineStore("contest", () => { + const userStore = useUserStore() + const [access, toggleAccess] = useToggle(false) + const contest = ref(null) + const problems = ref([]) + const now = ref(0) + + let timer = 0 + + const contestStatus = computed(() => { + if (!contest.value) return ContestStatus.initial + const start = getTime(parseISO(contest.value.start_time.toString())) + const end = getTime(parseISO(contest.value.end_time.toString())) + if (start > now.value) { + return ContestStatus.not_started + } else if (end < now.value) { + return ContestStatus.finished + } else { + return ContestStatus.underway + } + }) + + const countdown = computed(() => { + if (contestStatus.value === ContestStatus.finished) { + return "已结束" + } else if (contestStatus.value === ContestStatus.not_started) { + const d = duration(formatISO(now.value), contest.value!.start_time, true) + return "距离比赛开始 " + d + } else { + const d = duration(formatISO(now.value), contest.value!.end_time, true) + return "距离比赛结束 " + d + } + }) + + const isContestAdmin = computed( + () => + userStore.isSuperAdmin || + (userStore.isAuthed && + contest.value?.created_by.id === userStore.user!.id), + ) + + const isPrivate = computed( + () => contest.value!.contest_type === ContestType.private, + ) + + async function init(contestID: string) { + problems.value = [] + const res = await getContest(contestID) + contest.value = res.data + now.value = getTime(parseISO(res.data.now)) + if (contestStatus.value !== ContestStatus.finished) { + timer = setInterval(() => { + now.value = now.value + 1000 + }, 1000) + } + if (contest.value?.contest_type === ContestType.private) { + const res = await getContestAccess(contestID) + toggleAccess(res.data.access) + } + _getProblems(contestID) + } + + function clear() { + contest.value = null + problems.value = [] + toggleAccess(false) + now.value = 0 + if (timer) clearInterval(timer) + } + + async function checkPassword(contestID: string, password: string) { + try { + const res = await checkContestPassword(contestID, password) + toggleAccess(res.data) + if (res.data) { + _getProblems(contestID) + } + } catch (err) { + toggleAccess(false) + } + } + + async function _getProblems(contestID: string) { + try { + problems.value = await getContestProblems(contestID) + } catch (err) { + problems.value = [] + toggleAccess(false) + } + } + + return { + contest, + contestStatus, + isContestAdmin, + access, + problems, + isPrivate, + countdown, + init, + clear, + checkPassword, + } +}) diff --git a/apps/web/src/oj/store/problem.ts b/apps/web/src/oj/store/problem.ts new file mode 100644 index 0000000..ead60ec --- /dev/null +++ b/apps/web/src/oj/store/problem.ts @@ -0,0 +1,34 @@ +import { defineStore } from "pinia" +import type { LANGUAGE, Problem } from "utils/types" + +export const useProblemStore = defineStore("problem", () => { + const problem = ref(null) + const route = useRoute() + + const failCount = ref(0) + + const languages = computed(() => { + if (route.name === "problem" && problem.value?.allow_flowchart) { + return ["Flowchart", ...problem.value?.languages] + } + return problem.value?.languages ?? [] + }) + + function incrementFailCount() { + failCount.value++ + } + + watch( + () => problem.value?.id, + () => { + failCount.value = 0 + }, + ) + + return { + problem, + failCount, + languages, + incrementFailCount, + } +}) diff --git a/apps/web/src/oj/submission/components/ButtonWithSearch.vue b/apps/web/src/oj/submission/components/ButtonWithSearch.vue new file mode 100644 index 0000000..6e70507 --- /dev/null +++ b/apps/web/src/oj/submission/components/ButtonWithSearch.vue @@ -0,0 +1,53 @@ + + diff --git a/apps/web/src/oj/submission/components/FlowchartLink.vue b/apps/web/src/oj/submission/components/FlowchartLink.vue new file mode 100644 index 0000000..34a1cdb --- /dev/null +++ b/apps/web/src/oj/submission/components/FlowchartLink.vue @@ -0,0 +1,35 @@ + + + + diff --git a/apps/web/src/oj/submission/components/FlowchartScoreDetail.vue b/apps/web/src/oj/submission/components/FlowchartScoreDetail.vue new file mode 100644 index 0000000..9303ae4 --- /dev/null +++ b/apps/web/src/oj/submission/components/FlowchartScoreDetail.vue @@ -0,0 +1,226 @@ + + + + + diff --git a/apps/web/src/oj/submission/components/Grade.vue b/apps/web/src/oj/submission/components/Grade.vue new file mode 100644 index 0000000..3e89ad1 --- /dev/null +++ b/apps/web/src/oj/submission/components/Grade.vue @@ -0,0 +1,25 @@ + + + diff --git a/apps/web/src/oj/submission/components/SubmissionLink.vue b/apps/web/src/oj/submission/components/SubmissionLink.vue new file mode 100644 index 0000000..ced3704 --- /dev/null +++ b/apps/web/src/oj/submission/components/SubmissionLink.vue @@ -0,0 +1,52 @@ + + + diff --git a/apps/web/src/oj/submission/detail.vue b/apps/web/src/oj/submission/detail.vue new file mode 100644 index 0000000..db7ef00 --- /dev/null +++ b/apps/web/src/oj/submission/detail.vue @@ -0,0 +1,168 @@ + + + + + diff --git a/apps/web/src/oj/submission/list.vue b/apps/web/src/oj/submission/list.vue new file mode 100644 index 0000000..d15f77d --- /dev/null +++ b/apps/web/src/oj/submission/list.vue @@ -0,0 +1,573 @@ + + + diff --git a/apps/web/src/oj/transforms.ts b/apps/web/src/oj/transforms.ts new file mode 100644 index 0000000..80f8ac7 --- /dev/null +++ b/apps/web/src/oj/transforms.ts @@ -0,0 +1,29 @@ +import { DIFFICULTY } from "utils/constants" +import { getACRate } from "utils/functions" +import type { Problem } from "utils/types" + +// 把后端的 Problem 塑形成列表项需要的形状,与请求逻辑解耦。 +export function filterResult(result: Problem) { + const newResult = { + id: result.id, + _id: result._id, + title: result.title, + difficulty: DIFFICULTY[result.difficulty], + tags: result.tags, + submission: result.submission_number, + rate: getACRate(result.accepted_number, result.submission_number), + status: "", + author: result.created_by.username, + allow_flowchart: result.allow_flowchart, + show_flowchart: result.show_flowchart, + has_ast_rules: result.has_ast_rules, + } + if (result.my_status === null || result.my_status === undefined) { + newResult.status = "not_test" + } else if (result.my_status === 0) { + newResult.status = "passed" + } else { + newResult.status = "failed" + } + return newResult +} diff --git a/apps/web/src/oj/user/index.vue b/apps/web/src/oj/user/index.vue new file mode 100644 index 0000000..6898961 --- /dev/null +++ b/apps/web/src/oj/user/index.vue @@ -0,0 +1,331 @@ + + + diff --git a/apps/web/src/oj/user/message.vue b/apps/web/src/oj/user/message.vue new file mode 100644 index 0000000..02997b6 --- /dev/null +++ b/apps/web/src/oj/user/message.vue @@ -0,0 +1,71 @@ + + diff --git a/apps/web/src/oj/user/setting.vue b/apps/web/src/oj/user/setting.vue new file mode 100644 index 0000000..06be145 --- /dev/null +++ b/apps/web/src/oj/user/setting.vue @@ -0,0 +1,76 @@ + + + diff --git a/apps/web/src/routes.ts b/apps/web/src/routes.ts new file mode 100644 index 0000000..b815626 --- /dev/null +++ b/apps/web/src/routes.ts @@ -0,0 +1,339 @@ +import type { RouteRecordRaw } from "vue-router" + +export const ojs: RouteRecordRaw = { + path: "/", + component: () => import("shared/layout/default.vue"), + children: [ + { path: "", component: () => import("oj/problem/list.vue") }, + { + path: "problem/:problemID", + component: () => import("oj/problem/detail.vue"), + props: true, + name: "problem", + }, + { + path: "submission", + component: () => import("oj/submission/list.vue"), + name: "submissions", + }, + { + path: "submission/:submissionID", + component: () => import("oj/submission/detail.vue"), + props: true, + meta: { requiresAuth: true }, + }, + { + path: "contest", + component: () => import("oj/contest/list.vue"), + name: "contests", + }, + { + path: "contest/:contestID", + component: () => import("oj/contest/detail.vue"), + props: true, + meta: { requiresAuth: true }, + children: [ + { + path: "", + component: () => import("oj/contest/pages/problems.vue"), + props: true, + meta: { requiresAuth: true }, + name: "contest problems", + }, + { + path: "submission", + component: () => import("oj/submission/list.vue"), + meta: { requiresAuth: true }, + name: "contest submissions", + }, + { + path: "rank", + component: () => import("oj/contest/pages/rank.vue"), + props: true, + meta: { requiresAuth: true }, + name: "contest rank", + }, + ], + }, + { + path: "contest/:contestID/problem/:problemID", + component: () => import("oj/problem/detail.vue"), + props: true, + name: "contest problem", + meta: { requiresAuth: true }, + }, + { + path: "rank", + name: "rank", + component: () => import("oj/rank/list.vue"), + }, + { + path: "class", + name: "class", + component: () => import("oj/class/pk.vue"), + }, + { + path: "announcement", + name: "announcements", + component: () => import("oj/announcement/list.vue"), + }, + { + path: "user", + component: () => import("oj/user/index.vue"), + meta: { requiresAuth: true }, + }, + { + path: "achievement", + name: "achievement", + component: () => import("oj/achievement/index.vue"), + meta: { requiresAuth: true }, + }, + { + path: "setting", + component: () => import("oj/user/setting.vue"), + meta: { requiresAuth: true }, + }, + { + path: "message", + component: () => import("oj/user/message.vue"), + meta: { requiresAuth: true }, + }, + { + path: "learn/:type/:step", + name: "learn", + component: () => import("oj/learn/index.vue"), + props: true, + }, + { + path: "ai-analysis", + name: "ai", + component: () => import("oj/ai/analysis.vue"), + meta: { requiresAuth: true }, + }, + { + path: "flowchart", + name: "flowchart", + component: () => import("oj/flowchart/index.vue"), + }, + { + path: "problemset", + component: () => import("oj/problemset/list.vue"), + name: "problemsets", + }, + { + path: "problemset/:problemSetId", + component: () => import("oj/problemset/detail.vue"), + props: true, + name: "problemset", + }, + { + path: "problemset/:problemSetId/problem/:problemID", + component: () => import("oj/problem/detail.vue"), + props: true, + name: "problemset problem", + meta: { requiresAuth: true }, + }, + ], +} + +export const admins: RouteRecordRaw = { + path: "/admin", + component: () => import("shared/layout/admin.vue"), + children: [ + { + path: "", + name: "admin home", + component: () => import("admin/setting/home.vue"), + meta: { requiresSuperAdmin: true }, + }, + { + path: "config", + name: "admin config", + component: () => import("admin/setting/config.vue"), + meta: { requiresSuperAdmin: true }, + }, + { + path: "user/list", + name: "admin user list", + component: () => import("admin/user/list.vue"), + meta: { requiresSuperAdmin: true }, + }, + { + path: "user/generate", + name: "admin user generate", + component: () => import("admin/user/generate.vue"), + meta: { requiresSuperAdmin: true }, + }, + // admin和super_admin都可以访问的路由 (需要题目权限) + { + path: "problem/list", + name: "admin problem list", + component: () => import("admin/problem/list.vue"), + meta: { requiresProblemPermission: true }, + }, + { + path: "problem/tags", + name: "admin tag list", + component: () => import("admin/problem/tags.vue"), + meta: { requiresProblemPermission: true }, + }, + { + path: "problem/create", + name: "admin problem create", + component: () => import("admin/problem/detail.vue"), + meta: { requiresProblemPermission: true }, + }, + { + path: "problem/edit/:problemID", + name: "admin problem edit", + component: () => import("admin/problem/detail.vue"), + props: true, + meta: { requiresProblemPermission: true }, + }, + { + path: "contest/list", + name: "admin contest list", + component: () => import("admin/contest/list.vue"), + meta: { requiresTeacherAdmin: true }, + }, + { + path: "contest/create", + name: "admin contest create", + component: () => import("admin/contest/detail.vue"), + meta: { requiresTeacherAdmin: true }, + }, + { + path: "contest/edit/:contestID", + name: "admin contest edit", + component: () => import("admin/contest/detail.vue"), + props: true, + meta: { requiresTeacherAdmin: true }, + }, + { + path: "contest/:contestID/problem/list", + name: "admin contest problem list", + component: () => import("admin/problem/list.vue"), + props: true, + meta: { requiresTeacherAdmin: true }, + }, + { + path: "contest/:contestID/problem/create", + name: "admin contest problem create", + component: () => import("admin/problem/detail.vue"), + props: true, + meta: { requiresTeacherAdmin: true }, + }, + { + path: "contest/:contestID/problem/edit/:problemID", + name: "admin contest problem edit", + component: () => import("admin/problem/detail.vue"), + props: true, + meta: { requiresTeacherAdmin: true }, + }, + { + path: "contest/:contestID/helper", + name: "admin contest helper", + component: () => import("admin/contest/helper.vue"), + props: true, + meta: { requiresTeacherAdmin: true }, + }, + // 只有super_admin可以访问的路由 + { + path: "announcement/list", + name: "admin announcement list", + component: () => import("admin/announcement/list.vue"), + meta: { requiresSuperAdmin: true }, + }, + { + path: "announcement/create", + name: "admin announcement create", + component: () => import("admin/announcement/detail.vue"), + meta: { requiresSuperAdmin: true }, + }, + { + path: "announcement/edit/:announcementID", + name: "admin announcement edit", + component: () => import("admin/announcement/detail.vue"), + props: true, + meta: { requiresSuperAdmin: true }, + }, + { + path: "message/list", + name: "admin message list", + component: () => import("admin/communication/messages.vue"), + meta: { requiresSuperAdmin: true }, + }, + { + path: "tutorial/list", + name: "admin tutorial list", + component: () => import("admin/tutorial/list.vue"), + meta: { requiresSuperAdmin: true }, + }, + { + path: "tutorial/create", + name: "admin tutorial create", + component: () => import("admin/tutorial/detail.vue"), + meta: { requiresSuperAdmin: true }, + }, + { + path: "tutorial/edit/:tutorialID", + name: "admin tutorial edit", + component: () => import("admin/tutorial/detail.vue"), + props: true, + meta: { requiresSuperAdmin: true }, + }, + { + path: "problem/stuck", + name: "admin stuck problems", + component: () => import("admin/problem/Stuck.vue"), + meta: { requiresTeacherAdmin: true }, + }, + { + path: "problem/top_ac_trend", + name: "admin top ac trend", + component: () => import("admin/problem/TopACTrend.vue"), + meta: { requiresTeacherAdmin: true }, + }, + // 题单管理路由 + { + path: "problemset/list", + name: "admin problemset list", + component: () => import("admin/problemset/list.vue"), + meta: { requiresTeacherAdmin: true }, + }, + { + path: "achievement/list", + name: "admin achievement list", + // 后端 admin 接口是 super_admin_required,这里必须跟着用 + // requiresSuperAdmin,否则教师能进页面但每个请求都 403 + component: () => import("admin/achievement/list.vue"), + meta: { requiresSuperAdmin: true }, + }, + { + path: "problemset/create", + name: "admin problemset create", + component: () => import("admin/problemset/edit.vue"), + meta: { requiresTeacherAdmin: true }, + }, + { + path: "problemset/edit/:problemSetId", + name: "admin problemset edit", + component: () => import("admin/problemset/edit.vue"), + props: true, + meta: { requiresTeacherAdmin: true }, + }, + { + path: "problemset/:problemSetId", + name: "admin problemset detail", + component: () => import("admin/problemset/detail.vue"), + props: true, + meta: { requiresTeacherAdmin: true }, + }, + { + path: "ai/reports", + name: "admin ai reports", + component: () => import("admin/ai/list.vue"), + meta: { requiresTeacherAdmin: true }, + }, + ], +} diff --git a/apps/web/src/shared/api.ts b/apps/web/src/shared/api.ts new file mode 100644 index 0000000..939a4cb --- /dev/null +++ b/apps/web/src/shared/api.ts @@ -0,0 +1,34 @@ +import http from "utils/http" +import type { Profile, Tag } from "utils/types" + +export function login(data: { username: string; password: string }) { + return http.post("login", data) +} + +export function signup(data: { + username: string + email: string + password: string +}) { + return http.post("register", data) +} + +export function logout() { + return http.get("logout") +} + +export function getProfile(username: string = "") { + return http.get("profile", { params: { username } }) +} + +export function getProblemTagList() { + return http.get("problem/tags") +} + +export function getHitokoto() { + return http.get("hitokoto") +} + +export function getClassUsernames(classroom: string) { + return http.get("class_usernames", { params: { classroom: classroom } }) +} diff --git a/apps/web/src/shared/components/AchievementIcon.vue b/apps/web/src/shared/components/AchievementIcon.vue new file mode 100644 index 0000000..b8fb970 --- /dev/null +++ b/apps/web/src/shared/components/AchievementIcon.vue @@ -0,0 +1,50 @@ + + + + + diff --git a/apps/web/src/shared/components/AchievementToast.vue b/apps/web/src/shared/components/AchievementToast.vue new file mode 100644 index 0000000..1d83dc0 --- /dev/null +++ b/apps/web/src/shared/components/AchievementToast.vue @@ -0,0 +1,101 @@ + + + + + diff --git a/apps/web/src/shared/components/AuthorSelect.vue b/apps/web/src/shared/components/AuthorSelect.vue new file mode 100644 index 0000000..e2b2345 --- /dev/null +++ b/apps/web/src/shared/components/AuthorSelect.vue @@ -0,0 +1,35 @@ + + + diff --git a/apps/web/src/shared/components/Beian.vue b/apps/web/src/shared/components/Beian.vue new file mode 100644 index 0000000..3b1aa0a --- /dev/null +++ b/apps/web/src/shared/components/Beian.vue @@ -0,0 +1,69 @@ + + + diff --git a/apps/web/src/shared/components/CodeEditor.vue b/apps/web/src/shared/components/CodeEditor.vue new file mode 100644 index 0000000..91ab92d --- /dev/null +++ b/apps/web/src/shared/components/CodeEditor.vue @@ -0,0 +1,65 @@ + + + diff --git a/apps/web/src/shared/components/ContestTitle.vue b/apps/web/src/shared/components/ContestTitle.vue new file mode 100644 index 0000000..ba70df9 --- /dev/null +++ b/apps/web/src/shared/components/ContestTitle.vue @@ -0,0 +1,18 @@ + + + diff --git a/apps/web/src/shared/components/ContestType.vue b/apps/web/src/shared/components/ContestType.vue new file mode 100644 index 0000000..c2a98dc --- /dev/null +++ b/apps/web/src/shared/components/ContestType.vue @@ -0,0 +1,21 @@ + + + diff --git a/apps/web/src/shared/components/Copy.vue b/apps/web/src/shared/components/Copy.vue new file mode 100644 index 0000000..e83330d --- /dev/null +++ b/apps/web/src/shared/components/Copy.vue @@ -0,0 +1,35 @@ + + + diff --git a/apps/web/src/shared/components/FlowchartEditor/CustomNode.vue b/apps/web/src/shared/components/FlowchartEditor/CustomNode.vue new file mode 100644 index 0000000..8cd3cf1 --- /dev/null +++ b/apps/web/src/shared/components/FlowchartEditor/CustomNode.vue @@ -0,0 +1,275 @@ + + + + + diff --git a/apps/web/src/shared/components/FlowchartEditor/NodeActions.vue b/apps/web/src/shared/components/FlowchartEditor/NodeActions.vue new file mode 100644 index 0000000..c2695cc --- /dev/null +++ b/apps/web/src/shared/components/FlowchartEditor/NodeActions.vue @@ -0,0 +1,60 @@ + + + + + diff --git a/apps/web/src/shared/components/FlowchartEditor/NodeHandles.vue b/apps/web/src/shared/components/FlowchartEditor/NodeHandles.vue new file mode 100644 index 0000000..07e9977 --- /dev/null +++ b/apps/web/src/shared/components/FlowchartEditor/NodeHandles.vue @@ -0,0 +1,238 @@ + + + + + diff --git a/apps/web/src/shared/components/FlowchartEditor/Toolbar.vue b/apps/web/src/shared/components/FlowchartEditor/Toolbar.vue new file mode 100644 index 0000000..df95c7e --- /dev/null +++ b/apps/web/src/shared/components/FlowchartEditor/Toolbar.vue @@ -0,0 +1,413 @@ + + + + diff --git a/apps/web/src/shared/components/FlowchartEditor/index.vue b/apps/web/src/shared/components/FlowchartEditor/index.vue new file mode 100644 index 0000000..c4a665a --- /dev/null +++ b/apps/web/src/shared/components/FlowchartEditor/index.vue @@ -0,0 +1,325 @@ + + + + + diff --git a/apps/web/src/shared/components/FlowchartEditor/useCache.ts b/apps/web/src/shared/components/FlowchartEditor/useCache.ts new file mode 100644 index 0000000..bc332fd --- /dev/null +++ b/apps/web/src/shared/components/FlowchartEditor/useCache.ts @@ -0,0 +1,89 @@ +import { ref, watch, type Ref, type MaybeRefOrGetter } from "vue" +import { useStorage, useDebounceFn } from "@vueuse/core" +import type { Node, Edge } from "@vue-flow/core" + +/** + * 缓存管理 - 使用 @vueuse 的 useStorage + */ +export function useCache( + nodes: Ref, + edges: Ref, + storageKey: MaybeRefOrGetter = "flowchart-editor-data", +) { + const isSaving = ref(false) + const lastSaved = ref(null) + const hasUnsavedChanges = ref(false) + + // 使用 useStorage 管理数据存储,支持响应式 key(题目 ID 异步加载时自动切换) + const storedData = useStorage<{ + nodes: Node[] + edges: Edge[] + timestamp: string + }>(storageKey, { + nodes: [], + edges: [], + timestamp: "", + }) + + // 防抖保存:isSaving 在 watch 中置 true,保存完成后置 false,使 UI 能感知保存中状态 + const debouncedSave = useDebounceFn(() => { + storedData.value.nodes = nodes.value + storedData.value.edges = edges.value + storedData.value.timestamp = new Date().toISOString() + lastSaved.value = new Date() + hasUnsavedChanges.value = false + isSaving.value = false + }, 500) + + // 立即保存 + const saveToCache = () => { + isSaving.value = true + storedData.value.nodes = nodes.value + storedData.value.edges = edges.value + storedData.value.timestamp = new Date().toISOString() + lastSaved.value = new Date() + hasUnsavedChanges.value = false + isSaving.value = false + } + + // 从缓存加载数据 + const loadFromCache = () => { + if (storedData.value.nodes?.length || storedData.value.edges?.length) { + nodes.value = storedData.value.nodes + edges.value = storedData.value.edges + lastSaved.value = storedData.value.timestamp + ? new Date(storedData.value.timestamp) + : null + hasUnsavedChanges.value = false + return true + } + return false + } + + // 清除缓存数据 + const clearCache = () => { + storedData.value = { nodes: [], edges: [], timestamp: "" } + lastSaved.value = null + hasUnsavedChanges.value = false + } + + // 监听节点和边的变化,isSaving 在此置 true 以覆盖防抖等待窗口 + watch( + [nodes, edges], + () => { + hasUnsavedChanges.value = true + isSaving.value = true + debouncedSave() + }, + { deep: true }, + ) + + return { + isSaving, + lastSaved, + hasUnsavedChanges, + saveToCache, + loadFromCache, + clearCache, + } +} diff --git a/apps/web/src/shared/components/FlowchartEditor/useDnD.ts b/apps/web/src/shared/components/FlowchartEditor/useDnD.ts new file mode 100644 index 0000000..da6b761 --- /dev/null +++ b/apps/web/src/shared/components/FlowchartEditor/useDnD.ts @@ -0,0 +1,84 @@ +import { ref } from "vue" +import { useVueFlow } from "@vue-flow/core" +import { + getNodeTypeConfig, + createNodeStyle, + getNodeDimensions, +} from "./useNodeStyles" +import { getRandomId } from "utils/functions" + +// 模块级共享:当前拖拽的节点类型(Toolbar 写入,canvas 读取) +export const currentDragNodeType = ref(null) + +/** + * 简化的拖拽处理 + */ +export function useDnD() { + const { addNodes, screenToFlowCoordinate } = useVueFlow() + const isDragOver = ref(false) + const screenDragPos = ref<{ x: number; y: number } | null>(null) + + // 拖拽悬停处理 + const onDragOver = (event: DragEvent) => { + event.preventDefault() + isDragOver.value = true + screenDragPos.value = { x: event.clientX, y: event.clientY } + } + + // 拖拽离开处理 + const onDragLeave = () => { + isDragOver.value = false + screenDragPos.value = null + } + + // 拖拽放置处理 + const onDrop = (event: DragEvent) => { + event.preventDefault() + isDragOver.value = false + screenDragPos.value = null + currentDragNodeType.value = null + + const type = event.dataTransfer?.getData("application/vueflow") + if (!type) return + + // 获取鼠标在画布中的坐标 + const position = screenToFlowCoordinate({ + x: event.clientX, + y: event.clientY, + }) + + // 根据节点类型获取实际尺寸 + const dimensions = getNodeDimensions(type) + + // 调整位置,使节点中心点对齐到鼠标位置 + const adjustedPosition = { + x: position.x - dimensions.width / 2, + y: position.y - dimensions.height / 2, + } + + const nodeId = `node-${getRandomId()}` + const config = getNodeTypeConfig(type) + const newNode = { + id: nodeId, + type: "custom", + position: adjustedPosition, + data: { + label: config.label, + color: config.color, + originalType: type, + }, + style: createNodeStyle(type), + } + + addNodes([newNode]) + return newNode + } + + return { + isDragOver, + screenDragPos, + onDragOver, + onDragLeave, + onDrop, + } +} diff --git a/apps/web/src/shared/components/FlowchartEditor/useFlowOperations.ts b/apps/web/src/shared/components/FlowchartEditor/useFlowOperations.ts new file mode 100644 index 0000000..e9d41d4 --- /dev/null +++ b/apps/web/src/shared/components/FlowchartEditor/useFlowOperations.ts @@ -0,0 +1,142 @@ +import type { Ref } from "vue" +import type { Node, Edge, Connection } from "@vue-flow/core" +import { useVueFlow } from "@vue-flow/core" +import { getRandomId } from "utils/functions" + +export function useFlowOperations( + nodes: Ref, + edges: Ref, + addEdges: (edges: Edge[]) => void, + removeNodes: (nodeIds: string[]) => void, + removeEdges: (edgeIds: string[]) => void, + saveState: (nodes: Node[], edges: Edge[]) => void, +) { + const { findNode, getSelectedNodes, getSelectedEdges } = useVueFlow() + const getAutoLabel = ( + sourceNode: Node | undefined, + targetNode: Node | undefined, + sourceHandle: string | null | undefined, + targetHandle: string | null | undefined, + ) => { + const sourceType = sourceNode?.data?.originalType || sourceNode?.type + const targetType = targetNode?.data?.originalType || targetNode?.type + + // 如果是判断节点 + if (sourceType === "decision") { + // 根据handle ID推断标签 + if (sourceHandle === "yes") { + return "是" + } else if (sourceHandle === "no") { + return "否" + } + } + + // 如果是循环节点 + if (sourceType === "loop") { + // 根据handle ID推断标签 + if (sourceHandle === "continue") { + return "继续" + } else if (sourceHandle === "exit") { + return "退出" + } + } + + // 如果是循环体回到循环节点 + if (targetType === "loop") { + if (targetHandle === "return") { + return "返回" + } + } + // 默认情况 + return "" + } + + const handleConnect = (params: Connection) => { + const sourceNode = nodes.value.find((node) => node.id === params.source) + const targetNode = nodes.value.find((node) => node.id === params.target) + + // 自动推断标签 + const autoLabel = getAutoLabel( + sourceNode, + targetNode, + params.sourceHandle, + params.targetHandle, + ) + + const newEdge: Edge = { + id: `edge-${getRandomId()}`, + source: params.source, + target: params.target, + sourceHandle: params.sourceHandle, + targetHandle: params.targetHandle, + type: "default", + label: autoLabel, + } + + addEdges([newEdge]) + saveState(nodes.value, edges.value) + } + + const handleEdgeClick = ({ edge }: { edge: Edge }) => { + removeEdges([edge.id]) + saveState(nodes.value, edges.value) + } + + // 节点删除 + const handleNodeDelete = (nodeId: string) => { + // 删除相关边 + const relatedEdges = edges.value.filter( + (edge) => edge.source === nodeId || edge.target === nodeId, + ) + if (relatedEdges.length > 0) { + removeEdges(relatedEdges.map((edge) => edge.id)) + } + + removeNodes([nodeId]) + saveState(nodes.value, edges.value) + } + + // 节点更新,空标签时清除自定义标签(恢复默认类型名称) + const handleNodeUpdate = (nodeId: string, newLabel: string) => { + const node = findNode(nodeId) + if (node) { + if (newLabel) { + node.data = { ...node.data, customLabel: newLabel } + } else { + const { customLabel: _, ...rest } = node.data + node.data = rest + } + saveState(nodes.value, edges.value) + } + } + + // 清空画布 + const clearCanvas = () => { + nodes.value = [] + edges.value = [] + saveState(nodes.value, edges.value) + } + + // 删除选中的节点和边 + const deleteSelected = () => { + const selectedNodes = getSelectedNodes.value + const selectedEdges = getSelectedEdges.value + + if (selectedNodes.length > 0) { + removeNodes(selectedNodes.map((node) => node.id)) + } + if (selectedEdges.length > 0) { + removeEdges(selectedEdges.map((edge) => edge.id)) + } + saveState(nodes.value, edges.value) + } + + return { + handleConnect, + handleEdgeClick, + handleNodeDelete, + handleNodeUpdate, + clearCanvas, + deleteSelected, + } +} diff --git a/apps/web/src/shared/components/FlowchartEditor/useHistory.ts b/apps/web/src/shared/components/FlowchartEditor/useHistory.ts new file mode 100644 index 0000000..8ec9a50 --- /dev/null +++ b/apps/web/src/shared/components/FlowchartEditor/useHistory.ts @@ -0,0 +1,72 @@ +import { shallowRef, computed } from "vue" +import type { Node, Edge } from "@vue-flow/core" + +/** + * 简化的历史记录管理 + */ +export function useHistory() { + const history = shallowRef<{ nodes: Node[]; edges: Edge[] }[]>([]) + const historyIndex = ref(-1) + + // 是否可以撤销 + const canUndo = computed(() => historyIndex.value > 0) + + // 是否可以重做 + const canRedo = computed(() => historyIndex.value < history.value.length - 1) + + const deepCopyState = ( + nodes: Node[], + edges: Edge[], + ): { nodes: Node[]; edges: Edge[] } => + JSON.parse(JSON.stringify({ nodes, edges })) as { + nodes: Node[] + edges: Edge[] + } + + // 保存状态到历史记录 + const saveState = (nodes: Node[], edges: Edge[]) => { + const currentState = deepCopyState(nodes, edges) + + // 如果当前不在历史记录的末尾,删除后面的记录 + if (historyIndex.value < history.value.length - 1) { + history.value = history.value.slice(0, historyIndex.value + 1) + } + + history.value = [...history.value, currentState] + historyIndex.value = history.value.length - 1 + + // 限制历史记录数量 + if (history.value.length > 20) { + history.value = history.value.slice(1) + historyIndex.value-- + } + } + + // 撤销 + const undo = () => { + if (canUndo.value) { + historyIndex.value-- + const state = history.value[historyIndex.value] + return deepCopyState(state.nodes, state.edges) + } + return null + } + + // 重做 + const redo = () => { + if (canRedo.value) { + historyIndex.value++ + const state = history.value[historyIndex.value] + return deepCopyState(state.nodes, state.edges) + } + return null + } + + return { + canUndo, + canRedo, + saveState, + undo, + redo, + } +} diff --git a/apps/web/src/shared/components/FlowchartEditor/useNodeStyles.ts b/apps/web/src/shared/components/FlowchartEditor/useNodeStyles.ts new file mode 100644 index 0000000..45e2114 --- /dev/null +++ b/apps/web/src/shared/components/FlowchartEditor/useNodeStyles.ts @@ -0,0 +1,137 @@ +/** + * 节点样式管理 + */ + +/** + * 获取节点类型配置 + */ +export function getNodeTypeConfig(type: string) { + const configs: Record< + string, + { label: string; color: string; icon: string; description: string } + > = { + start: { + label: "开始", + color: "#10b981", + icon: "▶", + description: "流程开始", + }, + input: { + label: "输入", + color: "#06b6d4", + icon: "📥", + description: "数据输入", + }, + default: { + label: "赋值", + color: "#3b82f6", + icon: "⚙", + description: "赋值语句", + }, + decision: { + label: "判断", + color: "#f59e0b", + icon: "❓", + description: "条件语句", + }, + loop: { + label: "循环", + color: "#8b5cf6", + icon: "🔄", + description: "循环语句", + }, + output: { + label: "输出", + color: "#84cc16", + icon: "📤", + description: "数据输出", + }, + end: { + label: "结束", + color: "#ef4444", + icon: "⏹", + description: "流程结束", + }, + } + return ( + configs[type] || { + label: "节点", + color: "#6b7280", + icon: "⚪", + description: "未知节点", + } + ) +} + +/** + * 获取节点样式 + */ +export function getNodeStyle(type: string, color: string) { + const baseStyle = { + background: color, + color: "white", + border: `2px solid ${color}`, + borderRadius: "10px", + fontSize: "16px", + fontWeight: "500", + width: "auto", // 自动宽度 + height: "auto", // 自动高度 + minWidth: "100px", + minHeight: "40px", + maxWidth: "400px", + maxHeight: "160px", + display: "flex", + alignItems: "center", + justifyContent: "center", + boxShadow: "0 2px 8px rgba(0, 0, 0, 0.1)", + } + + // 根据节点类型调整样式 + switch (type) { + case "start": + case "end": + return { + ...baseStyle, + } + case "decision": + return { + ...baseStyle, + borderRadius: "8px", + minWidth: "140px", + minHeight: "50px", + } + case "loop": + return { + ...baseStyle, + borderRadius: "8px", + minWidth: "140px", + minHeight: "50px", + } + default: + return baseStyle + } +} + +/** + * 创建节点样式 + */ +export function createNodeStyle(type: string) { + const config = getNodeTypeConfig(type) + return getNodeStyle(type, config.color) +} + +/** + * 获取节点尺寸 + */ +export function getNodeDimensions(type: string) { + switch (type) { + case "start": + case "end": + return { width: 100, height: 40 } + case "decision": + case "loop": + return { width: 140, height: 50 } + default: + return { width: 120, height: 40 } + } +} diff --git a/apps/web/src/shared/components/FlowchartStatisticsPanel.vue b/apps/web/src/shared/components/FlowchartStatisticsPanel.vue new file mode 100644 index 0000000..fcbdaf4 --- /dev/null +++ b/apps/web/src/shared/components/FlowchartStatisticsPanel.vue @@ -0,0 +1,577 @@ + + + + + diff --git a/apps/web/src/shared/components/Header.vue b/apps/web/src/shared/components/Header.vue new file mode 100644 index 0000000..4e4db69 --- /dev/null +++ b/apps/web/src/shared/components/Header.vue @@ -0,0 +1,356 @@ + + + + + diff --git a/apps/web/src/shared/components/Hitokoto.vue b/apps/web/src/shared/components/Hitokoto.vue new file mode 100644 index 0000000..f8ae727 --- /dev/null +++ b/apps/web/src/shared/components/Hitokoto.vue @@ -0,0 +1,69 @@ + + + diff --git a/apps/web/src/shared/components/IconButton.vue b/apps/web/src/shared/components/IconButton.vue new file mode 100644 index 0000000..8fbe0b1 --- /dev/null +++ b/apps/web/src/shared/components/IconButton.vue @@ -0,0 +1,29 @@ + + diff --git a/apps/web/src/shared/components/Login.vue b/apps/web/src/shared/components/Login.vue new file mode 100644 index 0000000..25e565d --- /dev/null +++ b/apps/web/src/shared/components/Login.vue @@ -0,0 +1,201 @@ + + + + + diff --git a/apps/web/src/shared/components/LoginSummaryModal.vue b/apps/web/src/shared/components/LoginSummaryModal.vue new file mode 100644 index 0000000..be3083b --- /dev/null +++ b/apps/web/src/shared/components/LoginSummaryModal.vue @@ -0,0 +1,83 @@ + + + diff --git a/apps/web/src/shared/components/MarkdownEditor.vue b/apps/web/src/shared/components/MarkdownEditor.vue new file mode 100644 index 0000000..c0ee0f7 --- /dev/null +++ b/apps/web/src/shared/components/MarkdownEditor.vue @@ -0,0 +1,40 @@ + + + diff --git a/apps/web/src/shared/components/MermaidEditor.vue b/apps/web/src/shared/components/MermaidEditor.vue new file mode 100644 index 0000000..238e5ac --- /dev/null +++ b/apps/web/src/shared/components/MermaidEditor.vue @@ -0,0 +1,93 @@ + + + + + diff --git a/apps/web/src/shared/components/Pagination.vue b/apps/web/src/shared/components/Pagination.vue new file mode 100644 index 0000000..478d30d --- /dev/null +++ b/apps/web/src/shared/components/Pagination.vue @@ -0,0 +1,61 @@ + + + + diff --git a/apps/web/src/shared/components/Signup.vue b/apps/web/src/shared/components/Signup.vue new file mode 100644 index 0000000..1e22389 --- /dev/null +++ b/apps/web/src/shared/components/Signup.vue @@ -0,0 +1,131 @@ + + + diff --git a/apps/web/src/shared/components/StatisticsPanel.vue b/apps/web/src/shared/components/StatisticsPanel.vue new file mode 100644 index 0000000..52bd0b4 --- /dev/null +++ b/apps/web/src/shared/components/StatisticsPanel.vue @@ -0,0 +1,470 @@ + + + diff --git a/apps/web/src/shared/components/SubmissionResultTag.vue b/apps/web/src/shared/components/SubmissionResultTag.vue new file mode 100644 index 0000000..9bf859f --- /dev/null +++ b/apps/web/src/shared/components/SubmissionResultTag.vue @@ -0,0 +1,17 @@ + + + + + diff --git a/apps/web/src/shared/components/SyncCodeEditor.vue b/apps/web/src/shared/components/SyncCodeEditor.vue new file mode 100644 index 0000000..9c18670 --- /dev/null +++ b/apps/web/src/shared/components/SyncCodeEditor.vue @@ -0,0 +1,153 @@ + + + diff --git a/apps/web/src/shared/components/TextCopy.vue b/apps/web/src/shared/components/TextCopy.vue new file mode 100644 index 0000000..551c561 --- /dev/null +++ b/apps/web/src/shared/components/TextCopy.vue @@ -0,0 +1,33 @@ + + diff --git a/apps/web/src/shared/components/TextEditor.vue b/apps/web/src/shared/components/TextEditor.vue new file mode 100644 index 0000000..2bc8097 --- /dev/null +++ b/apps/web/src/shared/components/TextEditor.vue @@ -0,0 +1,144 @@ + + + + + diff --git a/apps/web/src/shared/components/UserBadge.vue b/apps/web/src/shared/components/UserBadge.vue new file mode 100644 index 0000000..9688acb --- /dev/null +++ b/apps/web/src/shared/components/UserBadge.vue @@ -0,0 +1,87 @@ + + + + + diff --git a/apps/web/src/shared/composables/breakpoints.ts b/apps/web/src/shared/composables/breakpoints.ts new file mode 100644 index 0000000..71cc669 --- /dev/null +++ b/apps/web/src/shared/composables/breakpoints.ts @@ -0,0 +1,20 @@ +import { + breakpointsTailwind, + useBreakpoints as useVueUseBreakpoints, +} from "@vueuse/core" + +/** + * 响应式断点检测 composable + * 每次调用创建新的断点检测实例 + */ +export function useBreakpoints() { + const breakpoints = useVueUseBreakpoints(breakpointsTailwind) + + const isMobile = breakpoints.smallerOrEqual("md") + const isDesktop = breakpoints.greater("md") + + return { + isMobile, + isDesktop, + } +} diff --git a/apps/web/src/shared/composables/configUpdate.ts b/apps/web/src/shared/composables/configUpdate.ts new file mode 100644 index 0000000..4974f36 --- /dev/null +++ b/apps/web/src/shared/composables/configUpdate.ts @@ -0,0 +1,41 @@ +import { useConfigStore } from "shared/store/config" +import { useUserStore } from "shared/store/user" +import { + useConfigWebSocket, + type ConfigUpdate, +} from "shared/composables/websocket" + +export function useConfigUpdate() { + const configStore = useConfigStore() + const userStore = useUserStore() + + // 处理 WebSocket 配置更新 + const handleConfigUpdate = (data: ConfigUpdate) => { + // 更新全局配置 - 使用响应式方式 + if (data.key in configStore.config) { + // 直接修改 ref 的值来触发响应式更新 + ;(configStore.config as any)[data.key] = data.value + } + } + + // 初始化 WebSocket - handler 会在 onMounted 时自动添加 + const { connect, disconnect } = useConfigWebSocket(handleConfigUpdate) + + // 监听登录状态变化 + watch( + () => userStore.isAuthed, + (isAuthed) => { + if (isAuthed) { + connect() + } else { + disconnect() + } + }, + { immediate: true }, + ) + + return { + connect, + disconnect, + } +} diff --git a/apps/web/src/shared/composables/learnProgress.ts b/apps/web/src/shared/composables/learnProgress.ts new file mode 100644 index 0000000..8a638e6 --- /dev/null +++ b/apps/web/src/shared/composables/learnProgress.ts @@ -0,0 +1,15 @@ +import { useStorage } from "@vueuse/core" +import { STORAGE_KEY } from "utils/constants" + +export type TutorialType = "python" | "c" + +// 模块级单例:学习页写入、导航栏读取,必须共用同一个响应式引用, +// 否则导航栏的链接不会随学习进度更新 +const learnStep = useStorage>( + STORAGE_KEY.LEARN_CURRENT_STEP, + { python: 1, c: 1 }, +) + +export function useLearnProgress() { + return { learnStep } +} diff --git a/apps/web/src/shared/composables/maxkb.ts b/apps/web/src/shared/composables/maxkb.ts new file mode 100644 index 0000000..a84f909 --- /dev/null +++ b/apps/web/src/shared/composables/maxkb.ts @@ -0,0 +1,132 @@ +import { useConfigStore } from "shared/store/config" +import { useUserStore } from "shared/store/user" +import { + useConfigWebSocket, + type ConfigUpdate, +} from "shared/composables/websocket" + +export function useMaxKB() { + const configStore = useConfigStore() + const userStore = useUserStore() + const isLoaded = ref(false) + + // 处理 WebSocket 配置更新 - 只处理 MaxKB 相关 + const handleConfigUpdate = (data: ConfigUpdate) => { + if (data.key === "enable_maxkb") { + if (data.value) { + loadMaxKBScript() + } else { + removeMaxKBScript() + } + } + } + + // 初始化 WebSocket + const { connect, disconnect } = useConfigWebSocket(handleConfigUpdate) + + // 监听登录状态变化 + watch( + () => userStore.isAuthed, + (isAuthed) => { + if (isAuthed) { + connect() + } else { + disconnect() + } + }, + { immediate: true }, + ) + + const loadMaxKBScript = () => { + const { enable_maxkb } = configStore.config + + if (!enable_maxkb) { + return + } + + const existingScript = document.querySelector( + `script[src="${import.meta.env.PUBLIC_MAXKB_URL}"]`, + ) + if (existingScript) { + isLoaded.value = true + return + } + + // 创建并插入脚本标签 + const script = document.createElement("script") + script.src = import.meta.env.PUBLIC_MAXKB_URL + script.async = true + script.defer = true + + script.onload = () => { + isLoaded.value = true + } + + script.onerror = () => { + console.error("Failed to load MaxKB script") + } + + document.head.appendChild(script) + } + + const removeMaxKBScript = () => { + // 把 script 也删除 + const script = document.querySelector( + `script[src="${import.meta.env.PUBLIC_MAXKB_URL}"]`, + ) + if (script) { + script.remove() + } + // 等待DOM加载完成后删除所有id以"maxkb-"开头的元素 + const removeMaxKBElements = () => { + // 查找所有id以"maxkb-"开头的元素 + const elements = document.querySelectorAll('[id^="maxkb-"]') + + elements.forEach((element) => { + element.remove() + }) + } + + // 如果DOM已经加载完成,直接执行删除 + if (document.readyState === "complete") { + removeMaxKBElements() + } else { + // 等待DOM加载完成 + window.addEventListener("load", removeMaxKBElements, { once: true }) + } + + // 移除MaxKB脚本标签 + const existingScript = document.querySelector( + `script[src="${import.meta.env.PUBLIC_MAXKB_URL}"]`, + ) + if (existingScript) { + existingScript.remove() + } + + // 重置加载状态 + isLoaded.value = false + } + + // 连接 WebSocket + onMounted(() => { + connect() + }) + + watch( + () => configStore.config.enable_maxkb, + (enabled) => { + if (enabled) { + loadMaxKBScript() + } else { + removeMaxKBScript() + } + }, + { immediate: true }, + ) + + return { + loadMaxKBScript, + removeMaxKBScript, + isLoaded: readonly(isLoaded), + } +} diff --git a/apps/web/src/shared/composables/pagination.ts b/apps/web/src/shared/composables/pagination.ts new file mode 100644 index 0000000..f0204e9 --- /dev/null +++ b/apps/web/src/shared/composables/pagination.ts @@ -0,0 +1,154 @@ +import { reactive, watch } from "vue" +import { useRoute, useRouter } from "vue-router" +import { filterEmptyValue } from "utils/functions" + +export interface PaginationQuery { + page: number + limit: number + [key: string]: any +} + +export interface UsePaginationOptions { + /** 默认每页条数 */ + defaultLimit?: number + /** 默认页码 */ + defaultPage?: number + /** 当其他查询条件变化时是否重置页码 */ + resetPageOnChange?: boolean +} + +/** + * 分页相关的 composable,处理分页状态和 URL 同步 + * 每次调用创建新的分页状态实例 + * @param initialQuery 初始查询参数对象 + * @param options 配置选项 + */ +export function usePagination>( + initialQuery: Omit = {} as Omit, + options: UsePaginationOptions = {}, +) { + const { + defaultLimit = 10, + defaultPage = 1, + resetPageOnChange = true, + } = options + + const route = useRoute() + const router = useRouter() + + // 从 URL 查询参数初始化状态 + const query = reactive({ + page: parseInt(route.query.page) || defaultPage, + limit: parseInt(route.query.limit) || defaultLimit, + ...initialQuery, + }) as unknown as T & PaginationQuery + + // 同步 URL 查询参数到本地状态 + function syncFromRoute() { + ;(query as any).page = parseInt(route.query.page) || defaultPage + ;(query as any).limit = parseInt(route.query.limit) || defaultLimit + + // 同步其他查询参数 + Object.keys(initialQuery).forEach((key) => { + const value = route.query[key] + if (value !== undefined) { + // 处理不同类型的参数 + if (typeof initialQuery[key] === "boolean") { + ;(query as any)[key] = value === "1" || value === "true" + } else if (typeof initialQuery[key] === "number") { + ;(query as any)[key] = parseInt(value) || initialQuery[key] + } else { + ;(query as any)[key] = value || initialQuery[key] + } + } + }) + } + + // 更新 URL + function updateRoute() { + const newQuery = filterEmptyValue(query) + router.push({ + path: route.path, + query: newQuery, + }) + } + + // 重置页码到第一页 + function resetPage() { + ;(query as any).page = defaultPage + } + + // 清空所有查询条件(除了分页参数) + function clearQuery() { + Object.keys(initialQuery).forEach((key) => { + const initialValue = initialQuery[key] + if (typeof initialValue === "string") { + ;(query as any)[key] = "" + } else if (typeof initialValue === "boolean") { + ;(query as any)[key] = false + } else if (typeof initialValue === "number") { + ;(query as any)[key] = 0 + } else { + ;(query as any)[key] = initialValue + } + }) + resetPage() + } + + // 监听页码变化,同步到 URL + watch(() => query.page, updateRoute) + + // 监听每页条数变化,重置页码并同步到 URL + watch( + () => query.limit, + () => { + if (resetPageOnChange) { + resetPage() + } + updateRoute() + }, + ) + + // 监听其他查询条件变化,重置页码并同步到 URL + if (resetPageOnChange && Object.keys(initialQuery).length > 0) { + const otherQueryKeys = Object.keys(initialQuery) + watch( + () => otherQueryKeys.map((key) => query[key]), + () => { + resetPage() + updateRoute() + }, + { deep: true }, + ) + } + + // 监听路由变化,同步到本地状态 + watch( + () => route.query, + () => { + syncFromRoute() + }, + { deep: true }, + ) + + return { + query, + updateRoute, + resetPage, + clearQuery, + syncFromRoute, + } +} + +/** + * 简化版本的分页 composable,只处理基本的分页逻辑 + * 每次调用创建新的分页状态实例 + * @param defaultLimit 默认每页条数 + * @param defaultPage 默认页码 + */ +export function useSimplePagination(defaultLimit = 10, defaultPage = 1) { + return usePagination( + {}, + { defaultLimit, defaultPage, resetPageOnChange: false }, + ) +} diff --git a/apps/web/src/shared/composables/rarity.ts b/apps/web/src/shared/composables/rarity.ts new file mode 100644 index 0000000..237170c --- /dev/null +++ b/apps/web/src/shared/composables/rarity.ts @@ -0,0 +1,15 @@ +import { useDark } from "@vueuse/core" +import { computed } from "vue" +import { RARITY_TEXT_COLOR } from "utils/constants" + +/** + * 成就稀有度的文字配色,跟随明暗主题切换。 + * 两套色值都压到 4.5:1 以上,不然浅色模式下白金和黄金的小字看不清。 + * 边框和色块不用这个,直接用 RARITY_COLOR 的原色。 + */ +export function useRarityColor() { + const isDark = useDark() + return computed(() => + isDark.value ? RARITY_TEXT_COLOR.dark : RARITY_TEXT_COLOR.light, + ) +} diff --git a/apps/web/src/shared/composables/sync.ts b/apps/web/src/shared/composables/sync.ts new file mode 100644 index 0000000..7dfa0f0 --- /dev/null +++ b/apps/web/src/shared/composables/sync.ts @@ -0,0 +1,412 @@ +import { useMessage } from "naive-ui" +import { useUserStore } from "../store/user" +import type { EditorView } from "@codemirror/view" +import { Compartment } from "@codemirror/state" +import type { WebrtcProvider } from "y-webrtc" +import type { Doc, Text } from "yjs" + +// 常量定义 +const SYNC_CONSTANTS = { + MAX_ROOM_USERS: 2, + AWARENESS_SYNC_DELAY: 500, + INIT_SYNC_TIMEOUT: 500, + SUPER_ADMIN_COLOR: "#ff6b6b", + REGULAR_USER_COLOR: "#4dabf7", +} as const + +// 错误类型码 +export const SYNC_ERROR_CODES = { + SUPER_ADMIN_LEFT: "SUPER_ADMIN_LEFT", + MISSING_SUPER_ADMIN: "MISSING_SUPER_ADMIN", +} as const + +// 界面和通知文案 +export const SYNC_MESSAGES = { + // 超管离开 + SUPER_ADMIN_LEFT: (name: string) => `超管 ${name} 已离开`, + + // 缺少超管 + MISSING_SUPER_ADMIN: "协同编辑需要超管", + + // 连接成功 + SYNC_ACTIVE: "协同编辑已激活!", + + // 连接断开 + CONNECTION_LOST: "协同编辑已断开", + + // 等待相关 + WAITING_STUDENT: "正在等待学生加入...", + WAITING_ADMIN: "正在等待超管加入...", + + // Form.vue 界面文案 + SYNC_ON: "断开同步", + SYNC_OFF: "开启同步", + SYNCING_WITH: (name: string) => `与 ${name} 同步中`, + STUDENT_LEFT: (name?: string) => (name ? `${name}已离开` : "可以关闭同步"), +} as const + +// 类型定义 +type SyncState = "waiting" | "active" | "error" +type SyncErrorCode = (typeof SYNC_ERROR_CODES)[keyof typeof SYNC_ERROR_CODES] + +interface UserInfo { + name: string + isSuperAdmin: boolean +} + +interface PeersEvent { + webrtcPeers: string[] +} + +interface StatusEvent { + connected: boolean +} + +interface SyncedEvent { + synced: boolean +} + +interface SyncOptions { + problemId: string + editorView: EditorView + onStatusChange?: (status: SyncStatus) => void +} + +export interface SyncStatus { + connected: boolean + roomUsers: number + canSync: boolean + message: string + error?: string + errorCode?: SyncErrorCode + otherUser?: UserInfo +} + +/** + * 代码同步 composable + * 每次调用创建新的同步实例 + */ +export function useCodeSync() { + const userStore = useUserStore() + const message = useMessage() + + // 每次调用创建新的实例变量 + let ydoc: Doc | null = null + let provider: WebrtcProvider | null = null + let ytext: Text | null = null + const collabCompartment = new Compartment() + let currentEditorView: EditorView | null = null + let lastSyncState: SyncState | null = null + let roomUserInfo = new Map() + let hasShownSuperAdminLeftMessage = false + + const updateStatus = ( + status: SyncStatus, + onStatusChange?: (status: SyncStatus) => void, + ) => { + onStatusChange?.(status) + } + + const normalizeClientId = (clientId: number | string): number => { + return typeof clientId === "string" ? parseInt(clientId, 10) : clientId + } + + const checkHasSuperAdmin = (awarenessStates: Map): boolean => { + if (userStore.isSuperAdmin) return true + return Array.from(awarenessStates.values()).some( + (state) => state.user?.isSuperAdmin, + ) + } + + const getOtherUserInfo = ( + awarenessStates: Map, + ): UserInfo | undefined => { + if (!provider) return undefined + + const localClientId = provider.awareness.clientID + for (const [clientId, state] of awarenessStates) { + if (clientId !== localClientId && state.user) { + return { + name: state.user.name, + isSuperAdmin: state.user.isSuperAdmin, + } + } + } + return undefined + } + + const checkIfSuperAdminLeft = ( + removedClientIds: number[], + onStatusChange?: (status: SyncStatus) => void, + ) => { + if (userStore.isSuperAdmin || hasShownSuperAdminLeftMessage) return + + const superAdminInfo = removedClientIds + .map((id) => roomUserInfo.get(id)) + .find((info) => info?.isSuperAdmin) + + if (superAdminInfo) { + hasShownSuperAdminLeftMessage = true + const leftMessage = SYNC_MESSAGES.SUPER_ADMIN_LEFT(superAdminInfo.name) + updateStatus( + { + connected: false, + roomUsers: 0, + canSync: false, + message: leftMessage, + error: leftMessage, + errorCode: SYNC_ERROR_CODES.SUPER_ADMIN_LEFT, + }, + onStatusChange, + ) + message.warning(leftMessage) + stopSync() + } + } + + const checkRoomPermissions = ( + roomUsers: number, + onStatusChange?: (status: SyncStatus) => void, + ) => { + const awarenessStates = provider?.awareness.getStates() + if (!awarenessStates) return + + const hasSuperAdmin = checkHasSuperAdmin(awarenessStates) + const canSync = roomUsers === SYNC_CONSTANTS.MAX_ROOM_USERS && hasSuperAdmin + const otherUser = getOtherUserInfo(awarenessStates) + + if (roomUsers === SYNC_CONSTANTS.MAX_ROOM_USERS && !hasSuperAdmin) { + if (lastSyncState === "error") return + + updateStatus( + { + connected: false, + roomUsers, + canSync: false, + message: SYNC_MESSAGES.MISSING_SUPER_ADMIN, + error: SYNC_MESSAGES.MISSING_SUPER_ADMIN, + errorCode: SYNC_ERROR_CODES.MISSING_SUPER_ADMIN, + otherUser, + }, + onStatusChange, + ) + message.error(SYNC_MESSAGES.MISSING_SUPER_ADMIN) + lastSyncState = "error" + stopSync() + return + } else if (canSync) { + updateStatus( + { + connected: true, + roomUsers, + canSync: true, + message: SYNC_MESSAGES.SYNC_ACTIVE, + otherUser, + }, + onStatusChange, + ) + if (lastSyncState !== "active") { + message.success(SYNC_MESSAGES.SYNC_ACTIVE) + lastSyncState = "active" + } + } else { + updateStatus( + { + connected: true, + roomUsers, + canSync: false, + message: + roomUsers === 1 + ? SYNC_MESSAGES.WAITING_STUDENT + : SYNC_MESSAGES.WAITING_ADMIN, + otherUser, + }, + onStatusChange, + ) + lastSyncState = "waiting" + } + } + + const setupContentSync = ( + ytext: Text, + provider: WebrtcProvider, + savedContent: string, + ) => { + let hasInitialized = false + + const initTimeout = setTimeout(() => { + if (!hasInitialized && ytext.length === 0 && savedContent) { + ytext.insert(0, savedContent) + } + hasInitialized = true + }, SYNC_CONSTANTS.INIT_SYNC_TIMEOUT) + + provider.on("synced", (event: SyncedEvent) => { + if (!event.synced || hasInitialized) return + + clearTimeout(initTimeout) + if (ytext.length === 0 && savedContent) { + ytext.insert(0, savedContent) + } + hasInitialized = true + }) + } + + async function startSync(options: SyncOptions): Promise<() => void> { + const { problemId, editorView, onStatusChange } = options + + if (!userStore.isAuthed) { + return () => {} + } + + // 动态导入 yjs 相关模块 + const [Y, { WebrtcProvider }, { yCollab }] = await Promise.all([ + import("yjs"), + import("y-webrtc"), + import("y-codemirror.next"), + ]) + + // 初始化文档和提供者 + ydoc = new Y.Doc() + ytext = ydoc.getText("codemirror") + const roomName = `problem-${problemId}` + + provider = new WebrtcProvider(roomName, ydoc, { + signaling: [import.meta.env.PUBLIC_SIGNALING_URL], + maxConns: 1, + filterBcConns: true, + }) + + // 监听连接状态 + provider.on("status", (event: StatusEvent) => { + if (!event.connected) { + updateStatus( + { + connected: false, + roomUsers: 0, + canSync: false, + message: SYNC_MESSAGES.CONNECTION_LOST, + error: SYNC_MESSAGES.CONNECTION_LOST, + }, + onStatusChange, + ) + message.warning(SYNC_MESSAGES.CONNECTION_LOST) + } + }) + + // 监听用户加入/离开 + provider.on("peers", (event: PeersEvent) => { + const roomUsers = event.webrtcPeers.length + 1 + setTimeout(() => { + checkRoomPermissions(roomUsers, onStatusChange) + }, SYNC_CONSTANTS.AWARENESS_SYNC_DELAY) + }) + + // 监听 awareness 变化 + provider.awareness.on("change", (changes: any) => { + if (!provider) return + + const awarenessStates = provider.awareness.getStates() + + if (changes.removed?.length > 0) { + checkIfSuperAdminLeft(changes.removed, onStatusChange) + } + + awarenessStates.forEach((state, clientId) => { + if (state.user) { + const normalizedId = normalizeClientId(clientId) + roomUserInfo.set(normalizedId, { + name: state.user.name, + isSuperAdmin: state.user.isSuperAdmin, + }) + } + }) + + checkRoomPermissions(awarenessStates.size, onStatusChange) + }) + + // 配置编辑器扩展 + if (editorView && ytext) { + currentEditorView = editorView + const userColor = userStore.isSuperAdmin + ? SYNC_CONSTANTS.SUPER_ADMIN_COLOR + : SYNC_CONSTANTS.REGULAR_USER_COLOR + const userName = userStore.user?.username || "匿名用户" + const savedContent = editorView.state.doc.toString() + + // 设置用户信息 + provider.awareness.setLocalStateField("user", { + name: userName, + color: userColor, + isSuperAdmin: userStore.isSuperAdmin, + }) + + // 清空编辑器并应用协同扩展 + editorView.dispatch({ + changes: { from: 0, to: editorView.state.doc.length, insert: "" }, + }) + + const collabExt = yCollab(ytext, provider.awareness) + editorView.dispatch({ + effects: collabCompartment.reconfigure(collabExt), + }) + + // 设置内容同步 + setupContentSync(ytext, provider, savedContent) + + // 设置初始状态 + const waitingMessage = userStore.isSuperAdmin + ? SYNC_MESSAGES.WAITING_STUDENT + : SYNC_MESSAGES.WAITING_ADMIN + + updateStatus( + { + connected: true, + roomUsers: 1, + canSync: false, + message: waitingMessage, + }, + onStatusChange, + ) + + message.info(waitingMessage) + lastSyncState = "waiting" + } + + return () => stopSync() + } + + function stopSync() { + if (currentEditorView) { + try { + currentEditorView.dispatch({ + effects: collabCompartment.reconfigure([]), + }) + } catch (error) { + console.warn("移除协同编辑扩展失败:", error) + } + currentEditorView = null + } + + provider?.disconnect() + provider?.destroy() + ydoc?.destroy() + + provider = null + ydoc = null + ytext = null + lastSyncState = null + roomUserInfo.clear() + hasShownSuperAdminLeftMessage = false + } + + function getInitialExtension() { + return collabCompartment.of([]) + } + + return { + startSync, + stopSync, + getInitialExtension, + } +} diff --git a/apps/web/src/shared/composables/useMermaid.ts b/apps/web/src/shared/composables/useMermaid.ts new file mode 100644 index 0000000..0925627 --- /dev/null +++ b/apps/web/src/shared/composables/useMermaid.ts @@ -0,0 +1,334 @@ +import { getRandomId } from "utils/functions" + +const mermaidThemeVariables = { + primaryColor: "#e0f2fe", + primaryTextColor: "#0f172a", + primaryBorderColor: "#0284c7", + lineColor: "#64748b", + arrowheadColor: "#64748b", + secondaryColor: "#f5f3ff", + tertiaryColor: "#ecfdf5", + background: "#ffffff", + mainBkg: "#f8fafc", + secondBkg: "#eef2ff", + tertiaryBkg: "#f0fdfa", + nodeBorder: "#2563eb", + clusterBkg: "#f8fafc", + clusterBorder: "#cbd5e1", + edgeLabelBackground: "#ffffff", + fontFamily: + 'Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif', +} + +const semanticNodeClasses = [ + "startNode", + "endNode", + "startEnd", + "input", + "output", + "process", + "decision", + "loop", +] + +const displayStyleId = "oj-mermaid-display-style" + +const mermaidDisplayStyle = ` +.oj-mermaid-flowchart { + max-width: 100%; + height: auto; +} + +.oj-mermaid-flowchart g.node rect, +.oj-mermaid-flowchart g.node polygon, +.oj-mermaid-flowchart g.node ellipse, +.oj-mermaid-flowchart g.node circle, +.oj-mermaid-flowchart g.node path { + stroke-width: 2px !important; + filter: drop-shadow(0 6px 12px rgba(15, 23, 42, 0.12)); +} + +.oj-mermaid-flowchart g.node.startNode rect, +.oj-mermaid-flowchart g.node.startNode polygon, +.oj-mermaid-flowchart g.node.startNode ellipse, +.oj-mermaid-flowchart g.node.startNode circle, +.oj-mermaid-flowchart g.node.startNode path, +.oj-mermaid-flowchart g.node.startEnd rect, +.oj-mermaid-flowchart g.node.startEnd polygon, +.oj-mermaid-flowchart g.node.startEnd ellipse, +.oj-mermaid-flowchart g.node.startEnd circle, +.oj-mermaid-flowchart g.node.startEnd path { + fill: #dcfce7 !important; + stroke: #16a34a !important; +} + +.oj-mermaid-flowchart g.node.endNode rect, +.oj-mermaid-flowchart g.node.endNode polygon, +.oj-mermaid-flowchart g.node.endNode ellipse, +.oj-mermaid-flowchart g.node.endNode circle, +.oj-mermaid-flowchart g.node.endNode path { + fill: #fee2e2 !important; + stroke: #dc2626 !important; +} + +.oj-mermaid-flowchart g.node.input rect, +.oj-mermaid-flowchart g.node.input polygon, +.oj-mermaid-flowchart g.node.input ellipse, +.oj-mermaid-flowchart g.node.input circle, +.oj-mermaid-flowchart g.node.input path { + fill: #dbeafe !important; + stroke: #2563eb !important; +} + +.oj-mermaid-flowchart g.node.output rect, +.oj-mermaid-flowchart g.node.output polygon, +.oj-mermaid-flowchart g.node.output ellipse, +.oj-mermaid-flowchart g.node.output circle, +.oj-mermaid-flowchart g.node.output path { + fill: #ede9fe !important; + stroke: #7c3aed !important; +} + +.oj-mermaid-flowchart g.node.process rect, +.oj-mermaid-flowchart g.node.process polygon, +.oj-mermaid-flowchart g.node.process ellipse, +.oj-mermaid-flowchart g.node.process circle, +.oj-mermaid-flowchart g.node.process path { + fill: #f0f9ff !important; + stroke: #0284c7 !important; +} + +.oj-mermaid-flowchart g.node.decision rect, +.oj-mermaid-flowchart g.node.decision polygon, +.oj-mermaid-flowchart g.node.decision ellipse, +.oj-mermaid-flowchart g.node.decision circle, +.oj-mermaid-flowchart g.node.decision path { + fill: #fef3c7 !important; + stroke: #d97706 !important; +} + +.oj-mermaid-flowchart g.node.loop rect, +.oj-mermaid-flowchart g.node.loop polygon, +.oj-mermaid-flowchart g.node.loop ellipse, +.oj-mermaid-flowchart g.node.loop circle, +.oj-mermaid-flowchart g.node.loop path { + fill: #fae8ff !important; + stroke: #c026d3 !important; +} + +.oj-mermaid-flowchart g.node.oj-node-palette-0 rect, +.oj-mermaid-flowchart g.node.oj-node-palette-0 polygon, +.oj-mermaid-flowchart g.node.oj-node-palette-0 ellipse, +.oj-mermaid-flowchart g.node.oj-node-palette-0 circle, +.oj-mermaid-flowchart g.node.oj-node-palette-0 path { + fill: #dbeafe !important; + stroke: #2563eb !important; +} + +.oj-mermaid-flowchart g.node.oj-node-palette-1 rect, +.oj-mermaid-flowchart g.node.oj-node-palette-1 polygon, +.oj-mermaid-flowchart g.node.oj-node-palette-1 ellipse, +.oj-mermaid-flowchart g.node.oj-node-palette-1 circle, +.oj-mermaid-flowchart g.node.oj-node-palette-1 path { + fill: #ccfbf1 !important; + stroke: #0d9488 !important; +} + +.oj-mermaid-flowchart g.node.oj-node-palette-2 rect, +.oj-mermaid-flowchart g.node.oj-node-palette-2 polygon, +.oj-mermaid-flowchart g.node.oj-node-palette-2 ellipse, +.oj-mermaid-flowchart g.node.oj-node-palette-2 circle, +.oj-mermaid-flowchart g.node.oj-node-palette-2 path { + fill: #ede9fe !important; + stroke: #7c3aed !important; +} + +.oj-mermaid-flowchart g.node.oj-node-palette-3 rect, +.oj-mermaid-flowchart g.node.oj-node-palette-3 polygon, +.oj-mermaid-flowchart g.node.oj-node-palette-3 ellipse, +.oj-mermaid-flowchart g.node.oj-node-palette-3 circle, +.oj-mermaid-flowchart g.node.oj-node-palette-3 path { + fill: #ffe4e6 !important; + stroke: #e11d48 !important; +} + +.oj-mermaid-flowchart g.node.oj-node-palette-4 rect, +.oj-mermaid-flowchart g.node.oj-node-palette-4 polygon, +.oj-mermaid-flowchart g.node.oj-node-palette-4 ellipse, +.oj-mermaid-flowchart g.node.oj-node-palette-4 circle, +.oj-mermaid-flowchart g.node.oj-node-palette-4 path { + fill: #fef3c7 !important; + stroke: #d97706 !important; +} + +.oj-mermaid-flowchart g.node.oj-node-palette-5 rect, +.oj-mermaid-flowchart g.node.oj-node-palette-5 polygon, +.oj-mermaid-flowchart g.node.oj-node-palette-5 ellipse, +.oj-mermaid-flowchart g.node.oj-node-palette-5 circle, +.oj-mermaid-flowchart g.node.oj-node-palette-5 path { + fill: #dcfce7 !important; + stroke: #16a34a !important; +} + +.oj-mermaid-flowchart g.node .label, +.oj-mermaid-flowchart g.node .nodeLabel, +.oj-mermaid-flowchart g.node .nodeLabel p, +.oj-mermaid-flowchart g.node .label span { + color: #0f172a !important; + fill: #0f172a !important; + font-weight: 650 !important; +} + +.oj-mermaid-flowchart .edgePaths path.path, +.oj-mermaid-flowchart .flowchart-link { + stroke: #64748b !important; + stroke-width: 2.4px !important; +} + +.oj-mermaid-flowchart marker path, +.oj-mermaid-flowchart .marker { + fill: #64748b !important; + stroke: #64748b !important; +} + +.oj-mermaid-flowchart .edgeLabel rect, +.oj-mermaid-flowchart .edgeLabel .labelBkg { + fill: rgba(255, 255, 255, 0.94) !important; + stroke: #cbd5e1 !important; +} + +.oj-mermaid-flowchart .edgeLabel, +.oj-mermaid-flowchart .edgeLabel span, +.oj-mermaid-flowchart .edgeLabel p { + color: #334155 !important; + font-weight: 600 !important; +} +` + +const svgNamespace = "http://www.w3.org/2000/svg" + +function getNodeLabel(node: SVGGElement): string { + const el = + node.querySelector(".nodeLabel p") || + node.querySelector(".nodeLabel") || + node.querySelector(".label span") || + node.querySelector(".label") + return el?.textContent?.trim() ?? "" +} + +function applyFlowchartDisplayStyle(container: HTMLElement) { + container.classList.add("oj-mermaid-surface") + + const svg = container.querySelector("svg") + if (!svg) return + + svg.classList.add("oj-mermaid-flowchart") + + const nodes = Array.from(svg.querySelectorAll("g.node")) + + // Assign palette indices by label so same label → same color, different labels → different colors + const labelPaletteMap = new Map() + let paletteCounter = 0 + + nodes.forEach((node) => { + const hasSemanticClass = semanticNodeClasses.some((className) => + node.classList.contains(className), + ) + if (!hasSemanticClass) { + const label = getNodeLabel(node) + if (!labelPaletteMap.has(label)) { + labelPaletteMap.set(label, paletteCounter % 6) + paletteCounter++ + } + node.classList.add(`oj-node-palette-${labelPaletteMap.get(label)}`) + } + }) + + svg.querySelector(`#${displayStyleId}`)?.remove() + const style = document.createElementNS(svgNamespace, "style") + style.setAttribute("id", displayStyleId) + style.textContent = mermaidDisplayStyle + svg.insertBefore(style, svg.firstChild) +} + +function getChromeVersion(): number { + const match = navigator.userAgent.match(/Chrome\/(\d+)/) + return match ? parseInt(match[1]) : Infinity +} + +let mermaidInstance: any = null +let mermaidIsLegacy = false + +async function loadMermaid() { + if (!mermaidInstance) { + if (getChromeVersion() < 94) { + mermaidInstance = (await import("mermaid-legacy")).default + mermaidIsLegacy = true + } else { + mermaidInstance = (await import("mermaid")).default + } + mermaidInstance.initialize({ + startOnLoad: false, + securityLevel: "strict", + theme: "base", + themeVariables: mermaidThemeVariables, + }) + } + return mermaidInstance +} + +export function useMermaid() { + const renderError = ref(null) + const renderSuccess = ref(false) + let renderGeneration = 0 + + const renderFlowchart = async ( + container: HTMLElement | null, + mermaidCode: string, + ) => { + renderError.value = null + renderSuccess.value = false + + if (container) container.innerHTML = "" + + if (!container || !mermaidCode?.trim()) return + + const gen = ++renderGeneration + try { + const m = await loadMermaid() + const id = `mermaid-${getRandomId()}` + // v9 (mermaid-legacy): callback-based render(id, code, cb) + // v10+: Promise-based render(id, code) → { svg } + const svg = mermaidIsLegacy + ? await new Promise((resolve, reject) => { + try { + m.render(id, mermaidCode, resolve) + } catch (e) { + reject(e) + } + }) + : (await m.render(id, mermaidCode)).svg + if (gen !== renderGeneration) return + container.innerHTML = svg + applyFlowchartDisplayStyle(container) + renderSuccess.value = true + } catch (error) { + if (gen !== renderGeneration) return + renderError.value = + error instanceof Error + ? error.message + : "流程图渲染失败,请检查代码格式" + } + } + + const clearError = () => { + renderError.value = null + } + + return { + renderError: readonly(renderError), + renderSuccess: readonly(renderSuccess), + renderFlowchart, + clearError, + } +} diff --git a/apps/web/src/shared/composables/websocket.ts b/apps/web/src/shared/composables/websocket.ts new file mode 100644 index 0000000..e267b3f --- /dev/null +++ b/apps/web/src/shared/composables/websocket.ts @@ -0,0 +1,544 @@ +import { ref, onUnmounted, type Ref } from "vue" + +/** + * WebSocket 连接状态 + */ +export type ConnectionStatus = + "disconnected" | "connecting" | "connected" | "error" + +/** + * WebSocket 消息类型 + */ +export interface WebSocketMessage { + type: string + [key: string]: any +} + +/** + * WebSocket 配置 + */ +export interface WebSocketConfig { + /** WebSocket 路径(如 '/ws/submission/') */ + path: string + /** 最大重连次数,默认 5 */ + maxReconnectAttempts?: number + /** 重连延迟(毫秒),默认 1000 */ + reconnectDelay?: number + /** 心跳间隔(毫秒),默认 30000(30秒) */ + heartbeatTime?: number + /** 是否启用心跳,默认 true */ + enableHeartbeat?: boolean + /** 是否启用自动重连,默认 true */ + enableAutoReconnect?: boolean +} + +/** + * WebSocket 消息处理器 + */ +export type MessageHandler = ( + data: T, +) => void + +/** + * WebSocket 基础连接管理类 + * 提供连接、重连、心跳等通用功能 + */ +export class BaseWebSocket { + protected ws: WebSocket | null = null + protected url: string + protected handlers: Set> = new Set() + protected reconnectAttempts = 0 + protected maxReconnectAttempts: number + protected reconnectDelay: number + protected heartbeatInterval: number | null = null + protected heartbeatTime: number + protected enableHeartbeat: boolean + protected enableAutoReconnect: boolean + protected disconnectTimer: number | null = null + + public status: Ref = ref("disconnected") + + constructor(config: WebSocketConfig) { + this.url = `${import.meta.env.PUBLIC_WS_URL}/${config.path}/` + + this.maxReconnectAttempts = config.maxReconnectAttempts ?? 5 + this.reconnectDelay = config.reconnectDelay ?? 1000 + this.heartbeatTime = config.heartbeatTime ?? 30000 + this.enableHeartbeat = config.enableHeartbeat ?? true + this.enableAutoReconnect = config.enableAutoReconnect ?? true + } + + /** + * 连接 WebSocket + */ + connect() { + if ( + this.ws && + (this.ws.readyState === WebSocket.OPEN || + this.ws.readyState === WebSocket.CONNECTING) + ) { + return + } + + this.status.value = "connecting" + + try { + this.ws = new WebSocket(this.url) + + this.ws.onopen = () => { + this.status.value = "connected" + this.reconnectAttempts = 0 + console.log(`[WebSocket] 连接成功: ${this.url}`) + if (this.enableHeartbeat) { + this.startHeartbeat() + } + this.onConnected() + } + + this.ws.onmessage = (event) => { + try { + const data = JSON.parse(event.data) as T + console.log(`[WebSocket] 收到消息:`, data) + + // 处理心跳响应 + if (data.type === "pong") { + return + } + + // 调用消息处理钩子 + this.onMessage(data) + } catch (error) { + console.error("[WebSocket] 解析消息失败:", error) + } + } + + this.ws.onerror = (error) => { + console.error("[WebSocket] 连接错误:", error) + this.status.value = "error" + this.onError(error) + } + + this.ws.onclose = (event) => { + console.log( + `[WebSocket] 连接关闭: code=${event.code}, reason=${event.reason}`, + ) + this.status.value = "disconnected" + this.stopHeartbeat() + this.onDisconnected(event) + + // 自动重连 + if ( + this.enableAutoReconnect && + this.reconnectAttempts < this.maxReconnectAttempts + ) { + this.reconnectAttempts++ + const delay = this.reconnectDelay * this.reconnectAttempts + console.log( + `[WebSocket] 将在 ${delay}ms 后重连 (尝试 ${this.reconnectAttempts}/${this.maxReconnectAttempts})`, + ) + setTimeout(() => this.connect(), delay) + } + } + } catch (error) { + console.error("Failed to create WebSocket connection:", error) + this.status.value = "error" + } + } + + /** + * 断开连接 + */ + disconnect() { + this.cancelScheduledDisconnect() + this.stopHeartbeat() + this.enableAutoReconnect = false // 停止自动重连 + if (this.ws) { + this.ws.close() + this.ws = null + } + this.status.value = "disconnected" + } + + /** + * 安排延迟断开连接 + * @param delay 延迟时间(毫秒),默认 900000(15分钟) + */ + scheduleDisconnect(delay: number = 15 * 60 * 1000) { + // 取消之前的定时器 + this.cancelScheduledDisconnect() + + // 设置新的定时器 + this.disconnectTimer = window.setTimeout(() => { + const minutes = Math.floor(delay / 60000) + console.log(`WebSocket idle for ${minutes} minutes, disconnecting...`) + this.disconnect() + // 断开后需要重新允许自动重连 + this.enableAutoReconnect = true + }, delay) + } + + /** + * 取消已安排的断开连接 + */ + cancelScheduledDisconnect() { + if (this.disconnectTimer !== null) { + clearTimeout(this.disconnectTimer) + this.disconnectTimer = null + } + } + + /** + * 发送消息 + */ + send(data: any) { + if (this.ws && this.ws.readyState === WebSocket.OPEN) { + this.ws.send(JSON.stringify(data)) + return true + } + return false + } + + /** + * 添加消息处理器 + */ + addHandler(handler: MessageHandler) { + this.handlers.add(handler) + } + + /** + * 移除消息处理器 + */ + removeHandler(handler: MessageHandler) { + this.handlers.delete(handler) + } + + /** + * 清除所有处理器 + */ + clearHandlers() { + this.handlers.clear() + } + + /** + * 发送心跳包 + */ + protected sendHeartbeat() { + this.send({ type: "ping", timestamp: Date.now() }) + } + + /** + * 开始心跳 + */ + protected startHeartbeat() { + this.stopHeartbeat() + this.heartbeatInterval = window.setInterval(() => { + this.sendHeartbeat() + }, this.heartbeatTime) + } + + /** + * 停止心跳 + */ + protected stopHeartbeat() { + if (this.heartbeatInterval) { + clearInterval(this.heartbeatInterval) + this.heartbeatInterval = null + } + } + + /** + * 连接成功钩子(子类可重写) + */ + protected onConnected() { + // 子类实现 + } + + /** + * 断开连接钩子(子类可重写) + */ + protected onDisconnected(event: CloseEvent) { + // 子类实现 + } + + /** + * 错误钩子(子类可重写) + */ + protected onError(error: Event) { + // 子类实现 + } + + /** + * 消息处理钩子(子类可重写) + */ + protected onMessage(data: T) { + // 通知所有处理器 + this.handlers.forEach((handler) => { + try { + handler(data) + } catch (error) { + console.error("Error in message handler:", error) + } + }) + } +} + +/** + * 提交状态更新的数据类型 + */ +export interface SubmissionUpdate extends WebSocketMessage { + type: "submission_update" + submission_id: string + result: number + status: "pending" | "judging" | "finished" | "error" + time_cost?: number + memory_cost?: number + score?: number + err_info?: string +} + +/** + * 提交 WebSocket 连接管理类 + */ +class SubmissionWebSocket extends BaseWebSocket { + constructor() { + super({ + path: "submission", + }) + } + + /** + * 订阅特定提交的更新 + */ + subscribe(submissionId: string) { + const success = this.send({ + type: "subscribe", + submission_id: submissionId, + }) + if (!success) { + console.error("[WebSocket] 订阅失败: 连接未就绪") + } + } +} + +/** + * 用于组件中使用 WebSocket 的 Composable + * 每次调用创建新的 WebSocket 实例 + */ +export function useSubmissionWebSocket( + handler?: MessageHandler, +) { + const ws = new SubmissionWebSocket() + + // 如果提供了处理器,添加到实例中 + if (handler) { + ws.addHandler(handler) + } + + // 组件卸载时清理资源 + onUnmounted(() => { + if (handler) { + ws.removeHandler(handler) + } + ws.disconnect() + }) + + return { + connect: () => ws.connect(), + disconnect: () => ws.disconnect(), + subscribe: (submissionId: string) => ws.subscribe(submissionId), + scheduleDisconnect: (delay?: number) => ws.scheduleDisconnect(delay), + cancelScheduledDisconnect: () => ws.cancelScheduledDisconnect(), + status: ws.status, + addHandler: (h: MessageHandler) => ws.addHandler(h), + removeHandler: (h: MessageHandler) => ws.removeHandler(h), + } +} + +/** + * 通用 WebSocket Composable 工厂函数 + * 用于创建自定义的 WebSocket composable + * + * @example + * ```ts + * // 创建通知 WebSocket + * interface NotificationMessage extends WebSocketMessage { + * type: 'notification' + * title: string + * content: string + * } + * + * class NotificationWebSocket extends BaseWebSocket { + * constructor() { + * super({ path: 'notification' }) + * } + * } + * + * let notificationWs: NotificationWebSocket | null = null + * + * export function useNotificationWebSocket(handler?: MessageHandler) { + * if (!notificationWs) { + * notificationWs = new NotificationWebSocket() + * } + * return createWebSocketComposable(notificationWs, handler) + * } + * ``` + */ +export function createWebSocketComposable( + ws: BaseWebSocket, + handler?: MessageHandler, +) { + if (handler) { + ws.addHandler(handler) + } + + onUnmounted(() => { + if (handler) { + ws.removeHandler(handler) + } + }) + + return { + connect: () => ws.connect(), + disconnect: () => ws.disconnect(), + send: (data: any) => ws.send(data), + status: ws.status, + addHandler: (h: MessageHandler) => ws.addHandler(h), + removeHandler: (h: MessageHandler) => ws.removeHandler(h), + } +} + +/** + * 流程图评分更新消息类型 + */ +export interface FlowchartEvaluationUpdate extends WebSocketMessage { + type: + | "flowchart_evaluation_completed" + | "flowchart_evaluation_failed" + | "flowchart_evaluation_update" + submission_id: string + score?: number + grade?: string + feedback?: string + suggestions?: string + criteriaDetails?: any + error?: string +} + +/** + * 流程图 WebSocket 连接管理类 + */ +class FlowchartWebSocket extends BaseWebSocket { + constructor() { + super({ + path: "flowchart", // 使用专门的 flowchart WebSocket 路径 + }) + } + + /** + * 订阅特定流程图提交的更新 + */ + subscribe(submissionId: string) { + const success = this.send({ + type: "subscribe", + submission_id: submissionId, + }) + if (!success) { + console.error("[Flowchart WebSocket] 订阅失败: 连接未就绪") + } + } +} + +/** + * 用于组件中使用流程图 WebSocket 的 Composable + */ +export function useFlowchartWebSocket( + handler?: MessageHandler, +) { + const ws = new FlowchartWebSocket() + + // 如果提供了处理器,添加到实例中 + if (handler) { + ws.addHandler(handler) + } + + // 组件卸载时清理资源 + onUnmounted(() => { + if (handler) { + ws.removeHandler(handler) + } + ws.disconnect() + }) + + return { + connect: () => ws.connect(), + disconnect: () => ws.disconnect(), + subscribe: (submissionId: string) => ws.subscribe(submissionId), + scheduleDisconnect: (delay?: number) => ws.scheduleDisconnect(delay), + cancelScheduledDisconnect: () => ws.cancelScheduledDisconnect(), + status: ws.status, + addHandler: (h: MessageHandler) => + ws.addHandler(h), + removeHandler: (h: MessageHandler) => + ws.removeHandler(h), + } +} + +/** + * 配置更新消息类型 + */ +export interface ConfigUpdate extends WebSocketMessage { + type: "config_update" + key: string + value: any +} + +/** + * 配置 WebSocket 连接管理类 + */ +class ConfigWebSocket extends BaseWebSocket { + constructor() { + super({ + path: "config", + }) + } + + /** + * 发送配置更新 + */ + updateConfig(key: string, value: any) { + this.send({ + type: "config_update", + key, + value, + }) + } +} + +/** + * 用于组件中使用配置 WebSocket 的 Composable + */ +export function useConfigWebSocket(handler?: MessageHandler) { + const ws = new ConfigWebSocket() + + onMounted(() => { + if (handler) { + ws.addHandler(handler) + } + }) + + onUnmounted(() => { + if (handler) { + ws.removeHandler(handler) + } + ws.disconnect() + }) + + return { + connect: () => ws.connect(), + disconnect: () => ws.disconnect(), + updateConfig: (key: string, value: any) => ws.updateConfig(key, value), + status: ws.status, + addHandler: (h: MessageHandler) => ws.addHandler(h), + removeHandler: (h: MessageHandler) => ws.removeHandler(h), + } +} diff --git a/apps/web/src/shared/extensions/autocompletion.ts b/apps/web/src/shared/extensions/autocompletion.ts new file mode 100644 index 0000000..ba77d68 --- /dev/null +++ b/apps/web/src/shared/extensions/autocompletion.ts @@ -0,0 +1,107 @@ +import type { + Completion, + CompletionContext, + CompletionResult, + CompletionSource, +} from "@codemirror/autocomplete" +import type { EditorView } from "@codemirror/view" +import { useProblemStore } from "oj/store/problem" +import type { LANGUAGE } from "utils/types" +import { c } from "./c" +import { python } from "./python" +import { sql } from "./sql" + +type ChineseCompletion = Pick< + Completion, + "label" | "detail" | "type" | "info" | "boost" | "apply" +> & { apply?: string | Completion["apply"] } + +// 中文注释提示 +const chineseAnnotations: Record = { + python, + c, + sql, +} + +// SQL 题:当前题目的表名和字段名补全,数据来自 sql_display +function sqlSchemaCompletions(): Completion[] { + const tables = useProblemStore().problem?.sql_display?.tables ?? [] + return tables.flatMap((table) => [ + { + label: table.name, + detail: "数据表", + type: "class", + info: `字段:${table.columns + .map((col) => (col.type ? `${col.name} ${col.type}` : col.name)) + .join(", ")}`, + boost: 110, + }, + ...table.columns.map((col) => ({ + label: col.name, + detail: col.type + ? `${table.name} 的字段 · ${col.type}` + : `${table.name} 的字段`, + type: "property", + boost: 105, + })), + ]) +} + +export function enhanceCompletion(language: LANGUAGE): CompletionSource { + return async function ( + context: CompletionContext, + ): Promise { + const word = context.matchBefore(/\w+/) + if (!word && !context.explicit) return null + + const trulyLanguage = + language === "SQL" + ? "sql" + : language.startsWith("Python") + ? "python" + : "c" + const completions: Completion[] = ( + chineseAnnotations[trulyLanguage] || [] + ).map((completion) => { + const insertText = + typeof completion.apply === "string" + ? completion.apply + : completion.label + const cursorOffset = insertText.includes("(") + ? insertText.indexOf("(") + 1 + : insertText.length + + if ( + (completion.type === "function" || completion.type === "method") && + insertText.includes(")") + ) { + return { + ...completion, + apply: ( + view: EditorView, + _c: Completion, + from: number, + to: number, + ) => { + view.dispatch({ + changes: { from, to, insert: insertText }, + selection: { anchor: from + cursorOffset }, + }) + }, + } + } + + return completion + }) + + if (trulyLanguage === "sql") { + completions.push(...sqlSchemaCompletions()) + } + + return { + from: word ? word.from : context.pos, + options: completions, + validFor: /^\w+$/, + } + } +} diff --git a/apps/web/src/shared/extensions/baseTheme.ts b/apps/web/src/shared/extensions/baseTheme.ts new file mode 100644 index 0000000..f663018 --- /dev/null +++ b/apps/web/src/shared/extensions/baseTheme.ts @@ -0,0 +1,9 @@ +import { EditorView } from "@codemirror/view" + +export const styleTheme = EditorView.baseTheme({ + "& .cm-scroller": { "font-family": "Monaco" }, + "&.cm-editor.cm-focused": { outline: "none" }, + "&.cm-editor .cm-tooltip.cm-tooltip-autocomplete ul": { + "font-family": "Monaco", + }, +}) diff --git a/apps/web/src/shared/extensions/c.ts b/apps/web/src/shared/extensions/c.ts new file mode 100644 index 0000000..3b1236b --- /dev/null +++ b/apps/web/src/shared/extensions/c.ts @@ -0,0 +1,257 @@ +export const c = [ + { + label: "printf", + detail: "格式化输出", + type: "function", + info: "标准输出函数,按格式打印,搭配 %d/%s 等占位符", + boost: 90, + apply: "printf();", + }, + { + label: "scanf", + detail: "格式化输入", + type: "function", + info: "标准输入函数,按格式读取,用 & 取地址接收变量", + boost: 88, + apply: "scanf();", + }, + { + label: "puts", + detail: "输出字符串", + type: "function", + info: "输出以 \\0 结尾的字符串并自动换行,比 printf 简洁", + boost: 84, + apply: "puts();", + }, + { + label: "gets", + detail: "读取字符串", + type: "function", + info: "读取一行字符串到缓冲区(不安全,建议使用 fgets)", + boost: 60, + apply: "gets();", + }, + { + label: "fgets", + detail: "安全读行", + type: "function", + info: "从文件流读取一行到缓冲区,限制长度,避免溢出", + boost: 82, + apply: "fgets();", + }, + { + label: "memset", + detail: "内存填充", + type: "function", + info: "按字节把内存填为指定值,常用于初始化数组或结构体", + boost: 80, + apply: "memset();", + }, + { + label: "memcpy", + detail: "内存拷贝", + type: "function", + info: "从源地址复制指定字节到目标地址,避免重叠", + boost: 78, + apply: "memcpy();", + }, + { + label: "strlen", + detail: "字符串长度", + type: "function", + info: "计算以 \\0 结尾的字符串长度(不含终止符)", + boost: 76, + apply: "strlen();", + }, + { + label: "strcmp", + detail: "字符串比较", + type: "function", + info: "按字典序比较两个字符串,相等为 0,小于返回负数", + boost: 74, + apply: "strcmp();", + }, + { + label: "strcpy", + detail: "字符串拷贝", + type: "function", + info: "把源字符串复制到目标(含终止符),目标要有足够空间", + boost: 72, + apply: "strcpy();", + }, + { + label: "int main", + detail: "程序入口", + type: "keyword", + info: "C 程序入口,通常返回 0 表示正常退出", + boost: 70, + }, + { + label: "for", + detail: "循环语句", + type: "keyword", + info: "for (init; condition; step) 结构,用于固定次数循环", + boost: 68, + }, + { + label: "while", + detail: "条件循环", + type: "keyword", + info: "while (condition) 条件循环,条件真则执行", + boost: 66, + }, + { + label: "if", + detail: "条件判断", + type: "keyword", + info: "if (condition) 条件分支,可配 else/else if", + boost: 64, + }, + { + label: "struct", + detail: "结构体定义", + type: "keyword", + info: "定义结构体,可组合不同类型的成员", + boost: 62, + }, + { + label: "typedef", + detail: "类型别名", + type: "keyword", + info: "为已有类型起别名,提升可读性", + boost: 60, + }, + { + label: "const", + detail: "只读限定", + type: "keyword", + info: "声明常量或只读指针,防止被修改", + boost: 58, + }, + { + label: "return", + detail: "返回值", + type: "keyword", + info: "结束函数并返回值,main 返回 0 表示成功", + boost: 56, + }, + { + label: "break", + detail: "终止循环", + type: "keyword", + info: "立即退出最近的 for/while/do 循环体", + boost: 54, + }, + { + label: "continue", + detail: "继续下一次循环", + type: "keyword", + info: "跳过当前循环余下语句,开始下一轮迭代", + boost: 52, + }, + { + label: "do", + detail: "do-while 循环", + type: "keyword", + info: "do { ... } while(condition); 后置条件循环,至少执行一次", + boost: 50, + }, + { + label: "else", + detail: "分支兜底", + type: "keyword", + info: "if/else if/else 结构中的兜底分支", + boost: 48, + }, + { + label: "switch", + detail: "多分支选择", + type: "keyword", + info: "switch (expr) { case ... } 多分支选择结构,常配合 break", + boost: 46, + }, + { + label: "case", + detail: "分支标签", + type: "keyword", + info: "switch 中的具体匹配分支,常在末尾使用 break", + boost: 44, + }, + { + label: "default", + detail: "默认分支", + type: "keyword", + info: "switch 中未匹配任何 case 时执行的分支", + boost: 42, + }, + { + label: "goto", + detail: "无条件跳转", + type: "keyword", + info: "跳转到指定标签位置,需谨慎使用以避免可读性问题", + boost: 40, + }, + { + label: "sizeof", + detail: "求字节大小", + type: "keyword", + info: "返回类型或表达式所占字节数,编译期求值", + boost: 38, + }, + { + label: "int", + detail: "整数类型", + type: "keyword", + info: "声明整型变量或返回值,通常占 4 字节", + boost: 36, + }, + { + label: "char", + detail: "字符类型", + type: "keyword", + info: "声明字符或字节型变量,通常占 1 字节", + boost: 34, + }, + { + label: "double", + detail: "双精度类型", + type: "keyword", + info: "声明双精度浮点变量,通常占 8 字节", + boost: 32, + }, + { + label: "float", + detail: "单精度类型", + type: "keyword", + info: "声明单精度浮点变量,通常占 4 字节", + boost: 30, + }, + { + label: "void", + detail: "空类型", + type: "keyword", + info: "表示无返回值或无类型指针(void*)", + boost: 28, + }, + { + label: "unsigned", + detail: "无符号修饰", + type: "keyword", + info: "与整型/字符型组合表示无符号数,扩大正数范围", + boost: 26, + }, + { + label: "long", + detail: "长整型修饰", + type: "keyword", + info: "与整型组合表示更大范围(long/long long)", + boost: 24, + }, + { + label: "short", + detail: "短整型修饰", + type: "keyword", + info: "与整型组合表示较小范围的整数类型", + boost: 22, + }, +] diff --git a/apps/web/src/shared/extensions/python.ts b/apps/web/src/shared/extensions/python.ts new file mode 100644 index 0000000..e93182d --- /dev/null +++ b/apps/web/src/shared/extensions/python.ts @@ -0,0 +1,608 @@ +export const python = [ + { + label: "print", + detail: "打印输出", + type: "function", + info: "最常用的输出函数,把内容打印到屏幕;sep 控制分隔符,end 控制行尾。", + boost: 100, + apply: "print()", + }, + { + label: "input", + detail: "读取输入", + type: "function", + info: "读取一行输入并返回字符串,可以传入提示文字。", + boost: 99, + apply: "input()", + }, + { + label: "len", + detail: "获取长度", + type: "function", + info: "返回序列或集合的长度,常用来统计列表、字符串中有多少个元素。", + boost: 90, + apply: "len()", + }, + { + label: "range", + detail: "生成整数序列", + type: "function", + info: "生成整数序列,支持起点、终点和步长,常配合 for 循环遍历次数。", + boost: 85, + apply: "range()", + }, + { + label: "enumerate", + detail: "枚举索引与值", + type: "function", + info: "遍历时同时得到索引和值,可用 start 指定起始编号,适合需要编号输出的场景。", + boost: 82, + apply: "enumerate()", + }, + { + label: "zip", + detail: "并行遍历", + type: "function", + info: "把多个可迭代对象按位置打包成元组并行遍历,长度取最短的序列。", + boost: 80, + apply: "zip()", + }, + { + label: "map", + detail: "映射函数", + type: "function", + info: "把函数作用到序列每个元素,返回惰性迭代器,需要 list() 展开后才能看到结果。", + boost: 78, + apply: "map()", + }, + { + label: "filter", + detail: "过滤元素", + type: "function", + info: "保留函数返回真值的元素,返回惰性迭代器,常用于筛选出符合条件的数据。", + boost: 76, + apply: "filter()", + }, + { + label: "sorted", + detail: "排序", + type: "function", + info: "返回排好序的新列表,支持 key 排序函数和 reverse 逆序,不会修改原序列。", + boost: 74, + apply: "sorted()", + }, + { + label: "sum", + detail: "求和", + type: "function", + info: "对可迭代对象求和,可设置初始值,常用于数字累计与前缀和。", + boost: 72, + apply: "sum()", + }, + { + label: "open", + detail: "文件读写", + type: "function", + info: "打开文件获得文件对象,常配合 with 自动关闭,支持读写追加等多种模式。", + boost: 70, + apply: "open()", + }, + { + label: "abs", + detail: "绝对值", + type: "function", + info: "返回数字的绝对值,把负数变成正数,常用于距离或差值计算。", + boost: 68, + apply: "abs()", + }, + { + label: "round", + detail: "四舍五入", + type: "function", + info: "按指定小数位四舍五入,默认保留到整数,适合处理成绩或金额保留位数。", + boost: 66, + apply: "round()", + }, + { + label: "isinstance", + detail: "类型检查", + type: "function", + info: "判断对象是否属于某个类型或类型元组,常用于分支处理不同数据。", + boost: 64, + apply: "isinstance()", + }, + { + label: "type", + detail: "获取类型", + type: "function", + info: "返回对象的类型,或用三个参数动态创建类型,用来了解变量真实类型。", + boost: 62, + apply: "type()", + }, + { + label: "list", + detail: "列表构造", + type: "function", + info: "把可迭代对象转换为列表,或创建空列表存放数据,支持列表推导式。", + boost: 60, + apply: "list()", + }, + { + label: "dict", + detail: "字典构造", + type: "function", + info: "根据映射或键值对序列创建字典,用来存储键值对信息。", + boost: 58, + apply: "dict()", + }, + { + label: "set", + detail: "集合构造", + type: "function", + info: "把可迭代对象转换为集合,自动去重,适合判重和集合运算。", + boost: 56, + apply: "set()", + }, + { + label: "tuple", + detail: "元组构造", + type: "function", + info: "把可迭代对象转换为元组,或创建不可变的序列,用作安全的组合数据。", + boost: 54, + apply: "tuple()", + }, + { + label: "int", + detail: "转整数", + type: "function", + info: "把参数转为整数,支持进制转换,如 int('101', 2) 表示二进制转十进制。", + boost: 74, + apply: "int()", + }, + { + label: "float", + detail: "转浮点数", + type: "function", + info: "把参数转为浮点数,接受字符串或数字,常用于保留小数的计算。", + boost: 72, + apply: "float()", + }, + { + label: "str", + detail: "转字符串", + type: "function", + info: "把对象转为字符串,常用于输出、拼接或写入文件。", + boost: 70, + apply: "str()", + }, + { + label: "bool", + detail: "转布尔值", + type: "function", + info: "按真值规则转为 True/False,空对象一般为 False,常用于条件判断。", + boost: 68, + apply: "bool()", + }, + { + label: "def", + detail: "定义函数", + type: "keyword", + info: "定义函数,可写位置参数、关键字参数和默认值,是封装和复用代码的基础。", + boost: 52, + }, + { + label: "class", + detail: "定义类", + type: "keyword", + info: "定义类,支持继承和魔术方法,用来创建自定义数据类型。", + boost: 50, + }, + { + label: "with", + detail: "上下文管理", + type: "keyword", + info: "进入上下文管理器,自动处理进入与退出,常用于文件、锁等需要收尾的资源。", + boost: 48, + }, + { + label: "try", + detail: "异常捕获", + type: "keyword", + info: "开始异常处理代码块,后面接 except/finally/else,防止程序因错误直接退出。", + boost: 46, + }, + { + label: "except", + detail: "处理异常", + type: "keyword", + info: "捕获并处理指定异常,与 try 连用,让程序能优雅处理输入或运行时错误。", + boost: 44, + }, + { + label: "finally", + detail: "收尾", + type: "keyword", + info: "无论是否出现异常都会执行的收尾代码块,常用于关闭文件或释放资源。", + boost: 42, + }, + { + label: "import", + detail: "导入模块", + type: "keyword", + info: "导入模块或包中的名称,可用 as 取别名,便于使用标准库或第三方库。", + boost: 40, + }, + { + label: "from", + detail: "按需导入", + type: "keyword", + info: "从模块中按名称导入对象,可与 import/as 组合,减少书写模块前缀。", + boost: 38, + }, + { + label: "return", + detail: "返回值", + type: "keyword", + info: "结束函数并返回一个值,不写时默认返回 None。", + boost: 36, + }, + { + label: "for", + detail: "for 循环", + type: "keyword", + info: "逐项遍历,可以配合 range 使用。", + boost: 45, + }, + { + label: "while", + detail: "while 循环", + type: "keyword", + info: "条件循环,条件为真时执行,支持 break/continue 和 else 分支。", + boost: 43, + }, + { + label: "if", + detail: "条件分支", + type: "keyword", + info: "if 判断,条件成立执行对应语句,可与 elif/else 组成多分支。", + boost: 41, + apply: "if ", + }, + { + label: "elif", + detail: "多分支判断", + type: "keyword", + info: "否则如果,用来依次判断多个条件,让逻辑更清晰。", + boost: 39, + apply: "elif ", + }, + { + label: "else", + detail: "否则", + type: "keyword", + info: "否则,在前面条件不满足或循环未提前退出时执行。", + boost: 37, + apply: "else:", + }, + { + label: "break", + detail: "退出循环", + type: "keyword", + info: "立即终止最近的循环(for 或 while),跳出当前循环体。", + boost: 35, + }, + { + label: "continue", + detail: "跳过本次", + type: "keyword", + info: "结束本轮循环迭代,直接开始下一轮条件判断。", + boost: 33, + }, + { + label: "pass", + detail: "空操作占位", + type: "keyword", + info: "占位语句,不执行任何操作,常用于占位或编写空的函数/类体。", + boost: 31, + }, + { + label: "lambda", + detail: "匿名函数", + type: "keyword", + info: "定义轻量级匿名函数,语法 lambda 参数: 表达式,返回表达式结果,适合简短逻辑。", + boost: 29, + }, + { + label: "yield", + detail: "生成器产出", + type: "keyword", + info: "在函数中产出一个值并暂停状态,把函数变成生成器,可配合 yield from 继续产出。", + boost: 27, + }, + { + label: "global", + detail: "声明全局变量", + type: "keyword", + info: "在函数内部声明写入模块级变量的权限,用于修改外层的全局变量。", + boost: 25, + }, + { + label: "nonlocal", + detail: "声明外层变量", + type: "keyword", + info: "在嵌套函数中声明使用最近外层作用域的变量,便于修改闭包变量。", + boost: 23, + }, + { + label: "True", + detail: "布尔真", + type: "keyword", + info: "布尔常量 True,与 False/None 一起常用于条件表达式和逻辑判断。", + boost: 21, + }, + { + label: "False", + detail: "布尔假", + type: "keyword", + info: "布尔常量 False,在逻辑判断中表示假值,常作为条件的否定结果。", + boost: 19, + }, + { + label: "None", + detail: "空值对象", + type: "keyword", + info: "表示空值或缺失值的单例对象,常用于默认参数或占位。", + boost: 17, + }, + { + label: "and", + detail: "逻辑与", + type: "keyword", + info: "逻辑与运算符,短路求值,返回最后被求值的操作数,常用于多条件同时成立。", + boost: 15, + }, + { + label: "or", + detail: "逻辑或", + type: "keyword", + info: "逻辑或运算符,短路求值,返回第一个真值或最后一个操作数,常用于设默认值。", + boost: 14, + }, + { + label: "not", + detail: "逻辑非", + type: "keyword", + info: "逻辑非运算符,返回布尔取反结果,把真变假、假变真。", + boost: 13, + }, + { + label: "in", + detail: "成员测试", + type: "keyword", + info: "判断元素是否在序列、集合或字典中,返回布尔结果,常用于查找。", + boost: 12, + }, + { + label: "is", + detail: "身份比较", + type: "keyword", + info: "比较两个对象是否是同一个对象,常用于和 None 比较以避免误判。", + boost: 11, + }, + { + label: "append", + detail: "列表追加", + type: "method", + info: "在列表尾部添加一个新元素,等价于 list.append(value)。", + boost: 48, + apply: "append()", + }, + { + label: "insert", + detail: "列表插入", + type: "method", + info: "在指定位置插入元素,语法 list.insert(index, value)。", + boost: 46, + apply: "insert()", + }, + { + label: "remove", + detail: "删除匹配值", + type: "method", + info: "删除列表中第一次出现的指定值,不存在会抛出异常 ValueError。", + boost: 44, + apply: "remove()", + }, + { + label: "pop", + detail: "弹出元素", + type: "method", + info: "移除并返回列表指定位置(默认尾部)的元素,用于栈或队列操作。", + boost: 42, + apply: "pop()", + }, + { + label: "count", + detail: "统计次数", + type: "method", + info: "返回某个对象在列表中出现的次数,常用于频次统计。", + boost: 40, + apply: "count()", + }, + { + label: "reverse", + detail: "反转列表", + type: "method", + info: "原地反转列表中元素的顺序,常与切片 [::-1] 效果类似。", + boost: 38, + apply: "reverse()", + }, + { + label: "sort", + detail: "列表排序", + type: "method", + info: "对列表进行原地排序,可指定 key 排序函数和 reverse 是否倒序。", + boost: 36, + apply: "sort()", + }, + { + label: "add", + detail: "集合添加", + type: "method", + info: "向集合添加单个元素,若元素已存在则忽略,保持集合去重特性。", + boost: 50, + apply: "add()", + }, + { + label: "clear", + detail: "清空集合", + type: "method", + info: "移除集合中所有元素,变成一个空集合。", + boost: 48, + apply: "clear()", + }, + { + label: "keys", + detail: "字典键", + type: "method", + info: "返回字典键的可迭代视图,用于遍历所有键。", + boost: 42, + apply: "keys()", + }, + { + label: "values", + detail: "字典值", + type: "method", + info: "返回字典值的可迭代视图,用于遍历所有值。", + boost: 40, + apply: "values()", + }, + { + label: "split", + detail: "字符串切分", + type: "method", + info: "按分隔符切分字符串,返回列表,默认按空白字符分割。", + boost: 52, + apply: "split()", + }, + { + label: "replace", + detail: "字符串替换", + type: "method", + info: "把字符串中的子串替换为新内容,可限制替换次数。", + boost: 50, + apply: "replace()", + }, + { + label: "format", + detail: "格式化字符串", + type: "method", + info: "使用占位符或命名参数进行字符串格式化,便于拼接变量输出。", + boost: 48, + apply: "format()", + }, + { + label: "strip", + detail: "去首尾字符", + type: "method", + info: "移除字符串首尾指定字符,默认移除空白符,常用于清理输入。", + boost: 46, + apply: "strip()", + }, + { + label: "lower", + detail: "转小写", + type: "method", + info: "把字符串中的字母转换为小写形式,常用于不区分大小写比较。", + boost: 44, + apply: "lower()", + }, + { + label: "upper", + detail: "转大写", + type: "method", + info: "把字符串中的字母转换为大写形式。", + boost: 42, + apply: "upper()", + }, + { + label: "swapcase", + detail: "大小写互换", + type: "method", + info: "把字符串中的大小写字母互换,便于切换展示风格。", + boost: 40, + apply: "swapcase()", + }, + { + label: "find", + detail: "查找子串位置", + type: "method", + info: "返回子串首次出现的索引,未找到返回 -1,适合安全查找。", + boost: 38, + apply: "find()", + }, + { + label: "index", + detail: "查找子串索引", + type: "method", + info: "返回子串首次出现的索引,未找到会抛出异常 ValueError。", + boost: 36, + apply: "index()", + }, + { + label: "startswith", + detail: "前缀判断", + type: "method", + info: "判断字符串是否以指定前缀开头,可指定检查的范围切片。", + boost: 34, + apply: "startswith()", + }, + { + label: "endswith", + detail: "后缀判断", + type: "method", + info: "判断字符串是否以指定后缀结尾,可指定范围,常用于文件名处理。", + boost: 32, + apply: "endswith()", + }, + { + label: "isalnum", + detail: "是否字母数字", + type: "method", + info: "检测字符串是否只由字母和数字组成,常用于基础输入校验。", + boost: 30, + apply: "isalnum()", + }, + { + label: "isalpha", + detail: "是否字母", + type: "method", + info: "检测字符串是否只由字母组成,用来判断名字等只含字母的场景。", + boost: 28, + apply: "isalpha()", + }, + { + label: "isdigit", + detail: "是否数字", + type: "method", + info: "检测字符串是否只由数字组成,用来判断输入是否为纯数字。", + boost: 26, + apply: "isdigit()", + }, + { + label: "islower", + detail: "是否全小写", + type: "method", + info: "检测字符串是否全部由小写字母组成且至少有一个字母。", + boost: 24, + apply: "islower()", + }, + { + label: "isupper", + detail: "是否全大写", + type: "method", + info: "检测字符串中所有字母是否都是大写且至少有一个字母。", + boost: 22, + apply: "isupper()", + }, +] diff --git a/apps/web/src/shared/extensions/sql.ts b/apps/web/src/shared/extensions/sql.ts new file mode 100644 index 0000000..dfc79ac --- /dev/null +++ b/apps/web/src/shared/extensions/sql.ts @@ -0,0 +1,404 @@ +export const sql = [ + { + label: "SELECT", + detail: "查询数据", + type: "keyword", + info: "从表中查询数据,后面接要查询的列名,用 * 表示所有列。", + boost: 100, + apply: "SELECT ", + }, + { + label: "FROM", + detail: "指定表", + type: "keyword", + info: "指定要查询的表,和 SELECT 搭配使用,如 SELECT * FROM 表名。", + boost: 98, + apply: "FROM ", + }, + { + label: "WHERE", + detail: "筛选条件", + type: "keyword", + info: "按条件筛选行,只保留满足条件的数据,如 WHERE age > 18。", + boost: 96, + apply: "WHERE ", + }, + { + label: "ORDER BY", + detail: "排序", + type: "keyword", + info: "按指定列排序,默认从小到大(ASC),加 DESC 表示从大到小。", + boost: 90, + apply: "ORDER BY ", + }, + { + label: "GROUP BY", + detail: "分组", + type: "keyword", + info: "按指定列分组,常配合 COUNT、SUM 等聚合函数统计每组数据。", + boost: 88, + apply: "GROUP BY ", + }, + { + label: "HAVING", + detail: "分组后筛选", + type: "keyword", + info: "对分组后的结果再筛选,WHERE 筛选行,HAVING 筛选组。", + boost: 86, + apply: "HAVING ", + }, + { + label: "LIMIT", + detail: "限制条数", + type: "keyword", + info: "限制返回的行数,如 LIMIT 5 只取前 5 条,常和 ORDER BY 搭配。", + boost: 84, + apply: "LIMIT ", + }, + { + label: "DISTINCT", + detail: "去重", + type: "keyword", + info: "去掉查询结果中的重复值,如 SELECT DISTINCT city FROM users。", + boost: 82, + apply: "DISTINCT ", + }, + { + label: "AS", + detail: "起别名", + type: "keyword", + info: "给列或表起别名,让结果更易读,如 SELECT name AS 姓名。", + boost: 80, + apply: "AS ", + }, + { + label: "JOIN", + detail: "连接表", + type: "keyword", + info: "把两张表按条件连接起来查询,需要用 ON 指定连接条件。", + boost: 78, + apply: "JOIN ", + }, + { + label: "LEFT JOIN", + detail: "左连接", + type: "keyword", + info: "以左表为主连接右表,左表的行都保留,右表没匹配的补 NULL。", + boost: 76, + apply: "LEFT JOIN ", + }, + { + label: "ON", + detail: "连接条件", + type: "keyword", + info: "指定两张表的连接条件,如 ON a.id = b.user_id,和 JOIN 搭配。", + boost: 74, + apply: "ON ", + }, + { + label: "INSERT INTO", + detail: "插入数据", + type: "keyword", + info: "向表中插入新行,如 INSERT INTO 表名 (列1, 列2) VALUES (值1, 值2)。", + boost: 72, + apply: "INSERT INTO ", + }, + { + label: "VALUES", + detail: "插入的值", + type: "keyword", + info: "和 INSERT INTO 搭配,写具体要插入的值,顺序要和列名对应。", + boost: 70, + apply: "VALUES ", + }, + { + label: "UPDATE", + detail: "更新数据", + type: "keyword", + info: "修改表中已有的数据,配合 SET 设置新值,别忘了加 WHERE 限定范围。", + boost: 68, + apply: "UPDATE ", + }, + { + label: "SET", + detail: "设置新值", + type: "keyword", + info: "和 UPDATE 搭配,指定要修改的列和新值,如 SET score = 90。", + boost: 66, + apply: "SET ", + }, + { + label: "DELETE FROM", + detail: "删除数据", + type: "keyword", + info: "删除表中的行,一定要配合 WHERE 使用,否则会删掉整张表的数据。", + boost: 64, + apply: "DELETE FROM ", + }, + { + label: "CREATE TABLE", + detail: "创建表", + type: "keyword", + info: "新建一张表,需要定义列名和类型,如 CREATE TABLE users (id INTEGER, name TEXT)。", + boost: 62, + apply: "CREATE TABLE ", + }, + { + label: "DROP TABLE", + detail: "删除表", + type: "keyword", + info: "删除整张表(包括结构和数据),操作不可恢复,要谨慎使用。", + boost: 60, + apply: "DROP TABLE ", + }, + { + label: "ALTER TABLE", + detail: "修改表结构", + type: "keyword", + info: "修改已有表的结构,比如添加列:ALTER TABLE 表名 ADD COLUMN 列名 类型。", + boost: 58, + apply: "ALTER TABLE ", + }, + { + label: "AND", + detail: "并且", + type: "keyword", + info: "连接多个条件,全部成立才算满足,如 WHERE age > 18 AND city = '上海'。", + boost: 56, + apply: "AND ", + }, + { + label: "OR", + detail: "或者", + type: "keyword", + info: "连接多个条件,任意一个成立就算满足。", + boost: 54, + apply: "OR ", + }, + { + label: "NOT", + detail: "取反", + type: "keyword", + info: "对条件取反,如 NOT IN、NOT LIKE、IS NOT NULL。", + boost: 52, + apply: "NOT ", + }, + { + label: "IN", + detail: "在列表中", + type: "keyword", + info: "判断值是否在给定列表中,如 WHERE city IN ('北京', '上海')。", + boost: 50, + apply: "IN ", + }, + { + label: "BETWEEN", + detail: "在区间内", + type: "keyword", + info: "判断值是否在某个范围内(包含两端),如 BETWEEN 60 AND 100。", + boost: 48, + apply: "BETWEEN ", + }, + { + label: "LIKE", + detail: "模糊匹配", + type: "keyword", + info: "模糊查询,% 匹配任意多个字符,_ 匹配单个字符,如 LIKE '张%'。", + boost: 46, + apply: "LIKE ", + }, + { + label: "IS NULL", + detail: "是否为空", + type: "keyword", + info: "判断值是否为 NULL(空值),不能写 = NULL,要用 IS NULL。", + boost: 44, + }, + { + label: "IS NOT NULL", + detail: "是否非空", + type: "keyword", + info: "判断值不为 NULL,常用于过滤掉缺失数据的行。", + boost: 42, + }, + { + label: "ASC", + detail: "升序", + type: "keyword", + info: "排序时从小到大排列,是 ORDER BY 的默认方式,可以省略。", + boost: 40, + }, + { + label: "DESC", + detail: "降序", + type: "keyword", + info: "排序时从大到小排列,如 ORDER BY score DESC 按分数从高到低。", + boost: 41, + }, + { + label: "UNION", + detail: "合并结果", + type: "keyword", + info: "合并两个查询的结果并去重,两个查询的列数和类型要一致。", + boost: 38, + apply: "UNION ", + }, + { + label: "CASE", + detail: "条件表达式", + type: "keyword", + info: "类似 if/else 的条件判断,语法 CASE WHEN 条件 THEN 值 ELSE 值 END。", + boost: 36, + apply: "CASE ", + }, + { + label: "WHEN", + detail: "当条件成立", + type: "keyword", + info: "和 CASE 搭配,写判断条件,成立时返回 THEN 后面的值。", + boost: 34, + apply: "WHEN ", + }, + { + label: "THEN", + detail: "返回值", + type: "keyword", + info: "和 WHEN 搭配,条件成立时返回的结果。", + boost: 32, + apply: "THEN ", + }, + { + label: "ELSE", + detail: "否则", + type: "keyword", + info: "CASE 中所有 WHEN 都不成立时返回的默认值。", + boost: 30, + apply: "ELSE ", + }, + { + label: "END", + detail: "结束 CASE", + type: "keyword", + info: "标记 CASE 表达式的结束,写 CASE 时不要漏掉。", + boost: 28, + }, + { + label: "NULL", + detail: "空值", + type: "keyword", + info: "表示没有值(缺失),判断时要用 IS NULL / IS NOT NULL。", + boost: 26, + }, + { + label: "COUNT", + detail: "统计行数", + type: "function", + info: "统计行数,COUNT(*) 统计所有行,COUNT(列名) 不统计 NULL。", + boost: 92, + apply: "COUNT()", + }, + { + label: "SUM", + detail: "求和", + type: "function", + info: "对某一列求和,只能用于数字列,如 SUM(score)。", + boost: 87, + apply: "SUM()", + }, + { + label: "AVG", + detail: "平均值", + type: "function", + info: "计算某一列的平均值,会自动忽略 NULL,如 AVG(score)。", + boost: 85, + apply: "AVG()", + }, + { + label: "MAX", + detail: "最大值", + type: "function", + info: "求某一列的最大值,如 MAX(score) 找最高分。", + boost: 83, + apply: "MAX()", + }, + { + label: "MIN", + detail: "最小值", + type: "function", + info: "求某一列的最小值,如 MIN(score) 找最低分。", + boost: 81, + apply: "MIN()", + }, + { + label: "LENGTH", + detail: "字符串长度", + type: "function", + info: "返回字符串的字符个数,如 LENGTH(name)。", + boost: 79, + apply: "LENGTH()", + }, + { + label: "UPPER", + detail: "转大写", + type: "function", + info: "把字符串中的字母转成大写,如 UPPER('abc') 得到 'ABC'。", + boost: 77, + apply: "UPPER()", + }, + { + label: "LOWER", + detail: "转小写", + type: "function", + info: "把字符串中的字母转成小写,如 LOWER('ABC') 得到 'abc'。", + boost: 75, + apply: "LOWER()", + }, + { + label: "SUBSTR", + detail: "截取子串", + type: "function", + info: "截取字符串的一部分,语法 SUBSTR(字符串, 起始位置, 长度),位置从 1 开始。", + boost: 73, + apply: "SUBSTR()", + }, + { + label: "REPLACE", + detail: "替换字符串", + type: "function", + info: "把字符串中的内容替换成新内容,语法 REPLACE(字符串, 旧内容, 新内容)。", + boost: 71, + apply: "REPLACE()", + }, + { + label: "ROUND", + detail: "四舍五入", + type: "function", + info: "按指定小数位四舍五入,如 ROUND(3.456, 2) 得到 3.46。", + boost: 69, + apply: "ROUND()", + }, + { + label: "ABS", + detail: "绝对值", + type: "function", + info: "返回数字的绝对值,把负数变成正数,如 ABS(-5) 得到 5。", + boost: 67, + apply: "ABS()", + }, + { + label: "IFNULL", + detail: "空值替代", + type: "function", + info: "如果第一个值是 NULL 就返回第二个值,如 IFNULL(score, 0) 把空分数当 0。", + boost: 65, + apply: "IFNULL()", + }, + { + label: "TRIM", + detail: "去首尾空格", + type: "function", + info: "去掉字符串首尾的空格,常用于清理输入数据。", + boost: 63, + apply: "TRIM()", + }, +] diff --git a/apps/web/src/shared/layout/admin.vue b/apps/web/src/shared/layout/admin.vue new file mode 100644 index 0000000..e83042b --- /dev/null +++ b/apps/web/src/shared/layout/admin.vue @@ -0,0 +1,204 @@ + + + diff --git a/apps/web/src/shared/layout/default.vue b/apps/web/src/shared/layout/default.vue new file mode 100644 index 0000000..7a01f47 --- /dev/null +++ b/apps/web/src/shared/layout/default.vue @@ -0,0 +1,50 @@ + + + + + diff --git a/apps/web/src/shared/store/achievement.ts b/apps/web/src/shared/store/achievement.ts new file mode 100644 index 0000000..75086d5 --- /dev/null +++ b/apps/web/src/shared/store/achievement.ts @@ -0,0 +1,64 @@ +import { + getPendingAchievements, + markAchievementsRead, +} from "oj/achievement/api" +import type { PendingAchievement } from "utils/types" + +/** + * 成就解锁弹窗队列。 + * + * 通知走推拉结合,后端的 UserAchievement.notified 是唯一真相来源: + * - 拉(主):布局层每次路由切换拉一次 pending,覆盖全部场景,绝不丢 + * - 推(增强):WebSocket 只在用户当场停留在问题页时把延迟压到几百毫秒 + * + * 之所以不能只靠推:前端 WebSocket 不是常驻连接,只在问题页且有提交监听时 + * 才建连,纯推会丢消息(尤其是题单奖章,那些页面根本没建连接)。 + */ +export const useAchievementStore = defineStore("achievement", () => { + const queue = ref([]) + const current = ref(null) + + // 成就和题单奖章的 id 来自两张不同的表,数值会重叠, + // 只按 id 去重会让奖章 5 把成就 5 挤掉 + function keyOf(item: PendingAchievement) { + return `${item.kind ?? "achievement"}:${item.id}` + } + + function enqueue(items: PendingAchievement[]) { + if (!items?.length) return + // 去重:WebSocket 推来的和 pending 拉来的可能是同一批 + const known = new Set([ + ...queue.value.map(keyOf), + ...(current.value ? [keyOf(current.value)] : []), + ]) + queue.value.push(...items.filter((i) => !known.has(keyOf(i)))) + } + + async function fetchPending() { + try { + // http 客户端返回 ApiResponse,真实载荷在 .data 里 + const res = await getPendingAchievements() + enqueue(res.data ?? []) + } catch { + // 拉取失败静默处理,下次路由切换会再拉 + } + } + + function next() { + current.value = queue.value.shift() ?? null + return current.value + } + + async function markRead(item: PendingAchievement) { + // 奖章不在 UserAchievement 表里,它的 id 传给标记接口会被当成成就 id, + // 把一个恰好同号、还没弹过的成就静默标记为已弹——那个奖杯就再也不会出现 + if (item.kind === "badge") return + try { + await markAchievementsRead([item.id]) + } catch { + // 标记失败下次会重复弹一次,可接受 + } + } + + return { queue, current, enqueue, fetchPending, next, markRead } +}) diff --git a/apps/web/src/shared/store/authModal.ts b/apps/web/src/shared/store/authModal.ts new file mode 100644 index 0000000..5a8f42b --- /dev/null +++ b/apps/web/src/shared/store/authModal.ts @@ -0,0 +1,155 @@ +import { defineStore } from "pinia" + +/** + * 认证状态管理 Store + * 统一管理登录、注册相关的模态框状态和表单状态 + */ +export const useAuthModalStore = defineStore("authModal", () => { + // ==================== 模态框状态 ==================== + const loginModalOpen = ref(false) + const signupModalOpen = ref(false) + + // ==================== 登录表单状态 ==================== + const loginForm = reactive({ + class: "", + username: "", + password: "", + }) + + const loginLoading = ref(false) + const loginError = ref("") + + // ==================== 注册表单状态 ==================== + const signupForm = reactive({ + username: "", + email: "", + password: "", + passwordAgain: "", + }) + + const signupLoading = ref(false) + const signupError = ref("") + + // ==================== 模态框操作 ==================== + /** + * 打开登录模态框 + */ + function openLoginModal() { + loginModalOpen.value = true + } + + /** + * 关闭登录模态框 + */ + function closeLoginModal() { + loginModalOpen.value = false + } + + /** + * 打开注册模态框 + */ + function openSignupModal() { + signupModalOpen.value = true + } + + /** + * 关闭注册模态框 + */ + function closeSignupModal() { + signupModalOpen.value = false + } + + /** + * 从登录切换到注册 + */ + function switchToSignup() { + closeLoginModal() + openSignupModal() + } + + /** + * 从注册切换到登录 + */ + function switchToLogin() { + closeSignupModal() + openLoginModal() + } + + // ==================== 登录表单操作 ==================== + /** + * 设置登录加载状态 + */ + function setLoginLoading(loading: boolean) { + loginLoading.value = loading + } + + /** + * 设置登录错误信息 + */ + function setLoginError(error: string) { + loginError.value = error + } + + /** + * 清空登录错误 + */ + function clearLoginError() { + loginError.value = "" + } + + // ==================== 注册表单操作 ==================== + /** + * 设置注册加载状态 + */ + function setSignupLoading(loading: boolean) { + signupLoading.value = loading + } + + /** + * 设置注册错误信息 + */ + function setSignupError(error: string) { + signupError.value = error + } + + /** + * 清空注册错误 + */ + function clearSignupError() { + signupError.value = "" + } + + return { + // 模态框状态 + loginModalOpen, + signupModalOpen, + + // 登录表单状态 + loginForm, + loginLoading, + loginError, + + // 注册表单状态 + signupForm, + signupLoading, + signupError, + + // 模态框操作 + openLoginModal, + closeLoginModal, + openSignupModal, + closeSignupModal, + switchToSignup, + switchToLogin, + + // 登录表单操作 + setLoginLoading, + setLoginError, + clearLoginError, + + // 注册表单操作 + setSignupLoading, + setSignupError, + clearSignupError, + } +}) diff --git a/apps/web/src/shared/store/config.ts b/apps/web/src/shared/store/config.ts new file mode 100644 index 0000000..b317895 --- /dev/null +++ b/apps/web/src/shared/store/config.ts @@ -0,0 +1,24 @@ +import { getWebsiteConfig } from "oj/api" +import type { WebsiteConfig } from "utils/types" + +export const useConfigStore = defineStore("config", () => { + const config = ref({ + website_base_url: "", + website_name: "", + website_name_shortcut: "", + website_footer: "", + submission_list_show_all: true, + allow_register: false, + class_list: [], + enable_maxkb: true, + }) + async function getConfig() { + const res = await getWebsiteConfig() + config.value = res.data + document.title = res.data.website_name + } + return { + config, + getConfig, + } +}) diff --git a/apps/web/src/shared/store/loginSummary.ts b/apps/web/src/shared/store/loginSummary.ts new file mode 100644 index 0000000..36c1cad --- /dev/null +++ b/apps/web/src/shared/store/loginSummary.ts @@ -0,0 +1,74 @@ +import { getAILoginSummary } from "oj/api" + +interface LoginSummary { + start: string + end: string + new_problem_count: number + submission_count: number + accepted_count: number + solved_count: number + flowchart_submission_count: number +} + +export const useLoginSummaryStore = defineStore("loginSummary", () => { + const show = ref(false) + const loading = ref(false) + const summary = ref(null) + const analysis = ref("") + const analysisError = ref("") + + function shouldShowSummary(nextSummary: LoginSummary | null) { + if (!nextSummary) { + return false + } + const values = [ + nextSummary.new_problem_count, + nextSummary.submission_count, + nextSummary.accepted_count, + nextSummary.solved_count, + nextSummary.flowchart_submission_count, + ] + const zeroCount = values.filter((value) => value === 0).length + return zeroCount < Math.floor(values.length / 2) + 1 + } + + async function fetchSummary() { + loading.value = true + analysis.value = "" + analysisError.value = "" + try { + const res = await getAILoginSummary() + summary.value = res.data.summary + analysis.value = res.data.analysis || "" + analysisError.value = res.data.analysis_error || "" + } catch (err) { + analysisError.value = "获取登录统计失败,请稍后再试" + } finally { + loading.value = false + } + } + + async function open() { + await fetchSummary() + if (!summary.value && analysisError.value) { + show.value = true + return + } + show.value = shouldShowSummary(summary.value) + } + + function close() { + show.value = false + } + + return { + show, + loading, + summary, + analysis, + analysisError, + fetchSummary, + open, + close, + } +}) diff --git a/apps/web/src/shared/store/myFlowchart.ts b/apps/web/src/shared/store/myFlowchart.ts new file mode 100644 index 0000000..411bb31 --- /dev/null +++ b/apps/web/src/shared/store/myFlowchart.ts @@ -0,0 +1,18 @@ +import { defineStore } from "pinia" + +export const useMyFlowchartStore = defineStore("myFlowchart", () => { + const showing = ref(false) + const mermaidCode = ref("") + + function show(code: string) { + mermaidCode.value = code + showing.value = true + } + + function hide() { + showing.value = false + mermaidCode.value = "" + } + + return { showing, mermaidCode, show, hide } +}) diff --git a/apps/web/src/shared/store/screenMode.ts b/apps/web/src/shared/store/screenMode.ts new file mode 100644 index 0000000..29b25c6 --- /dev/null +++ b/apps/web/src/shared/store/screenMode.ts @@ -0,0 +1,34 @@ +import { defineStore } from "pinia" +import { ScreenMode } from "utils/constants" + +export const useScreenModeStore = defineStore("screenMode", () => { + const { state: screenMode, next: switchScreenMode } = useCycleList( + Object.values(ScreenMode), + { + initialValue: ScreenMode.both, + }, + ) + + // 计算属性 + const isBothMode = computed(() => screenMode.value === ScreenMode.both) + const isCodeOnlyMode = computed(() => screenMode.value === ScreenMode.code) + + const shouldShowProblem = computed( + () => + screenMode.value === ScreenMode.both || + screenMode.value === ScreenMode.problem, + ) + + function resetScreenMode() { + screenMode.value = ScreenMode.both + } + + return { + screenMode, + isBothMode, + isCodeOnlyMode, + shouldShowProblem, + switchScreenMode, + resetScreenMode, + } +}) diff --git a/apps/web/src/shared/store/user.ts b/apps/web/src/shared/store/user.ts new file mode 100644 index 0000000..f19c39d --- /dev/null +++ b/apps/web/src/shared/store/user.ts @@ -0,0 +1,94 @@ +import { PROBLEM_PERMISSION, STORAGE_KEY, USER_TYPE } from "utils/constants" +import storage from "utils/storage" +import type { Profile, User } from "utils/types" +import { getProfile } from "../api" +import { useConfigStore } from "./config" + +export const useUserStore = defineStore("user", () => { + const configStore = useConfigStore() + + const profile = ref(null) + const [isFinished] = useToggle(false) + const user = computed(() => profile.value?.user ?? null) + const isAuthed = computed(() => !!user.value?.email) + + // 演示模式:超管临时把界面伪装成普通学生,方便上课投屏 + const demoMode = ref(storage.get(STORAGE_KEY.DEMO_MODE) ?? false) + + // 不受伪装影响的真实身份,只用于判断能否切换演示模式。 + // 若这里用被伪装后的 isSuperAdmin,一进入演示模式入口就消失了,退不出来。 + const realIsSuperAdmin = computed( + () => user.value?.admin_type === USER_TYPE.SUPER_ADMIN, + ) + + const isAdminRole = computed( + () => + !demoMode.value && + (user.value?.admin_type === USER_TYPE.STUDENT_ADMIN || + user.value?.admin_type === USER_TYPE.TEACHER_ADMIN || + user.value?.admin_type === USER_TYPE.SUPER_ADMIN), + ) + const isStudentAdmin = computed( + () => !demoMode.value && user.value?.admin_type === USER_TYPE.STUDENT_ADMIN, + ) + const isTeacherAdmin = computed( + () => !demoMode.value && user.value?.admin_type === USER_TYPE.TEACHER_ADMIN, + ) + const isTeacherOrAbove = computed( + () => + !demoMode.value && + (user.value?.admin_type === USER_TYPE.TEACHER_ADMIN || + user.value?.admin_type === USER_TYPE.SUPER_ADMIN), + ) + const isSuperAdmin = computed(() => !demoMode.value && realIsSuperAdmin.value) + const hasProblemPermission = computed( + () => + !demoMode.value && + user.value?.problem_permission !== PROBLEM_PERMISSION.NONE, + ) + + const canToggleDemoMode = computed(() => realIsSuperAdmin.value) + + function toggleDemoMode() { + demoMode.value = !demoMode.value + storage.set(STORAGE_KEY.DEMO_MODE, demoMode.value) + } + + const showSubmissions = computed(() => { + let flag = configStore.config.submission_list_show_all + if (isAdminRole.value) flag = true + return flag + }) + + async function getMyProfile() { + isFinished.value = false + const res = await getProfile() + profile.value = res.data + isFinished.value = true + storage.set(STORAGE_KEY.AUTHED, !!user.value?.email) + } + + function clearProfile() { + profile.value = null + demoMode.value = false + storage.clear() + } + return { + profile, + isFinished, + user, + isAdminRole, + isStudentAdmin, + isTeacherAdmin, + isTeacherOrAbove, + isSuperAdmin, + hasProblemPermission, + demoMode, + canToggleDemoMode, + toggleDemoMode, + isAuthed, + showSubmissions, + getMyProfile, + clearProfile, + } +}) diff --git a/apps/web/src/shared/themes/createTheme.ts b/apps/web/src/shared/themes/createTheme.ts new file mode 100644 index 0000000..8190216 --- /dev/null +++ b/apps/web/src/shared/themes/createTheme.ts @@ -0,0 +1,110 @@ +import { + HighlightStyle, + TagStyle, + syntaxHighlighting, +} from "@codemirror/language" +import { Extension } from "@codemirror/state" +import { EditorView } from "@codemirror/view" + +interface Options { + /** + * Theme variant. Determines which styles CodeMirror will apply by default. + */ + variant: Variant + + /** + * Settings to customize the look of the editor, like background, gutter, selection and others. + */ + settings: Settings + + /** + * Syntax highlighting styles. + */ + styles: TagStyle[] +} + +type Variant = "light" | "dark" + +interface Settings { + /** + * Editor background. + */ + background: string + + /** + * Default text color. + */ + foreground: string + + /** + * Caret color. + */ + caret: string + + /** + * Selection background. + */ + selection: string + + /** + * Background of highlighted lines. + */ + lineHighlight: string + + /** + * Gutter background. + */ + gutterBackground: string + + /** + * Text color inside gutter. + */ + gutterForeground: string + + gutterBorderRight: string +} + +export const createTheme = ({ + variant, + settings, + styles, +}: Options): Extension => { + const theme = EditorView.theme( + { + // eslint-disable-next-line @typescript-eslint/naming-convention + "&": { + backgroundColor: settings.background, + color: settings.foreground, + }, + ".cm-content": { + caretColor: settings.caret, + }, + ".cm-cursor, .cm-dropCursor": { + borderLeftColor: settings.caret, + }, + "&.cm-focused .cm-selectionBackground, .cm-selectionBackground, .cm-content ::selection": + { + backgroundColor: settings.selection, + }, + ".cm-activeLine": { + backgroundColor: settings.lineHighlight, + }, + ".cm-gutters": { + backgroundColor: settings.gutterBackground, + borderRight: settings.gutterBorderRight, + color: settings.gutterForeground, + }, + ".cm-activeLineGutter": { + backgroundColor: settings.lineHighlight, + }, + }, + { + dark: variant === "dark", + }, + ) + + const highlightStyle = HighlightStyle.define(styles) + const extension = [theme, syntaxHighlighting(highlightStyle)] + + return extension +} diff --git a/apps/web/src/shared/themes/oneDark.ts b/apps/web/src/shared/themes/oneDark.ts new file mode 100644 index 0000000..2a848c9 --- /dev/null +++ b/apps/web/src/shared/themes/oneDark.ts @@ -0,0 +1,149 @@ +import { HighlightStyle, syntaxHighlighting } from "@codemirror/language" +import { Extension } from "@codemirror/state" +import { EditorView } from "@codemirror/view" +import { tags as t } from "@lezer/highlight" + +// Using https://github.com/one-dark/vscode-one-dark-theme/ as reference for the colors + +const chalky = "#e5c07b", + coral = "#e06c75", + cyan = "#56b6c2", + invalid = "#ffffff", + ivory = "#abb2bf", + stone = "#7d8799", // Brightened compared to original to increase contrast + malibu = "#61afef", + sage = "#98c379", + whiskey = "#d19a66", + violet = "#c678dd", + darkBackground = "#26262a", + highlightBackground = "#2c313a", + background = "#101014", // naive-ui + tooltipBackground = "#353a42", + selection = "#3E4451", + cursor = "#528bff" + +/// The editor theme styles for One Dark. +const oneDarkTheme = EditorView.theme( + { + "&": { + color: ivory, + backgroundColor: background, + }, + + ".cm-content": { + caretColor: cursor, + }, + + ".cm-cursor, .cm-dropCursor": { borderLeftColor: cursor }, + "&.cm-focused .cm-selectionBackground, .cm-selectionBackground, .cm-content ::selection": + { backgroundColor: selection }, + + ".cm-panels": { backgroundColor: darkBackground, color: ivory }, + ".cm-panels.cm-panels-top": { borderBottom: "2px solid black" }, + ".cm-panels.cm-panels-bottom": { borderTop: "2px solid black" }, + + ".cm-searchMatch": { + backgroundColor: "#72a1ff59", + outline: "1px solid #457dff", + }, + ".cm-searchMatch.cm-searchMatch-selected": { + backgroundColor: "#6199ff2f", + }, + + ".cm-activeLine": { backgroundColor: "#6699ff0b" }, + ".cm-selectionMatch": { backgroundColor: "#aafe661a" }, + + "&.cm-focused .cm-matchingBracket, &.cm-focused .cm-nonmatchingBracket": { + backgroundColor: "#bad0f847", + }, + + ".cm-gutters": { + backgroundColor: background, + color: stone, + border: "none", + }, + + ".cm-activeLineGutter": { + backgroundColor: highlightBackground, + }, + + ".cm-foldPlaceholder": { + backgroundColor: "transparent", + border: "none", + color: "#ddd", + }, + + ".cm-tooltip": { + border: "none", + backgroundColor: tooltipBackground, + }, + ".cm-tooltip .cm-tooltip-arrow:before": { + borderTopColor: "transparent", + borderBottomColor: "transparent", + }, + ".cm-tooltip .cm-tooltip-arrow:after": { + borderTopColor: tooltipBackground, + borderBottomColor: tooltipBackground, + }, + ".cm-tooltip-autocomplete": { + "& > ul > li[aria-selected]": { + backgroundColor: highlightBackground, + color: ivory, + }, + }, + }, + { dark: true }, +) + +/// The highlighting style for code in the One Dark theme. +const oneDarkHighlightStyle = HighlightStyle.define([ + { tag: t.keyword, color: violet }, + { + tag: [t.name, t.deleted, t.character, t.propertyName, t.macroName], + color: coral, + }, + { tag: [t.function(t.variableName), t.labelName], color: malibu }, + { tag: [t.color, t.constant(t.name), t.standard(t.name)], color: whiskey }, + { tag: [t.definition(t.name), t.separator], color: ivory }, + { + tag: [ + t.typeName, + t.className, + t.number, + t.changed, + t.annotation, + t.modifier, + t.self, + t.namespace, + ], + color: chalky, + }, + { + tag: [ + t.operator, + t.operatorKeyword, + t.url, + t.escape, + t.regexp, + t.link, + t.special(t.string), + ], + color: cyan, + }, + { tag: [t.meta, t.comment], color: stone }, + { tag: t.strong, fontWeight: "bold" }, + { tag: t.emphasis, fontStyle: "italic" }, + { tag: t.strikethrough, textDecoration: "line-through" }, + { tag: t.link, color: stone, textDecoration: "underline" }, + { tag: t.heading, fontWeight: "bold", color: coral }, + { tag: [t.atom, t.bool, t.special(t.variableName)], color: whiskey }, + { tag: [t.processingInstruction, t.string, t.inserted], color: sage }, + { tag: t.invalid, color: invalid }, +]) + +/// Extension to enable the One Dark theme (both the editor theme and +/// the highlight style). +export const oneDark: Extension = [ + oneDarkTheme, + syntaxHighlighting(oneDarkHighlightStyle), +] diff --git a/apps/web/src/shared/themes/smoothy.ts b/apps/web/src/shared/themes/smoothy.ts new file mode 100644 index 0000000..bb64ee1 --- /dev/null +++ b/apps/web/src/shared/themes/smoothy.ts @@ -0,0 +1,100 @@ +import { tags as t } from "@lezer/highlight" +import { EditorView } from "@codemirror/view" +import type { Extension } from "@codemirror/state" +import { createTheme } from "./createTheme" + +const tooltipTheme = EditorView.theme({ + ".cm-tooltip": { + border: "1px solid #e0e0e0", + backgroundColor: "#f8f8f8", + }, + ".cm-tooltip-autocomplete": { + "& > ul > li[aria-selected]": { + backgroundColor: "#e8e8e8", + color: "#000000", + }, + }, +}) + +// Author: Kenneth Reitz +const smoothyBase = createTheme({ + variant: "light", + settings: { + background: "#FFFFFF", + foreground: "#000000", + caret: "#000000", + selection: "#FFFD0054", + gutterBackground: "#FFFFFF", + gutterForeground: "#00000070", + gutterBorderRight: "none", + lineHighlight: "#00000008", + }, + styles: [ + { + tag: t.comment, + color: "#CFCFCF", + }, + { + tag: [t.number, t.bool, t.null], + color: "#E66C29", + }, + { + tag: [ + t.className, + t.definition(t.propertyName), + t.function(t.variableName), + t.labelName, + t.definition(t.typeName), + ], + color: "#2EB43B", + }, + { + tag: t.keyword, + color: "#D8B229", + }, + { + tag: t.operator, + color: "#4EA44E", + fontWeight: "bold", + }, + { + tag: [t.definitionKeyword, t.modifier], + color: "#925A47", + }, + { + tag: t.string, + color: "#704D3D", + }, + { + tag: t.typeName, + color: "#2F8996", + }, + { + tag: [t.variableName, t.propertyName], + color: "#77ACB0", + }, + { + tag: t.self, + color: "#77ACB0", + fontWeight: "bold", + }, + { + tag: t.regexp, + color: "#E3965E", + }, + { + tag: [t.tagName, t.angleBracket], + color: "#BAA827", + }, + { + tag: t.attributeName, + color: "#B06520", + }, + { + tag: t.derefOperator, + color: "#000", + }, + ], +}) + +export const smoothy: Extension = [smoothyBase, tooltipTheme] diff --git a/apps/web/src/utils/constants.ts b/apps/web/src/utils/constants.ts new file mode 100644 index 0000000..fe0b84c --- /dev/null +++ b/apps/web/src/utils/constants.ts @@ -0,0 +1,370 @@ +import type { AchievementRarity, SUBMISSION_RESULT, ReactionKey } from "./types" + +export enum SubmissionStatus { + compile_error = -2, + wrong_answer = -1, + accepted = 0, + time_limit_exceeded = 1 | 2, + memory_limit_exceeded = 3, + runtime_error = 4, + system_error = 5, + pending = 6, + judging = 7, + partial_accepted = 8, + submitting = 9, + ast_check_failed = 10, +} + +export enum ContestStatus { + initial = "2", // 这里不需要传入到后端,只是为了一开始加载数据的时候,做一个初始位 + not_started = "1", + underway = "0", + finished = "-1", +} + +export enum ContestType { + public = "Public", + private = "Password Protected", +} + +export const JUDGE_STATUS: { + [key in SUBMISSION_RESULT]: { + name: string + title: string + type: "error" | "success" | "warning" | "info" + } +} = { + "-2": { + name: "编译失败", + title: "编译失败", + type: "warning", + }, + "-1": { + name: "答案错误", + title: "答案错误", + type: "error", + }, + "0": { + name: "答案正确", + title: "答案正确", + type: "success", + }, + "1": { + name: "运行超时", + title: "运行超时", + type: "error", + }, + "2": { + name: "运行超时", + title: "运行超时", + type: "error", + }, + "3": { + name: "内存超限", + title: "内存超限", + type: "error", + }, + "4": { + name: "运行时错误", + title: "运行时错误", + type: "warning", + }, + "5": { + name: "系统错误", + title: "系统错误", + type: "error", + }, + "6": { + name: "等待评分", + title: "等待评分", + type: "warning", + }, + "7": { + name: "正在评分", + title: "正在评分", + type: "warning", + }, + "8": { + name: "部分正确", + title: "部分正确", + type: "warning", + }, + "9": { + name: "正在提交", + title: "正在提交", + type: "info", + }, + "10": { + name: "语法未通过", + title: "答案正确,但语法未通过", + type: "success", + }, +} + +export const CONTEST_STATUS: { + [key in ContestStatus]: { + name: string + type: "error" | "success" | "warning" + } +} = { + // 这里不需要传入到后端,只是为了一开始加载数据的时候,做一个初始位 + "2": { + name: "未开始", + type: "warning", + }, + "1": { + name: "未开始", + type: "warning", + }, + "0": { + name: "进行中", + type: "success", + }, + "-1": { + name: "已结束", + type: "error", + }, +} + +export const CONTEST_TYPE = { + PUBLIC: "Public", + PRIVATE: "Password Protected", +} + +export const USER_TYPE = { + REGULAR_USER: "Regular User", + STUDENT_ADMIN: "Student Admin", + TEACHER_ADMIN: "Teacher Admin", + SUPER_ADMIN: "Super Admin", +} + +export const PROBLEM_PERMISSION = { + NONE: "None", + OWN: "Own", + ALL: "All", +} + +export const STORAGE_KEY = { + AUTHED: "authed", + LANGUAGE: "problemLanguage", + LEARN_CURRENT_STEP: "learnStep", + ADMIN_PROBLEM: "adminProblem", + ADMIN_PROBLEM_TAGS: "adminProblemTags", + DEMO_MODE: "demoMode", +} + +export const DIFFICULTY = { + Low: "简单", + Mid: "中等", + High: "困难", +} + +const cSource = + "#include\r\n\r\nint main()\r\n{\r\n \r\n return 0;\r\n}" +const cppSource = + "#include\r\n\r\nusing namespace std;\r\n\r\nint main()\r\n{\r\n \r\n return 0;\r\n}" +const pythonSource = "" +const javaSource = + 'public class Main {\r\n public static void main(String[] args) {\r\n System.out.println("黄岩一职");\r\n }\r\n}' + +export const SOURCES = { + C: cSource, + "C++": cppSource, + Java: javaSource, + Python3: pythonSource, + Python2: "", + JavaScript: "", + Golang: "", + Flowchart: "", + SQL: "", +} as const + +export const LANGUAGE_ID = { + C: 50, + "C++": 54, + Java: 62, + Python3: 71, + Python2: 0, + JavaScript: 0, + Golang: 0, + Flowchart: 0, + SQL: 0, +} as const + +export const LANGUAGE_FORMAT_VALUE = { + C: "c", + "C++": "cpp", + Java: "java", + Python2: "python", + Python3: "python", + JavaScript: "javascript", + Golang: "go", + Flowchart: "flowchart", + SQL: "sql", +} as const + +export const LANGUAGE_SHOW_VALUE = { + Flowchart: "流程图", + C: "C语言", + "C++": "C++", + Java: "Java", + Python2: "Python", + Python3: "Python", + JavaScript: "JS", + Golang: "Go", + SQL: "SQL", +} as const + +export const ICON_SET = { + Flowchart: "vscode-icons:file-type-drawio", + Python2: "devicon:python", + Python3: "devicon:python", + C: "devicon:c", + "C++": "devicon:cplusplus", + Java: "devicon:java", + JavaScript: "devicon:javascript", + Golang: "devicon:go", + SQL: "devicon:sqlite", +} as const + +const cTemplate = `//TEMPLATE BEGIN +#include + +int main() { + printf("黄岩一职"); + return 0; +} +//TEMPLATE END` + +const cppTemplate = `//TEMPLATE BEGIN +#include + +int main() { + return 0; +} +//TEMPLATE END` + +const blankTemplate = `//PREPEND BEGIN +//PREPEND END + +//TEMPLATE BEGIN +//TEMPLATE END + +//APPEND BEGIN +//APPEND END` + +export const CODE_TEMPLATES = { + C: cTemplate, + "C++": cppTemplate, + Python2: blankTemplate, + Python3: blankTemplate, + Java: blankTemplate, + JavaScript: blankTemplate, + Golang: blankTemplate, + Flowchart: blankTemplate, + SQL: blankTemplate, +} as const + +export enum ScreenMode { + both = "双栏", + code = "自测", + problem = "题目", +} + +export enum ChartType { + Rank, + Activity, +} + +// 成就稀有度 +export const RARITY_LABEL: Record = { + bronze: "青铜", + silver: "白银", + gold: "黄金", + platinum: "白金", +} + +// 边框、色块用的原色:饱和度够高,明暗底上都醒目。 +// 青铜往红偏(色相 23),黄金往纯黄推(色相 47),中间隔开两档亮度, +// 不然两个都落在橙棕区,扫一眼分不出来 +export const RARITY_COLOR: Record = { + bronze: "#c2703d", + silver: "#9fa6b2", + gold: "#f2c012", + platinum: "#7dd3fc", +} + +// 文字要另配一套。上面那组是照深色底调的,搬到白底上白金只有 1.7:1、 +// 黄金 2.2:1,12px 的稀有度标签根本看不清。取色见 useRarityColor +export const RARITY_TEXT_COLOR: Record< + "dark" | "light", + Record +> = { + dark: { + bronze: "#e08d63", + silver: "#b6bcc7", + gold: "#f2c012", + platinum: "#7dd3fc", + }, + light: { + bronze: "#a13d1e", + silver: "#6b7280", + gold: "#8f6f00", + platinum: "#0369a1", + }, +} + +// 时间范围配置 +export const DURATION_OPTIONS = [ + { label: "本节课内", value: "hours:1" }, + { label: "两节课内", value: "hours:2" }, + { label: "一天内", value: "days:1" }, + { label: "一周内", value: "weeks:1" }, + { label: "一个月内", value: "months:1" }, + { label: "两个月内", value: "months:2" }, + { label: "半年内", value: "months:6" }, + { label: "一年内", value: "years:1" }, +] as const + +// 班级号的位数范围。学生用户名形如 ks<班级号><姓名>,班级号还要跟 +// 网站配置里的班级列表对得上。 +// 后端 OnlineJudge/utils/shortcuts.py 的 CLASS_NAME_MIN/MAX_DIGITS +// 是同一条规则的另一份,改这里必须同时改那边。 +export const CLASS_NAME_MIN_DIGITS = 3 +export const CLASS_NAME_MAX_DIGITS = 4 + +/** 合法班级号:3~4 位纯数字 */ +export const CLASS_NAME_RE = new RegExp( + `^\\d{${CLASS_NAME_MIN_DIGITS},${CLASS_NAME_MAX_DIGITS}}$`, +) + +/** 用户名开头的 ks<班级号>,用于从用户名里认出班级 */ +export const USERNAME_CLASS_RE = new RegExp( + `^ks\\d{${CLASS_NAME_MIN_DIGITS},${CLASS_NAME_MAX_DIGITS}}`, +) + +/** 班级号作为数字时的上下界,给 n-input-number 用 */ +export const CLASS_NAME_MIN_VALUE = 10 ** (CLASS_NAME_MIN_DIGITS - 1) +export const CLASS_NAME_MAX_VALUE = 10 ** CLASS_NAME_MAX_DIGITS - 1 + +export const REACTIONS: { + key: ReactionKey + label: string + icon: string +}[] = [ + { + key: "too_easy", + label: "太简单", + icon: "fluent-emoji:smiling-face-with-sunglasses", + }, + { key: "too_hard", label: "太难了", icon: "fluent-emoji:exploding-head" }, + { + key: "confusing", + label: "没看懂", + icon: "fluent-emoji:face-with-spiral-eyes", + }, + { key: "buggy", label: "题目有错", icon: "fluent-emoji:bug" }, + { key: "learned", label: "学到了", icon: "fluent-emoji:light-bulb" }, + { key: "interesting", label: "有意思", icon: "fluent-emoji:star-struck" }, + { key: "want_explain", label: "想听讲解", icon: "fluent-emoji:books" }, +] diff --git a/apps/web/src/utils/download.ts b/apps/web/src/utils/download.ts new file mode 100644 index 0000000..327e17a --- /dev/null +++ b/apps/web/src/utils/download.ts @@ -0,0 +1,23 @@ +import axios from "axios" + +const http = axios.create({ + baseURL: "/api/admin", + responseType: "blob", + xsrfHeaderName: "X-CSRFToken", + xsrfCookieName: "csrftoken", +}) + +async function download(url: string) { + const res = await http.get(url) + const headers = res.headers + const link = document.createElement("a") + link.href = window.URL.createObjectURL( + new window.Blob([res.data], { type: headers["content-type"] }), + ) + link.download = (headers["content-disposition"] || "").split("filename=")[1] + document.body.appendChild(link) + link.click() + link.remove() +} + +export default download diff --git a/apps/web/src/utils/functions.ts b/apps/web/src/utils/functions.ts new file mode 100644 index 0000000..3ec9c7d --- /dev/null +++ b/apps/web/src/utils/functions.ts @@ -0,0 +1,600 @@ +import { getTime, intervalToDuration, parseISO, type Duration } from "date-fns" +import { User } from "./types" +import { USER_TYPE } from "./constants" +import { + strFromU8, + strToU8, + unzlibSync, + zipSync, + zlibSync, + type Zippable, +} from "fflate" +import copyTextFallback from "copy-text-to-clipboard" +import { customAlphabet } from "nanoid" + +function calculateACRate(acCount: number, totalCount: number): string { + if (totalCount === 0) return "0.00" + if (acCount >= totalCount) return "100.00" + return ((acCount / totalCount) * 100).toFixed(2) +} + +export function getACRate(acCount: number, totalCount: number): string { + return `${calculateACRate(acCount, totalCount)}%` +} + +export function getACRateNumber(acCount: number, totalCount: number): number { + return parseFloat(calculateACRate(acCount, totalCount)) +} + +export function filterEmptyValue>( + object: T, +): Partial { + return Object.entries(object).reduce((query, [key, value]) => { + if (value != null && value !== "" && value !== undefined) { + query[key as keyof T] = value + } + return query + }, {} as Partial) +} + +export function getTagColor( + tag: "Low" | "Mid" | "High" | "简单" | "中等" | "困难", +) { + return <"success" | "info" | "error">{ + Low: "success", + Mid: "info", + High: "error", + 简单: "success", + 中等: "info", + 困难: "error", + }[tag] +} + +// 2023-04-03T02:43:28.673156Z +export function parseTime(utc: Date | string, format = "YYYY年M月D日") { + const time = useDateFormat(utc, format, { locales: "zh-CN" }) + return time.value +} + +function getDurationObject(start: Date | string, end: Date | string) { + return intervalToDuration({ + start: getTime(parseISO(start.toString())), + end: getTime(parseISO(end.toString())), + }) +} + +function formatDurationUnits( + duration: Duration, + units: Array<{ key: keyof Duration; suffix: string }>, +): string { + return units + .filter(({ key }) => duration[key]) + .map(({ key, suffix }) => duration[key] + suffix) + .join("") +} + +export function duration( + start: Date | string, + end: Date | string, + showSeconds = false, +): string { + const durationObj = getDurationObject(start, end) + const units = [ + { key: "years" as const, suffix: "年" }, + { key: "months" as const, suffix: "月" }, + { key: "days" as const, suffix: "天" }, + { key: "hours" as const, suffix: "小时" }, + { key: "minutes" as const, suffix: "分钟" }, + ...(showSeconds ? [{ key: "seconds" as const, suffix: "秒" }] : []), + ] + return formatDurationUnits(durationObj, units) +} + +export function durationToDays( + start: Date | string, + end: Date | string, +): string { + const durationObj = getDurationObject(start, end) + const units = [ + { key: "years" as const, suffix: "年" }, + { key: "months" as const, suffix: "月" }, + { key: "days" as const, suffix: "天" }, + ] + const result = formatDurationUnits(durationObj, units) + return result || "一天以内" +} + +export function secondsToDuration(seconds: number): string { + const duration = intervalToDuration({ + start: 0, + end: seconds * 1000, + }) + const hours = (duration.days ?? 0) * 24 + (duration.hours ?? 0) + const pad = (n: number) => String(n).padStart(2, "0") + return [hours, pad(duration.minutes ?? 0), pad(duration.seconds ?? 0)].join( + ":", + ) +} + +export function submissionMemoryFormat(memory: number | string | undefined) { + if (memory === undefined) return "--" + // 1048576 = 1024 * 1024 + let t = parseInt(memory + "") / 1048576 + return String(t.toFixed(0)) + "MB" +} + +export function submissionTimeFormat(time: number | string | undefined) { + if (time === undefined) return "--" + return time + "ms" +} + +export function debounce any>( + fn: T, + delay = 100, +): (...args: Parameters) => void { + let timeoutId: ReturnType + return (...args: Parameters) => { + clearTimeout(timeoutId) + timeoutId = setTimeout(() => fn(...args), delay) + } +} + +export function getUserRole(role: User["admin_type"]): { + type: "default" | "info" | "warning" | "error" + label: "普通" | "学生管理员" | "教师管理员" | "超管" +} { + const roleMap = { + [USER_TYPE.REGULAR_USER]: { + type: "default" as const, + label: "普通" as const, + }, + [USER_TYPE.STUDENT_ADMIN]: { + type: "info" as const, + label: "学生管理员" as const, + }, + [USER_TYPE.TEACHER_ADMIN]: { + type: "warning" as const, + label: "教师管理员" as const, + }, + [USER_TYPE.SUPER_ADMIN]: { + type: "error" as const, + label: "超管" as const, + }, + } + + return roleMap[role] || roleMap[USER_TYPE.REGULAR_USER] +} + +export function unique(arr: T[]): T[] { + return [...new Set(arr)] +} + +export function encode(string?: string): string { + try { + return btoa(String.fromCharCode(...new TextEncoder().encode(string ?? ""))) + } catch (error) { + console.error("编码失败:", error) + return "" + } +} + +export function decode(bytes?: string): string { + try { + if (!bytes) return "" + const latin = atob(bytes) + return new TextDecoder("utf-8").decode( + Uint8Array.from({ length: latin.length }, (_, index) => + latin.charCodeAt(index), + ), + ) + } catch (error) { + console.error("解码失败:", error) + return "" + } +} + +export function getCSRFToken(): string { + if (typeof document === "undefined") { + return "" + } + const match = document.cookie.match(/(?:^|;\s*)csrftoken=([^;]+)/) + return match ? decodeURIComponent(match[1]) : "" +} + +export function utoa(data: string): string { + const buffer = strToU8(data) + const zipped = zlibSync(buffer, { level: 9 }) + const binary = strFromU8(zipped, true) + return btoa(binary) +} + +export function atou(base64: string): string { + const binary = atob(base64) + const buffer = strToU8(binary, true) + const unzipped = unzlibSync(buffer) + return strFromU8(unzipped) +} + +/** + * 把若干文本文件打包成 zip Blob + * @param files 文件名和文本内容 + * @param mtime 归档内的修改时间,默认当前时间 + */ +export function createZipBlob( + files: { name: string; content: string }[], + mtime: Date = new Date(), +): Blob { + const entries: Zippable = {} + for (const f of files) { + entries[f.name] = strToU8(f.content) + } + return new Blob([zipSync(entries, { mtime })], { type: "application/zip" }) +} + +/** + * 复制文本到剪贴板 + * 优先使用 Clipboard API(支持在 modal 中使用),失败时回退到 copy-text-to-clipboard + * @param text 要复制的文本 + * @returns Promise 复制是否成功 + */ +export async function copyToClipboard(text: string): Promise { + // 优先使用现代 Clipboard API + if (navigator.clipboard && window.isSecureContext) { + try { + await navigator.clipboard.writeText(text) + return true + } catch (error) { + console.warn("Clipboard API 复制失败,尝试使用回退方法:", error) + } + } + + // 回退到 copy-text-to-clipboard + try { + const success = copyTextFallback(text) + return success + } catch (error) { + console.error("复制失败:", error) + return false + } +} + +export function getRandomId() { + const nanoid = customAlphabet("0123456789abcdefghijklmnopqrstuvwxyz") + return nanoid() +} + +/** + * 恶搞效果函数 - 随机触发不同的页面恶搞效果 + */ +export function trickOrTreat() { + const effects = [ + // 效果1: 中文乱码 + () => { + document.body.innerHTML = document.body.innerHTML.replace( + /[\u4e00-\u9fa5]/g, + function (c) { + return String.fromCharCode(c.charCodeAt(0) ^ 0xa5) // 将中文字符转为乱码 + }, + ) + }, + // 效果2: 页面一直缩放 + () => { + const style = document.createElement("style") + style.id = "trick-scale-style" + style.textContent = ` + body { + animation: trickScale 0.5s ease-in-out infinite alternate; + } + @keyframes trickScale { + from { transform: scale(0.8); } + to { transform: scale(1.2); } + } + ` + document.head.appendChild(style) + }, + // 效果3: 页面左右颠倒 + () => { + document.body.style.transform = "scaleX(-1)" + }, + // 效果4: 去掉鼠标 + () => { + const style = document.createElement("style") + style.id = "trick-cursor-none-style" + style.textContent = ` + * { + cursor: none !important; + } + ` + document.head.appendChild(style) + }, + // 效果5: 页面一直旋转 + () => { + const style = document.createElement("style") + style.id = "trick-rotate-style" + style.textContent = ` + body { + animation: trickRotate 2s linear infinite; + } + @keyframes trickRotate { + from { transform: rotate(0deg); } + to { transform: rotate(360deg); } + } + ` + document.head.appendChild(style) + }, + // 效果6: 页面上下颠倒 + () => { + document.body.style.transform = "scaleY(-1)" + }, + // 效果7: 页面颜色反转(反色) + () => { + const style = document.createElement("style") + style.id = "trick-invert-style" + style.textContent = ` + body { + filter: invert(1) !important; + } + ` + document.head.appendChild(style) + }, + // 效果8: 页面抖动/震动 + () => { + const style = document.createElement("style") + style.id = "trick-shake-style" + style.textContent = ` + body { + animation: trickShake 0.1s ease-in-out infinite; + } + @keyframes trickShake { + 0%, 100% { transform: translate(0, 0); } + 25% { transform: translate(-5px, -5px); } + 50% { transform: translate(5px, 5px); } + 75% { transform: translate(-5px, 5px); } + } + ` + document.head.appendChild(style) + }, + // 效果9: 页面模糊 + () => { + const style = document.createElement("style") + style.id = "trick-blur-style" + style.textContent = ` + body { + filter: blur(5px) !important; + } + ` + document.head.appendChild(style) + }, + // 效果10: 点击哪里,哪里的DOM就飞掉 + () => { + // 添加CSS动画样式 + const style = document.createElement("style") + style.id = "trick-flyaway-style" + style.textContent = ` + .trick-flyaway { + animation: trickFlyAway 0.5s ease-out forwards !important; + pointer-events: none !important; + } + @keyframes trickFlyAway { + 0% { + transform: translate(0, 0) rotate(0deg); + opacity: 1; + } + 100% { + transform: translate(var(--fly-x, 500px), var(--fly-y, -500px)) rotate(720deg); + opacity: 0; + } + } + ` + document.head.appendChild(style) + + // 添加全局点击事件监听器 + const clickHandler = (e: MouseEvent) => { + const target = e.target as HTMLElement + // 跳过body和html元素,以及已经飞走的元素 + if ( + target === document.body || + target === document.documentElement || + target.classList.contains("trick-flyaway") + ) { + return + } + + // 获取点击位置相对于视口的位置 + const rect = target.getBoundingClientRect() + const clickX = e.clientX - rect.left - rect.width / 2 + const clickY = e.clientY - rect.top - rect.height / 2 + + // 计算飞出方向(随机方向) + const angle = Math.random() * Math.PI * 2 + const distance = 1000 + Math.random() * 500 + const flyX = Math.cos(angle) * distance + const flyY = Math.sin(angle) * distance + + // 设置CSS变量 + target.style.setProperty("--fly-x", `${flyX}px`) + target.style.setProperty("--fly-y", `${flyY}px`) + + // 添加飞走动画类 + target.classList.add("trick-flyaway") + + // 动画结束后移除元素或隐藏 + setTimeout(() => { + target.style.display = "none" + }, 500) + } + + document.addEventListener("click", clickHandler, true) + }, + // 效果11: 页面元素随机位置 + () => { + const style = document.createElement("style") + style.id = "trick-random-position-style" + style.textContent = ` + * { + position: relative !important; + } + ` + document.head.appendChild(style) + + // 随机移动所有元素 + const allElements = document.querySelectorAll("*") + allElements.forEach((el) => { + if (el === document.body || el === document.documentElement) return + const element = el as HTMLElement + const randomX = (Math.random() - 0.5) * 200 + const randomY = (Math.random() - 0.5) * 200 + element.style.transform = `translate(${randomX}px, ${randomY}px)` + }) + }, + // 效果12: 页面变成3D倾斜效果 + () => { + const style = document.createElement("style") + style.id = "trick-3d-tilt-style" + style.textContent = ` + body { + perspective: 1000px; + transform-style: preserve-3d; + animation: trick3DTilt 3s ease-in-out infinite alternate; + } + @keyframes trick3DTilt { + 0% { + transform: perspective(1000px) rotateX(0deg) rotateY(0deg); + } + 25% { + transform: perspective(1000px) rotateX(15deg) rotateY(-15deg); + } + 50% { + transform: perspective(1000px) rotateX(-15deg) rotateY(15deg); + } + 75% { + transform: perspective(1000px) rotateX(15deg) rotateY(15deg); + } + 100% { + transform: perspective(1000px) rotateX(-15deg) rotateY(-15deg); + } + } + ` + document.head.appendChild(style) + }, + // 效果13: 页面所有链接失效 + () => { + const clickHandler = (e: MouseEvent) => { + const target = e.target as HTMLElement + // 检查是否是链接或按钮 + if ( + target.tagName === "A" || + target.closest("a") || + (target.tagName === "BUTTON" && !target.closest("n-button")) + ) { + e.preventDefault() + e.stopPropagation() + e.stopImmediatePropagation() + return false + } + } + document.addEventListener("click", clickHandler, true) + }, + // 效果14: 页面变成波浪效果 + () => { + const style = document.createElement("style") + style.id = "trick-wave-style" + style.textContent = ` + body { + animation: trickWave 2s ease-in-out infinite; + } + @keyframes trickWave { + 0%, 100% { + transform: translateY(0) scaleY(1); + } + 25% { + transform: translateY(-10px) scaleY(1.02); + } + 50% { + transform: translateY(0) scaleY(1); + } + 75% { + transform: translateY(10px) scaleY(0.98); + } + } + ` + document.head.appendChild(style) + }, + // 效果15: 页面变成故障效果(Glitch) + () => { + const style = document.createElement("style") + style.id = "trick-glitch-style" + style.textContent = ` + body { + animation: trickGlitch 0.3s infinite; + } + @keyframes trickGlitch { + 0% { + transform: translate(0); + filter: hue-rotate(0deg); + } + 20% { + transform: translate(-2px, 2px); + filter: hue-rotate(90deg); + } + 40% { + transform: translate(-2px, -2px); + filter: hue-rotate(180deg); + } + 60% { + transform: translate(2px, 2px); + filter: hue-rotate(270deg); + } + 80% { + transform: translate(2px, -2px); + filter: hue-rotate(360deg); + } + 100% { + transform: translate(0); + filter: hue-rotate(0deg); + } + } + body::before { + content: ''; + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: + repeating-linear-gradient( + 0deg, + transparent, + transparent 2px, + rgba(255, 0, 0, 0.03) 2px, + rgba(255, 0, 0, 0.03) 4px + ), + repeating-linear-gradient( + 90deg, + transparent, + transparent 2px, + rgba(0, 255, 255, 0.03) 2px, + rgba(0, 255, 255, 0.03) 4px + ); + pointer-events: none; + z-index: 9999; + mix-blend-mode: difference; + } + ` + document.head.appendChild(style) + }, + ] + + // 随机选择一种效果 + const randomEffect = effects[Math.floor(Math.random() * effects.length)] + randomEffect() +} + +// function getChromeVersion() { +// var raw = navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./) +// return raw ? parseInt(raw[2], 10) : 0 +// } + +// export const isLowVersion = getChromeVersion() < 80 + +// export const protocol = isLowVersion ? "http" : "https" diff --git a/apps/web/src/utils/http.ts b/apps/web/src/utils/http.ts new file mode 100644 index 0000000..40d90be --- /dev/null +++ b/apps/web/src/utils/http.ts @@ -0,0 +1,79 @@ +import axios, { type AxiosRequestConfig } from "axios" +import { createDiscreteApi } from "naive-ui" +import { useAuthModalStore } from "shared/store/authModal" +import storage from "./storage" +import { STORAGE_KEY } from "./constants" + +const { message } = createDiscreteApi(["message"]) + +// 后端统一返回 { error, data } 信封;拦截器剥掉 axios 外层后, +// 调用方拿到的就是这个信封,data 才是真正的业务数据。 +export interface ApiResponse { + error: string | null + data: T +} + +// 让 http.get() 的类型真实反映"解包后返回信封"这件事, +// 调用方 res.data 直接拿到带类型的 T,不再依赖 axios 的 AxiosResponse 巧合对齐。 +interface Http { + get( + url: string, + config?: AxiosRequestConfig, + ): Promise> + delete( + url: string, + config?: AxiosRequestConfig, + ): Promise> + post( + url: string, + data?: unknown, + config?: AxiosRequestConfig, + ): Promise> + put( + url: string, + data?: unknown, + config?: AxiosRequestConfig, + ): Promise> +} + +const instance = axios.create({ + baseURL: "/api", + xsrfHeaderName: "X-CSRFToken", + xsrfCookieName: "csrftoken", +}) + +// 统一剥掉空字符串 / null / undefined 的 query 参数, +// 各 api 函数不必再手写过滤逻辑(保留 0、false)。 +instance.interceptors.request.use((config) => { + if (config.params) { + config.params = Object.fromEntries( + Object.entries(config.params).filter( + ([, v]) => v !== "" && v !== null && v !== undefined, + ), + ) + } + return config +}) + +instance.interceptors.response.use( + (res) => { + if (res.data.error) { + if (res.data.error === "login-required") { + storage.remove(STORAGE_KEY.AUTHED) + useAuthModalStore().openLoginModal() + } else if (res.data.error === "permission-denied") { + message.error(res.data.data || "权限不足") + } + return Promise.reject(res.data) + } else { + return Promise.resolve(res.data) + } + }, + (err) => { + return Promise.reject(err) + }, +) + +const http = instance as unknown as Http + +export default http diff --git a/apps/web/src/utils/judge.ts b/apps/web/src/utils/judge.ts new file mode 100644 index 0000000..6015d21 --- /dev/null +++ b/apps/web/src/utils/judge.ts @@ -0,0 +1,37 @@ +import axios from "axios" +import { decode, encode } from "./functions" +import { Code } from "./types" + +const http = axios.create({ baseURL: import.meta.env.PUBLIC_JUDGE0_URL }) + +export async function createTestSubmission(code: Code, input: string) { + const encodedCode = encode(code.value) + const id = { + C: 50, + "C++": 54, + Java: 62, + Golang: 60, + JavaScript: 63, + Python2: 70, + Python3: 71, + }[code.language] + let compilerOptions = "" + if (id === 50) compilerOptions = "-lm" // 解决 GCC 的链接问题 + const payload = { + source_code: encodedCode, + language_id: id, + stdin: encode(input), + redirect_stderr_to_stdout: true, + compiler_options: compilerOptions, + } + const response = await http.post("/submissions", payload, { + params: { base64_encoded: true, wait: true }, + }) + const data = response.data + return { + status: data.status && data.status.id, + output: [decode(data.compile_output), decode(data.stdout)] + .join("\n") + .trim(), + } +} diff --git a/apps/web/src/utils/permissions.ts b/apps/web/src/utils/permissions.ts new file mode 100644 index 0000000..861529d --- /dev/null +++ b/apps/web/src/utils/permissions.ts @@ -0,0 +1,108 @@ +import { useUserStore } from "shared/store/user" + +export function usePermissions() { + const userStore = useUserStore() + + return { + isAuthenticated: computed(() => userStore.isAuthed), + isAdminRole: computed(() => userStore.isAdminRole), + isTeacherOrAbove: computed(() => userStore.isTeacherOrAbove), + isSuperAdmin: computed(() => userStore.isSuperAdmin), + hasProblemPermission: computed(() => userStore.hasProblemPermission), + + canManageUsers: computed(() => userStore.isSuperAdmin), + canManageAnnouncements: computed(() => userStore.isSuperAdmin), + canManageTutorials: computed(() => userStore.isSuperAdmin), + canManageSystemConfig: computed(() => userStore.isSuperAdmin), + canSendMessages: computed(() => userStore.isSuperAdmin), + + canManageProblems: computed(() => userStore.hasProblemPermission), + canManageContests: computed(() => userStore.isTeacherOrAbove), + canManageProblemsets: computed(() => userStore.isTeacherOrAbove), + canViewClassroomData: computed(() => userStore.isTeacherOrAbove), + + canManageAllProblems: computed( + () => + userStore.user?.problem_permission === "All" || userStore.isSuperAdmin, + ), + canManageOwnProblems: computed( + () => + userStore.user?.problem_permission === "Own" && !userStore.isSuperAdmin, + ), + + getUserPermissionLevel: computed(() => { + if (userStore.isSuperAdmin) return "超级管理员" + if (userStore.isTeacherAdmin) return "教师管理员" + if (userStore.isStudentAdmin) return "学生管理员" + return "普通用户" + }), + + getProblemPermissionLevel: computed(() => { + if (!userStore.user) return "无权限" + switch (userStore.user.problem_permission) { + case "All": + return "管理所有题目" + case "Own": + return "管理自己的题目" + case "None": + return "无题目权限" + default: + return "无权限" + } + }), + } +} + +export function checkRoutePermission(routeName: string): boolean { + const userStore = useUserStore() + + const superAdminRoutes = [ + "admin home", + "admin config", + "admin user list", + "admin user generate", + "admin announcement list", + "admin announcement create", + "admin announcement edit", + "admin message list", + "admin tutorial list", + "admin tutorial create", + "admin tutorial edit", + ] + + const teacherAdminRoutes = [ + "admin contest list", + "admin contest create", + "admin contest edit", + "admin contest problem list", + "admin contest problem create", + "admin contest problem edit", + "admin contest helper", + "admin problemset list", + "admin problemset create", + "admin problemset edit", + "admin problemset detail", + "admin stuck problems", + "admin top ac trend", + ] + + const problemPermissionRoutes = [ + "admin problem list", + "admin problem create", + "admin problem edit", + ] + + if (superAdminRoutes.includes(routeName)) { + return userStore.isSuperAdmin + } + + if (teacherAdminRoutes.includes(routeName)) { + return userStore.isTeacherOrAbove + } + + if (problemPermissionRoutes.includes(routeName)) { + return userStore.hasProblemPermission + } + + return true +} diff --git a/apps/web/src/utils/renders.ts b/apps/web/src/utils/renders.ts new file mode 100644 index 0000000..02f4be1 --- /dev/null +++ b/apps/web/src/utils/renders.ts @@ -0,0 +1,9 @@ +import { Icon } from "@iconify/vue" +import { NFlex } from "naive-ui" + +export function renderTableTitle(title: string, icon: string): any { + return h(NFlex, { align: "center", size: 4 }, () => [ + h(Icon, { icon: icon, width: 20, height: 20 }), + title, + ]) +} diff --git a/apps/web/src/utils/storage.ts b/apps/web/src/utils/storage.ts new file mode 100644 index 0000000..feb1ca5 --- /dev/null +++ b/apps/web/src/utils/storage.ts @@ -0,0 +1,21 @@ +const localStorage = window.localStorage + +export default { + set(key: string, value: any) { + localStorage.setItem(key, JSON.stringify(value)) + }, + get(key: string) { + const content = localStorage.getItem(key) + if (content) { + return JSON.parse(content) + } else { + return null + } + }, + remove(key: string) { + localStorage.removeItem(key) + }, + clear() { + localStorage.clear() + }, +} diff --git a/apps/web/src/utils/stream.ts b/apps/web/src/utils/stream.ts new file mode 100644 index 0000000..fc6b6ef --- /dev/null +++ b/apps/web/src/utils/stream.ts @@ -0,0 +1,94 @@ +export interface JSONEventStreamHandlers { + onMessage: (data: T, event?: string) => void + onEvent?: (event: string) => void + signal?: AbortSignal | null +} + +export async function consumeJSONEventStream( + response: Response, + handlers: JSONEventStreamHandlers, +) { + if (!response.body) { + throw new Error("当前环境不支持可读流") + } + + const reader = response.body.getReader() + const decoder = new TextDecoder("utf-8") + let buffer = "" + + const { onMessage, onEvent, signal } = handlers + + const handleEvent = (raw: string) => { + const lines = raw.split("\n") + let eventName: string | undefined + const dataLines: string[] = [] + + for (const line of lines) { + const trimmed = line.trim() + if (!trimmed) continue + if (trimmed.startsWith("event:")) { + eventName = trimmed.slice(6).trim() + } else if (trimmed.startsWith("data:")) { + dataLines.push(trimmed.slice(5).trim()) + } + } + + if (dataLines.length === 0) { + if (eventName && onEvent) { + onEvent(eventName) + } + return + } + + const payloadStr = dataLines.join("\n") + + let parsed: T + try { + parsed = JSON.parse(payloadStr) + } catch (error) { + throw new Error(`无法解析服务端事件数据: ${payloadStr}`) + } + + onMessage(parsed, eventName) + } + + const processBuffer = (flush = false) => { + let idx = buffer.indexOf("\n\n") + while (idx !== -1) { + const rawEvent = buffer.slice(0, idx) + buffer = buffer.slice(idx + 2) + if (rawEvent.trim()) { + handleEvent(rawEvent) + } + idx = buffer.indexOf("\n\n") + } + + if (flush && buffer.trim()) { + handleEvent(buffer.trim()) + buffer = "" + } + } + + try { + while (true) { + if (signal?.aborted) { + await reader.cancel() + break + } + + const { value, done } = await reader.read() + if (value) { + buffer += decoder.decode(value, { stream: true }) + processBuffer() + } + + if (done) { + buffer += decoder.decode() + processBuffer(true) + break + } + } + } finally { + reader.releaseLock() + } +} diff --git a/apps/web/src/utils/types.ts b/apps/web/src/utils/types.ts new file mode 100644 index 0000000..4ab9426 --- /dev/null +++ b/apps/web/src/utils/types.ts @@ -0,0 +1,790 @@ +import { ContestStatus, ContestType, LANGUAGE_SHOW_VALUE } from "./constants" + +export interface Profile { + id: number + user: User + real_name: string + acm_problems_status: { + problems: { + [key: string]: { + _id: string + status: number + } + } + } + avatar: string + blog: null + mood: string + github: string + school: string + major: string + language: string + accepted_number: number + submission_number: number +} + +export type UserAdminType = + "Regular User" | "Student Admin" | "Teacher Admin" | "Super Admin" + +export interface User { + id: number + username: string + real_name: string + email: string + admin_type: UserAdminType + problem_permission: string + create_time: Date + last_login: Date + open_api: boolean + is_disabled: boolean + password?: string + raw_password?: string + class_name?: string | null +} + +export type LANGUAGE = + | "C" + | "C++" + | "Python2" + | "Python3" + | "Java" + | "JavaScript" + | "Golang" + | "Flowchart" + | "SQL" + +export interface SQLConfig { + mode: "query" | "modify" + order_sensitive: boolean +} + +export interface SQLDisplayColumn { + name: string + type?: string +} + +export interface SQLDisplayTable { + name: string + columns: SQLDisplayColumn[] + rows: (string | number | null)[][] + total_rows: number + truncated: boolean + dropped?: boolean +} + +export interface SQLDisplay { + tables: SQLDisplayTable[] + expected: + | { + columns: SQLDisplayColumn[] + rows: (string | number | null)[][] + total_rows: number + truncated: boolean + } + | { changed_tables: SQLDisplayTable[] } +} + +export type LANGUAGE_SHOW_LABEL = + (typeof LANGUAGE_SHOW_VALUE)[keyof typeof LANGUAGE_SHOW_VALUE] + +export type SUBMISSION_RESULT = + -2 | -1 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 + +export type ProblemStatus = "passed" | "failed" | "not_test" + +interface SampleUser { + id: number + username: string + real_name: string | null +} + +export interface Tag { + id: number + name: string +} + +export interface AdminTag { + id: number + name: string + problem_count: number +} + +export interface TestcaseUploadedReturns { + id: string + info: Testcase[] +} + +export interface Testcase { + input_name: string + output_name: string + score: string +} + +export interface Problem { + _id: string + id: number + tags: string[] + created_by: SampleUser + template: { [key in LANGUAGE]?: string } + title: string + description: string + input_description: string + output_description: string + samples: { + input: string + output: string + }[] + hint: string + languages: Array + create_time: Date + last_update_time: null + time_limit: number + memory_limit: number + difficulty: "Low" | "Mid" | "High" + source: string + prompt: string + answers: { language: LANGUAGE; code: string }[] + submission_number: number + accepted_number: number + statistic_info: { [key: string]: number } + share_submission: boolean + contest: number + my_status: number + my_failed_count?: number + visible: boolean + + // 流程图相关字段 + allow_flowchart: boolean + mermaid_code?: string + flowchart_data?: Record + flowchart_hint?: string + show_flowchart?: boolean + ast_rules?: { + [key: string]: { + engine: string + target?: string + min?: number + max?: number + message: string + }[] + } | null + has_ast_rules?: boolean + + // SQL 题配置(非 SQL 题为 null) + sql_config?: SQLConfig | null + + // SQL 题展示数据(后端保存题目时自动生成) + sql_display?: SQLDisplay | null +} + +export type AdminProblem = Problem & + AlterProblem & { + // 后台题目列表接口附带的最高票评价,比赛题目列表不返回 + top_reaction?: { type: ReactionKey; count: number } | null + } + +interface AlterProblem { + test_case_id: string + test_case_score: Testcase[] + contest_id?: string +} + +type ExcludeKeys = + | "id" + | "created_by" + | "create_time" + | "last_update_time" + | "my_status" + | "contest" + | "statistic_info" + | "accepted_number" + | "submission_number" + +export type BlankProblem = Omit & + AlterProblem & { id?: number } + +export interface ProblemFiltered { + _id: string + id: number + title: string + difficulty: "简单" | "中等" | "困难" + tags: string[] + submission: number + rate: string + status: "not_test" | "passed" | "failed" + author: string + allow_flowchart: boolean + show_flowchart: boolean + has_ast_rules: boolean +} + +export interface AdminProblemFiltered { + _id: string + id: number + title: string + visible: boolean + username: string + create_time: string + difficulty: "Low" | "Mid" | "High" + tags: string[] + has_ast_rules: boolean + allow_flowchart: boolean + show_flowchart: boolean + // 比赛题目列表接口不返回这个字段 + top_reaction?: { type: ReactionKey; count: number } | null +} + +// 题单相关类型 +export interface ProblemSet { + id: number + title: string + description: string + created_by: SampleUser + create_time: Date + difficulty: "Easy" | "Medium" | "Hard" + status: "active" | "archived" | "draft" + end_time: Date | null + visible: boolean + problems_count: number + completed_count: number + user_progress: { + is_joined: boolean + progress_percentage: number + completed_count: number + total_count: number + is_completed: boolean + } +} + +export interface ProblemSetList { + id: number + title: string + description: string + created_by: SampleUser + create_time: Date + difficulty: "Easy" | "Medium" | "Hard" + status: "active" | "archived" | "draft" + end_time: Date | null + problems_count: number + visible: boolean + user_progress: { + is_joined: boolean + progress_percentage: number + completed_count: number + total_count: number + is_completed: boolean + } + badges: ProblemSetBadge[] +} + +export interface ProblemSetProblem { + id: number + problemset: number + problem: Problem + order: number + is_required: boolean + score: number + hint: string + is_completed: boolean +} + +export interface ProblemSetBadge { + id: number + problemset: number + name: string + description: string + icon: string + condition_type: "all_problems" | "problem_count" | "score" + condition_value: number + is_earned?: boolean +} + +export interface UserBadge { + id: number + user: number + badge: ProblemSetBadge + earned_time: Date +} + +export interface CompletedProblem { + id: number + _id: string + title: string +} + +export interface ProblemSetProgress { + id: number + problemset: ProblemSetList + user: SampleUser + join_time: Date + completed_problems_count: number + total_problems_count: number + progress_percentage: number + is_completed: boolean + completed_problems: CompletedProblem[] +} + +export interface CreateProblemSetData { + title: string + description: string + difficulty: "Easy" | "Medium" | "Hard" + status: "active" | "archived" | "draft" + end_time?: Date | null +} + +export interface EditProblemSetData { + id: number + title?: string + description?: string + difficulty?: "Easy" | "Medium" | "Hard" + status?: "active" | "archived" | "draft" + end_time?: Date | null + visible?: boolean +} + +export interface Code { + language: LANGUAGE + value: string +} + +export interface SubmitCodePayload { + problem_id: number + language: LANGUAGE + code: string + contest_id?: number +} + +// ==================== 流程图相关类型 ==================== + +export const FlowchartSubmissionStatus = { + PENDING: 0, // 等待AI评分 + PROCESSING: 1, // AI评分中 + COMPLETED: 2, // 评分完成 + FAILED: 3, // 评分失败 +} as const + +export interface FlowchartSubmission { + id: string + user: number + problem: number + mermaid_code: string + flowchart_data: Record + status: number + create_time: string + ai_score?: number + ai_grade?: string + ai_feedback?: string + ai_suggestions?: string + ai_criteria_details: Record + ai_provider?: string + ai_model?: string + processing_time?: number + evaluation_time?: string +} + +// 列表接口返回的字段(包含 username 和 problem_title) +export interface FlowchartSubmissionListItem { + id: string + create_time: string + evaluation_time: string + ai_score: number + ai_grade: Grade + ai_model: string + ai_provider: string + processing_time: number + status: number + username: string + problem_title: string + problem: string + show_link: boolean +} +export interface SubmitFlowchartPayload { + problem_id: number + mermaid_code: string + flowchart_data?: Record +} + +interface Info { + err: string | null + data: { + error: number + memory: number + output: null + result: SUBMISSION_RESULT + signal: number + cpu_time: number + exit_code: number + real_time: number + test_case: string + output_md5: string + }[] +} + +export interface Submission { + id: string + create_time: Date + user_id: number + username: string + code: string + result: SUBMISSION_RESULT + info: Info + language: LANGUAGE + shared: boolean + show_link: boolean + statistic_info: { + score?: number + err_info?: string + time_cost?: number + memory_cost?: number + ast_results?: Array<{ description: string; passed: boolean }> + } + ip: string + contest: number + problem: number // 不是 display_id + can_unshare: boolean +} + +export interface SubmissionListItem { + id: string + problem: string + problem_title: string + show_link: boolean + create_time: string + user_id: number + username: string + result: SUBMISSION_RESULT + language: LANGUAGE + shared: boolean + statistic_info: { + time_cost: number + memory_cost: number + } +} + +export interface SubmissionListPayload { + myself?: "1" | "0" + result?: string + username?: string + contest_id?: string + problem_id?: string + language: LANGUAGE | "" + today?: "1" | "0" + page: number + limit: number + offset: number +} + +export interface Rank { + id: number + user: SampleUser + acm_problems_status: { + problems: { + [key: string]: { + _id: string + status: number + } + } + contest_problems?: { + [key: string]: { + [key: string]: { + _id: string + status: number + } + } + } + } + real_name: null | string + avatar: string + blog: null + mood: null | string + github: null + school: null | string + major: null | string + language: null | string + accepted_number: number + submission_number: number +} + +export interface Contest extends BlankContest { + id: number + created_by: SampleUser + status: ContestStatus + contest_type: ContestType + create_time: string + now: string + last_update_time: string +} + +export interface BlankContest { + title: string + description: string + tag: string + start_time: string + end_time: string + password: string + visible: boolean + allowed_ip_ranges: { value: string }[] +} + +interface SubmissionInfo { + is_ac: boolean + ac_time: number + is_first_ac: boolean + error_number: number + checked?: boolean +} + +export interface ContestRank { + id: number + user: SampleUser + submission_number: number + accepted_number: number + total_time: number + submission_info: { [key: string]: SubmissionInfo } + contest: number +} + +export interface WebsiteConfig { + website_base_url: string + website_name: string + website_name_shortcut: string + website_footer: string + allow_register: boolean + submission_list_show_all: boolean + class_list: string[] & never[] + enable_maxkb: boolean +} + +export interface Server { + id: number + status: "abnormal" | "normal" + hostname: string + ip: string + judger_version: string + cpu_core: number + memory_usage: number + cpu_usage: number + last_heartbeat: Date + create_time: Date + task_number: number + service_url: string + is_disabled: boolean +} + +export interface AnnouncementEdit { + id: number + title: string + tag: string + content: string + visible: boolean + top: boolean +} + +export interface Announcement extends AnnouncementEdit { + created_by: SampleUser + create_time: Date + last_update_time: Date +} + +export interface Message { + sender: User + create_time: Date + message: string + submission: Submission +} + +export interface CreateMessage { + sender: string + recipient: string + submission: string + message: string +} + +export type ReactionKey = + | "too_easy" + | "too_hard" + | "confusing" + | "buggy" + | "learned" + | "interesting" + | "want_explain" + +export type ReactionCounts = Record + +export interface ReactionState { + mine: ReactionKey | null + counts: ReactionCounts | null +} + +export interface Tutorial { + id: number + title: string + content: string + code: string + is_public: boolean + order: number + type: "python" | "c" + created_by?: User + updated_at?: Date + created_at?: Date +} + +export interface ExerciseMcqData { + question: string + options: string[] + answer: number[] +} + +export interface ExerciseSortData { + question: string + lines: string[] +} + +export interface ExerciseFillData { + question: string + code: string +} + +export interface ExerciseMatchData { + question: string + left: string[] + right: string[] + answer: number[] +} + +export interface ExercisePredictData { + question: string + code: string + answer: string[] +} + +export interface ExerciseDebugData { + question: string + lines: string[] + answer: number[] + explanation?: string +} + +export interface ExerciseGroupData { + question: string + buckets: string[] + items: string[] + answer: number[] +} + +export type ExerciseType = + "mcq" | "sort" | "fill" | "match" | "predict" | "debug" | "group" + +export interface Exercise { + id: number + type: ExerciseType + data: + | ExerciseMcqData + | ExerciseSortData + | ExerciseFillData + | ExerciseMatchData + | ExercisePredictData + | ExerciseDebugData + | ExerciseGroupData + order: number +} + +export interface DurationData { + unit: string + index: number + start: string + end: string + grade: Grade + problem_count: number + submission_count: number +} + +export interface SolvedProblem { + problem: { + title: string + display_id: string + contest_title: string + contest_id: number + } + ac_time: string + rank: number + ac_count: number + grade: Grade + period_rank: number + period_ac_count: number + difficulty: string +} + +export interface FlowchartSummary { + problem__id: string + problem_title: string + submission_count: number + best_score: number + best_grade: string + latest_submission_time: string + avg_score: number +} + +export interface DetailsData { + start: string + end: string + grade: Grade + class_name: string + tags: { [key: string]: number } + difficulty: { [key: string]: number } + contest_count: number + solved: SolvedProblem[] + flowcharts: FlowchartSummary[] +} + +export type Grade = "S" | "A" | "B" | "C" + +// ==================== 成就相关类型 ==================== + +export type AchievementRarity = "bronze" | "silver" | "gold" | "platinum" + +export interface Achievement { + id: number + name: string + description: string + icon: string + rarity: AchievementRarity + hidden: boolean + // 隐藏成就未解锁时,后端已做掩码处理,以下四个字段为 null + metric: string | null + operator: "gte" | "lte" | null + threshold: number | null + unlocked: boolean + unlock_time: string | null + backfilled: boolean + progress: number | null + unlock_rate: number +} + +export interface AchievementRarityStat { + rarity: AchievementRarity + label: string + total: number + unlocked: number +} + +export interface PendingAchievement { + id: number + name: string + description: string + icon: string + rarity: AchievementRarity + // 弹窗队列里混着两种东西:全站成就和题单奖章。它们的 id 来自两张不同的表, + // 数值会重叠,所以去重和标记已读都必须带上 kind 一起判断。 + // pending 接口只返回成就,不带这个字段,缺省按 achievement 处理。 + kind?: "achievement" | "badge" +} + +export interface AchievementSummary { + username: string + total: number + unlocked: number + percent: number + rarity: AchievementRarityStat[] + recent: PendingAchievement[] +} diff --git a/apps/web/tests/submitButtonState.test.ts b/apps/web/tests/submitButtonState.test.ts new file mode 100644 index 0000000..d3f3735 --- /dev/null +++ b/apps/web/tests/submitButtonState.test.ts @@ -0,0 +1,51 @@ +import assert from "node:assert/strict" +import test from "node:test" +import { getSubmitButtonState } from "../src/oj/problem/components/submitButtonState.ts" + +const idleInput = { + isAuthed: true, + hasCode: true, + isFormatting: false, + isSubmitting: false, + isJudging: false, + isCooldown: false, +} + +test("shows a disabled loading state while formatting", () => { + assert.deepEqual(getSubmitButtonState({ ...idleInput, isFormatting: true }), { + disabled: true, + label: "格式化中", + icon: "eos-icons:loading", + }) +}) + +test("shows submitting immediately after formatting", () => { + assert.deepEqual(getSubmitButtonState({ ...idleInput, isSubmitting: true }), { + disabled: true, + label: "正在提交", + icon: "eos-icons:loading", + }) +}) + +test("preserves existing login, judging, cooldown, and idle states", () => { + assert.deepEqual(getSubmitButtonState({ ...idleInput, isAuthed: false }), { + disabled: true, + label: "请先登录", + icon: "ph:play-fill", + }) + assert.deepEqual(getSubmitButtonState({ ...idleInput, isJudging: true }), { + disabled: true, + label: "正在评分", + icon: "eos-icons:loading", + }) + assert.deepEqual(getSubmitButtonState({ ...idleInput, isCooldown: true }), { + disabled: true, + label: "正在冷却", + icon: "ph:lightbulb-fill", + }) + assert.deepEqual(getSubmitButtonState(idleInput), { + disabled: false, + label: "提交代码", + icon: "ph:play-fill", + }) +}) diff --git a/apps/web/tsconfig.app.json b/apps/web/tsconfig.app.json new file mode 100644 index 0000000..47335e5 --- /dev/null +++ b/apps/web/tsconfig.app.json @@ -0,0 +1,23 @@ +{ + "extends": "@vue/tsconfig/tsconfig.dom.json", + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", + // Vite(oxc) 遵守 verbatimModuleSyntax,会保留未写 type 的纯类型导入导致构建报错; + // 关掉以让转换器自动消除类型导入(与原 Rsbuild 行为一致) + "verbatimModuleSyntax": false, + "paths": { + "utils/*": ["./src/utils/*"], + "oj/*": ["./src/oj/*"], + "admin/*": ["./src/admin/*"], + "shared/*": ["./src/shared/*"] + }, + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true + }, + "include": ["src/**/*.ts", "src/**/*.vue"] +} diff --git a/apps/web/tsconfig.json b/apps/web/tsconfig.json new file mode 100644 index 0000000..1ffef60 --- /dev/null +++ b/apps/web/tsconfig.json @@ -0,0 +1,7 @@ +{ + "files": [], + "references": [ + { "path": "./tsconfig.app.json" }, + { "path": "./tsconfig.node.json" } + ] +} diff --git a/apps/web/tsconfig.node.json b/apps/web/tsconfig.node.json new file mode 100644 index 0000000..db0becc --- /dev/null +++ b/apps/web/tsconfig.node.json @@ -0,0 +1,24 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", + "target": "ES2022", + "lib": ["ES2023"], + "module": "ESNext", + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "isolatedModules": true, + "moduleDetection": "force", + "noEmit": true, + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true + }, + "include": ["vite.config.ts"] +} diff --git a/apps/web/vite.config.ts b/apps/web/vite.config.ts new file mode 100644 index 0000000..50db440 --- /dev/null +++ b/apps/web/vite.config.ts @@ -0,0 +1,175 @@ +import { fileURLToPath, URL } from "node:url" +import { defineConfig, loadEnv, type Plugin } from "vite" +import vue from "@vitejs/plugin-vue" +import legacy from "@vitejs/plugin-legacy" +import AutoImport from "unplugin-auto-import/vite" +import Components from "unplugin-vue-components/vite" +import { NaiveUiResolver } from "unplugin-vue-components/resolvers" + +// 显式保留 Chrome 90 所需的运行时兼容项,避免 plugin-legacy 对每个产物执行 Babel 扫描。 +// 升级前端依赖后需要重新审计此列表。 +const polyfills = [ + "es.aggregate-error.cause", + "es.array-buffer.detached", + "es.array-buffer.transfer-to-fixed-length", + "es.array-buffer.transfer", + "es.array.at", + "es.array.find-last-index", + "es.array.push", + "es.array.to-reversed", + "es.array.to-sorted", + "es.array.to-spliced", + "es.array.with", + "es.error.cause", + "es.iterator.constructor", + "es.iterator.drop", + "es.iterator.every", + "es.iterator.filter", + "es.iterator.find", + "es.iterator.flat-map", + "es.iterator.for-each", + "es.iterator.map", + "es.iterator.reduce", + "es.iterator.some", + "es.iterator.to-array", + "es.json.parse", + "es.json.stringify", + "es.map.get-or-insert-computed", + "es.map.get-or-insert", + "es.object.has-own", + "es.regexp.flags", + "es.set.difference.v2", + "es.set.intersection.v2", + "es.set.is-disjoint-from.v2", + "es.set.is-subset-of.v2", + "es.set.is-superset-of.v2", + "es.set.symmetric-difference.v2", + "es.set.union.v2", + "es.string.at-alternative", + "es.typed-array.at", + "es.typed-array.find-last-index", + "es.typed-array.find-last", + "es.typed-array.set", + "es.typed-array.to-reversed", + "es.typed-array.to-sorted", + "es.typed-array.with", + "es.uint8-array.set-from-base64", + "es.uint8-array.set-from-hex", + "es.uint8-array.to-base64", + "es.uint8-array.to-hex", + "es.weak-map.get-or-insert-computed", + "es.weak-map.get-or-insert", + "esnext.array.group", + "web.dom-exception.stack", + "web.immediate", + "web.structured-clone", + "web.url-search-params.delete", + "web.url-search-params.has", + "web.url-search-params.size", + "web.url.can-parse", +] + +// index.html 里按需注入 MaxKB 脚本(原 Rsbuild EJS 模板的等价实现) +function injectMaxkb(maxkbUrl: string | undefined): Plugin { + return { + name: "inject-maxkb", + transformIndexHtml(html) { + if (!maxkbUrl) return html + return { + html, + tags: [ + { + tag: "script", + attrs: { async: true, defer: true, src: maxkbUrl }, + injectTo: "head", + }, + ], + } + }, + } +} + +export default defineConfig(({ mode }) => { + const env = loadEnv(mode, process.cwd(), "PUBLIC_") + + const proxyConfig = { + target: env["PUBLIC_OJ_URL"], + changeOrigin: true, + } + + const wsProxyConfig = { + target: env["PUBLIC_WS_URL"], + ws: true, + changeOrigin: true, + } + + return { + plugins: [ + vue(), + // 机房存在 Chrome 91 等旧浏览器:不做 SystemJS 双构建, + // 只按使用情况给现代产物注入 core-js API polyfill(Array.at 等) + legacy({ + renderLegacyChunks: false, + modernTargets: "chrome>=90", + modernPolyfills: polyfills, + }), + AutoImport({ + imports: [ + "vue", + "vue-router", + "@vueuse/core", + "pinia", + { + "naive-ui": [ + "useDialog", + "useMessage", + "useNotification", + "useLoadingBar", + ], + }, + { + from: "naive-ui", + imports: [ + "DataTableColumn", + "FormRules", + "FormItemRule", + "SelectOption", + "UploadCustomRequestOptions", + "UploadFileInfo", + "MenuOption", + "DropdownDividerOption", + "DropdownOption", + ], + type: true, + }, + ], + dts: "./src/auto-imports.d.ts", + }), + Components({ + resolvers: [NaiveUiResolver()], + dts: "./src/components.d.ts", + }), + injectMaxkb(env["PUBLIC_MAXKB_URL"]), + ], + envPrefix: "PUBLIC_", + resolve: { + alias: { + // mermaid-legacy (mermaid@9) 写死了 UMD 路径,新版 cytoscape 的 exports + // 不允许 import 条件访问它,转到 ESM 产物 + "cytoscape/dist/cytoscape.umd.js": "cytoscape/dist/cytoscape.esm.mjs", + utils: fileURLToPath(new URL("./src/utils", import.meta.url)), + oj: fileURLToPath(new URL("./src/oj", import.meta.url)), + admin: fileURLToPath(new URL("./src/admin", import.meta.url)), + shared: fileURLToPath(new URL("./src/shared", import.meta.url)), + }, + }, + server: { + port: 5173, + proxy: { + "/api": proxyConfig, + "/public": proxyConfig, + "/ws": wsProxyConfig, + }, + }, + } +}) diff --git a/bun.lock b/bun.lock index f31ddda..1d518e3 100644 --- a/bun.lock +++ b/bun.lock @@ -23,6 +23,64 @@ "drizzle-kit": "^0.31.10", }, }, + "apps/web": { + "name": "@oj2/web", + "version": "1.9.0", + "dependencies": { + "@codemirror/autocomplete": "^6.20.3", + "@codemirror/lang-cpp": "^6.0.3", + "@codemirror/lang-python": "^6.2.1", + "@codemirror/lang-sql": "^6.10.0", + "@oj2/contract": "workspace:*", + "@vue-flow/background": "^1.3.2", + "@vue-flow/controls": "^1.1.3", + "@vue-flow/core": "^1.48.2", + "@vue-flow/minimap": "^1.5.4", + "@vue-flow/node-resizer": "^1.5.1", + "@vue-flow/node-toolbar": "^1.1.1", + "@vueuse/core": "^14.4.0", + "@vueuse/router": "^14.4.0", + "@wangeditor-next/editor": "^6.2.0", + "@wangeditor-next/editor-for-vue": "^6.2.0", + "axios": "^1.19.0", + "canvas-confetti": "^1.9.4", + "chart.js": "^4.5.1", + "chartjs-chart-wordcloud": "^4.4.5", + "codemirror": "^6.0.2", + "copy-text-to-clipboard": "^3.2.2", + "date-fns": "^4.4.0", + "fflate": "^0.8.3", + "highlight.js": "^11.11.1", + "md-editor-v3": "^6.5.6", + "mermaid": "^11.16.1", + "mermaid-legacy": "npm:mermaid@^9.4.3", + "naive-ui": "^2.44.1", + "nanoid": "^6.0.1", + "normalize.css": "^8.0.1", + "pinia": "^4.0.2", + "skulpt": "^1.2.0", + "vue": "^3.5.41", + "vue-chartjs": "^5.3.4", + "vue-codemirror": "^6.1.1", + "vue-router": "^5.2.0", + "y-codemirror.next": "^0.3.5", + "y-webrtc": "^10.3.0", + "yjs": "^13.6.32", + }, + "devDependencies": { + "@iconify/vue": "^5.0.1", + "@types/canvas-confetti": "^1.9.0", + "@types/node": "^26.1.2", + "@vitejs/plugin-legacy": "^8.2.3", + "@vitejs/plugin-vue": "^6.0.8", + "@vue/tsconfig": "^0.9.1", + "prettier": "^3.9.6", + "typescript": "^7.0.2", + "unplugin-auto-import": "^21.1.0", + "unplugin-vue-components": "^32.1.0", + "vite": "^8.2.1", + }, + }, "packages/contract": { "name": "@oj2/contract", "version": "0.0.0", @@ -32,202 +90,1438 @@ }, }, "packages": { - "@drizzle-team/brocli": ["@drizzle-team/brocli@0.10.2", "", {}, "sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w=="], + "@antfu/install-pkg": ["@antfu/install-pkg@1.1.0", "", { "dependencies": { "package-manager-detector": "^1.3.0", "tinyexec": "^1.0.1" } }, "sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ=="], - "@esbuild-kit/core-utils": ["@esbuild-kit/core-utils@3.3.2", "", { "dependencies": { "esbuild": "~0.18.20", "source-map-support": "^0.5.21" } }, "sha512-sPRAnw9CdSsRmEtnsl2WXWdyquogVpB3yZ3dgwJfe8zrOzTsV7cJvmwrKVa+0ma5BoiGJ+BoqkMvawbayKUsqQ=="], + "@babel/code-frame": ["@babel/code-frame@7.29.7", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.29.7", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw=="], - "@esbuild-kit/esm-loader": ["@esbuild-kit/esm-loader@2.6.5", "", { "dependencies": { "@esbuild-kit/core-utils": "^3.3.2", "get-tsconfig": "^4.7.0" } }, "sha512-FxEMIkJKnodyA1OaCUoEvbYRkoZlLZ4d/eXFu9Fh8CbBBgP5EmZxrfTRyN0qpXZ4vOvqnE5YdRdcrmUUXuU+dA=="], + "@babel/compat-data": ["@babel/compat-data@7.29.7", "", {}, "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg=="], - "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.12", "", { "os": "aix", "cpu": "ppc64" }, "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA=="], + "@babel/core": ["@babel/core@7.29.7", "", { "dependencies": { "@babel/code-frame": "^7.29.7", "@babel/generator": "^7.29.7", "@babel/helper-compilation-targets": "^7.29.7", "@babel/helper-module-transforms": "^7.29.7", "@babel/helpers": "^7.29.7", "@babel/parser": "^7.29.7", "@babel/template": "^7.29.7", "@babel/traverse": "^7.29.7", "@babel/types": "^7.29.7", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA=="], - "@esbuild/android-arm": ["@esbuild/android-arm@0.25.12", "", { "os": "android", "cpu": "arm" }, "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg=="], + "@babel/generator": ["@babel/generator@8.0.0", "", { "dependencies": { "@babel/parser": "^8.0.0", "@babel/types": "^8.0.0", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "@types/jsesc": "^2.5.0", "jsesc": "^3.0.2" } }, "sha512-NT9NrVwJsbSV6Y2FSstWa71EETOnzrjkL5/wX3D2mYHtKM+qvqB1DvR4D0Setb/gDBsHzRICifwEWMO8CnTF6g=="], - "@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.12", "", { "os": "android", "cpu": "arm64" }, "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg=="], + "@babel/helper-annotate-as-pure": ["@babel/helper-annotate-as-pure@7.29.7", "", { "dependencies": { "@babel/types": "^7.29.7" } }, "sha512-OoK6239jHPuSQOoS0kfTVKn0b/rVTk0seKq4Gd2UMLtmOVLjDC0ki3e+c90Trqv2gMfvJFqkiljrr568+qddiw=="], - "@esbuild/android-x64": ["@esbuild/android-x64@0.25.12", "", { "os": "android", "cpu": "x64" }, "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg=="], + "@babel/helper-compilation-targets": ["@babel/helper-compilation-targets@7.29.7", "", { "dependencies": { "@babel/compat-data": "^7.29.7", "@babel/helper-validator-option": "^7.29.7", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" } }, "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g=="], - "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.12", "", { "os": "darwin", "cpu": "arm64" }, "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg=="], + "@babel/helper-create-class-features-plugin": ["@babel/helper-create-class-features-plugin@7.29.7", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.29.7", "@babel/helper-member-expression-to-functions": "^7.29.7", "@babel/helper-optimise-call-expression": "^7.29.7", "@babel/helper-replace-supers": "^7.29.7", "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7", "@babel/traverse": "^7.29.7", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-IY3ZD9Tmooqr3TUhc3DUWxiuo8xx1DWLhd5M7hQ+ZWJamqM2BbalrBJb2MisSLoYorOj75U03qULCxQTY9r3hg=="], - "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.12", "", { "os": "darwin", "cpu": "x64" }, "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA=="], + "@babel/helper-create-regexp-features-plugin": ["@babel/helper-create-regexp-features-plugin@7.29.7", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.29.7", "regexpu-core": "^6.3.1", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-907Uymvqgg1dwUA+7IGwFAOSYzQOuzPXKNJ1yxzwPffzkYFg2q2eHi1fIOs6sXkG9NbIUMunnUlkYsfRFNvomg=="], - "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.12", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg=="], + "@babel/helper-define-polyfill-provider": ["@babel/helper-define-polyfill-provider@0.6.8", "", { "dependencies": { "@babel/helper-compilation-targets": "^7.28.6", "@babel/helper-plugin-utils": "^7.28.6", "debug": "^4.4.3", "lodash.debounce": "^4.0.8", "resolve": "^1.22.11" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "sha512-47UwBLPpQi1NoWzLuHNjRoHlYXMwIJoBf7MFou6viC/sIHWYygpvr0B6IAyh5sBdA2nr2LPIRww8lfaUVQINBA=="], - "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.12", "", { "os": "freebsd", "cpu": "x64" }, "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ=="], + "@babel/helper-globals": ["@babel/helper-globals@7.29.7", "", {}, "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA=="], - "@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.12", "", { "os": "linux", "cpu": "arm" }, "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw=="], + "@babel/helper-member-expression-to-functions": ["@babel/helper-member-expression-to-functions@7.29.7", "", { "dependencies": { "@babel/traverse": "^7.29.7", "@babel/types": "^7.29.7" } }, "sha512-j+7JYmk1JYDtACIGj0QJqqWZjoUpMoEikQGADMaHgCMCSDqd2+P32rfcibUNrGOMWrlzK1WJBdxrB3JJQZwWtg=="], - "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.12", "", { "os": "linux", "cpu": "arm64" }, "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ=="], + "@babel/helper-module-imports": ["@babel/helper-module-imports@7.29.7", "", { "dependencies": { "@babel/traverse": "^7.29.7", "@babel/types": "^7.29.7" } }, "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g=="], - "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.12", "", { "os": "linux", "cpu": "ia32" }, "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA=="], + "@babel/helper-module-transforms": ["@babel/helper-module-transforms@7.29.7", "", { "dependencies": { "@babel/helper-module-imports": "^7.29.7", "@babel/helper-validator-identifier": "^7.29.7", "@babel/traverse": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg=="], - "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng=="], + "@babel/helper-optimise-call-expression": ["@babel/helper-optimise-call-expression@7.29.7", "", { "dependencies": { "@babel/types": "^7.29.7" } }, "sha512-+kmGVjcT9RGYzoDwdwEqEvGgKe3BYq+O1iGzjFubaNgZHwYHP6lsF2Yghf4kEuv9BV7tYDZ913aBW9am6YKong=="], - "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw=="], + "@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.29.7", "", {}, "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw=="], - "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.12", "", { "os": "linux", "cpu": "ppc64" }, "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA=="], + "@babel/helper-remap-async-to-generator": ["@babel/helper-remap-async-to-generator@7.29.7", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.29.7", "@babel/helper-wrap-function": "^7.29.7", "@babel/traverse": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-16AMiW26DbXWBbr3B8wNozKM0ydMLB892vaOaJW/fPJdnT8vJk5sdkQcU/isqUxyCE0cEoa8wZOcbgDuC4b6Og=="], - "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w=="], + "@babel/helper-replace-supers": ["@babel/helper-replace-supers@7.29.7", "", { "dependencies": { "@babel/helper-member-expression-to-functions": "^7.29.7", "@babel/helper-optimise-call-expression": "^7.29.7", "@babel/traverse": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-atfGXWSeCiF4DnKZIfmJfQRkSw9b9gNNXR1kqKjbhG4pGYCOnkp8OcTB8E3NXjBu8NpheSnOeNKz8KT7UNFTmQ=="], - "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.12", "", { "os": "linux", "cpu": "s390x" }, "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg=="], + "@babel/helper-skip-transparent-expression-wrappers": ["@babel/helper-skip-transparent-expression-wrappers@7.29.7", "", { "dependencies": { "@babel/traverse": "^7.29.7", "@babel/types": "^7.29.7" } }, "sha512-brcMGQaVzIeUb+6/bs1Av0f8YuNNjKY2JyvfRCsFuFsdKccEQ5Ges2y74D74NZ1Rz8lKJ9ksJkfqwQFJ/iNEyQ=="], - "@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.12", "", { "os": "linux", "cpu": "x64" }, "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw=="], + "@babel/helper-string-parser": ["@babel/helper-string-parser@7.29.7", "", {}, "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw=="], - "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.12", "", { "os": "none", "cpu": "arm64" }, "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg=="], + "@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.29.7", "", {}, "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg=="], - "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.12", "", { "os": "none", "cpu": "x64" }, "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ=="], + "@babel/helper-validator-option": ["@babel/helper-validator-option@7.29.7", "", {}, "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw=="], - "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.12", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A=="], + "@babel/helper-wrap-function": ["@babel/helper-wrap-function@7.29.7", "", { "dependencies": { "@babel/template": "^7.29.7", "@babel/traverse": "^7.29.7", "@babel/types": "^7.29.7" } }, "sha512-iES0Skag9ERIF68aXadpO6dbXa03mNWK3sEqJaMnLNs/eC3l0lkImdfoy6Y09/SfkpawdAB4RjQ7PVA7TcVGdw=="], - "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.12", "", { "os": "openbsd", "cpu": "x64" }, "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw=="], + "@babel/helpers": ["@babel/helpers@7.29.7", "", { "dependencies": { "@babel/template": "^7.29.7", "@babel/types": "^7.29.7" } }, "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg=="], - "@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.25.12", "", { "os": "none", "cpu": "arm64" }, "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg=="], + "@babel/parser": ["@babel/parser@7.29.8", "", { "dependencies": { "@babel/types": "^7.29.8" }, "bin": "./bin/babel-parser.js" }, "sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA=="], - "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.12", "", { "os": "sunos", "cpu": "x64" }, "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w=="], + "@babel/plugin-bugfix-firefox-class-in-computed-class-key": ["@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7", "@babel/traverse": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-j8SrR0zLZrRsC09DlszEx8FpMiwukKffYXMK0d5LmOglO7vGG6sz/BR/20yHqWH+Lnn31JTt2PE3hIWNgM2J6w=="], - "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.12", "", { "os": "win32", "cpu": "arm64" }, "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg=="], + "@babel/plugin-bugfix-safari-class-field-initializer-scope": ["@babel/plugin-bugfix-safari-class-field-initializer-scope@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-r8j8escF+U2FUHo0KOhPUdMzUO+jp9fInva6+ACVAF3Y97Ev+5iNZwiqTghmzNeWwDkOPlYuTcfb1vDaoZKmAQ=="], - "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.12", "", { "os": "win32", "cpu": "ia32" }, "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ=="], + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": ["@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-GE1TFSiuFeGsCxmYXZl8HwoPrVlwe4rHPFE8weieGKZqnDORK+Ar3vgWMgW+AOxQ6/2TgLSKx9p6W7O4rC6qgQ=="], - "@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.12", "", { "os": "win32", "cpu": "x64" }, "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA=="], + "@babel/plugin-bugfix-safari-rest-destructuring-rhs-array": ["@babel/plugin-bugfix-safari-rest-destructuring-rhs-array@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7", "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-oBNVCvnO5tND+xSopWvV8WNGfpTfgP4Zr/YXXSj8zfmcPktp5Ku/aZlsIowgSD4fjmgHn6sGmB9APVsU5zOdhA=="], + + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": ["@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7", "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7", "@babel/plugin-transform-optional-chaining": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.13.0" } }, "sha512-QQt9qKHZ2sg/kivaLr7lnQr8HVrQDdBNSfCsTjiDxRuX/K5ORyKq+Bu8Xr0cDE3Dfkv0cw28Ve0EKyKMvulkOw=="], + + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": ["@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7", "@babel/traverse": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-pn6QacGLgvCcwc+syUhKE/qSjV2D1IHDB84RNxWYSt1mW3K/SCtjinZ2p0cETJxAWBjPy3K/1lHwG5BjjPxNlw=="], + + "@babel/plugin-proposal-private-property-in-object": ["@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2", "", { "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w=="], + + "@babel/plugin-syntax-import-assertions": ["@babel/plugin-syntax-import-assertions@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-/An1OCBN93thpBAGyfsK2pcf0jvju1SAtKkL2Ny++B5Sy6sqgzXDQH1cZxWbF96Wuk+bn41MDA9bLd4VVAw6rw=="], + + "@babel/plugin-syntax-import-attributes": ["@babel/plugin-syntax-import-attributes@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-zGYcYfq/WmZ4V+kBIXQon9dSSc8ircGZqw9ZaNhhGj9nZkeBu1jHLBDQqYYi5WA9uawvA2sIMbry2nCFhf5Djg=="], + + "@babel/plugin-syntax-unicode-sets-regex": ["@babel/plugin-syntax-unicode-sets-regex@7.18.6", "", { "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.18.6", "@babel/helper-plugin-utils": "^7.18.6" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg=="], + + "@babel/plugin-transform-arrow-functions": ["@babel/plugin-transform-arrow-functions@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-N7zArUXWzAMzm+/N0uPBeVB3Fam5lMxtUwMmDK5f/IBBS7a7p1qeUoxd/6CckXoxUdgsntq1Dh8xNW06maZbDQ=="], + + "@babel/plugin-transform-async-generator-functions": ["@babel/plugin-transform-async-generator-functions@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7", "@babel/helper-remap-async-to-generator": "^7.29.7", "@babel/traverse": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-d98gXZkgswvkyohMBABkhm3GeXhYj8psWfwQ2C7gtfrKGTykQa/iOIi+JJhwMjPlZ6Vm2XN+DCf3Es1EoG4ZLA=="], + + "@babel/plugin-transform-async-to-generator": ["@babel/plugin-transform-async-to-generator@7.29.7", "", { "dependencies": { "@babel/helper-module-imports": "^7.29.7", "@babel/helper-plugin-utils": "^7.29.7", "@babel/helper-remap-async-to-generator": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-pcUb2SS+RMo9TWVBwKGI5ShtoG7R+zBsFmCKDa6fe8c+hPr3XJlZgoE5j6i8W7gDjhyvy+85vmYexanvXh3d1w=="], + + "@babel/plugin-transform-block-scoped-functions": ["@babel/plugin-transform-block-scoped-functions@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-cUSmjh72N+rN4PrkFlN1dJwNCwjVp5d38/CQrEsFggkD10UiFlBFgdH3tv5dNsLuHY+3S8db2xCHjhZcv5WgvA=="], + + "@babel/plugin-transform-block-scoping": ["@babel/plugin-transform-block-scoping@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-ONyr4+AZhKh8yKWInVxU9AXA9EbsyeLcL6V0dJy6M2/62vuvpGm29zzuymbTpdc451GEpDIdAyPLP3r+P61yKQ=="], + + "@babel/plugin-transform-class-properties": ["@babel/plugin-transform-class-properties@7.29.7", "", { "dependencies": { "@babel/helper-create-class-features-plugin": "^7.29.7", "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-GtcpjFvanPfzNQi3eTitsCqtRRmmqzpy/A+yhTR1HaZo1Ly3EA8ZXxlPyHdR8/IuRMYc3E4wdGBewB2QKQjAaA=="], + + "@babel/plugin-transform-class-static-block": ["@babel/plugin-transform-class-static-block@7.29.7", "", { "dependencies": { "@babel/helper-create-class-features-plugin": "^7.29.7", "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.12.0" } }, "sha512-kibJgmEdX2iMwsHY2tSZNDgj8PwIlCQz7FK9KuGKO8zsuoUwSEhoNnNVp/emKWrbY4HeO6kkXfdMqRKKKXBm2A=="], + + "@babel/plugin-transform-classes": ["@babel/plugin-transform-classes@7.29.7", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.29.7", "@babel/helper-compilation-targets": "^7.29.7", "@babel/helper-globals": "^7.29.7", "@babel/helper-plugin-utils": "^7.29.7", "@babel/helper-replace-supers": "^7.29.7", "@babel/traverse": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-qV0OGGBVacduzQHE649JyCneOFI/maT+YKsO+K4Yi3xv2wTPNjM/W2o2gdzMwEAZz7fXNTHAe0NcSg30bIN69g=="], + + "@babel/plugin-transform-computed-properties": ["@babel/plugin-transform-computed-properties@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7", "@babel/template": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-RK7/IyU5phpuCdBAuig5VkzG/EnbDaui5SQGdU9BFrHdV+mV4cUjLMQ9lJDjLNtWHsqtiefpGZUXQP2BiTYMsA=="], + + "@babel/plugin-transform-destructuring": ["@babel/plugin-transform-destructuring@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7", "@babel/traverse": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-iPX8aD6H9zV5s7ZsqTdNocPN/MGQ5sSMnElKrktxjJRMnB2jN/1p2+R7GkfD6CAYoVFqy5A4XnSIUeGgJzIWpg=="], + + "@babel/plugin-transform-dotall-regex": ["@babel/plugin-transform-dotall-regex@7.29.7", "", { "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.29.7", "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-3qc18hsD2RdZiyJNDNc7HQpv6xbncwh8FYtxNFFzclSyh/trPD9KkVR9BDECUjDLvb7yJVF15GfYUuC+LMkkiQ=="], + + "@babel/plugin-transform-duplicate-keys": ["@babel/plugin-transform-duplicate-keys@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-6IvRRriEMqnBwD6chtxdLpMYCHWEzN+oL5cyQtjykya19UgzbmKhxmhZgKC/LHxS2nYr9Q/qYPZ5Lr6jOL9+yQ=="], + + "@babel/plugin-transform-duplicate-named-capturing-groups-regex": ["@babel/plugin-transform-duplicate-named-capturing-groups-regex@7.29.7", "", { "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.29.7", "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-2wiIyo2BjtgU7HufSeDnL9L2O7zr8jmhFKuSr65VpRkUiRKRNpb0mdlk56+XPPKoIrfHqzbMuglDvZun0RISsA=="], + + "@babel/plugin-transform-dynamic-import": ["@babel/plugin-transform-dynamic-import@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-giOlEm/EFjfjr+te9NsdjkUo2v4f8rS/SXPumRVHAtbNcyNlvtREkU1dZzaIDclNpnaVhlCqRdFKhJBjBikzLg=="], + + "@babel/plugin-transform-explicit-resource-management": ["@babel/plugin-transform-explicit-resource-management@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7", "@babel/plugin-transform-destructuring": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-Rstj7coNz8sE+7Ju7ihpHLI564lsK5pUpNNlvptCIC/16E/S5hbl6n3kESPKdNRmqEWlpn5xpS5Q2dvXBsySLw=="], + + "@babel/plugin-transform-exponentiation-operator": ["@babel/plugin-transform-exponentiation-operator@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-zFpMOTLZBdW5LfObqcSbL6kefg4R4eLdmvS0wbN9M6D5Mym/sKm9toOoWyVOa+xDjvCnuWcHls2YonXwHvH3CQ=="], + + "@babel/plugin-transform-export-namespace-from": ["@babel/plugin-transform-export-namespace-from@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-24B2nOy2TeJSMheqwPD4DDQOV/elLSIlKxjZt4i05H5AgdPdWR3n18HnNrcJ+j76WJd9gbwb9jPjNYUy6RautA=="], + + "@babel/plugin-transform-for-of": ["@babel/plugin-transform-for-of@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7", "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-zeSIHh0+E1Um1WJRXCFlHQYu2ieJNdivLLjlBEp+dIBu3S51n+SZZmIXjxnItw6pz56Cn+KvK68BIBVsxq2JiQ=="], + + "@babel/plugin-transform-function-name": ["@babel/plugin-transform-function-name@7.29.7", "", { "dependencies": { "@babel/helper-compilation-targets": "^7.29.7", "@babel/helper-plugin-utils": "^7.29.7", "@babel/traverse": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-otRWaHXE6fbAGkePvaj/kvs3HsqXfPhlnzwSOlnFgbqCPMd975dW+4wZ00WFBt+/YlBGcJwNrARQTOJOb4ZrIg=="], + + "@babel/plugin-transform-json-strings": ["@babel/plugin-transform-json-strings@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-RRnE2+eon1rJAq8MnoF1b5kTpY1vU88twHcvcKMrsqP/jxIRqDVs9iJB5fqPuqyeFAW0wJo4MlUIPpQCq/aRsg=="], + + "@babel/plugin-transform-literals": ["@babel/plugin-transform-literals@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-DZ/oLP21ZuWx1vKqnoNv6/tvEK48AQOBRai40CX9dTjGluvT/YZCyY3rryDtyUqCEoyNroy5KKPwX2iQCiRvyw=="], + + "@babel/plugin-transform-logical-assignment-operators": ["@babel/plugin-transform-logical-assignment-operators@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-A0H91hh6W8MFRkp5TqJmMr39jzGD1A1E1Ysiv2O06Sfbhkapm+XyIzxWCEh5kqwOZ1/8QZ0dY3SeQ7XBqfJd5Q=="], + + "@babel/plugin-transform-member-expression-literals": ["@babel/plugin-transform-member-expression-literals@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-hl1kwFZCCiDyfH25Xmco9jTrkPgnS9pmOzSG7W5I4SaGbLeqKv417hcU2RKmaxoPEgsoJh7ZPOrnPGq99bHoUg=="], + + "@babel/plugin-transform-modules-amd": ["@babel/plugin-transform-modules-amd@7.29.7", "", { "dependencies": { "@babel/helper-module-transforms": "^7.29.7", "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-fxtQoH3m5ywUSIfaH0FGCzWu4McsYon5bD3K4XnskC7f+OyQMj7rsOMi4NvvmJ83WwBAg4UCe+ov4VZlqEvyew=="], + + "@babel/plugin-transform-modules-commonjs": ["@babel/plugin-transform-modules-commonjs@7.29.7", "", { "dependencies": { "@babel/helper-module-transforms": "^7.29.7", "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-j0vCldybPC5b5dwCQOJ21uKtHzt7hxLygJTg9eF1ScfaikEDNfzn94XoW5Fi+seBR0nCyL23xaBFFkq7dTM8XQ=="], + + "@babel/plugin-transform-modules-systemjs": ["@babel/plugin-transform-modules-systemjs@7.29.8", "", { "dependencies": { "@babel/helper-module-transforms": "^7.29.7", "@babel/helper-plugin-utils": "^7.29.7", "@babel/helper-validator-identifier": "^7.29.7", "@babel/traverse": "^7.29.8" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-6iSnEK0zlkLKU4heofK/AdmRD4e2SHVpJMtrwnTCzhnaM98ria4rTrOXBBi45BTTYnJtO8txnPsX4fChYXkmeA=="], + + "@babel/plugin-transform-modules-umd": ["@babel/plugin-transform-modules-umd@7.29.7", "", { "dependencies": { "@babel/helper-module-transforms": "^7.29.7", "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-B4UkaTK3QpgCwJnrxKfMPKdo92CN7OKXAlpAAnM3UPu0Q0lCCk57ylA9AJbRy2v8dDKOPAAWcoR6CMyeoHwRCA=="], + + "@babel/plugin-transform-named-capturing-groups-regex": ["@babel/plugin-transform-named-capturing-groups-regex@7.29.7", "", { "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.29.7", "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-vuFoLwr4qnv2xbZ16SQd6uPcH5FNrLHhk/Jzo++0XJFcaDsr4gjJVg6j398oMHiC+83k/GiBzviwF5KBJkPUtQ=="], + + "@babel/plugin-transform-new-target": ["@babel/plugin-transform-new-target@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-fEo41GmsOUhOBlw8ioo6zvjX5Xc2Lqkzlyfqbpsk3eB6TReV18uhxZ0esfEokVbY2+PVJAQHNKxER6lGrzNd3A=="], + + "@babel/plugin-transform-nullish-coalescing-operator": ["@babel/plugin-transform-nullish-coalescing-operator@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-idmp1dFaekP9GbcMvG24Kvw2BfhFZjHnNJCkV4WuIY4PskJzwI3f1N5OdgYke38T7rftO6ERulFRn2cFeZwRkg=="], + + "@babel/plugin-transform-numeric-separator": ["@babel/plugin-transform-numeric-separator@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-zR7fv/z14OjgHl4AgRtkDBvBMhIzCxqV/qN/2BCRC7LjFwvuzjYe7gDWxC4Wl/SNsLM6SE1IWvRPYMgSJaUvNw=="], + + "@babel/plugin-transform-object-rest-spread": ["@babel/plugin-transform-object-rest-spread@7.29.7", "", { "dependencies": { "@babel/helper-compilation-targets": "^7.29.7", "@babel/helper-plugin-utils": "^7.29.7", "@babel/plugin-transform-destructuring": "^7.29.7", "@babel/plugin-transform-parameters": "^7.29.7", "@babel/traverse": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-Ld98jn4c0smUywL57m7SgsHq3OpThOa6LqZJif3G6jYOovPleoFhVrBJ1WegRApSFB2wu4+RelAj9AC9G08Z4A=="], + + "@babel/plugin-transform-object-super": ["@babel/plugin-transform-object-super@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7", "@babel/helper-replace-supers": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-Ea/diGcw0twB5IlZPO5sgET6fJsLJqPABqTuFWIR+iMPGPZJkATEIWx0wa+aEQ5UY1CBQyP/gkAiLEqn1vBiQA=="], + + "@babel/plugin-transform-optional-catch-binding": ["@babel/plugin-transform-optional-catch-binding@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-sLsyndxK2VwX6yNUOakMb7Sh553ZTe/vVM1XJ+9Z5aW1ytsc8xOIwmyk05NNjN60vkc5/KqoTH6hB4V41LJhng=="], + + "@babel/plugin-transform-optional-chaining": ["@babel/plugin-transform-optional-chaining@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7", "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-6GM1dhvK3gNODkXcEcMCOLEDCLSoZ/sBbro2Ax8HURyasQ4NshagQixkRFdh5niI6E4gmA/jYI/4aT7rRos3ZQ=="], + + "@babel/plugin-transform-parameters": ["@babel/plugin-transform-parameters@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-ZDOBqV/qLYJI0YElr8DcENEyARsFQeESqWXH6gZlghYXuPPjvweuDhP4VyEi4BlUBlLRFZVjxoZDMjxhLW766g=="], + + "@babel/plugin-transform-private-methods": ["@babel/plugin-transform-private-methods@7.29.7", "", { "dependencies": { "@babel/helper-create-class-features-plugin": "^7.29.7", "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-/6Rz4DK1ETDEM/bWHsPHcaEe7ZaT1EqSXjtSP/L0DijOYuaUhiRiOKcwpZ8P7zR4xXEHc2ITdiCgBm9Tpyv9ug=="], + + "@babel/plugin-transform-private-property-in-object": ["@babel/plugin-transform-private-property-in-object@7.29.7", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.29.7", "@babel/helper-create-class-features-plugin": "^7.29.7", "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-+BNo06dnrzdNNqCm1X6YUaVv0DKk8Q+JYcoZfOkLhYWNCXzlwTSRq8zGWayT1csjcpNXV9CQTBRRbmTLZac5cA=="], + + "@babel/plugin-transform-property-literals": ["@babel/plugin-transform-property-literals@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-bOMRLQuI0A5ZqHq3OWJ89/rXpJ/NJrbVhXiP4zwPGMs6kpcVsuTUNjwoE30K0Qm3mf48a/TnRYYD6vPNqcg6jA=="], + + "@babel/plugin-transform-regenerator": ["@babel/plugin-transform-regenerator@7.29.8", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-0UpIXPtdDtMXfnV2OJAVMLpj3H/92vmkA6lpSRakmycJvj3VUy6Xs1dM8tXRugupykr5WB+LpiVl0J8LMVg2mg=="], + + "@babel/plugin-transform-regexp-modifiers": ["@babel/plugin-transform-regexp-modifiers@7.29.7", "", { "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.29.7", "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-mB5Fs0VWrJ42ZCmc8114v60qetdaUVNkj9PmSZRmanCZM3S9hm0CFRLjRmYIsuXav14l2jvZ+4T8iiCGnhj3nQ=="], + + "@babel/plugin-transform-reserved-words": ["@babel/plugin-transform-reserved-words@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-5+YhdpVgmfSmwZyLMftfaiffLRMHjzIRHFHHLdibcSyJm2pasMrKHrO3Ptrt2DRshjvpgjEJJ1zVW14WPq/6QA=="], + + "@babel/plugin-transform-shorthand-properties": ["@babel/plugin-transform-shorthand-properties@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-I+WYbGBAiCn7nA6xBrlgPH+MB7HWb4u8pv5S0Pv7OtwNvIFvCCb24YlttKEeUFVurfBCEaOTnuhlqsb7f0Z5Dg=="], + + "@babel/plugin-transform-spread": ["@babel/plugin-transform-spread@7.29.8", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7", "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-4S9ksMGVWUshvgK0mKfvZky7leuG5/uoFVwMpAomJ8bMoDJiNHRVmc1EglwW/CmGVSqqWpEbXm9FmbRit22qoA=="], + + "@babel/plugin-transform-sticky-regex": ["@babel/plugin-transform-sticky-regex@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-BCHzNYJGe9l7EpwwDBN/ztlL2NYFFq8hp9ddjtUEM9f2O7S7kKV/lL6Fwo7IF7NSkYhPK2vO+86nIGltA90MsA=="], + + "@babel/plugin-transform-template-literals": ["@babel/plugin-transform-template-literals@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-NCSEJ4sLFU2gqAub45HYh4fus2yQ36rr6ei6vpU7NdoJqCpxvEG8E6eJpscGyXP3VHD2Ny+fSXr04k1hoUrFqA=="], + + "@babel/plugin-transform-typeof-symbol": ["@babel/plugin-transform-typeof-symbol@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-223mNGoTkBiTEWFoK+Q6Go3tueMRclO8vxxxxquNCYuNI4jWOofFKJRRDu6SDrB8Sgo1UEGW9T4GAQ8ZyRso1A=="], + + "@babel/plugin-transform-unicode-escapes": ["@babel/plugin-transform-unicode-escapes@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-jCfXxSjf94lf4E0hKE0AByxF6F3/pVFqRdUUNkDJhsY0m1ZKjnN6ZYyMeHNpzflxb/0q5b7t3p+BE+SLF1WOtA=="], + + "@babel/plugin-transform-unicode-property-regex": ["@babel/plugin-transform-unicode-property-regex@7.29.7", "", { "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.29.7", "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-OgZ+zoAJgZLUCunsTRQ5LAjOywDv5zzZ2/hQ5aMw1pGXyY2rtE8/chXYUmu3AlVHKpm10KEdG9aMwbI/K76ZGw=="], + + "@babel/plugin-transform-unicode-regex": ["@babel/plugin-transform-unicode-regex@7.29.7", "", { "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.29.7", "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-7D/x/23/d/3VqZ0QA+LGbZMlGwZjztBygSWWWsfTPoQ1oQ6Q1P6Mr3d0kk42XabyUVw+fha3LqdRsFqeKqvCyA=="], + + "@babel/plugin-transform-unicode-sets-regex": ["@babel/plugin-transform-unicode-sets-regex@7.29.7", "", { "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.29.7", "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-BLOhLht9DOJwIxlmp91wHvkXv1lguuHS3/FwUO8HL1H0u8s4hR1gASVFyilu9iGtcTRYqjTZmlsFFeQletntEg=="], + + "@babel/preset-env": ["@babel/preset-env@7.29.7", "", { "dependencies": { "@babel/compat-data": "^7.29.7", "@babel/helper-compilation-targets": "^7.29.7", "@babel/helper-plugin-utils": "^7.29.7", "@babel/helper-validator-option": "^7.29.7", "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.29.7", "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.29.7", "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.29.7", "@babel/plugin-bugfix-safari-rest-destructuring-rhs-array": "^7.29.7", "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.29.7", "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.29.7", "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", "@babel/plugin-syntax-import-assertions": "^7.29.7", "@babel/plugin-syntax-import-attributes": "^7.29.7", "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", "@babel/plugin-transform-arrow-functions": "^7.29.7", "@babel/plugin-transform-async-generator-functions": "^7.29.7", "@babel/plugin-transform-async-to-generator": "^7.29.7", "@babel/plugin-transform-block-scoped-functions": "^7.29.7", "@babel/plugin-transform-block-scoping": "^7.29.7", "@babel/plugin-transform-class-properties": "^7.29.7", "@babel/plugin-transform-class-static-block": "^7.29.7", "@babel/plugin-transform-classes": "^7.29.7", "@babel/plugin-transform-computed-properties": "^7.29.7", "@babel/plugin-transform-destructuring": "^7.29.7", "@babel/plugin-transform-dotall-regex": "^7.29.7", "@babel/plugin-transform-duplicate-keys": "^7.29.7", "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.29.7", "@babel/plugin-transform-dynamic-import": "^7.29.7", "@babel/plugin-transform-explicit-resource-management": "^7.29.7", "@babel/plugin-transform-exponentiation-operator": "^7.29.7", "@babel/plugin-transform-export-namespace-from": "^7.29.7", "@babel/plugin-transform-for-of": "^7.29.7", "@babel/plugin-transform-function-name": "^7.29.7", "@babel/plugin-transform-json-strings": "^7.29.7", "@babel/plugin-transform-literals": "^7.29.7", "@babel/plugin-transform-logical-assignment-operators": "^7.29.7", "@babel/plugin-transform-member-expression-literals": "^7.29.7", "@babel/plugin-transform-modules-amd": "^7.29.7", "@babel/plugin-transform-modules-commonjs": "^7.29.7", "@babel/plugin-transform-modules-systemjs": "^7.29.7", "@babel/plugin-transform-modules-umd": "^7.29.7", "@babel/plugin-transform-named-capturing-groups-regex": "^7.29.7", "@babel/plugin-transform-new-target": "^7.29.7", "@babel/plugin-transform-nullish-coalescing-operator": "^7.29.7", "@babel/plugin-transform-numeric-separator": "^7.29.7", "@babel/plugin-transform-object-rest-spread": "^7.29.7", "@babel/plugin-transform-object-super": "^7.29.7", "@babel/plugin-transform-optional-catch-binding": "^7.29.7", "@babel/plugin-transform-optional-chaining": "^7.29.7", "@babel/plugin-transform-parameters": "^7.29.7", "@babel/plugin-transform-private-methods": "^7.29.7", "@babel/plugin-transform-private-property-in-object": "^7.29.7", "@babel/plugin-transform-property-literals": "^7.29.7", "@babel/plugin-transform-regenerator": "^7.29.7", "@babel/plugin-transform-regexp-modifiers": "^7.29.7", "@babel/plugin-transform-reserved-words": "^7.29.7", "@babel/plugin-transform-shorthand-properties": "^7.29.7", "@babel/plugin-transform-spread": "^7.29.7", "@babel/plugin-transform-sticky-regex": "^7.29.7", "@babel/plugin-transform-template-literals": "^7.29.7", "@babel/plugin-transform-typeof-symbol": "^7.29.7", "@babel/plugin-transform-unicode-escapes": "^7.29.7", "@babel/plugin-transform-unicode-property-regex": "^7.29.7", "@babel/plugin-transform-unicode-regex": "^7.29.7", "@babel/plugin-transform-unicode-sets-regex": "^7.29.7", "@babel/preset-modules": "0.1.6-no-external-plugins", "babel-plugin-polyfill-corejs2": "^0.4.15", "babel-plugin-polyfill-corejs3": "^0.14.0", "babel-plugin-polyfill-regenerator": "^0.6.6", "core-js-compat": "^3.48.0", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-GYzX36n1nsciIb0uyH0GHwxwtNwPQIcpxSeiVLDtG/B7jB5xXgchnmL1f/jCX5o+pwnaDBtO60ONSJhEBJfxYA=="], + + "@babel/preset-modules": ["@babel/preset-modules@0.1.6-no-external-plugins", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.0.0", "@babel/types": "^7.4.4", "esutils": "^2.0.2" }, "peerDependencies": { "@babel/core": "^7.0.0-0 || ^8.0.0-0 <8.0.0" } }, "sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA=="], + + "@babel/runtime": ["@babel/runtime@7.29.7", "", {}, "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw=="], + + "@babel/template": ["@babel/template@7.29.7", "", { "dependencies": { "@babel/code-frame": "^7.29.7", "@babel/parser": "^7.29.7", "@babel/types": "^7.29.7" } }, "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg=="], + + "@babel/traverse": ["@babel/traverse@7.29.8", "", { "dependencies": { "@babel/code-frame": "^7.29.7", "@babel/generator": "^7.29.8", "@babel/helper-globals": "^7.29.7", "@babel/parser": "^7.29.8", "@babel/template": "^7.29.7", "@babel/types": "^7.29.8", "debug": "^4.3.1" } }, "sha512-I5z7H3bf/41ktsNVLtpN0wAa336HkqIHQ5BuPLEhTkt1jVSyZpeNKIzTgEWmlxjdg81R0IgUCcaE+Ok3NvrfZg=="], + + "@babel/types": ["@babel/types@7.29.8", "", { "dependencies": { "@babel/helper-string-parser": "^7.29.7", "@babel/helper-validator-identifier": "^7.29.7" } }, "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg=="], + + "@braintree/sanitize-url": ["@braintree/sanitize-url@7.1.2", "", {}, "sha512-jigsZK+sMF/cuiB7sERuo9V7N9jx+dhmHHnQyDSVdpZwVutaBu7WvNYqMDLSgFgfB30n452TP3vjDAvFC973mA=="], + + "@chevrotain/types": ["@chevrotain/types@11.1.2", "", {}, "sha512-U+HFai5+zmJCkK86QsaJtoITlboZHBqrVketcO2ROv865xfCMSFpELQoz1GkX5GzME8pTa+3kbKrZHQtI0gdbw=="], + + "@codemirror/autocomplete": ["@codemirror/autocomplete@6.20.3", "", { "dependencies": { "@codemirror/language": "^6.0.0", "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.17.0", "@lezer/common": "^1.0.0" } }, "sha512-tlosUqb+3BbxCxZdu4tKeRghPFC+QM7q4X5YhKV2eCmPG+1r2F3f4AaSz5sCrFqUtX4Jh20VFTKecl16MgiV9g=="], + + "@codemirror/commands": ["@codemirror/commands@6.10.4", "", { "dependencies": { "@codemirror/language": "^6.0.0", "@codemirror/state": "^6.7.0", "@codemirror/view": "^6.27.0", "@lezer/common": "^1.1.0" } }, "sha512-Ryk9y9T0FFVF0cUGhAknveAyUOl/A1qReTFi+qPKtOh2Z9F4AUBz3XOrYD4ZEgZirdugVzHvd/2/Wcwy5OliTg=="], + + "@codemirror/lang-angular": ["@codemirror/lang-angular@0.1.4", "", { "dependencies": { "@codemirror/lang-html": "^6.0.0", "@codemirror/lang-javascript": "^6.1.2", "@codemirror/language": "^6.0.0", "@lezer/common": "^1.2.0", "@lezer/highlight": "^1.0.0", "@lezer/lr": "^1.3.3" } }, "sha512-oap+gsltb/fzdlTQWD6BFF4bSLKcDnlxDsLdePiJpCVNKWXSTAbiiQeYI3UmES+BLAdkmIC1WjyztC1pi/bX4g=="], + + "@codemirror/lang-cpp": ["@codemirror/lang-cpp@6.0.3", "", { "dependencies": { "@codemirror/language": "^6.0.0", "@lezer/cpp": "^1.0.0" } }, "sha512-URM26M3vunFFn9/sm6rzqrBzDgfWuDixp85uTY49wKudToc2jTHUrKIGGKs+QWND+YLofNNZpxcNGRynFJfvgA=="], + + "@codemirror/lang-css": ["@codemirror/lang-css@6.3.1", "", { "dependencies": { "@codemirror/autocomplete": "^6.0.0", "@codemirror/language": "^6.0.0", "@codemirror/state": "^6.0.0", "@lezer/common": "^1.0.2", "@lezer/css": "^1.1.7" } }, "sha512-kr5fwBGiGtmz6l0LSJIbno9QrifNMUusivHbnA1H6Dmqy4HZFte3UAICix1VuKo0lMPKQr2rqB+0BkKi/S3Ejg=="], + + "@codemirror/lang-go": ["@codemirror/lang-go@6.0.1", "", { "dependencies": { "@codemirror/autocomplete": "^6.0.0", "@codemirror/language": "^6.6.0", "@codemirror/state": "^6.0.0", "@lezer/common": "^1.0.0", "@lezer/go": "^1.0.0" } }, "sha512-7fNvbyNylvqCphW9HD6WFnRpcDjr+KXX/FgqXy5H5ZS0eC5edDljukm/yNgYkwTsgp2busdod50AOTIy6Jikfg=="], + + "@codemirror/lang-html": ["@codemirror/lang-html@6.4.12", "", { "dependencies": { "@codemirror/autocomplete": "^6.0.0", "@codemirror/lang-css": "^6.0.0", "@codemirror/lang-javascript": "^6.0.0", "@codemirror/language": "^6.4.0", "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.17.0", "@lezer/common": "^1.0.0", "@lezer/css": "^1.1.0", "@lezer/html": "^1.3.12" } }, "sha512-pw2ReWKUqSkbvh76RAT4NYxiogRu+PWkR2ukAwO9uOgrm8uipkzjtKKtNpyeAQwHOqxEeSvAXZ6vr3AfyB9y/w=="], + + "@codemirror/lang-java": ["@codemirror/lang-java@6.0.2", "", { "dependencies": { "@codemirror/language": "^6.0.0", "@lezer/java": "^1.0.0" } }, "sha512-m5Nt1mQ/cznJY7tMfQTJchmrjdjQ71IDs+55d1GAa8DGaB8JXWsVCkVT284C3RTASaY43YknrK2X3hPO/J3MOQ=="], + + "@codemirror/lang-javascript": ["@codemirror/lang-javascript@6.2.5", "", { "dependencies": { "@codemirror/autocomplete": "^6.0.0", "@codemirror/language": "^6.6.0", "@codemirror/lint": "^6.0.0", "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.17.0", "@lezer/common": "^1.0.0", "@lezer/javascript": "^1.0.0" } }, "sha512-zD4e5mS+50htS7F+TYjBPsiIFGanfVqg4HyUz6WNFikgOPf2BgKlx+TQedI1w6n/IqRBVBbBWmGFdLB/7uxO4A=="], + + "@codemirror/lang-jinja": ["@codemirror/lang-jinja@6.0.1", "", { "dependencies": { "@codemirror/autocomplete": "^6.0.0", "@codemirror/lang-html": "^6.0.0", "@codemirror/language": "^6.0.0", "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.0.0", "@lezer/common": "^1.2.0", "@lezer/highlight": "^1.2.0", "@lezer/lr": "^1.4.0" } }, "sha512-P5kyHLObzjtbGj16h+hyvZTxJhSjBEeSx4wMjbnAf3b0uwTy2+F0zGjMZL4PQOm/mh2eGZ5xUDVZXgwP783Nsw=="], + + "@codemirror/lang-json": ["@codemirror/lang-json@6.0.2", "", { "dependencies": { "@codemirror/language": "^6.0.0", "@lezer/json": "^1.0.0" } }, "sha512-x2OtO+AvwEHrEwR0FyyPtfDUiloG3rnVTSZV1W8UteaLL8/MajQd8DpvUb2YVzC+/T18aSDv0H9mu+xw0EStoQ=="], + + "@codemirror/lang-less": ["@codemirror/lang-less@6.0.2", "", { "dependencies": { "@codemirror/lang-css": "^6.2.0", "@codemirror/language": "^6.0.0", "@lezer/common": "^1.2.0", "@lezer/highlight": "^1.0.0", "@lezer/lr": "^1.0.0" } }, "sha512-EYdQTG22V+KUUk8Qq582g7FMnCZeEHsyuOJisHRft/mQ+ZSZ2w51NupvDUHiqtsOy7It5cHLPGfHQLpMh9bqpQ=="], + + "@codemirror/lang-liquid": ["@codemirror/lang-liquid@6.3.2", "", { "dependencies": { "@codemirror/autocomplete": "^6.0.0", "@codemirror/lang-html": "^6.0.0", "@codemirror/language": "^6.0.0", "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.0.0", "@lezer/common": "^1.0.0", "@lezer/highlight": "^1.0.0", "@lezer/lr": "^1.3.1" } }, "sha512-6PDVU3ZnfeYyz1at1E/ttorErZvZFXXt1OPhtfe1EZJ2V2iDFa0CwPqPgG5F7NXN0yONGoBogKmFAafKTqlwIw=="], + + "@codemirror/lang-markdown": ["@codemirror/lang-markdown@6.5.2", "", { "dependencies": { "@codemirror/autocomplete": "^6.7.1", "@codemirror/lang-html": "^6.0.0", "@codemirror/language": "^6.3.0", "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.0.0", "@lezer/common": "^1.2.1", "@lezer/markdown": "^1.0.0" } }, "sha512-AwBOdkWYuA//WcM0xO5PfHPUcmz/O2i5o0Nsg1U69SII/loCJlFI1Romd9xp2HYb1kYJRGZotyqRghuHH5n8Kw=="], + + "@codemirror/lang-php": ["@codemirror/lang-php@6.0.2", "", { "dependencies": { "@codemirror/lang-html": "^6.0.0", "@codemirror/language": "^6.0.0", "@codemirror/state": "^6.0.0", "@lezer/common": "^1.0.0", "@lezer/php": "^1.0.0" } }, "sha512-ZKy2v1n8Fc8oEXj0Th0PUMXzQJ0AIR6TaZU+PbDHExFwdu+guzOA4jmCHS1Nz4vbFezwD7LyBdDnddSJeScMCA=="], + + "@codemirror/lang-python": ["@codemirror/lang-python@6.2.1", "", { "dependencies": { "@codemirror/autocomplete": "^6.3.2", "@codemirror/language": "^6.8.0", "@codemirror/state": "^6.0.0", "@lezer/common": "^1.2.1", "@lezer/python": "^1.1.4" } }, "sha512-IRjC8RUBhn9mGR9ywecNhB51yePWCGgvHfY1lWN/Mrp3cKuHr0isDKia+9HnvhiWNnMpbGhWrkhuWOc09exRyw=="], + + "@codemirror/lang-rust": ["@codemirror/lang-rust@6.0.2", "", { "dependencies": { "@codemirror/language": "^6.0.0", "@lezer/rust": "^1.0.0" } }, "sha512-EZaGjCUegtiU7kSMvOfEZpaCReowEf3yNidYu7+vfuGTm9ow4mthAparY5hisJqOHmJowVH3Upu+eJlUji6qqA=="], + + "@codemirror/lang-sass": ["@codemirror/lang-sass@6.0.2", "", { "dependencies": { "@codemirror/lang-css": "^6.2.0", "@codemirror/language": "^6.0.0", "@codemirror/state": "^6.0.0", "@lezer/common": "^1.0.2", "@lezer/sass": "^1.0.0" } }, "sha512-l/bdzIABvnTo1nzdY6U+kPAC51czYQcOErfzQ9zSm9D8GmNPD0WTW8st/CJwBTPLO8jlrbyvlSEcN20dc4iL0Q=="], + + "@codemirror/lang-sql": ["@codemirror/lang-sql@6.10.0", "", { "dependencies": { "@codemirror/autocomplete": "^6.0.0", "@codemirror/language": "^6.0.0", "@codemirror/state": "^6.0.0", "@lezer/common": "^1.2.0", "@lezer/highlight": "^1.0.0", "@lezer/lr": "^1.0.0" } }, "sha512-6ayPkEd/yRw0XKBx5uAiToSgGECo/GY2NoJIHXIIQh1EVwLuKoU8BP/qK0qH5NLXAbtJRLuT73hx7P9X34iO4w=="], + + "@codemirror/lang-vue": ["@codemirror/lang-vue@0.1.3", "", { "dependencies": { "@codemirror/lang-html": "^6.0.0", "@codemirror/lang-javascript": "^6.1.2", "@codemirror/language": "^6.0.0", "@lezer/common": "^1.2.0", "@lezer/highlight": "^1.0.0", "@lezer/lr": "^1.3.1" } }, "sha512-QSKdtYTDRhEHCfo5zOShzxCmqKJvgGrZwDQSdbvCRJ5pRLWBS7pD/8e/tH44aVQT6FKm0t6RVNoSUWHOI5vNug=="], + + "@codemirror/lang-wast": ["@codemirror/lang-wast@6.0.2", "", { "dependencies": { "@codemirror/language": "^6.0.0", "@lezer/common": "^1.2.0", "@lezer/highlight": "^1.0.0", "@lezer/lr": "^1.0.0" } }, "sha512-Imi2KTpVGm7TKuUkqyJ5NRmeFWF7aMpNiwHnLQe0x9kmrxElndyH0K6H/gXtWwY6UshMRAhpENsgfpSwsgmC6Q=="], + + "@codemirror/lang-xml": ["@codemirror/lang-xml@6.1.0", "", { "dependencies": { "@codemirror/autocomplete": "^6.0.0", "@codemirror/language": "^6.4.0", "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.0.0", "@lezer/common": "^1.0.0", "@lezer/xml": "^1.0.0" } }, "sha512-3z0blhicHLfwi2UgkZYRPioSgVTo9PV5GP5ducFH6FaHy0IAJRg+ixj5gTR1gnT/glAIC8xv4w2VL1LoZfs+Jg=="], + + "@codemirror/lang-yaml": ["@codemirror/lang-yaml@6.1.3", "", { "dependencies": { "@codemirror/autocomplete": "^6.0.0", "@codemirror/language": "^6.0.0", "@codemirror/state": "^6.0.0", "@lezer/common": "^1.2.0", "@lezer/highlight": "^1.2.0", "@lezer/lr": "^1.0.0", "@lezer/yaml": "^1.0.0" } }, "sha512-AZ8DJBuXGVHybpBQhmZtgew5//4hv3tdkXnr3vDmOUMJRuB6vn/uuwtmTOTlqEaQFg3hQSVeA90NmvIQyUV6FQ=="], + + "@codemirror/language": ["@codemirror/language@6.12.4", "", { "dependencies": { "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.23.0", "@lezer/common": "^1.5.0", "@lezer/highlight": "^1.0.0", "@lezer/lr": "^1.0.0", "style-mod": "^4.0.0" } }, "sha512-1q4PaT+o6PbgpkJt4Q8Fv5XJxTy4FUZ4MWETtyiDw3J0Pyr9E2vqcKL+k9wcvjNTIsauxvE7OfmWj3FRPHQ76A=="], + + "@codemirror/language-data": ["@codemirror/language-data@6.5.2", "", { "dependencies": { "@codemirror/lang-angular": "^0.1.0", "@codemirror/lang-cpp": "^6.0.0", "@codemirror/lang-css": "^6.0.0", "@codemirror/lang-go": "^6.0.0", "@codemirror/lang-html": "^6.0.0", "@codemirror/lang-java": "^6.0.0", "@codemirror/lang-javascript": "^6.0.0", "@codemirror/lang-jinja": "^6.0.0", "@codemirror/lang-json": "^6.0.0", "@codemirror/lang-less": "^6.0.0", "@codemirror/lang-liquid": "^6.0.0", "@codemirror/lang-markdown": "^6.0.0", "@codemirror/lang-php": "^6.0.0", "@codemirror/lang-python": "^6.0.0", "@codemirror/lang-rust": "^6.0.0", "@codemirror/lang-sass": "^6.0.0", "@codemirror/lang-sql": "^6.0.0", "@codemirror/lang-vue": "^0.1.1", "@codemirror/lang-wast": "^6.0.0", "@codemirror/lang-xml": "^6.0.0", "@codemirror/lang-yaml": "^6.0.0", "@codemirror/language": "^6.0.0", "@codemirror/legacy-modes": "^6.4.0" } }, "sha512-CPkWBKrNS8stYbEU5kwBwTf3JB1kghlbh4FSAwzGW2TEscdeHHH4FGysREW86Mqnj3Qn09s0/6Ea/TutmoTobg=="], + + "@codemirror/legacy-modes": ["@codemirror/legacy-modes@6.5.3", "", { "dependencies": { "@codemirror/language": "^6.0.0" } }, "sha512-xCsmIzH78MyWkib9jlPaaun57XNkfbMIhagfaZVd0iLTqlpw3jXaIcbZm72MTmmn64eTZpBVNjbyYh+QXnxRsg=="], + + "@codemirror/lint": ["@codemirror/lint@6.9.7", "", { "dependencies": { "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.42.0", "crelt": "^1.0.5" } }, "sha512-28/+iWLYxKxsvGYhSYL7zaCZqLz5+FFFDq9tVsvGv9kv8RY4fFAchJ5WX9M3YrrRlTIsECjsXPqeNgnSmNP2dg=="], + + "@codemirror/search": ["@codemirror/search@6.7.1", "", { "dependencies": { "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.37.0", "crelt": "^1.0.5" } }, "sha512-uMe5UO6PamJtSHrXhhHOzSX3ReWtiJrva6GnPMwSOrZtiExb5X5eExhr2OUZQVvdxPsKpY3Ro2mFbQadpPWmHA=="], + + "@codemirror/state": ["@codemirror/state@6.7.1", "", { "dependencies": { "@marijn/find-cluster-break": "^1.0.0" } }, "sha512-9QzNDgE4EYDnAHfrTlR2lwiPciiOymLtwKK+8yHQzCc7GXhAP9xdEbEJFy2IWB1j9UGUl9BsgMmTo/ImA02T7A=="], + + "@codemirror/view": ["@codemirror/view@6.43.8", "", { "dependencies": { "@codemirror/state": "^6.7.0", "crelt": "^1.0.6", "style-mod": "^4.1.0", "w3c-keyname": "^2.2.4" } }, "sha512-qtItTDssZ/5GFfi94hrILu9j/VUeFPDPkhovEfmWFj2ipTxnzPB8DdHgfbb8HYTzLTYhrndKmyQxXUz/PDLenw=="], + + "@css-render/plugin-bem": ["@css-render/plugin-bem@0.15.14", "", { "peerDependencies": { "css-render": "~0.15.14" } }, "sha512-QK513CJ7yEQxm/P3EwsI+d+ha8kSOcjGvD6SevM41neEMxdULE+18iuQK6tEChAWMOQNQPLG/Rw3Khb69r5neg=="], + + "@css-render/vue3-ssr": ["@css-render/vue3-ssr@0.15.14", "", { "peerDependencies": { "vue": "^3.0.11" } }, "sha512-//8027GSbxE9n3QlD73xFY6z4ZbHbvrOVB7AO6hsmrEzGbg+h2A09HboUyDgu+xsmj7JnvJD39Irt+2D0+iV8g=="], + + "@drizzle-team/brocli": ["@drizzle-team/brocli@0.10.2", "https://registry.npmjs.com/@drizzle-team/brocli/-/brocli-0.10.2.tgz", {}, "sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w=="], + + "@emotion/hash": ["@emotion/hash@0.8.0", "", {}, "sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow=="], + + "@esbuild-kit/core-utils": ["@esbuild-kit/core-utils@3.3.2", "https://registry.npmjs.com/@esbuild-kit/core-utils/-/core-utils-3.3.2.tgz", { "dependencies": { "esbuild": "~0.18.20", "source-map-support": "^0.5.21" } }, "sha512-sPRAnw9CdSsRmEtnsl2WXWdyquogVpB3yZ3dgwJfe8zrOzTsV7cJvmwrKVa+0ma5BoiGJ+BoqkMvawbayKUsqQ=="], + + "@esbuild-kit/esm-loader": ["@esbuild-kit/esm-loader@2.6.5", "https://registry.npmjs.com/@esbuild-kit/esm-loader/-/esm-loader-2.6.5.tgz", { "dependencies": { "@esbuild-kit/core-utils": "^3.3.2", "get-tsconfig": "^4.7.0" } }, "sha512-FxEMIkJKnodyA1OaCUoEvbYRkoZlLZ4d/eXFu9Fh8CbBBgP5EmZxrfTRyN0qpXZ4vOvqnE5YdRdcrmUUXuU+dA=="], + + "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.12", "https://registry.npmjs.com/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", { "os": "aix", "cpu": "ppc64" }, "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA=="], + + "@esbuild/android-arm": ["@esbuild/android-arm@0.25.12", "https://registry.npmjs.com/@esbuild/android-arm/-/android-arm-0.25.12.tgz", { "os": "android", "cpu": "arm" }, "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg=="], + + "@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.12", "https://registry.npmjs.com/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", { "os": "android", "cpu": "arm64" }, "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg=="], + + "@esbuild/android-x64": ["@esbuild/android-x64@0.25.12", "https://registry.npmjs.com/@esbuild/android-x64/-/android-x64-0.25.12.tgz", { "os": "android", "cpu": "x64" }, "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg=="], + + "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.12", "https://registry.npmjs.com/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", { "os": "darwin", "cpu": "arm64" }, "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg=="], + + "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.12", "https://registry.npmjs.com/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", { "os": "darwin", "cpu": "x64" }, "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA=="], + + "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.12", "https://registry.npmjs.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", { "os": "freebsd", "cpu": "arm64" }, "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg=="], + + "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.12", "https://registry.npmjs.com/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", { "os": "freebsd", "cpu": "x64" }, "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ=="], + + "@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.12", "https://registry.npmjs.com/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", { "os": "linux", "cpu": "arm" }, "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw=="], + + "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.12", "https://registry.npmjs.com/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", { "os": "linux", "cpu": "arm64" }, "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ=="], + + "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.12", "https://registry.npmjs.com/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", { "os": "linux", "cpu": "ia32" }, "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA=="], + + "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.12", "https://registry.npmjs.com/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", { "os": "linux", "cpu": "none" }, "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng=="], + + "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.12", "https://registry.npmjs.com/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", { "os": "linux", "cpu": "none" }, "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw=="], + + "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.12", "https://registry.npmjs.com/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", { "os": "linux", "cpu": "ppc64" }, "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA=="], + + "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.12", "https://registry.npmjs.com/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", { "os": "linux", "cpu": "none" }, "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w=="], + + "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.12", "https://registry.npmjs.com/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", { "os": "linux", "cpu": "s390x" }, "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg=="], + + "@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.12", "https://registry.npmjs.com/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", { "os": "linux", "cpu": "x64" }, "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw=="], + + "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.12", "https://registry.npmjs.com/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", { "os": "none", "cpu": "arm64" }, "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg=="], + + "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.12", "https://registry.npmjs.com/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", { "os": "none", "cpu": "x64" }, "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ=="], + + "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.12", "https://registry.npmjs.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", { "os": "openbsd", "cpu": "arm64" }, "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A=="], + + "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.12", "https://registry.npmjs.com/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", { "os": "openbsd", "cpu": "x64" }, "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw=="], + + "@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.25.12", "https://registry.npmjs.com/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", { "os": "none", "cpu": "arm64" }, "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg=="], + + "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.12", "https://registry.npmjs.com/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", { "os": "sunos", "cpu": "x64" }, "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w=="], + + "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.12", "https://registry.npmjs.com/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", { "os": "win32", "cpu": "arm64" }, "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg=="], + + "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.12", "https://registry.npmjs.com/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", { "os": "win32", "cpu": "ia32" }, "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ=="], + + "@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.12", "https://registry.npmjs.com/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", { "os": "win32", "cpu": "x64" }, "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA=="], + + "@iconify/types": ["@iconify/types@2.0.0", "", {}, "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg=="], + + "@iconify/utils": ["@iconify/utils@3.1.4", "", { "dependencies": { "@antfu/install-pkg": "^1.1.0", "@iconify/types": "^2.0.0", "import-meta-resolve": "^4.2.0" } }, "sha512-b1S7B1k9ohZ+iNTi2ATxbRYG9fTrJmUT0rc46bvVnNxqNRGW7dyo/vRREwyniI5IRN2RSJHDcm+s3BjWrSAjHw=="], + + "@iconify/vue": ["@iconify/vue@5.0.1", "", { "dependencies": { "@iconify/types": "^2.0.0" }, "peerDependencies": { "vue": ">=3.0.0" } }, "sha512-aumwwooJlFJ5H5qYWB6ZTAyM0C8hpfcSVLB9/a3qnH1GGvIJ+FEbpEs4s/HfErYe/M5qZeLjwmESR5fFm3lXEw=="], + + "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="], + + "@jridgewell/remapping": ["@jridgewell/remapping@2.3.5", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="], + + "@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="], + + "@jridgewell/source-map": ["@jridgewell/source-map@0.3.11", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25" } }, "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA=="], + + "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="], + + "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="], + + "@juggle/resize-observer": ["@juggle/resize-observer@3.4.0", "", {}, "sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA=="], + + "@kurkle/color": ["@kurkle/color@0.3.4", "", {}, "sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w=="], + + "@lezer/common": ["@lezer/common@1.5.2", "", {}, "sha512-sxQE460fPZyU3sdc8lafxiPwJHBzZRy/udNFynGQky1SePYBdhkBl1kOagA9uT3pxR8K09bOrmTUqA9wb/PjSQ=="], + + "@lezer/cpp": ["@lezer/cpp@1.1.6", "", { "dependencies": { "@lezer/common": "^1.2.0", "@lezer/highlight": "^1.0.0", "@lezer/lr": "^1.0.0" } }, "sha512-vh9gWWJOXFVY8HBHK3Twzq8MgwG2iN4GSyzBP9sCGTe37P15x2R14VaBQk0VA0ezTRN1KHYBBsHhvpGZ2Xy/pA=="], + + "@lezer/css": ["@lezer/css@1.3.6", "", { "dependencies": { "@lezer/common": "^1.2.0", "@lezer/highlight": "^1.0.0", "@lezer/lr": "^1.3.0" } }, "sha512-YJE78Wcg+zX8f10hiHWQ4Az48Qr/c13eId0VtRQYLBpxHDmDeSrXIlkbl+fJGW42rWC/uoUco9mhBZeVWP/A1g=="], + + "@lezer/go": ["@lezer/go@1.0.1", "", { "dependencies": { "@lezer/common": "^1.2.0", "@lezer/highlight": "^1.0.0", "@lezer/lr": "^1.3.0" } }, "sha512-xToRsYxwsgJNHTgNdStpcvmbVuKxTapV0dM0wey1geMMRc9aggoVyKgzYp41D2/vVOx+Ii4hmE206kvxIXBVXQ=="], + + "@lezer/highlight": ["@lezer/highlight@1.2.3", "", { "dependencies": { "@lezer/common": "^1.3.0" } }, "sha512-qXdH7UqTvGfdVBINrgKhDsVTJTxactNNxLk7+UMwZhU13lMHaOBlJe9Vqp907ya56Y3+ed2tlqzys7jDkTmW0g=="], + + "@lezer/html": ["@lezer/html@1.3.13", "", { "dependencies": { "@lezer/common": "^1.2.0", "@lezer/highlight": "^1.0.0", "@lezer/lr": "^1.0.0" } }, "sha512-oI7n6NJml729m7pjm9lvLvmXbdoMoi2f+1pwSDJkl9d68zGr7a9Btz8NdHTGQZtW2DA25ybeuv/SyDb9D5tseg=="], + + "@lezer/java": ["@lezer/java@1.1.3", "", { "dependencies": { "@lezer/common": "^1.2.0", "@lezer/highlight": "^1.0.0", "@lezer/lr": "^1.0.0" } }, "sha512-yHquUfujwg6Yu4Fd1GNHCvidIvJwi/1Xu2DaKl/pfWIA2c1oXkVvawH3NyXhCaFx4OdlYBVX5wvz2f7Aoa/4Xw=="], + + "@lezer/javascript": ["@lezer/javascript@1.5.4", "", { "dependencies": { "@lezer/common": "^1.2.0", "@lezer/highlight": "^1.1.3", "@lezer/lr": "^1.3.0" } }, "sha512-vvYx3MhWqeZtGPwDStM2dwgljd5smolYD2lR2UyFcHfxbBQebqx8yjmFmxtJ/E6nN6u1D9srOiVWm3Rb4tmcUA=="], + + "@lezer/json": ["@lezer/json@1.0.3", "", { "dependencies": { "@lezer/common": "^1.2.0", "@lezer/highlight": "^1.0.0", "@lezer/lr": "^1.0.0" } }, "sha512-BP9KzdF9Y35PDpv04r0VeSTKDeox5vVr3efE7eBbx3r4s3oNLfunchejZhjArmeieBH+nVOpgIiBJpEAv8ilqQ=="], + + "@lezer/lr": ["@lezer/lr@1.4.10", "", { "dependencies": { "@lezer/common": "^1.0.0" } }, "sha512-rnCpTIBafOx4mRp43xOxDJbFipJm/c0cia/V5TiGlhmMa+wsSdoGmUN3w5Bqrks/09Q/D4tNAmWaT8p6NRi77A=="], + + "@lezer/markdown": ["@lezer/markdown@1.7.2", "", { "dependencies": { "@lezer/common": "^1.5.0", "@lezer/highlight": "^1.0.0" } }, "sha512-iTkYvoVcKt3WkeL7qUDyXHONZEwLio4wj8KTNi2dnjQEXBZKMV63BpQrPqfsM+OkvuRbiSTAcycYAsQzLhRNoQ=="], + + "@lezer/php": ["@lezer/php@1.0.5", "", { "dependencies": { "@lezer/common": "^1.2.0", "@lezer/highlight": "^1.0.0", "@lezer/lr": "^1.1.0" } }, "sha512-W7asp9DhM6q0W6DYNwIkLSKOvxlXRrif+UXBMxzsJUuqmhE7oVU+gS3THO4S/Puh7Xzgm858UNaFi6dxTP8dJA=="], + + "@lezer/python": ["@lezer/python@1.1.19", "", { "dependencies": { "@lezer/common": "^1.2.0", "@lezer/highlight": "^1.0.0", "@lezer/lr": "^1.0.0" } }, "sha512-MhQIURHRytsNzP/YXnqpYKW6la6voAH3kyplTOOiCdjyFY6cWWGFVmYVdHIPrElqSDf4iCDktQCockB9FxuhzQ=="], + + "@lezer/rust": ["@lezer/rust@1.0.2", "", { "dependencies": { "@lezer/common": "^1.2.0", "@lezer/highlight": "^1.0.0", "@lezer/lr": "^1.0.0" } }, "sha512-Lz5sIPBdF2FUXcWeCu1//ojFAZqzTQNRga0aYv6dYXqJqPfMdCAI0NzajWUd4Xijj1IKJLtjoXRPMvTKWBcqKg=="], + + "@lezer/sass": ["@lezer/sass@1.1.0", "", { "dependencies": { "@lezer/common": "^1.2.0", "@lezer/highlight": "^1.0.0", "@lezer/lr": "^1.0.0" } }, "sha512-3mMGdCTUZ/84ArHOuXWQr37pnf7f+Nw9ycPUeKX+wu19b7pSMcZGLbaXwvD2APMBDOGxPmpK/O6S1v1EvLoqgQ=="], + + "@lezer/xml": ["@lezer/xml@1.0.6", "", { "dependencies": { "@lezer/common": "^1.2.0", "@lezer/highlight": "^1.0.0", "@lezer/lr": "^1.0.0" } }, "sha512-CdDwirL0OEaStFue/66ZmFSeppuL6Dwjlk8qk153mSQwiSH/Dlri4GNymrNWnUmPl2Um7QfV1FO9KFUyX3Twww=="], + + "@lezer/yaml": ["@lezer/yaml@1.0.4", "", { "dependencies": { "@lezer/common": "^1.2.0", "@lezer/highlight": "^1.0.0", "@lezer/lr": "^1.4.0" } }, "sha512-2lrrHqxalACEbxIbsjhqGpSW8kWpUKuY6RHgnSAFZa6qK62wvnPxA8hGOwOoDbwHcOFs5M4o27mjGu+P7TvBmw=="], + + "@lucide/vue": ["@lucide/vue@1.29.0", "", { "peerDependencies": { "vue": ">=3.0.1" } }, "sha512-UnbPPAdxWGqABrjcQJZFqawSgZTBmeKOrNFPI9sqKaDkgLoEbYyx+ivIdiYRR1YoYhWWg7dCdvjtCwK6zBxWDQ=="], + + "@marijn/find-cluster-break": ["@marijn/find-cluster-break@1.0.3", "", {}, "sha512-FY+MKLBoTsLNJF/eLWaOsXGdz6uh3Iu1axjPf6TUq92IYumcTcXWHoS747JARLkcdlJ/Waiaxc5wQfFO8jC6NA=="], + + "@mermaid-js/parser": ["@mermaid-js/parser@1.2.0", "", { "dependencies": { "@chevrotain/types": "~11.1.2" } }, "sha512-oYPyv8A4As1yH5Bx+04iQEQxXuIQDe0GKCNSRgao6z8AM9jixXIfP0vsppRLvGf+nKIOb9/LdpWA4YuJiVvESA=="], "@oj2/api": ["@oj2/api@workspace:apps/api"], "@oj2/contract": ["@oj2/contract@workspace:packages/contract"], + "@oj2/web": ["@oj2/web@workspace:apps/web"], + + "@oxc-project/types": ["@oxc-project/types@0.143.0", "", {}, "sha512-u6JZdLBTLotrNC9Vd6vPssINdzcCzleKAH6EJKImQb7GtYvX5keN2dxkoK44stCc4tffE6QQRtZTXVSzsLUlWA=="], + + "@rolldown/binding-android-arm64": ["@rolldown/binding-android-arm64@1.2.3", "", { "os": "android", "cpu": "arm64" }, "sha512-zrJtHDcaZJ1Fp7xf4hNl+7seH9Cn/N5TwLYkhgXREtBwAd/jaqW3uqeHxpDugJLVICWg4eW44kOQEGJ1r6jCGw=="], + + "@rolldown/binding-darwin-arm64": ["@rolldown/binding-darwin-arm64@1.2.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-ieIiibVCp0tX7TLu2cafoNPv8wJyYi01ekXpbf8q2j7F4rGAhhXb/eQh7ge9DRBY78GwmRQtvjZDux7EDbA8kA=="], + + "@rolldown/binding-darwin-x64": ["@rolldown/binding-darwin-x64@1.2.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-Zh9tCon19eDXJoihx0rqKhMUlMYqzwj3aPsSuHmI4RWZh62dWUL+DJN4C5YQya5TcQBJU/Fe8+rY0jhXTQITqA=="], + + "@rolldown/binding-freebsd-x64": ["@rolldown/binding-freebsd-x64@1.2.3", "", { "os": "freebsd", "cpu": "x64" }, "sha512-nGbJWewA1wrXXZiQhjAT5rhibGfns5ZNkDVqxsO6zJ3f3YvpoDNNmGMSbbhLuXKjNScaBJVOAboztAWVespQMg=="], + + "@rolldown/binding-linux-arm-gnueabihf": ["@rolldown/binding-linux-arm-gnueabihf@1.2.3", "", { "os": "linux", "cpu": "arm" }, "sha512-QNniJr5Kml0kDEB98jiDOJjXNroxIIi0IXIbdYzY26Xt1pVbeP62+KnoIZLwirOymX/0jDk/2gI/bNUv7A7OIw=="], + + "@rolldown/binding-linux-arm64-gnu": ["@rolldown/binding-linux-arm64-gnu@1.2.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-TkqEAcmmvH3I/q4114NB4RVt6241Dao48pF45uLcFGrwAaIn0iITgTAKP/dLjbN0R4buJjGb91+UHSoFmpgIWw=="], + + "@rolldown/binding-linux-arm64-musl": ["@rolldown/binding-linux-arm64-musl@1.2.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-NHqjnxpsndf4MPymxteFAWHHfkTL8HjWh1KB7z23ofZ6QO2euONuxDXjat69dKZRALnGypg8k8SsK8vZJoXv1Q=="], + + "@rolldown/binding-linux-ppc64-gnu": ["@rolldown/binding-linux-ppc64-gnu@1.2.3", "", { "os": "linux", "cpu": "ppc64" }, "sha512-6tbrbwfz5GB9DQ4Jwo6hy9v+vR31xZlvzZ6n5Xut6Hhx5PvrA9q/HsK8KMaYQp063iqZGXwNvZtYNLD7EM/x0w=="], + + "@rolldown/binding-linux-s390x-gnu": ["@rolldown/binding-linux-s390x-gnu@1.2.3", "", { "os": "linux", "cpu": "s390x" }, "sha512-oyuXxXmoZHjXC917IAPFAAv4wWAa0cM9afk8nx1+9/jNNOX1uPf8yDA6p7G0RypOfw/X0PQt5IfoquY1um+zSg=="], + + "@rolldown/binding-linux-x64-gnu": ["@rolldown/binding-linux-x64-gnu@1.2.3", "", { "os": "linux", "cpu": "x64" }, "sha512-TytMwF2KVGqP2tgd0I1OY0PAv78dZRAYcF5ssDzjM34SUXCED3uXvSd5+lHoC0bTD6eEdFz7LdQNCO1y0oVk9w=="], + + "@rolldown/binding-linux-x64-musl": ["@rolldown/binding-linux-x64-musl@1.2.3", "", { "os": "linux", "cpu": "x64" }, "sha512-/E9m3qstrJFVPoULV25mVQblSNExY2+kBsYe4sy0Tn0yOOgJ8wZbZt3KnRbF/XeU2Gl1STKUQnDNTqhIE5MD4A=="], + + "@rolldown/binding-openharmony-arm64": ["@rolldown/binding-openharmony-arm64@1.2.3", "", { "os": "none", "cpu": "arm64" }, "sha512-Kr0OcsoQI816i6HOl3vFHpd1K0eZyh76zgfj4c1nTyaTsd5r2Mj1lwM4R90y/qaCfmTn9eHy0SKwi98eitRxug=="], + + "@rolldown/binding-win32-arm64-msvc": ["@rolldown/binding-win32-arm64-msvc@1.2.3", "", { "os": "win32", "cpu": "arm64" }, "sha512-hOtMwTqnME+/gJcH/PCZ0wn0zPUjiWOgkHpxbSJpfGKMezHltx1S7/k1SitzVa7Ww2cqrDDaFbZEhcJZO8o+Jw=="], + + "@rolldown/binding-win32-x64-msvc": ["@rolldown/binding-win32-x64-msvc@1.2.3", "", { "os": "win32", "cpu": "x64" }, "sha512-ekcqMMkI2PlhYnfzQnB/cEdYUVVJViWvoUyLrbzgDoi3Snfc1mVBwdnc306ufA5ejy8JSPjT2RlW1nQSjW7efg=="], + + "@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.1", "", {}, "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw=="], + + "@transloadit/prettier-bytes": ["@transloadit/prettier-bytes@0.3.5", "", {}, "sha512-xF4A3d/ZyX2LJWeQZREZQw+qFX4TGQ8bGVP97OLRt6sPO6T0TNHBFTuRHOJh7RNmYOBmQ9MHxpolD9bXihpuVA=="], + "@types/bun": ["@types/bun@1.3.14", "https://registry.npmjs.com/@types/bun/-/bun-1.3.14.tgz", { "dependencies": { "bun-types": "1.3.14" } }, "sha512-h1hFqFVcvAvD9j9K7ZW7vd82aSA+rTdznZa+5bwvCwqSB1jmmfLcbIWhOLx1/+boy/xmjgCs/OMUL8hRJSmnPw=="], + "@types/canvas-confetti": ["@types/canvas-confetti@1.9.0", "", {}, "sha512-aBGj/dULrimR1XDZLtG9JwxX1b4HPRF6CX9Yfwh3NvstZEm1ZL7RBnel4keCPSqs1ANRu1u2Aoz9R+VmtjYuTg=="], + + "@types/d3": ["@types/d3@7.4.3", "", { "dependencies": { "@types/d3-array": "*", "@types/d3-axis": "*", "@types/d3-brush": "*", "@types/d3-chord": "*", "@types/d3-color": "*", "@types/d3-contour": "*", "@types/d3-delaunay": "*", "@types/d3-dispatch": "*", "@types/d3-drag": "*", "@types/d3-dsv": "*", "@types/d3-ease": "*", "@types/d3-fetch": "*", "@types/d3-force": "*", "@types/d3-format": "*", "@types/d3-geo": "*", "@types/d3-hierarchy": "*", "@types/d3-interpolate": "*", "@types/d3-path": "*", "@types/d3-polygon": "*", "@types/d3-quadtree": "*", "@types/d3-random": "*", "@types/d3-scale": "*", "@types/d3-scale-chromatic": "*", "@types/d3-selection": "*", "@types/d3-shape": "*", "@types/d3-time": "*", "@types/d3-time-format": "*", "@types/d3-timer": "*", "@types/d3-transition": "*", "@types/d3-zoom": "*" } }, "sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww=="], + + "@types/d3-array": ["@types/d3-array@3.2.2", "", {}, "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw=="], + + "@types/d3-axis": ["@types/d3-axis@3.0.6", "", { "dependencies": { "@types/d3-selection": "*" } }, "sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw=="], + + "@types/d3-brush": ["@types/d3-brush@3.0.6", "", { "dependencies": { "@types/d3-selection": "*" } }, "sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A=="], + + "@types/d3-chord": ["@types/d3-chord@3.0.6", "", {}, "sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg=="], + + "@types/d3-cloud": ["@types/d3-cloud@1.2.9", "", { "dependencies": { "@types/d3": "^3" } }, "sha512-5EWJvnlCrqTThGp8lYHx+DL00sOjx2HTlXH1WRe93k5pfOIhPQaL63NttaKYIbT7bTXp/USiunjNS/N4ipttIQ=="], + + "@types/d3-color": ["@types/d3-color@3.1.3", "", {}, "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A=="], + + "@types/d3-contour": ["@types/d3-contour@3.0.6", "", { "dependencies": { "@types/d3-array": "*", "@types/geojson": "*" } }, "sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg=="], + + "@types/d3-delaunay": ["@types/d3-delaunay@6.0.4", "", {}, "sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw=="], + + "@types/d3-dispatch": ["@types/d3-dispatch@3.0.7", "", {}, "sha512-5o9OIAdKkhN1QItV2oqaE5KMIiXAvDWBDPrD85e58Qlz1c1kI/J0NcqbEG88CoTwJrYe7ntUCVfeUl2UJKbWgA=="], + + "@types/d3-drag": ["@types/d3-drag@3.0.7", "", { "dependencies": { "@types/d3-selection": "*" } }, "sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ=="], + + "@types/d3-dsv": ["@types/d3-dsv@3.0.7", "", {}, "sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g=="], + + "@types/d3-ease": ["@types/d3-ease@3.0.2", "", {}, "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA=="], + + "@types/d3-fetch": ["@types/d3-fetch@3.0.7", "", { "dependencies": { "@types/d3-dsv": "*" } }, "sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA=="], + + "@types/d3-force": ["@types/d3-force@3.0.10", "", {}, "sha512-ZYeSaCF3p73RdOKcjj+swRlZfnYpK1EbaDiYICEEp5Q6sUiqFaFQ9qgoshp5CzIyyb/yD09kD9o2zEltCexlgw=="], + + "@types/d3-format": ["@types/d3-format@3.0.4", "", {}, "sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g=="], + + "@types/d3-geo": ["@types/d3-geo@3.1.1", "", { "dependencies": { "@types/geojson": "*" } }, "sha512-65Emv9fQiQQqphLlRkuQ5ypPsOmWPhtBGCMv61JDPEPMvsx+gzhGf74yw1a78xFKPj6zw4AgQICJoQv0vK9M2w=="], + + "@types/d3-hierarchy": ["@types/d3-hierarchy@3.1.7", "", {}, "sha512-tJFtNoYBtRtkNysX1Xq4sxtjK8YgoWUNpIiUee0/jHGRwqvzYxkq0hGVbbOGSz+JgFxxRu4K8nb3YpG3CMARtg=="], + + "@types/d3-interpolate": ["@types/d3-interpolate@3.0.4", "", { "dependencies": { "@types/d3-color": "*" } }, "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA=="], + + "@types/d3-path": ["@types/d3-path@3.1.1", "", {}, "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg=="], + + "@types/d3-polygon": ["@types/d3-polygon@3.0.2", "", {}, "sha512-ZuWOtMaHCkN9xoeEMr1ubW2nGWsp4nIql+OPQRstu4ypeZ+zk3YKqQT0CXVe/PYqrKpZAi+J9mTs05TKwjXSRA=="], + + "@types/d3-quadtree": ["@types/d3-quadtree@3.0.6", "", {}, "sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg=="], + + "@types/d3-random": ["@types/d3-random@3.0.4", "", {}, "sha512-UHYId5WTCx4L4YNel7NU00XUXXgvgpgZOvp10PuvsQENjMDXhh2RyFc0KBjO7B45ne4Ha1yVH7ii0vnzKkuzWA=="], + + "@types/d3-scale": ["@types/d3-scale@4.0.9", "", { "dependencies": { "@types/d3-time": "*" } }, "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw=="], + + "@types/d3-scale-chromatic": ["@types/d3-scale-chromatic@3.1.0", "", {}, "sha512-iWMJgwkK7yTRmWqRB5plb1kadXyQ5Sj8V/zYlFGMUBbIPKQScw+Dku9cAAMgJG+z5GYDoMjWGLVOvjghDEFnKQ=="], + + "@types/d3-selection": ["@types/d3-selection@3.0.11", "", {}, "sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w=="], + + "@types/d3-shape": ["@types/d3-shape@3.1.8", "", { "dependencies": { "@types/d3-path": "*" } }, "sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w=="], + + "@types/d3-time": ["@types/d3-time@3.0.4", "", {}, "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g=="], + + "@types/d3-time-format": ["@types/d3-time-format@4.0.3", "", {}, "sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg=="], + + "@types/d3-timer": ["@types/d3-timer@3.0.2", "", {}, "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw=="], + + "@types/d3-transition": ["@types/d3-transition@3.0.9", "", { "dependencies": { "@types/d3-selection": "*" } }, "sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg=="], + + "@types/d3-zoom": ["@types/d3-zoom@3.0.8", "", { "dependencies": { "@types/d3-interpolate": "*", "@types/d3-selection": "*" } }, "sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw=="], + + "@types/estree": ["@types/estree@1.0.9", "", {}, "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg=="], + + "@types/event-emitter": ["@types/event-emitter@0.3.5", "", {}, "sha512-zx2/Gg0Eg7gwEiOIIh5w9TrhKKTeQh7CPCOPNc0el4pLSwzebA8SmnHwZs2dWlLONvyulykSwGSQxQHLhjGLvQ=="], + + "@types/geojson": ["@types/geojson@7946.0.16", "", {}, "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg=="], + + "@types/jsesc": ["@types/jsesc@2.5.1", "", {}, "sha512-9VN+6yxLOPLOav+7PwjZbxiID2bVaeq0ED4qSQmdQTdjnXJSaCVKTR58t15oqH1H5t8Ng2ZX1SabJVoN9Q34bw=="], + + "@types/linkify-it": ["@types/linkify-it@5.0.0", "", {}, "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q=="], + + "@types/lodash": ["@types/lodash@4.17.25", "", {}, "sha512-+K1NIO8I+F9/wNulfVvu23QYd0Pe9/OCqRrim4NoYIf1VoEDL90Ve4ClzpyqBLc7NpGGWRvYNCKZ1BE/Jpf8dQ=="], + + "@types/lodash-es": ["@types/lodash-es@4.17.12", "", { "dependencies": { "@types/lodash": "*" } }, "sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ=="], + + "@types/markdown-it": ["@types/markdown-it@14.1.2", "", { "dependencies": { "@types/linkify-it": "^5", "@types/mdurl": "^2" } }, "sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog=="], + + "@types/mdurl": ["@types/mdurl@2.0.0", "", {}, "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg=="], + "@types/node": ["@types/node@26.1.2", "https://registry.npmjs.com/@types/node/-/node-26.1.2.tgz", { "dependencies": { "undici-types": "~8.3.0" } }, "sha512-Vu4a5UFA9rIIFJ7rB/Vaafh9lrCQszopTCx6KjFboXTGQbPNasehVR5TEiithSDGyd1DEiUByggTZsg8jukeIg=="], - "buffer-from": ["buffer-from@1.1.2", "", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="], + "@types/retry": ["@types/retry@0.12.2", "", {}, "sha512-XISRgDJ2Tc5q4TRqvgJtzsRkFYNJzZrhTdtMoGVBttwzzQJkPnS3WWTFc7kuDRoPtPakl+T+OfdEUjYJj7Jbow=="], + + "@types/trusted-types": ["@types/trusted-types@2.0.7", "", {}, "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw=="], + + "@types/web-bluetooth": ["@types/web-bluetooth@0.0.21", "", {}, "sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA=="], + + "@typescript/typescript-aix-ppc64": ["@typescript/typescript-aix-ppc64@7.0.2", "", { "os": "aix", "cpu": "ppc64" }, "sha512-MTKKkWB7p/0E9xi1d1tHtZ5PiLkGEMIq88pK2CubZjOsLtYTLqhgIgi6zepFa+9GHZ6h05NMCkQxGKiPXMxXtQ=="], + + "@typescript/typescript-darwin-arm64": ["@typescript/typescript-darwin-arm64@7.0.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-gowzar9MwS/aRWp6f3a4KUqzRjAZjOsmGNCM6LcTgXum+dBfgsBVMN+AgvOCCbguXyick6LJhpBszxMebJ8syA=="], + + "@typescript/typescript-darwin-x64": ["@typescript/typescript-darwin-x64@7.0.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-SZ9xZInqApNlNGc9s0W1VSsktYSOe9cFqNOIqmN1Gs8SmkjKZYFt017G4VwPxASInODuAdbTW7sXiFUf893RgA=="], + + "@typescript/typescript-freebsd-arm64": ["@typescript/typescript-freebsd-arm64@7.0.2", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-W5NH4y/J0plIIS5b2xvTEkU7JFxyqdMAOgf+Ilhl0vHQXKO5dZoxd+C/jEtq56c4F3wk71RB4BMRQ2XdI+bwYQ=="], + + "@typescript/typescript-freebsd-x64": ["@typescript/typescript-freebsd-x64@7.0.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-UMGDx5sTpzNw3WiPebH7l90IWfJggEd+egHt/q6p7/Cm3zqoV7VxkGXt+3DxPIw8CcmvAB0j3sVVfbhX+M4Tpw=="], + + "@typescript/typescript-linux-arm": ["@typescript/typescript-linux-arm@7.0.2", "", { "os": "linux", "cpu": "arm" }, "sha512-gffT3xPz9sR7j/YJExkyPntrI0P2EP9XbOyWzth2/Gs0RstK+90RBcO0ncXoXy/beYll1SXw846Nf2zdnEz0QQ=="], + + "@typescript/typescript-linux-arm64": ["@typescript/typescript-linux-arm64@7.0.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-Qh4eU4/y3yDjnfjjyPYihMj5/ODIlmt+Bzu17OI+fiSRDW57QmU5SiN63exPRNJPKUzcc1INa1NXdrJ+MqHjUQ=="], + + "@typescript/typescript-linux-loong64": ["@typescript/typescript-linux-loong64@7.0.2", "", { "os": "linux", "cpu": "none" }, "sha512-uEHck9i8hoAzXPiYRib1O7miOnz23SxIeVl6F4LXox+qov1K35jHcEW6VHKvZI+pyvl7fZEP4MCU5LYvIq1GuQ=="], + + "@typescript/typescript-linux-mips64el": ["@typescript/typescript-linux-mips64el@7.0.2", "", { "os": "linux", "cpu": "none" }, "sha512-R4KvAMnE43W5Qeqb0Ly56O3mWMWIAgsMyz36DCaycd5nbg/9kzm0liw3JocfRqyJY0KPmzFjbswozXyW0DnIYA=="], + + "@typescript/typescript-linux-ppc64": ["@typescript/typescript-linux-ppc64@7.0.2", "", { "os": "linux", "cpu": "ppc64" }, "sha512-DORx5b3sd/4S7eayxm4FQv+A7CrkUIGRaHiwI8oiHTAI1fAPWhF4J0vAlkC8biAlHSVVwxMQ3tjZ2/DVbnQiiA=="], + + "@typescript/typescript-linux-riscv64": ["@typescript/typescript-linux-riscv64@7.0.2", "", { "os": "linux", "cpu": "none" }, "sha512-wf0jqEDOjrPRnKwYRyyJDRo11KMbvMFrU+q4zqKyChODBzvlkbhNQfKvLxQCcwTpdDaXSHZTVuh0JoCrKCUMHQ=="], + + "@typescript/typescript-linux-s390x": ["@typescript/typescript-linux-s390x@7.0.2", "", { "os": "linux", "cpu": "s390x" }, "sha512-IkwJc3L7yhytWd/ewjyxNDfOmswCm9GWMJT/ue/dU4aZNbwZeYAetq42VyLmsmSjvoX7z74X6ZaYCtzAr0EuGw=="], + + "@typescript/typescript-linux-x64": ["@typescript/typescript-linux-x64@7.0.2", "", { "os": "linux", "cpu": "x64" }, "sha512-EYdf2cNg7rgCWJnxCdJ+F3V39O8ihb37eHAu1LK8oAFizgTQbPOK7zHHXbPt8rX24COqODXeI3sIf0fCXG7H/A=="], + + "@typescript/typescript-netbsd-arm64": ["@typescript/typescript-netbsd-arm64@7.0.2", "", { "os": "none", "cpu": "arm64" }, "sha512-+polYF4MF04aPpO5FTkHran9yUQDSXqy5GiSDKpsll5jy3l3+g9QLhpf39T+ePtefhXLOGrLl0QIjkQP6VnelA=="], + + "@typescript/typescript-netbsd-x64": ["@typescript/typescript-netbsd-x64@7.0.2", "", { "os": "none", "cpu": "x64" }, "sha512-8YIT0EHM/3dq10ZOVF/A7pc/YSMtbcecct4rWtexrnSCHOPcpC2KTLXfTCR6vDpnSiY12heNb1GiN/wu+T/FyA=="], + + "@typescript/typescript-openbsd-arm64": ["@typescript/typescript-openbsd-arm64@7.0.2", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-APT8+ClYnuYm1u9+kgGXoMj2VzWzcymwh2gNSQVySHfkRDGOTVkoWLjCmOQSaO+PoqQ57B0flRp9SA+7GnnkzQ=="], + + "@typescript/typescript-openbsd-x64": ["@typescript/typescript-openbsd-x64@7.0.2", "", { "os": "openbsd", "cpu": "x64" }, "sha512-yX7s+Q0Dln0Dt9tEzZsAjXXR/+ytBM7AlglaqyeMPxQszJ1JhlJdZ6jLA+IzldHtflX81em7lDao1xXu+aRRkg=="], + + "@typescript/typescript-sunos-x64": ["@typescript/typescript-sunos-x64@7.0.2", "", { "os": "sunos", "cpu": "x64" }, "sha512-dLJDGaLZ1D4HPQn62u1n8mBDkJREwMsAkCdkwd4Ieqw+x3TUyTsqY0YiBCtE6H6OzzgGk3iuZ3vFWRS+E8/d1g=="], + + "@typescript/typescript-win32-arm64": ["@typescript/typescript-win32-arm64@7.0.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-Gyl1Vy6OsWesLzmq+EP0Fb7b4Nid5232AvcA2SFcdYreldpNtYFFofPjnt62y9hQy7VTaZp65ICJjuAQRaVcIQ=="], + + "@typescript/typescript-win32-x64": ["@typescript/typescript-win32-x64@7.0.2", "", { "os": "win32", "cpu": "x64" }, "sha512-0BQ3HkAHHlKLSp1qRvf3SUhGpGsDuhB/jgFw75guyqbxJqEaS0Cw/VFO8i2nHglJUzQCRtMMR/IBAKE3ETMC4g=="], + + "@uppy/companion-client": ["@uppy/companion-client@5.1.1", "", { "dependencies": { "@uppy/utils": "^7.1.1", "namespace-emitter": "^2.0.1", "p-retry": "^6.1.0" }, "peerDependencies": { "@uppy/core": "^5.1.1" } }, "sha512-DzrOWTbIZHvtgAFXBMYHk2wD27NjpBSVhY2tEiEIUhPd2CxbFRZjHM/N3HOt3VwZEAP471QWFLlJRWPcIY3A2Q=="], + + "@uppy/core": ["@uppy/core@5.2.0", "", { "dependencies": { "@transloadit/prettier-bytes": "^0.3.4", "@uppy/store-default": "^5.0.0", "@uppy/utils": "^7.1.4", "lodash": "^4.17.21", "mime-match": "^1.0.2", "namespace-emitter": "^2.0.1", "nanoid": "^5.0.9", "preact": "^10.5.13" } }, "sha512-uvfNyz4cnaplt7LYJmEZHuqOuav0tKp4a9WKJIaH6iIj7XiqYvS2J5SEByexAlUFlzefOAyjzj4Ja2dd/8aMrw=="], + + "@uppy/store-default": ["@uppy/store-default@5.0.0", "", {}, "sha512-hQtCSQ1yGiaval/wVYUWquYGDJ+bpQ7e4FhUUAsRQz1x1K+o7NBtjfp63O9I4Ks1WRoKunpkarZ+as09l02cPw=="], + + "@uppy/utils": ["@uppy/utils@7.2.0", "", { "dependencies": { "lodash": "^4.17.23", "preact": "^10.26.10" } }, "sha512-6lC246qszMv6bTyl/+QyHwrudgeguWkA94ME1wHn+a6uRAvmtAEaUManIfGqTJfoKvWAiCJqdJPl5xRJjhAloQ=="], + + "@uppy/xhr-upload": ["@uppy/xhr-upload@5.2.0", "", { "dependencies": { "@uppy/companion-client": "^5.1.1", "@uppy/utils": "^7.2.0" }, "peerDependencies": { "@uppy/core": "^5.2.0" } }, "sha512-3LV/X5Of6BINnKplP+CwUJ0a4/7cRFfzxwGyXnW+uCrNQHoo09dttcz3begWHejGvzenQHuUnMO3Fxyc71Pryg=="], + + "@upsetjs/venn.js": ["@upsetjs/venn.js@2.0.0", "", { "optionalDependencies": { "d3-selection": "^3.0.0", "d3-transition": "^3.0.1" } }, "sha512-WbBhLrooyePuQ1VZxrJjtLvTc4NVfpOyKx0sKqioq9bX1C1m7Jgykkn8gLrtwumBioXIqam8DLxp88Adbue6Hw=="], + + "@vavt/copy2clipboard": ["@vavt/copy2clipboard@1.0.3", "", {}, "sha512-HtG48r2FBYp9eRvGB3QGmtRBH1zzRRAVvFbGgFstOwz4/DDaNiX0uZc3YVKPydqgOav26pibr9MtoCaWxn7aeA=="], + + "@vavt/markdown-theme": ["@vavt/markdown-theme@5.0.2", "", {}, "sha512-cGYEV2VIUmhT9p8Wua9yXh7sKN1f3cbztTFHlkYyH9SzbpiKUnQrcbDts/0fP6cmvZPOe9SWWsDujmyuHepJdQ=="], + + "@vavt/util": ["@vavt/util@2.1.2", "", {}, "sha512-L3UbSJthJwr3wq0x93O5TrCepimrmVZaIl2ciZbeL18G5++gBhJXNhcH7RcVk/6rr3SavWOvwhig0mqRLoR7dw=="], + + "@vitejs/plugin-legacy": ["@vitejs/plugin-legacy@8.2.3", "", { "dependencies": { "@babel/core": "^7.29.7", "@babel/plugin-transform-dynamic-import": "^7.29.7", "@babel/plugin-transform-modules-systemjs": "^7.29.8", "@babel/preset-env": "^7.29.7", "babel-plugin-polyfill-corejs3": "^0.14.2", "babel-plugin-polyfill-regenerator": "^0.6.8", "browserslist": "^4.28.7", "browserslist-to-esbuild": "^2.1.1", "core-js": "^3.49.0", "magic-string": "^1.1.0", "regenerator-runtime": "^0.14.1", "systemjs": "^6.15.1" }, "peerDependencies": { "terser": "^5.16.0", "vite": "^8.0.0" } }, "sha512-MMHLsn/0FrBkCQyb1x44cPYGwkfqcGr2FblcEP2ne79aXe7d5tFKoZ09xs5C4wuh8fOu2JRaNhgAS3K3uvUeJA=="], + + "@vitejs/plugin-vue": ["@vitejs/plugin-vue@6.0.8", "", { "dependencies": { "@rolldown/pluginutils": "^1.0.1" }, "peerDependencies": { "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0", "vue": "^3.2.25" } }, "sha512-0ZjgOg7oO6farnNGup7yvoM/YXZV84OZxHAwtflItNa/6zzQyVb5LNxyea3FEKEX2XlagIKzrlH7wwxkKgtiew=="], + + "@vue-flow/background": ["@vue-flow/background@1.3.2", "", { "peerDependencies": { "@vue-flow/core": "^1.23.0", "vue": "^3.3.0" } }, "sha512-eJPhDcLj1wEo45bBoqTXw1uhl0yK2RaQGnEINqvvBsAFKh/camHJd5NPmOdS1w+M9lggc9igUewxaEd3iCQX2w=="], + + "@vue-flow/controls": ["@vue-flow/controls@1.1.3", "", { "peerDependencies": { "@vue-flow/core": "^1.23.0", "vue": "^3.3.0" } }, "sha512-XCf+G+jCvaWURdFlZmOjifZGw3XMhN5hHlfMGkWh9xot+9nH9gdTZtn+ldIJKtarg3B21iyHU8JjKDhYcB6JMw=="], + + "@vue-flow/core": ["@vue-flow/core@1.48.2", "", { "dependencies": { "@vueuse/core": "^10.5.0", "d3-drag": "^3.0.0", "d3-interpolate": "^3.0.1", "d3-selection": "^3.0.0", "d3-zoom": "^3.0.0" }, "peerDependencies": { "vue": "^3.3.0" } }, "sha512-raxhgKWE+G/mcEvXJjGFUDYW9rAI3GOtiHR3ZkNpwBWuIaCC1EYiBmKGwJOoNzVFgwO7COgErnK7i08i287AFA=="], + + "@vue-flow/minimap": ["@vue-flow/minimap@1.5.4", "", { "dependencies": { "d3-selection": "^3.0.0", "d3-zoom": "^3.0.0" }, "peerDependencies": { "@vue-flow/core": "^1.23.0", "vue": "^3.3.0" } }, "sha512-l4C+XTAXnRxsRpUdN7cAVFBennC1sVRzq4bDSpVK+ag7tdMczAnhFYGgbLkUw3v3sY6gokyWwMl8CDonp8eB2g=="], + + "@vue-flow/node-resizer": ["@vue-flow/node-resizer@1.5.1", "", { "dependencies": { "d3-drag": "^3.0.0", "d3-selection": "^3.0.0" }, "peerDependencies": { "@vue-flow/core": "^1.23.0", "vue": "^3.3.0" } }, "sha512-DNYgXTF39GXPAygVMFmgn5azgFztaWfqbWvv8D/X0HGgeunRrexJ0W94DyXH1rnndGyYAJ0KexEjyelsb+Nlbg=="], + + "@vue-flow/node-toolbar": ["@vue-flow/node-toolbar@1.1.1", "", { "peerDependencies": { "@vue-flow/core": "^1.23.0", "vue": "^3.3.0" } }, "sha512-vgSnGMLd3FXstdpvC721qcBDzGkPsudmyA8urEP55/EMikCp59klgzPvVULTDxxsew5MdXtTBCumsqOi+PXJdg=="], + + "@vue-macros/common": ["@vue-macros/common@3.1.4", "", { "dependencies": { "@vue/compiler-sfc": "^3.5.22", "ast-kit": "^2.1.2", "local-pkg": "^1.1.2", "magic-string-ast": "^1.0.2", "unplugin-utils": "^0.3.0" }, "peerDependencies": { "vue": "^2.7.0 || ^3.2.25" }, "optionalPeers": ["vue"] }, "sha512-/5Fv+6DgIcM9ajY05ZmKBv+LMX1M9A0X+IUwDRVdt67ciw8OV9bvG2r34p3RiEadlsQybjhKPRKNXDC8Bp23cw=="], + + "@vue/compiler-core": ["@vue/compiler-core@3.5.41", "", { "dependencies": { "@babel/parser": "^7.29.8", "@vue/shared": "3.5.41", "entities": "^7.0.1", "estree-walker": "^2.0.2", "source-map-js": "^1.2.1" } }, "sha512-q0Xtv/F9w2YO/7htQhtiL+Ev2WCJbe5N2hc+XfgyKkEKqWpSxknmT8QOuGdEKNdjPq0c3F7rNpFkTo3Kfrm7pg=="], + + "@vue/compiler-dom": ["@vue/compiler-dom@3.5.41", "", { "dependencies": { "@vue/compiler-core": "3.5.41", "@vue/shared": "3.5.41" } }, "sha512-oKacVfNglLvGjnS6BXOlGL7EyG2h8X03pqXCjzotRZUaXGjbrTJUnVAQjrCqUnS+lyu31nwQjZY/d817GmCnfw=="], + + "@vue/compiler-sfc": ["@vue/compiler-sfc@3.5.41", "", { "dependencies": { "@babel/parser": "^7.29.8", "@vue/compiler-core": "3.5.41", "@vue/compiler-dom": "3.5.41", "@vue/compiler-ssr": "3.5.41", "@vue/shared": "3.5.41", "estree-walker": "^2.0.2", "magic-string": "^0.30.21", "postcss": "^8.5.19", "source-map-js": "^1.2.1" } }, "sha512-XJhip7R2wy6vX3knCxdZN4KracFaZUef58s1KYewqluedHIJaPIVfXoYT7MF1F8nCvv6k8bWWxDC8opMkg1VTQ=="], + + "@vue/compiler-ssr": ["@vue/compiler-ssr@3.5.41", "", { "dependencies": { "@vue/compiler-dom": "3.5.41", "@vue/shared": "3.5.41" } }, "sha512-U3v5OejKEGqOI0Wy0+Sz7hGuIFZHA4LSXzrNM3IMIeDyJEBBfTpX26n3SDgToRpP2bLc9FfI2j/kSgcJ8Emq5A=="], + + "@vue/devtools-api": ["@vue/devtools-api@8.2.1", "", { "dependencies": { "@vue/devtools-kit": "^8.2.1" } }, "sha512-6u4vXBlIBAC1wMplIZgpyPn7uh/s4Bf6F5bMzvLv+EdJ0aHs/+4B7Ygv864EStQSjRbsRzTko/kUG1A1IejQ3A=="], + + "@vue/devtools-kit": ["@vue/devtools-kit@8.2.1", "", { "dependencies": { "@vue/devtools-shared": "^8.2.1", "birpc": "^2.6.1", "hookable": "^5.5.3", "perfect-debounce": "^2.0.0" } }, "sha512-FIGIuq3AWReEpbAHY/cRGeHDfI0qOb8OCQ3YjbEAX04uaxIDbGc9rhkbVcG7rnfHPXE3RsU5KrWOu9V/okd8AQ=="], + + "@vue/devtools-shared": ["@vue/devtools-shared@8.2.1", "", {}, "sha512-Fkac7lUdGReh6pVOi3AYPRGe82LQqRmAfThW7RRligOAP0ZA/Z1z9XLHDM9dv34pV2HRc79DK8uKPeG2fLnA/g=="], + + "@vue/reactivity": ["@vue/reactivity@3.5.41", "", { "dependencies": { "@vue/shared": "3.5.41" } }, "sha512-rznsqKM0np0x18EjzF8x88MpEhdNsffbvFbckLL5+oUKz1BxAImEmO7J1ArRYSyo6aQaVoBDp7jEkT91OOxydA=="], + + "@vue/runtime-core": ["@vue/runtime-core@3.5.41", "", { "dependencies": { "@vue/reactivity": "3.5.41", "@vue/shared": "3.5.41" } }, "sha512-Vcry58hiAKwGen9Z1jUZE0feFsNArPCMOImYI8el48A9Idf6DuQYD0U05zZIF2Iad1hGhPSvcbBbAOhNr55fhg=="], + + "@vue/runtime-dom": ["@vue/runtime-dom@3.5.41", "", { "dependencies": { "@vue/reactivity": "3.5.41", "@vue/runtime-core": "3.5.41", "@vue/shared": "3.5.41", "csstype": "^3.2.3" } }, "sha512-3vVBahVBS9+U6cmXBLyb8nE6/yYo4J/CGI9eVFs3KiMc0YHuudwKyShTD65jtJy/L9PUUxNAFu4cj4LiJ0UFbw=="], + + "@vue/server-renderer": ["@vue/server-renderer@3.5.41", "", { "dependencies": { "@vue/compiler-ssr": "3.5.41", "@vue/runtime-dom": "3.5.41", "@vue/shared": "3.5.41" } }, "sha512-n6hx/pNFfbD6SuyeuMVkvqox8bwf/ET9JlA/kAz/imw8sw++wkqKe2mHX5KutjPpbKE4Z56yTHszoOjGMI9igQ=="], + + "@vue/shared": ["@vue/shared@3.5.41", "", {}, "sha512-IOnwSCma8j+9xJT6b8H0dEYidC80NsYmNMlZxRsukYcSoGaDBohog5hDxzeUXdFeGWFA++vWvxqOmrr96VlqMA=="], + + "@vue/tsconfig": ["@vue/tsconfig@0.9.1", "", { "peerDependencies": { "typescript": ">= 5.8", "vue": "^3.4.0" }, "optionalPeers": ["typescript", "vue"] }, "sha512-buvjm+9NzLCJL29KY1j1991YYJ5e6275OiK+G4jtmfIb+z4POywbdm0wXusT9adVWqe0xqg70TbI7+mRx4uU9w=="], + + "@vueuse/core": ["@vueuse/core@14.4.0", "", { "dependencies": { "@types/web-bluetooth": "^0.0.21", "@vueuse/metadata": "14.4.0", "@vueuse/shared": "14.4.0" }, "peerDependencies": { "vue": "^3.5.0" } }, "sha512-X4WHz1HlCzCBoYXesUkifzzWBAcZgXG8Fi5iNPQg/epdzOB3gu8Fawj3hvuwYR1nGcXGnvxwYYcUC/71++svtQ=="], + + "@vueuse/metadata": ["@vueuse/metadata@14.4.0", "", {}, "sha512-swx/255R6JyHZFJhx845iz5CRWDZdCfvkZOpACWc5+c5WHcG24mv8gUT1WIdFQaHt6dq79rvILd9QnCWiyVm9g=="], + + "@vueuse/router": ["@vueuse/router@14.4.0", "", { "dependencies": { "@vueuse/shared": "14.4.0" }, "peerDependencies": { "vue": "^3.5.0", "vue-router": "^4.0.0 || ^5.0.0" } }, "sha512-q+dRUKI6xYGT4vNPq2t3fujrXPw2KBFV5Jxhw38liTFzwzGXBYcf8Qdjk7AiQpjEH2xLSin6WVTvj85EEIpo9w=="], + + "@vueuse/shared": ["@vueuse/shared@14.4.0", "", { "peerDependencies": { "vue": "^3.5.0" } }, "sha512-JRgY90Sz8DDtPMsaDflvPMp9xYk69JZAmbuDvAquUVXKr2gEjqtzGNTTthLfckH0BzBqvnu31gb4a8TGLRe79g=="], + + "@wangeditor-next/basic-modules": ["@wangeditor-next/basic-modules@6.2.0", "", { "dependencies": { "is-url": "^1.2.4" }, "peerDependencies": { "@wangeditor-next/core": "^6.2.0", "dom7": "^3.0.0 || ^4.0.0", "lodash.throttle": "^4.1.1", "nanoid": "^5.0.0", "slate": "^0.124.0", "snabbdom": "^3.6.0" } }, "sha512-TZF9qtiMq1go5gRMTFicq4mQmaBYsO6QOCe3Z8/A7DHbDDBHNvFKe5BnXhRBBS3kp3ukQK5ZIsdeR8kBrtWY7A=="], + + "@wangeditor-next/code-highlight": ["@wangeditor-next/code-highlight@6.2.0", "", { "dependencies": { "prismjs": "^1.23.0" }, "peerDependencies": { "@wangeditor-next/core": "^6.2.0", "dom7": "^3.0.0 || ^4.0.0", "slate": "^0.124.0", "snabbdom": "^3.6.0" } }, "sha512-7vWVazX+1uc72AG+bH/OS5UwGTYc0xLgErVEv/UbANnxP1d7eje67X2XY+tS7UH9UmirySmZoTKFtyduJSRCyg=="], + + "@wangeditor-next/core": ["@wangeditor-next/core@6.2.0", "", { "dependencies": { "@types/event-emitter": "^0.3.3", "event-emitter": "^0.3.5", "html-void-elements": "^3.0.0", "i18next": "^23.0.0", "scroll-into-view-if-needed": "^3.0.0", "slate-history": "^0.115.0" }, "peerDependencies": { "@uppy/core": "^2.1.1 || ^5.0.0", "@uppy/xhr-upload": "^2.0.3 || ^5.0.0", "dom7": "^3.0.0 || ^4.0.0", "is-hotkey": "^0.2.0", "lodash.camelcase": "^4.3.0", "lodash.clonedeep": "^4.5.0", "lodash.debounce": "^4.0.8", "lodash.foreach": "^4.5.0", "lodash.throttle": "^4.1.1", "lodash.toarray": "^4.4.0", "nanoid": "^5.0.0", "slate": "^0.124.0", "snabbdom": "^3.6.0" } }, "sha512-uHPgMUGJGMhV9LUhH9ZgjbfmTGowU/3G9KFHT+PdPvM7CDhdIq1gBGVbGapEWS1nLlfbx9JeOcDhFq1yBKwmaw=="], + + "@wangeditor-next/editor": ["@wangeditor-next/editor@6.2.0", "", { "dependencies": { "@uppy/core": "^5.0.0", "@uppy/xhr-upload": "^5.0.0", "@wangeditor-next/basic-modules": "^6.2.0", "@wangeditor-next/code-highlight": "^6.2.0", "@wangeditor-next/core": "^6.2.0", "@wangeditor-next/list-module": "^6.2.0", "@wangeditor-next/table-module": "^6.2.0", "@wangeditor-next/upload-image-module": "^6.2.0", "@wangeditor-next/video-module": "^6.2.0", "dom7": "^4.0.0", "is-hotkey": "^0.2.0", "lodash.camelcase": "^4.3.0", "lodash.clonedeep": "^4.5.0", "lodash.debounce": "^4.0.8", "lodash.foreach": "^4.5.0", "lodash.throttle": "^4.1.1", "lodash.toarray": "^4.4.0", "nanoid": "^5.0.0", "slate": "^0.124.0", "snabbdom": "^3.6.0" } }, "sha512-8x/JdD9NWAw/gd+LPtnEEVU8ef2y2/Db7DGpVnYENITyvet1tjcObBZxyG5edCe6KACgseaqklhVnpfG8xayJA=="], + + "@wangeditor-next/editor-for-vue": ["@wangeditor-next/editor-for-vue@6.2.0", "", { "peerDependencies": { "@wangeditor-next/editor": "^6.2.0", "vue": "^3.0.5" } }, "sha512-c8QDYnioeaySIIsSdRwM9GL+HyvbTuy2WyaxfvSf+GHoYGe3Hi5NeA8AL8OQnJIJRSkkU0dS7CZlnjnWMD+7AA=="], + + "@wangeditor-next/list-module": ["@wangeditor-next/list-module@6.2.0", "", { "peerDependencies": { "@wangeditor-next/core": "^6.2.0", "dom7": "^3.0.0 || ^4.0.0", "slate": "^0.124.0", "snabbdom": "^3.6.0" } }, "sha512-rmefXe7HYY0o+treOWdhq9xDLilcoQJzBZ33V1f9vp6mvlBNXvpdPfFwnNK3TrFVtUF6ELtiGA4dFe/CeajiQQ=="], + + "@wangeditor-next/table-module": ["@wangeditor-next/table-module@6.2.0", "", { "peerDependencies": { "@wangeditor-next/core": "^6.2.0", "dom7": "^3.0.0 || ^4.0.0", "lodash.debounce": "^4.0.8", "lodash.throttle": "^4.1.1", "nanoid": "^5.0.0", "slate": "^0.124.0", "snabbdom": "^3.6.0" } }, "sha512-XAipfwV5/aYwwxQaz4Ty1EZbyOr9ssYq8NL7oqzUrQB6Atpt2MZLeS88+npEhu8V5xntqunvijIx1ohl8/uRCg=="], + + "@wangeditor-next/upload-image-module": ["@wangeditor-next/upload-image-module@6.2.0", "", { "peerDependencies": { "@uppy/core": "^2.0.3 || ^5.0.0", "@uppy/xhr-upload": "^2.0.3 || ^5.0.0", "@wangeditor-next/basic-modules": "^6.2.0", "@wangeditor-next/core": "^6.2.0", "dom7": "^3.0.0 || ^4.0.0", "lodash.foreach": "^4.5.0", "slate": "^0.124.0", "snabbdom": "^3.6.0" } }, "sha512-w65xUavqDi62kIwItnyKBjmMeBrUCKZkerACQKNU7MeLj1w71q7IPGmvi5It6dwA4Pk64sl+l0eKCsLI6dLRFA=="], + + "@wangeditor-next/video-module": ["@wangeditor-next/video-module@6.2.0", "", { "peerDependencies": { "@uppy/core": "^2.1.4 || ^5.0.0", "@uppy/xhr-upload": "^2.0.7 || ^5.0.0", "@wangeditor-next/core": "^6.2.0", "dom7": "^3.0.0 || ^4.0.0", "nanoid": "^5.0.0", "slate": "^0.124.0", "snabbdom": "^3.6.0" } }, "sha512-4SDqjHb+VwOSmyV+XlCBrR9SLWU0svnN7TnfDmgZHO/wUGxK6uVY0biKsn6GDpQjGL77XqiQNE/PK8YT4noY2Q=="], + + "acorn": ["acorn@8.18.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ=="], + + "agent-base": ["agent-base@6.0.2", "", { "dependencies": { "debug": "4" } }, "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ=="], + + "argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], + + "ast-kit": ["ast-kit@2.2.0", "", { "dependencies": { "@babel/parser": "^7.28.5", "pathe": "^2.0.3" } }, "sha512-m1Q/RaVOnTp9JxPX+F+Zn7IcLYMzM8kZofDImfsKZd8MbR+ikdOzTeztStWqfrqIxZnYWryyI9ePm3NGjnZgGw=="], + + "ast-walker-scope": ["ast-walker-scope@0.9.0", "", { "dependencies": { "@babel/parser": "^7.29.2", "@babel/types": "^7.29.0", "ast-kit": "^2.2.0" } }, "sha512-IJdzo2vLiElBxKzwS36VsCue/62d6IdWjnPB2v3nuPKeWGynp6FF/CYoLa5i/3jXH/z97ZDdsXz6abpgM6w07A=="], + + "async-validator": ["async-validator@4.2.5", "", {}, "sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg=="], + + "asynckit": ["asynckit@0.4.0", "", {}, "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="], + + "axios": ["axios@1.19.0", "", { "dependencies": { "follow-redirects": "^1.16.0", "form-data": "^4.0.6", "https-proxy-agent": "^5.0.1", "proxy-from-env": "^2.1.0" } }, "sha512-ht/iuYZXEjFxLH/Hkezgd7m6JKlHHXEUSneaDz8uZe1Gj5QZtCnpyDsckvAiEnT89OEbCLmnte4R4sn7P0EKFw=="], + + "babel-plugin-polyfill-corejs2": ["babel-plugin-polyfill-corejs2@0.4.17", "", { "dependencies": { "@babel/compat-data": "^7.28.6", "@babel/helper-define-polyfill-provider": "^0.6.8", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "sha512-aTyf30K/rqAsNwN76zYrdtx8obu0E4KoUME29B1xj+B3WxgvWkp943vYQ+z8Mv3lw9xHXMHpvSPOBxzAkIa94w=="], + + "babel-plugin-polyfill-corejs3": ["babel-plugin-polyfill-corejs3@0.14.2", "", { "dependencies": { "@babel/helper-define-polyfill-provider": "^0.6.8", "core-js-compat": "^3.48.0" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "sha512-coWpDLJ410R781Npmn/SIBZEsAetR4xVi0SxLMXPaMO4lSf1MwnkGYMtkFxew0Dn8B3/CpbpYxN0JCgg8mn67g=="], + + "babel-plugin-polyfill-regenerator": ["babel-plugin-polyfill-regenerator@0.6.8", "", { "dependencies": { "@babel/helper-define-polyfill-provider": "^0.6.8" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "sha512-M762rNHfSF1EV3SLtnCJXFoQbbIIz0OyRwnCmV0KPC7qosSfCO0QLTSuJX3ayAebubhE6oYBAYPrBA5ljowaZg=="], + + "base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="], + + "baseline-browser-mapping": ["baseline-browser-mapping@2.11.12", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-r7WnVImvVCeFpf2DOXfy41aPWzeNg3H/A2X4dKmy1QL0MSyyk/e7z8ihJ3N6Nn2PsdhkVlqnEfnUE4a05P2aTA=="], + + "birpc": ["birpc@2.9.0", "", {}, "sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw=="], + + "browserslist": ["browserslist@4.28.7", "", { "dependencies": { "baseline-browser-mapping": "^2.10.44", "caniuse-lite": "^1.0.30001806", "electron-to-chromium": "^1.5.393", "node-releases": "^2.0.51", "update-browserslist-db": "^1.2.3" }, "bin": { "browserslist": "cli.js" } }, "sha512-JxV13hNrFxqjOc8alRbq9dK1MM79NEXYpma2B2J4wAtpWS5zIEIKqWPGCl7N4o7Uc7B7itylh7SuDujATRyyTw=="], + + "browserslist-to-esbuild": ["browserslist-to-esbuild@2.1.1", "", { "dependencies": { "meow": "^13.0.0" }, "peerDependencies": { "browserslist": "*" }, "bin": { "browserslist-to-esbuild": "cli/index.js" } }, "sha512-KN+mty6C3e9AN8Z5dI1xeN15ExcRNeISoC3g7V0Kax/MMF9MSoYA2G7lkTTcVUFntiEjkpI0HNgqJC1NjdyNUw=="], + + "buffer": ["buffer@6.0.3", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.2.1" } }, "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA=="], + + "buffer-from": ["buffer-from@1.1.2", "https://registry.npmjs.com/buffer-from/-/buffer-from-1.1.2.tgz", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="], "bun-types": ["bun-types@1.3.14", "https://registry.npmjs.com/bun-types/-/bun-types-1.3.14.tgz", { "dependencies": { "@types/node": "*" } }, "sha512-4N0ig0fEomHt5R0KCFWjovxow98rIoRwKolrYdCcknNwMekCXRnWEUvgu5soYV8QXtVsrUD8B95MBOZGPvr6KQ=="], - "drizzle-kit": ["drizzle-kit@0.31.10", "", { "dependencies": { "@drizzle-team/brocli": "^0.10.2", "@esbuild-kit/esm-loader": "^2.5.5", "esbuild": "^0.25.4", "tsx": "^4.21.0" }, "bin": { "drizzle-kit": "bin.cjs" } }, "sha512-7OZcmQUrdGI+DUNNsKBn1aW8qSoKuTH7d0mYgSP8bAzdFzKoovxEFnoGQp2dVs82EOJeYycqRtciopszwUf8bw=="], + "call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="], - "drizzle-orm": ["drizzle-orm@0.45.2", "", { "peerDependencies": { "@aws-sdk/client-rds-data": ">=3", "@cloudflare/workers-types": ">=4", "@electric-sql/pglite": ">=0.2.0", "@libsql/client": ">=0.10.0", "@libsql/client-wasm": ">=0.10.0", "@neondatabase/serverless": ">=0.10.0", "@op-engineering/op-sqlite": ">=2", "@opentelemetry/api": "^1.4.1", "@planetscale/database": ">=1.13", "@prisma/client": "*", "@tidbcloud/serverless": "*", "@types/better-sqlite3": "*", "@types/pg": "*", "@types/sql.js": "*", "@upstash/redis": ">=1.34.7", "@vercel/postgres": ">=0.8.0", "@xata.io/client": "*", "better-sqlite3": ">=7", "bun-types": "*", "expo-sqlite": ">=14.0.0", "gel": ">=2", "knex": "*", "kysely": "*", "mysql2": ">=2", "pg": ">=8", "postgres": ">=3", "sql.js": ">=1", "sqlite3": ">=5" }, "optionalPeers": ["@aws-sdk/client-rds-data", "@cloudflare/workers-types", "@electric-sql/pglite", "@libsql/client", "@libsql/client-wasm", "@neondatabase/serverless", "@op-engineering/op-sqlite", "@opentelemetry/api", "@planetscale/database", "@prisma/client", "@tidbcloud/serverless", "@types/better-sqlite3", "@types/pg", "@types/sql.js", "@upstash/redis", "@vercel/postgres", "@xata.io/client", "better-sqlite3", "bun-types", "expo-sqlite", "gel", "knex", "kysely", "mysql2", "pg", "postgres", "sql.js", "sqlite3"] }, "sha512-kY0BSaTNYWnoDMVoyY8uxmyHjpJW1geOmBMdSSicKo9CIIWkSxMIj2rkeSR51b8KAPB7m+qysjuHme5nKP+E5Q=="], + "caniuse-lite": ["caniuse-lite@1.0.30001807", "", {}, "sha512-daRXJ9EB/rdRgu7kV+TTl1YUKtlsMWblPl2sLnpg9DZae16QCegol6A1SmCE31Lm9mXC1sRWGt/krouH+/dl7Q=="], - "esbuild": ["esbuild@0.25.12", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.12", "@esbuild/android-arm": "0.25.12", "@esbuild/android-arm64": "0.25.12", "@esbuild/android-x64": "0.25.12", "@esbuild/darwin-arm64": "0.25.12", "@esbuild/darwin-x64": "0.25.12", "@esbuild/freebsd-arm64": "0.25.12", "@esbuild/freebsd-x64": "0.25.12", "@esbuild/linux-arm": "0.25.12", "@esbuild/linux-arm64": "0.25.12", "@esbuild/linux-ia32": "0.25.12", "@esbuild/linux-loong64": "0.25.12", "@esbuild/linux-mips64el": "0.25.12", "@esbuild/linux-ppc64": "0.25.12", "@esbuild/linux-riscv64": "0.25.12", "@esbuild/linux-s390x": "0.25.12", "@esbuild/linux-x64": "0.25.12", "@esbuild/netbsd-arm64": "0.25.12", "@esbuild/netbsd-x64": "0.25.12", "@esbuild/openbsd-arm64": "0.25.12", "@esbuild/openbsd-x64": "0.25.12", "@esbuild/openharmony-arm64": "0.25.12", "@esbuild/sunos-x64": "0.25.12", "@esbuild/win32-arm64": "0.25.12", "@esbuild/win32-ia32": "0.25.12", "@esbuild/win32-x64": "0.25.12" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg=="], + "canvas-confetti": ["canvas-confetti@1.9.4", "", {}, "sha512-yxQbJkAVrFXWNbTUjPqjF7G+g6pDotOUHGbkZq2NELZUMDpiJ85rIEazVb8GTaAptNW2miJAXbs1BtioA251Pw=="], - "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], + "chart.js": ["chart.js@4.5.1", "", { "dependencies": { "@kurkle/color": "^0.3.0" } }, "sha512-GIjfiT9dbmHRiYi6Nl2yFCq7kkwdkp1W/lp2J99rX0yo9tgJGn3lKQATztIjb5tVtevcBtIdICNWqlq5+E8/Pw=="], - "get-tsconfig": ["get-tsconfig@4.14.1", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-Dz/6HxkrxgNehhxLVeyv8sad9UzF2xBVeaKBQNDfJ5XiSXmp2gTR0eO0RWiT2NCKS5aGP9jjkOMggTN90qU50A=="], + "chartjs-chart-wordcloud": ["chartjs-chart-wordcloud@4.4.5", "", { "dependencies": { "@types/d3-cloud": "^1.2.9", "d3-cloud": "^1.2.7" }, "peerDependencies": { "chart.js": "^4.1.0" } }, "sha512-x7gdE5BZyj31+bjHbZ/0tX4hB6un7TQhvwdO5qHhqpmJCS6bONHjMuDzSjL/Qw2uI6CT2/U04LGqBREHwBiK3g=="], - "hono": ["hono@4.13.0", "", {}, "sha512-jhunvfHWxd7J5EFfSgH4xsYJzSe/lfqbUCxiyyeaQasUsXeEHXtzVid+7EOGByc5JnFa23SSFL3Y2RV/z1T+eQ=="], + "chokidar": ["chokidar@5.0.0", "", { "dependencies": { "readdirp": "^5.0.0" } }, "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw=="], - "postgres": ["postgres@3.4.9", "", {}, "sha512-GD3qdB0x1z9xgFI6cdRD6xu2Sp2WCOEoe3mtnyB5Ee0XrrL5Pe+e4CCnJrRMnL1zYtRDZmQQVbvOttLnKDLnaw=="], + "codemirror": ["codemirror@6.0.2", "", { "dependencies": { "@codemirror/autocomplete": "^6.0.0", "@codemirror/commands": "^6.0.0", "@codemirror/language": "^6.0.0", "@codemirror/lint": "^6.0.0", "@codemirror/search": "^6.0.0", "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.0.0" } }, "sha512-VhydHotNW5w1UGK0Qj96BwSk/Zqbp9WbnyK2W/eVMv4QyF41INRGpjUhFJY7/uDNuudSc33a/PKr4iDqRduvHw=="], - "resolve-pkg-maps": ["resolve-pkg-maps@1.0.0", "", {}, "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw=="], + "combined-stream": ["combined-stream@1.0.8", "", { "dependencies": { "delayed-stream": "~1.0.0" } }, "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg=="], - "source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="], + "commander": ["commander@2.20.3", "", {}, "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="], - "source-map-support": ["source-map-support@0.5.21", "", { "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" } }, "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w=="], + "compute-scroll-into-view": ["compute-scroll-into-view@3.1.1", "", {}, "sha512-VRhuHOLoKYOy4UbilLbUzbYg93XLjv2PncJC50EuTWPA3gaja1UjBsUP/D/9/juV3vQFr6XBEzn9KCAHdUvOHw=="], - "tsx": ["tsx@4.23.9", "", { "dependencies": { "esbuild": "~0.28.0" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "bin": { "tsx": "dist/cli.mjs" } }, "sha512-6q8uTORRGauQVjqMQnKUucLFoeXZAfw6zKvG35GLbdKWbLdeOtZ3H4mhyA5mxuUd2o2cRTskhj59nLLQseUvUw=="], + "confbox": ["confbox@0.2.4", "", {}, "sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ=="], + + "convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="], + + "copy-text-to-clipboard": ["copy-text-to-clipboard@3.2.2", "", {}, "sha512-T6SqyLd1iLuqPA90J5N4cTalrtovCySh58iiZDGJ6FGznbclKh4UI+FGacQSgFzwKG77W7XT5gwbVEbd9cIH1A=="], + + "core-js": ["core-js@3.50.0", "", {}, "sha512-BRWgOLKkFeCgRudR6zrs8p9XJZcE14grzKMMssoYrk6krtuEZ7MTKPIY5RzOnqsEKIR9kst7wNzphttraT+Yqw=="], + + "core-js-compat": ["core-js-compat@3.50.0", "", { "dependencies": { "browserslist": "^4.28.7" } }, "sha512-XGpFGbMLHwSt74YLTKho7Ib242qi6O8MSX+sRokV4oz7iKXvQWGYZthjIhjRGMxjzVkAubBO512dKGYcefmX3Q=="], + + "cose-base": ["cose-base@1.0.3", "", { "dependencies": { "layout-base": "^1.0.0" } }, "sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg=="], + + "crelt": ["crelt@1.0.7", "", {}, "sha512-aK6BbWfhf4U/wCcLHKPJl/xa6VkVstRaPywWtMKGwuOLc/wZTyQYuoxgvZnNsBvv7Kg3YTBQYYBCggcviQczuA=="], + + "css-render": ["css-render@0.15.14", "", { "dependencies": { "@emotion/hash": "~0.8.0", "csstype": "~3.0.5" } }, "sha512-9nF4PdUle+5ta4W5SyZdLCCmFd37uVimSjg1evcTqKJCyvCEEj12WKzOSBNak6r4im4J4iYXKH1OWpUV5LBYFg=="], + + "cssfilter": ["cssfilter@0.0.10", "", {}, "sha512-FAaLDaplstoRsDR8XGYH51znUN0UY7nMc6Z9/fvE8EXGwvJE9hu7W2vHwx1+bd6gCYnln9nLbzxFTrcO9YQDZw=="], + + "csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="], + + "cytoscape": ["cytoscape@3.34.0", "", {}, "sha512-62rNSrioXw93uliKFBwjukeQyeWwH2PqDrTac31r2P6464u3AUvTk0xS4LVvT251g7IgkFunrI48ZEZGjywSOg=="], + + "cytoscape-cose-bilkent": ["cytoscape-cose-bilkent@4.1.0", "", { "dependencies": { "cose-base": "^1.0.0" }, "peerDependencies": { "cytoscape": "^3.2.0" } }, "sha512-wgQlVIUJF13Quxiv5e1gstZ08rnZj2XaLHGoFMYXz7SkNfCDOOteKBE6SYRfA9WxxI/iBc3ajfDoc6hb/MRAHQ=="], + + "cytoscape-fcose": ["cytoscape-fcose@2.2.0", "", { "dependencies": { "cose-base": "^2.2.0" }, "peerDependencies": { "cytoscape": "^3.2.0" } }, "sha512-ki1/VuRIHFCzxWNrsshHYPs6L7TvLu3DL+TyIGEsRcvVERmxokbf5Gdk7mFxZnTdiGtnA4cfSmjZJMviqSuZrQ=="], + + "d": ["d@1.0.2", "", { "dependencies": { "es5-ext": "^0.10.64", "type": "^2.7.2" } }, "sha512-MOqHvMWF9/9MX6nza0KgvFH4HpMU0EF5uUDXqX/BtxtU8NfB0QzRtJ8Oe/6SuS4kbhyzVJwjd97EA4PKrzJ8bw=="], + + "d3": ["d3@7.9.0", "", { "dependencies": { "d3-array": "3", "d3-axis": "3", "d3-brush": "3", "d3-chord": "3", "d3-color": "3", "d3-contour": "4", "d3-delaunay": "6", "d3-dispatch": "3", "d3-drag": "3", "d3-dsv": "3", "d3-ease": "3", "d3-fetch": "3", "d3-force": "3", "d3-format": "3", "d3-geo": "3", "d3-hierarchy": "3", "d3-interpolate": "3", "d3-path": "3", "d3-polygon": "3", "d3-quadtree": "3", "d3-random": "3", "d3-scale": "4", "d3-scale-chromatic": "3", "d3-selection": "3", "d3-shape": "3", "d3-time": "3", "d3-time-format": "4", "d3-timer": "3", "d3-transition": "3", "d3-zoom": "3" } }, "sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA=="], + + "d3-array": ["d3-array@3.2.4", "", { "dependencies": { "internmap": "1 - 2" } }, "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg=="], + + "d3-axis": ["d3-axis@3.0.0", "", {}, "sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw=="], + + "d3-brush": ["d3-brush@3.0.0", "", { "dependencies": { "d3-dispatch": "1 - 3", "d3-drag": "2 - 3", "d3-interpolate": "1 - 3", "d3-selection": "3", "d3-transition": "3" } }, "sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ=="], + + "d3-chord": ["d3-chord@3.0.1", "", { "dependencies": { "d3-path": "1 - 3" } }, "sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g=="], + + "d3-cloud": ["d3-cloud@1.2.9", "", { "dependencies": { "d3-dispatch": "^1.0.3" } }, "sha512-leL1GLneC9ZQtnV+6TGWrNlGfI1WX7S2arcTv2vae12DaXo5wjm6GBCkskXbrDlyOymd/A75Pyj1H37MW4BZ/Q=="], + + "d3-color": ["d3-color@3.1.0", "", {}, "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA=="], + + "d3-contour": ["d3-contour@4.0.2", "", { "dependencies": { "d3-array": "^3.2.0" } }, "sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA=="], + + "d3-delaunay": ["d3-delaunay@6.0.4", "", { "dependencies": { "delaunator": "5" } }, "sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A=="], + + "d3-dispatch": ["d3-dispatch@3.0.1", "", {}, "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg=="], + + "d3-drag": ["d3-drag@3.0.0", "", { "dependencies": { "d3-dispatch": "1 - 3", "d3-selection": "3" } }, "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg=="], + + "d3-dsv": ["d3-dsv@3.0.1", "", { "dependencies": { "commander": "7", "iconv-lite": "0.6", "rw": "1" }, "bin": { "csv2json": "bin/dsv2json.js", "csv2tsv": "bin/dsv2dsv.js", "dsv2dsv": "bin/dsv2dsv.js", "dsv2json": "bin/dsv2json.js", "json2csv": "bin/json2dsv.js", "json2dsv": "bin/json2dsv.js", "json2tsv": "bin/json2dsv.js", "tsv2csv": "bin/dsv2dsv.js", "tsv2json": "bin/dsv2json.js" } }, "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q=="], + + "d3-ease": ["d3-ease@3.0.1", "", {}, "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w=="], + + "d3-fetch": ["d3-fetch@3.0.1", "", { "dependencies": { "d3-dsv": "1 - 3" } }, "sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw=="], + + "d3-force": ["d3-force@3.0.0", "", { "dependencies": { "d3-dispatch": "1 - 3", "d3-quadtree": "1 - 3", "d3-timer": "1 - 3" } }, "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg=="], + + "d3-format": ["d3-format@3.1.2", "", {}, "sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg=="], + + "d3-geo": ["d3-geo@3.1.1", "", { "dependencies": { "d3-array": "2.5.0 - 3" } }, "sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q=="], + + "d3-hierarchy": ["d3-hierarchy@3.1.2", "", {}, "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA=="], + + "d3-interpolate": ["d3-interpolate@3.0.1", "", { "dependencies": { "d3-color": "1 - 3" } }, "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g=="], + + "d3-path": ["d3-path@3.1.0", "", {}, "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ=="], + + "d3-polygon": ["d3-polygon@3.0.1", "", {}, "sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg=="], + + "d3-quadtree": ["d3-quadtree@3.0.1", "", {}, "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw=="], + + "d3-random": ["d3-random@3.0.1", "", {}, "sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ=="], + + "d3-sankey": ["d3-sankey@0.12.3", "", { "dependencies": { "d3-array": "1 - 2", "d3-shape": "^1.2.0" } }, "sha512-nQhsBRmM19Ax5xEIPLMY9ZmJ/cDvd1BG3UVvt5h3WRxKg5zGRbvnteTyWAbzeSvlh3tW7ZEmq4VwR5mB3tutmQ=="], + + "d3-scale": ["d3-scale@4.0.2", "", { "dependencies": { "d3-array": "2.10.0 - 3", "d3-format": "1 - 3", "d3-interpolate": "1.2.0 - 3", "d3-time": "2.1.1 - 3", "d3-time-format": "2 - 4" } }, "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ=="], + + "d3-scale-chromatic": ["d3-scale-chromatic@3.1.0", "", { "dependencies": { "d3-color": "1 - 3", "d3-interpolate": "1 - 3" } }, "sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ=="], + + "d3-selection": ["d3-selection@3.0.0", "", {}, "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ=="], + + "d3-shape": ["d3-shape@3.2.0", "", { "dependencies": { "d3-path": "^3.1.0" } }, "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA=="], + + "d3-time": ["d3-time@3.1.0", "", { "dependencies": { "d3-array": "2 - 3" } }, "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q=="], + + "d3-time-format": ["d3-time-format@4.1.0", "", { "dependencies": { "d3-time": "1 - 3" } }, "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg=="], + + "d3-timer": ["d3-timer@3.0.1", "", {}, "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA=="], + + "d3-transition": ["d3-transition@3.0.1", "", { "dependencies": { "d3-color": "1 - 3", "d3-dispatch": "1 - 3", "d3-ease": "1 - 3", "d3-interpolate": "1 - 3", "d3-timer": "1 - 3" }, "peerDependencies": { "d3-selection": "2 - 3" } }, "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w=="], + + "d3-zoom": ["d3-zoom@3.0.0", "", { "dependencies": { "d3-dispatch": "1 - 3", "d3-drag": "2 - 3", "d3-interpolate": "1 - 3", "d3-selection": "2 - 3", "d3-transition": "2 - 3" } }, "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw=="], + + "dagre-d3-es": ["dagre-d3-es@7.0.14", "", { "dependencies": { "d3": "^7.9.0", "lodash-es": "^4.17.21" } }, "sha512-P4rFMVq9ESWqmOgK+dlXvOtLwYg0i7u0HBGJER0LZDJT2VHIPAMZ/riPxqJceWMStH5+E61QxFra9kIS3AqdMg=="], + + "date-fns": ["date-fns@4.4.0", "", {}, "sha512-+1UMbeh68lH1SegH83CGWwpb6OHHbpSgr3+s5Eww5M4CAgswBpoWS0AjTOfEJ33HiYKz1hdj/KTFprzXHmq/6w=="], + + "date-fns-tz": ["date-fns-tz@3.2.0", "", { "peerDependencies": { "date-fns": "^3.0.0 || ^4.0.0" } }, "sha512-sg8HqoTEulcbbbVXeg84u5UnlsQa8GS5QXMqjjYIhS4abEVVKIUwe0/l/UhrZdKaL/W5eWZNlbTeEIiOXTcsBQ=="], + + "dayjs": ["dayjs@1.11.21", "", {}, "sha512-98IT+HOahAisibz/yjKbzuOBwYcjJ7BCLPzARyHiyEBmRz4fatF+KPJszEHXsGYjUG234aH/cOjW1wwTbKUZlA=="], + + "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], + + "delaunator": ["delaunator@5.1.0", "", { "dependencies": { "robust-predicates": "^3.0.2" } }, "sha512-AGrQ4QSgssa1NGmWmLPqN5NY2KajF5MqxetNEO+o0n3ZwZZeTmt7bBnvzHWrmkZFxGgr4HdyFgelzgi06otLuQ=="], + + "delayed-stream": ["delayed-stream@1.0.0", "", {}, "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="], + + "detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], + + "dom7": ["dom7@4.0.6", "", { "dependencies": { "ssr-window": "^4.0.0" } }, "sha512-emjdpPLhpNubapLFdjNL9tP06Sr+GZkrIHEXLWvOGsytACUrkbeIdjO5g77m00BrHTznnlcNqgmn7pCN192TBA=="], + + "dompurify": ["dompurify@3.4.13", "", { "optionalDependencies": { "@types/trusted-types": "^2.0.7" } }, "sha512-2vmYIoqjze2d+kakP8S/nS5shfsl587kzwEjcGlTdiksUVgFHnFCsLYDVj/JNqJVOQZGSYBTmuycv0PodwmnMQ=="], + + "drizzle-kit": ["drizzle-kit@0.31.10", "https://registry.npmjs.com/drizzle-kit/-/drizzle-kit-0.31.10.tgz", { "dependencies": { "@drizzle-team/brocli": "^0.10.2", "@esbuild-kit/esm-loader": "^2.5.5", "esbuild": "^0.25.4", "tsx": "^4.21.0" }, "bin": { "drizzle-kit": "bin.cjs" } }, "sha512-7OZcmQUrdGI+DUNNsKBn1aW8qSoKuTH7d0mYgSP8bAzdFzKoovxEFnoGQp2dVs82EOJeYycqRtciopszwUf8bw=="], + + "drizzle-orm": ["drizzle-orm@0.45.2", "https://registry.npmjs.com/drizzle-orm/-/drizzle-orm-0.45.2.tgz", { "peerDependencies": { "@aws-sdk/client-rds-data": ">=3", "@cloudflare/workers-types": ">=4", "@electric-sql/pglite": ">=0.2.0", "@libsql/client": ">=0.10.0", "@libsql/client-wasm": ">=0.10.0", "@neondatabase/serverless": ">=0.10.0", "@op-engineering/op-sqlite": ">=2", "@opentelemetry/api": "^1.4.1", "@planetscale/database": ">=1.13", "@prisma/client": "*", "@tidbcloud/serverless": "*", "@types/better-sqlite3": "*", "@types/pg": "*", "@types/sql.js": "*", "@upstash/redis": ">=1.34.7", "@vercel/postgres": ">=0.8.0", "@xata.io/client": "*", "better-sqlite3": ">=7", "bun-types": "*", "expo-sqlite": ">=14.0.0", "gel": ">=2", "knex": "*", "kysely": "*", "mysql2": ">=2", "pg": ">=8", "postgres": ">=3", "sql.js": ">=1", "sqlite3": ">=5" }, "optionalPeers": ["@aws-sdk/client-rds-data", "@cloudflare/workers-types", "@electric-sql/pglite", "@libsql/client", "@libsql/client-wasm", "@neondatabase/serverless", "@op-engineering/op-sqlite", "@opentelemetry/api", "@planetscale/database", "@prisma/client", "@tidbcloud/serverless", "@types/better-sqlite3", "@types/pg", "@types/sql.js", "@upstash/redis", "@vercel/postgres", "@xata.io/client", "better-sqlite3", "bun-types", "expo-sqlite", "gel", "knex", "kysely", "mysql2", "pg", "postgres", "sql.js", "sqlite3"] }, "sha512-kY0BSaTNYWnoDMVoyY8uxmyHjpJW1geOmBMdSSicKo9CIIWkSxMIj2rkeSR51b8KAPB7m+qysjuHme5nKP+E5Q=="], + + "dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="], + + "electron-to-chromium": ["electron-to-chromium@1.5.402", "", {}, "sha512-/oOpMaPT6Yg+6/1XQhyIPlzgj7Ye9zf+nNM2Uh6OcE2G2oNptWazFa+qB2Pdqqbsc9KnIDzgAntoYN0dbwOXwA=="], + + "elkjs": ["elkjs@0.8.2", "", {}, "sha512-L6uRgvZTH+4OF5NE/MBbzQx/WYpru1xCBE9respNj6qznEewGUIfhzmm7horWWxbNO2M0WckQypGctR8lH79xQ=="], + + "entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="], + + "err-code": ["err-code@3.0.1", "", {}, "sha512-GiaH0KJUewYok+eeY05IIgjtAe4Yltygk9Wqp1V5yVWLdhf0hYZchRjNIT9bb0mSwRcIusT3cx7PJUf3zEIfUA=="], + + "es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="], + + "es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="], + + "es-object-atoms": ["es-object-atoms@1.1.2", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw=="], + + "es-set-tostringtag": ["es-set-tostringtag@2.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA=="], + + "es-toolkit": ["es-toolkit@1.50.0", "", {}, "sha512-OyZKhUVvEep9ITEiwHn8GKnMRQIVqoSIX7WnRbkWgJkllCujilqP2rD0u979tkl8wqyc8ICwlc1UBVv/Sl1G6w=="], + + "es5-ext": ["es5-ext@0.10.64", "", { "dependencies": { "es6-iterator": "^2.0.3", "es6-symbol": "^3.1.3", "esniff": "^2.0.1", "next-tick": "^1.1.0" } }, "sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg=="], + + "es6-iterator": ["es6-iterator@2.0.3", "", { "dependencies": { "d": "1", "es5-ext": "^0.10.35", "es6-symbol": "^3.1.1" } }, "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g=="], + + "es6-symbol": ["es6-symbol@3.1.4", "", { "dependencies": { "d": "^1.0.2", "ext": "^1.7.0" } }, "sha512-U9bFFjX8tFiATgtkJ1zg25+KviIXpgRvRHS8sau3GfhVzThRQrOeksPeT0BWW2MNZs1OEWJ1DPXOQMn0KKRkvg=="], + + "esbuild": ["esbuild@0.25.12", "https://registry.npmjs.com/esbuild/-/esbuild-0.25.12.tgz", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.12", "@esbuild/android-arm": "0.25.12", "@esbuild/android-arm64": "0.25.12", "@esbuild/android-x64": "0.25.12", "@esbuild/darwin-arm64": "0.25.12", "@esbuild/darwin-x64": "0.25.12", "@esbuild/freebsd-arm64": "0.25.12", "@esbuild/freebsd-x64": "0.25.12", "@esbuild/linux-arm": "0.25.12", "@esbuild/linux-arm64": "0.25.12", "@esbuild/linux-ia32": "0.25.12", "@esbuild/linux-loong64": "0.25.12", "@esbuild/linux-mips64el": "0.25.12", "@esbuild/linux-ppc64": "0.25.12", "@esbuild/linux-riscv64": "0.25.12", "@esbuild/linux-s390x": "0.25.12", "@esbuild/linux-x64": "0.25.12", "@esbuild/netbsd-arm64": "0.25.12", "@esbuild/netbsd-x64": "0.25.12", "@esbuild/openbsd-arm64": "0.25.12", "@esbuild/openbsd-x64": "0.25.12", "@esbuild/openharmony-arm64": "0.25.12", "@esbuild/sunos-x64": "0.25.12", "@esbuild/win32-arm64": "0.25.12", "@esbuild/win32-ia32": "0.25.12", "@esbuild/win32-x64": "0.25.12" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg=="], + + "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="], + + "escape-string-regexp": ["escape-string-regexp@5.0.0", "", {}, "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw=="], + + "esniff": ["esniff@2.0.1", "", { "dependencies": { "d": "^1.0.1", "es5-ext": "^0.10.62", "event-emitter": "^0.3.5", "type": "^2.7.2" } }, "sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg=="], + + "estree-walker": ["estree-walker@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g=="], + + "esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="], + + "event-emitter": ["event-emitter@0.3.5", "", { "dependencies": { "d": "1", "es5-ext": "~0.10.14" } }, "sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA=="], + + "evtd": ["evtd@0.2.4", "", {}, "sha512-qaeGN5bx63s/AXgQo8gj6fBkxge+OoLddLniox5qtLAEY5HSnuSlISXVPxnSae1dWblvTh4/HoMIB+mbMsvZzw=="], + + "exsolve": ["exsolve@1.1.1", "", {}, "sha512-9U/jZUgjnSGyntRr6y5Muu1MJcwFl6kPu7k8qLF0IMNfLqvw0NZ4nnVDq0RVoZ0RvCyumib4Ez3KYrVfilrw+g=="], + + "ext": ["ext@1.7.0", "", { "dependencies": { "type": "^2.7.2" } }, "sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw=="], + + "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], + + "fflate": ["fflate@0.8.3", "", {}, "sha512-tbZNuJrLwGUp3zshBtdy4W+ORxZuIh8a5ilyIEQDC5rY1f3U20JMry0Ll3WBzU58EZKsEuJFXhb5gwv8CsPvgA=="], + + "follow-redirects": ["follow-redirects@1.16.0", "", {}, "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw=="], + + "form-data": ["form-data@4.0.6", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "hasown": "^2.0.4", "mime-types": "^2.1.35" } }, "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ=="], + + "fsevents": ["fsevents@2.3.3", "https://registry.npmjs.com/fsevents/-/fsevents-2.3.3.tgz", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], + + "function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="], + + "gensync": ["gensync@1.0.0-beta.2", "", {}, "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="], + + "get-browser-rtc": ["get-browser-rtc@1.1.0", "", {}, "sha512-MghbMJ61EJrRsDe7w1Bvqt3ZsBuqhce5nrn/XAwgwOXhcsz53/ltdxOse1h/8eKXj5slzxdsz56g5rzOFSGwfQ=="], + + "get-intrinsic": ["get-intrinsic@1.3.0", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="], + + "get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="], + + "get-tsconfig": ["get-tsconfig@4.14.1", "https://registry.npmjs.com/get-tsconfig/-/get-tsconfig-4.14.1.tgz", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-Dz/6HxkrxgNehhxLVeyv8sad9UzF2xBVeaKBQNDfJ5XiSXmp2gTR0eO0RWiT2NCKS5aGP9jjkOMggTN90qU50A=="], + + "gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="], + + "hachure-fill": ["hachure-fill@0.5.2", "", {}, "sha512-3GKBOn+m2LX9iq+JC1064cSFprJY4jL1jCXTcpnfER5HYE2l/4EfWSGzkPa/ZDBmYI0ZOEj5VHV/eKnPGkHuOg=="], + + "has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="], + + "has-tostringtag": ["has-tostringtag@1.0.2", "", { "dependencies": { "has-symbols": "^1.0.3" } }, "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw=="], + + "hasown": ["hasown@2.0.4", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A=="], + + "highlight.js": ["highlight.js@11.11.1", "", {}, "sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w=="], + + "hono": ["hono@4.13.0", "https://registry.npmjs.com/hono/-/hono-4.13.0.tgz", {}, "sha512-jhunvfHWxd7J5EFfSgH4xsYJzSe/lfqbUCxiyyeaQasUsXeEHXtzVid+7EOGByc5JnFa23SSFL3Y2RV/z1T+eQ=="], + + "hookable": ["hookable@5.5.3", "", {}, "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ=="], + + "html-void-elements": ["html-void-elements@3.0.0", "", {}, "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg=="], + + "https-proxy-agent": ["https-proxy-agent@5.0.1", "", { "dependencies": { "agent-base": "6", "debug": "4" } }, "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA=="], + + "i18next": ["i18next@23.16.8", "", { "dependencies": { "@babel/runtime": "^7.23.2" } }, "sha512-06r/TitrM88Mg5FdUXAKL96dJMzgqLE5dv3ryBAra4KCwD9mJ4ndOTS95ZuymIGoE+2hzfdaMak2X11/es7ZWg=="], + + "iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="], + + "ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="], + + "import-meta-resolve": ["import-meta-resolve@4.2.0", "", {}, "sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg=="], + + "inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="], + + "internmap": ["internmap@2.0.3", "", {}, "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg=="], + + "is-core-module": ["is-core-module@2.16.2", "", { "dependencies": { "hasown": "^2.0.3" } }, "sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA=="], + + "is-hotkey": ["is-hotkey@0.2.0", "", {}, "sha512-UknnZK4RakDmTgz4PI1wIph5yxSs/mvChWs9ifnlXsKuXgWmOkY/hAE0H/k2MIqH0RlRye0i1oC07MCRSD28Mw=="], + + "is-network-error": ["is-network-error@1.3.2", "", {}, "sha512-PhBY86zaxNZUuWP6h13Vu5oFe0XY6/UlKzQnYFELzGVHygP3MxmvTfYSG7GN3aIab/iWudSMgjSnG9Dq+nHrgA=="], + + "is-url": ["is-url@1.2.4", "", {}, "sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww=="], + + "isomorphic.js": ["isomorphic.js@0.2.5", "", {}, "sha512-PIeMbHqMt4DnUP3MA/Flc0HElYjMXArsw1qwJZcm9sqR8mq3l8NYizFMty0pWwE/tzIGH3EKK5+jes5mAr85yw=="], + + "js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], + + "jsbi": ["jsbi@3.2.5", "", {}, "sha512-aBE4n43IPvjaddScbvWRA2YlTzKEynHzu7MqOyTipdHucf/VxS63ViCjxYRg86M8Rxwbt/GfzHl1kKERkt45fQ=="], + + "jsesc": ["jsesc@3.1.0", "", { "bin": { "jsesc": "bin/jsesc" } }, "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="], + + "json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="], + + "katex": ["katex@0.16.47", "", { "dependencies": { "commander": "^8.3.0" }, "bin": { "katex": "cli.js" } }, "sha512-Eeo8Ys1doU1z+x8AZsPpQu+p/QcZBI5PeOo7QGQdy2x2m0MU/hYagBbGOmXwr5KVbEfVuWv9LpnQWeehogurjg=="], + + "khroma": ["khroma@2.1.0", "", {}, "sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw=="], + + "layout-base": ["layout-base@1.0.2", "", {}, "sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg=="], + + "lib0": ["lib0@0.2.117", "", { "dependencies": { "isomorphic.js": "^0.2.4" }, "bin": { "0serve": "bin/0serve.js", "0gentesthtml": "bin/gentesthtml.js", "0ecdsa-generate-keypair": "bin/0ecdsa-generate-keypair.js" } }, "sha512-DeXj9X5xDCjgKLU/7RR+/HQEVzuuEUiwldwOGsHK/sfAfELGWEyTcf0x+uOvCvK3O2zPmZePXWL85vtia6GyZw=="], + + "lightningcss": ["lightningcss@1.33.0", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.33.0", "lightningcss-darwin-arm64": "1.33.0", "lightningcss-darwin-x64": "1.33.0", "lightningcss-freebsd-x64": "1.33.0", "lightningcss-linux-arm-gnueabihf": "1.33.0", "lightningcss-linux-arm64-gnu": "1.33.0", "lightningcss-linux-arm64-musl": "1.33.0", "lightningcss-linux-x64-gnu": "1.33.0", "lightningcss-linux-x64-musl": "1.33.0", "lightningcss-win32-arm64-msvc": "1.33.0", "lightningcss-win32-x64-msvc": "1.33.0" } }, "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA=="], + + "lightningcss-android-arm64": ["lightningcss-android-arm64@1.33.0", "", { "os": "android", "cpu": "arm64" }, "sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg=="], + + "lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.33.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg=="], + + "lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.33.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ=="], + + "lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.33.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg=="], + + "lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.33.0", "", { "os": "linux", "cpu": "arm" }, "sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ=="], + + "lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.33.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg=="], + + "lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.33.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ=="], + + "lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.33.0", "", { "os": "linux", "cpu": "x64" }, "sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg=="], + + "lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.33.0", "", { "os": "linux", "cpu": "x64" }, "sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw=="], + + "lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.33.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA=="], + + "lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.33.0", "", { "os": "win32", "cpu": "x64" }, "sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA=="], + + "linkify-it": ["linkify-it@5.0.2", "", { "dependencies": { "uc.micro": "^2.0.0" } }, "sha512-ONTm2jCMAVZjgQa/Fy1kScXsuOoF5NPTsoFBdE1KVIZ2vAh/r9+Bqo+0jINCBYnavTPQZz38QzFTme79ENoN3Q=="], + + "local-pkg": ["local-pkg@1.2.1", "", { "dependencies": { "mlly": "^1.7.4", "pkg-types": "^2.3.0", "quansync": "^0.2.11" } }, "sha512-++gUqRDEvcnN6Zhqrr+y/CkVEHhlrR96vZn3nZZPYzMcBUyBtTKzB9NadClFIsIVSsu+3i9tfk/erqy9kAmt7Q=="], + + "lodash": ["lodash@4.18.1", "", {}, "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q=="], + + "lodash-es": ["lodash-es@4.18.1", "", {}, "sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A=="], + + "lodash.camelcase": ["lodash.camelcase@4.3.0", "", {}, "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA=="], + + "lodash.clonedeep": ["lodash.clonedeep@4.5.0", "", {}, "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ=="], + + "lodash.debounce": ["lodash.debounce@4.0.8", "", {}, "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow=="], + + "lodash.foreach": ["lodash.foreach@4.5.0", "", {}, "sha512-aEXTF4d+m05rVOAUG3z4vZZ4xVexLKZGF0lIxuHZ1Hplpk/3B6Z1+/ICICYRLm7c41Z2xiejbkCkJoTlypoXhQ=="], + + "lodash.throttle": ["lodash.throttle@4.1.1", "", {}, "sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ=="], + + "lodash.toarray": ["lodash.toarray@4.4.0", "", {}, "sha512-QyffEA3i5dma5q2490+SgCvDN0pXLmRGSyAANuVi0HQ01Pkfr9fuoKQW8wm1wGBnJITs/mS7wQvS6VshUEBFCw=="], + + "lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="], + + "magic-string": ["magic-string@1.1.0", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-kS3VHe0nEPST2saQV4Rbkchcd3UBRkVTQHo1D3h/ZTwFDhai/mfKkmtPAtD129EOI7K3HlHIsFOt0WrI2/oU9g=="], + + "magic-string-ast": ["magic-string-ast@1.0.3", "", { "dependencies": { "magic-string": "^0.30.19" } }, "sha512-CvkkH1i81zl7mmb94DsRiFeG9V2fR2JeuK8yDgS8oiZSFa++wWLEgZ5ufEOyLHbvSbD1gTRKv9NdX69Rnvr9JA=="], + + "markdown-it": ["markdown-it@14.3.0", "", { "dependencies": { "argparse": "^2.0.1", "entities": "^4.5.0", "linkify-it": "^5.0.2", "mdurl": "^2.0.0", "punycode.js": "^2.3.1", "uc.micro": "^2.1.0" }, "bin": { "markdown-it": "bin/markdown-it.mjs" } }, "sha512-RCEsPjR+sr0x+AuYp601tKTkgFG4YEPLCzHST3cQ/fhlJkqAkz1L2/Qbp1j9qw5SBwQHFBoW8+hoN5xssOF0Tw=="], + + "markdown-it-image-figures": ["markdown-it-image-figures@2.1.1", "", { "peerDependencies": { "markdown-it": "*" } }, "sha512-mwXSQ2nPeVUzCMIE3HlLvjRioopiqyJLNph0pyx38yf9mpqFDhNGnMpAXF9/A2Xv0oiF2cVyg9xwfF0HNAz05g=="], + + "markdown-it-sub": ["markdown-it-sub@2.0.0", "", {}, "sha512-iCBKgwCkfQBRg2vApy9vx1C1Tu6D8XYo8NvevI3OlwzBRmiMtsJ2sXupBgEA7PPxiDwNni3qIUkhZ6j5wofDUA=="], + + "markdown-it-sup": ["markdown-it-sup@2.0.0", "", {}, "sha512-5VgmdKlkBd8sgXuoDoxMpiU+BiEt3I49GItBzzw7Mxq9CxvnhE/k09HFli09zgfFDRixDQDfDxi0mgBCXtaTvA=="], + + "marked": ["marked@16.4.2", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-TI3V8YYWvkVf3KJe1dRkpnjs68JUPyEa5vjKrp1XEEJUAOaQc+Qj+L1qWbPd0SJuAdQkFU0h73sXXqwDYxsiDA=="], + + "math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="], + + "md-editor-v3": ["md-editor-v3@6.5.6", "", { "dependencies": { "@codemirror/autocomplete": "^6.18.7", "@codemirror/commands": "^6.8.1", "@codemirror/lang-markdown": "^6.3.4", "@codemirror/language": "^6.11.3", "@codemirror/language-data": "^6.5.1", "@codemirror/search": "^6.5.11", "@codemirror/state": "^6.5.2", "@codemirror/view": "^6.38.2", "@lezer/highlight": "^1.2.1", "@lucide/vue": "^1.16.0", "@types/markdown-it": "^14.0.1", "@vavt/copy2clipboard": "^1.0.1", "@vavt/markdown-theme": "^5.0.2", "@vavt/util": "^2.1.2", "codemirror": "^6.0.2", "markdown-it": "^14.0.0", "markdown-it-image-figures": "^2.1.1", "markdown-it-sub": "^2.0.0", "markdown-it-sup": "^2.0.0", "medium-zoom": "^1.1.0", "xss": "^1.0.15" }, "peerDependencies": { "vue": "^3.5.3" } }, "sha512-bD+xKTpaC9mGDE7W+9x25Y5mEsv21Y735J2Q0dxV+lWQTXH21/sKNdwaUYQ5dhXU2fl+oqrOJsEjSd58FLyNSA=="], + + "mdurl": ["mdurl@2.1.0", "", {}, "sha512-1+HBaOx0zi/dQWht8rNv9MYf9qqpqL/kxI0hXImU6Y547zM6Sni8BQibt7ifgMcYtQg41ao3Ivd6cnSM86inpg=="], + + "medium-zoom": ["medium-zoom@1.1.0", "", {}, "sha512-ewyDsp7k4InCUp3jRmwHBRFGyjBimKps/AJLjRSox+2q/2H4p/PNpQf+pwONWlJiOudkBXtbdmVbFjqyybfTmQ=="], + + "meow": ["meow@13.2.0", "", {}, "sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA=="], + + "mermaid": ["mermaid@11.16.1", "", { "dependencies": { "@braintree/sanitize-url": "^7.1.2", "@iconify/utils": "^3.0.2", "@mermaid-js/parser": "^1.2.0", "@types/d3": "^7.4.3", "@upsetjs/venn.js": "^2.0.0", "cytoscape": "^3.33.3", "cytoscape-cose-bilkent": "^4.1.0", "cytoscape-fcose": "^2.2.0", "d3": "^7.9.0", "d3-sankey": "^0.12.3", "dagre-d3-es": "7.0.14", "dayjs": "^1.11.20", "dompurify": "^3.3.3", "es-toolkit": "^1.45.1", "katex": "^0.16.45", "khroma": "^2.1.0", "marked": "^16.3.0", "roughjs": "^4.6.6", "stylis": "^4.3.6", "ts-dedent": "^2.2.0", "uuid": "^11.1.0 || ^12 || ^13 || ^14.0.0" } }, "sha512-TQsq6u22fAn3rek5VOubrhKPo1g5hwC3FXUN9hiyupTckcYiGuuKGkNQrKYwGJkXUxZdojwRG46gsSCFZMDp4g=="], + + "mermaid-legacy": ["mermaid@9.4.3", "", { "dependencies": { "@braintree/sanitize-url": "^6.0.0", "cytoscape": "^3.23.0", "cytoscape-cose-bilkent": "^4.1.0", "cytoscape-fcose": "^2.1.0", "d3": "^7.4.0", "dagre-d3-es": "7.0.9", "dayjs": "^1.11.7", "dompurify": "2.4.3", "elkjs": "^0.8.2", "khroma": "^2.0.0", "lodash-es": "^4.17.21", "non-layered-tidy-tree-layout": "^2.0.2", "stylis": "^4.1.2", "ts-dedent": "^2.2.0", "uuid": "^9.0.0", "web-worker": "^1.2.0" } }, "sha512-TLkQEtqhRSuEHSE34lh5bCa94KATCyluAXmFnNI2PRZwOpXFeqiJWwZl+d2CcemE1RS6QbbueSSq9QIg8Uxcyw=="], + + "mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="], + + "mime-match": ["mime-match@1.0.2", "", { "dependencies": { "wildcard": "^1.1.0" } }, "sha512-VXp/ugGDVh3eCLOBCiHZMYWQaTNUHv2IJrut+yXA6+JbLPXHglHwfS/5A5L0ll+jkCY7fIzRJcH6OIunF+c6Cg=="], + + "mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="], + + "mlly": ["mlly@1.8.2", "", { "dependencies": { "acorn": "^8.16.0", "pathe": "^2.0.3", "pkg-types": "^1.3.1", "ufo": "^1.6.3" } }, "sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA=="], + + "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + + "muggle-string": ["muggle-string@0.4.1", "", {}, "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ=="], + + "naive-ui": ["naive-ui@2.44.1", "", { "dependencies": { "@css-render/plugin-bem": "^0.15.14", "@css-render/vue3-ssr": "^0.15.14", "@types/lodash": "^4.17.20", "@types/lodash-es": "^4.17.12", "async-validator": "^4.2.5", "css-render": "^0.15.14", "csstype": "^3.1.3", "date-fns": "^4.1.0", "date-fns-tz": "^3.2.0", "evtd": "^0.2.4", "highlight.js": "^11.8.0", "lodash": "^4.17.21", "lodash-es": "^4.17.21", "seemly": "^0.3.10", "treemate": "^0.3.11", "vdirs": "^0.1.8", "vooks": "^0.2.12", "vueuc": "^0.4.65" }, "peerDependencies": { "vue": "^3.0.0" } }, "sha512-reo8Esw0p58liZwbUutC7meW24Xbn3EwNv91zReWKm2W4JPu+zfgJRn/F7aO0BFmvN+h2brA2M5lRvYqLq4kuA=="], + + "namespace-emitter": ["namespace-emitter@2.0.1", "", {}, "sha512-N/sMKHniSDJBjfrkbS/tpkPj4RAbvW3mr8UAzvlMHyun93XEm83IAvhWtJVHo+RHn/oO8Job5YN4b+wRjSVp5g=="], + + "nanoid": ["nanoid@6.0.1", "", { "bin": { "nanoid": "bin/nanoid.js" } }, "sha512-3wVS3i51pE2pi1k5FFL/95BGfVS0kSsvDVuGXHOtxox/TywUmtgq+3qiTOTbs9J7KfHaXPiN171k/A6dBnaXFw=="], + + "next-tick": ["next-tick@1.1.0", "", {}, "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ=="], + + "node-releases": ["node-releases@2.0.53", "", {}, "sha512-D9UOmYG3UH1V+ENW56t5QXBwJw1YEY18ruVeus89Rw+SyIgjPkCO84bRzO3uNIYosJbNwiabWVn48o3uJLjxFQ=="], + + "non-layered-tidy-tree-layout": ["non-layered-tidy-tree-layout@2.0.2", "", {}, "sha512-gkXMxRzUH+PB0ax9dUN0yYF0S25BqeAYqhgMaLUFmpXLEk7Fcu8f4emJuOAY0V8kjDICxROIKsTAKsV/v355xw=="], + + "normalize.css": ["normalize.css@8.0.1", "", {}, "sha512-qizSNPO93t1YUuUhP22btGOo3chcvDFqFaj2TRybP0DMxkHOCTYwp3n34fel4a31ORXy4m1Xq0Gyqpb5m33qIg=="], + + "nostics": ["nostics@1.2.0", "", {}, "sha512-FGqEfhQjrvo1lL8KFifdTQiNwwQHJxC1jtYE1Rc54qF/jxONUNL+kC9gS1krX8Q65PgrQ5fCqH/I4NhWBvdSqg=="], + + "obug": ["obug@2.1.4", "", {}, "sha512-4a+OsYv9UktOJKE+l1A4OufDgdRF9PifWj+tJnHURo/P+WOxpG4GzUFL9qCalmWauao6ogiG+QvnCovwPoyAWA=="], + + "p-retry": ["p-retry@6.2.1", "", { "dependencies": { "@types/retry": "0.12.2", "is-network-error": "^1.0.0", "retry": "^0.13.1" } }, "sha512-hEt02O4hUct5wtwg4H4KcWgDdm+l1bOaEy/hWzd8xtXB9BqxTWBBhb+2ImAtH4Cv4rPjV76xN3Zumqk3k3AhhQ=="], + + "package-manager-detector": ["package-manager-detector@1.8.0", "", {}, "sha512-yQA4H19AmPEoMUeavPMDIe1higySl/gH/yaQrkT/s07Qp+7pp2hYz30N3z2l5BkjVkF9Ow6o0wjJamm2y7Sn0A=="], + + "path-data-parser": ["path-data-parser@0.1.0", "", {}, "sha512-NOnmBpt5Y2RWbuv0LMzsayp3lVylAHLPUTut412ZA3l+C4uw4ZVkQbjShYCQ8TCpUMdPapr4YjUqLYD6v68j+w=="], + + "path-parse": ["path-parse@1.0.7", "", {}, "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="], + + "pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="], + + "perfect-debounce": ["perfect-debounce@2.1.0", "", {}, "sha512-LjgdTytVFXeUgtHZr9WYViYSM/g8MkcTPYDlPa3cDqMirHjKiSZPYd6DoL7pK8AJQr+uWkQvCjHNdiMqsrJs+g=="], + + "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], + + "picomatch": ["picomatch@4.0.5", "", {}, "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A=="], + + "pinia": ["pinia@4.0.2", "", { "dependencies": { "nostics": "^1.1.4" }, "peerDependencies": { "@vue/devtools-api": "^8.1.5", "typescript": ">=5.6.0", "vue": "^3.5.11" }, "optionalPeers": ["typescript"] }, "sha512-yKVVA7bSj5oRZFp/Ab9wLlmyb5gPUYEiIm4ryiWTe/xe7PtkRdMVOp1X1ggvq0c6Uj7Q0Du1HnV2mtAwM0Ks1g=="], + + "pkg-types": ["pkg-types@2.3.1", "", { "dependencies": { "confbox": "^0.2.4", "exsolve": "^1.0.8", "pathe": "^2.0.3" } }, "sha512-y+ichcgc2LrADuhLNAx8DFjVfgz91pRxfZdI3UDhxHvcVEZsenLO+7XaU5vOp0u/7V/wZ+plyuQxtrDlZJ+yeg=="], + + "points-on-curve": ["points-on-curve@0.2.0", "", {}, "sha512-0mYKnYYe9ZcqMCWhUjItv/oHjvgEsfKvnUTg8sAtnHr3GVy7rGkXCb6d5cSyqrWqL4k81b9CPg3urd+T7aop3A=="], + + "points-on-path": ["points-on-path@0.2.1", "", { "dependencies": { "path-data-parser": "0.1.0", "points-on-curve": "0.2.0" } }, "sha512-25ClnWWuw7JbWZcgqY/gJ4FQWadKxGWk+3kR/7kD0tCaDtPPMj7oHu2ToLaVhfpnHrZzYby2w6tUA0eOIuUg8g=="], + + "postcss": ["postcss@8.5.26", "", { "dependencies": { "nanoid": "^3.3.17", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ=="], + + "postgres": ["postgres@3.4.9", "https://registry.npmjs.com/postgres/-/postgres-3.4.9.tgz", {}, "sha512-GD3qdB0x1z9xgFI6cdRD6xu2Sp2WCOEoe3mtnyB5Ee0XrrL5Pe+e4CCnJrRMnL1zYtRDZmQQVbvOttLnKDLnaw=="], + + "preact": ["preact@10.29.8", "", { "peerDependencies": { "preact-render-to-string": ">=5" }, "optionalPeers": ["preact-render-to-string"] }, "sha512-ej2aVZ+vZ8WO7tvlQWRM9N63A0KzF9q4mWJfDUHgYaIofWY9hu74QdnQrjoPMmZi2/nZ5gN0bJCQF49xQqx09Q=="], + + "prettier": ["prettier@3.9.6", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-OpN0zzVdiaiAhxpuuj5efpIS4sY9j7bY6uR5mnj5yPzGkdkjNKSJeUThPb60Jw29QuAZgA4o+/iB49kFiaBX6g=="], + + "prismjs": ["prismjs@1.30.0", "", {}, "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw=="], + + "proxy-from-env": ["proxy-from-env@2.1.0", "", {}, "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA=="], + + "punycode.js": ["punycode.js@2.3.1", "", {}, "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA=="], + + "quansync": ["quansync@0.2.11", "", {}, "sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA=="], + + "queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="], + + "randombytes": ["randombytes@2.1.0", "", { "dependencies": { "safe-buffer": "^5.1.0" } }, "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ=="], + + "readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="], + + "readdirp": ["readdirp@5.1.1", "", {}, "sha512-Kko+Y5XQ6fM+Ce3dq3m9YGxnacYZYl9cA1wZjaF3Vbry2L3i1qVg8+CAgNPsXRArPMUMCaOR7oa9Nqntc43JKA=="], + + "regenerate": ["regenerate@1.4.2", "", {}, "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A=="], + + "regenerate-unicode-properties": ["regenerate-unicode-properties@10.2.2", "", { "dependencies": { "regenerate": "^1.4.2" } }, "sha512-m03P+zhBeQd1RGnYxrGyDAPpWX/epKirLrp8e3qevZdVkKtnCrjjWczIbYc8+xd6vcTStVlqfycTx1KR4LOr0g=="], + + "regenerator-runtime": ["regenerator-runtime@0.14.1", "", {}, "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw=="], + + "regexpu-core": ["regexpu-core@6.4.0", "", { "dependencies": { "regenerate": "^1.4.2", "regenerate-unicode-properties": "^10.2.2", "regjsgen": "^0.8.0", "regjsparser": "^0.13.0", "unicode-match-property-ecmascript": "^2.0.0", "unicode-match-property-value-ecmascript": "^2.2.1" } }, "sha512-0ghuzq67LI9bLXpOX/ISfve/Mq33a4aFRzoQYhnnok1JOFpmE/A2TBGkNVenOGEeSBCjIiWcc6MVOG5HEQv0sA=="], + + "regjsgen": ["regjsgen@0.8.0", "", {}, "sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q=="], + + "regjsparser": ["regjsparser@0.13.2", "", { "dependencies": { "jsesc": "~3.1.0" }, "bin": { "regjsparser": "bin/parser" } }, "sha512-NgRBy2Nx/bE+9F27nVHnqcN5HjyLmecqsqx2PJHu3/IEtADD4WuxuXIVExD5PoSDFVrl78dOonfcOe5O+5nbzQ=="], + + "resolve": ["resolve@1.22.12", "", { "dependencies": { "es-errors": "^1.3.0", "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA=="], + + "resolve-pkg-maps": ["resolve-pkg-maps@1.0.0", "https://registry.npmjs.com/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", {}, "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw=="], + + "retry": ["retry@0.13.1", "", {}, "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg=="], + + "robust-predicates": ["robust-predicates@3.0.3", "", {}, "sha512-NS3levdsRIUOmiJ8FZWCP7LG3QpJyrs/TE0Zpf1yvZu8cAJJ6QMW92H1c7kWpdIHo8RvmLxN/o2JXTKHp74lUA=="], + + "rolldown": ["rolldown@1.2.3", "", { "dependencies": { "@oxc-project/types": "=0.143.0", "@rolldown/pluginutils": "^1.0.0" }, "optionalDependencies": { "@rolldown/binding-android-arm64": "1.2.3", "@rolldown/binding-darwin-arm64": "1.2.3", "@rolldown/binding-darwin-x64": "1.2.3", "@rolldown/binding-freebsd-x64": "1.2.3", "@rolldown/binding-linux-arm-gnueabihf": "1.2.3", "@rolldown/binding-linux-arm64-gnu": "1.2.3", "@rolldown/binding-linux-arm64-musl": "1.2.3", "@rolldown/binding-linux-ppc64-gnu": "1.2.3", "@rolldown/binding-linux-s390x-gnu": "1.2.3", "@rolldown/binding-linux-x64-gnu": "1.2.3", "@rolldown/binding-linux-x64-musl": "1.2.3", "@rolldown/binding-openharmony-arm64": "1.2.3", "@rolldown/binding-win32-arm64-msvc": "1.2.3", "@rolldown/binding-win32-x64-msvc": "1.2.3" }, "bin": { "rolldown": "./bin/cli.mjs" } }, "sha512-rn9wpmxplLf7NLNyCk9FyWh3FM43DbY8jOzCdEPzH7uflhTftRbCEpqi6Ly2osgoU8OwObtmavMbWLaWy4LX7A=="], + + "roughjs": ["roughjs@4.6.6", "", { "dependencies": { "hachure-fill": "^0.5.2", "path-data-parser": "^0.1.0", "points-on-curve": "^0.2.0", "points-on-path": "^0.2.1" } }, "sha512-ZUz/69+SYpFN/g/lUlo2FXcIjRkSu3nDarreVdGGndHEBJ6cXPdKguS8JGxwj5HA5xIbVKSmLgr5b3AWxtRfvQ=="], + + "rw": ["rw@1.3.3", "", {}, "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ=="], + + "safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="], + + "safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="], + + "scroll-into-view-if-needed": ["scroll-into-view-if-needed@3.1.0", "", { "dependencies": { "compute-scroll-into-view": "^3.0.2" } }, "sha512-49oNpRjWRvnU8NyGVmUaYG4jtTkNonFZI86MmGRDqBphEK2EXT9gdEUoQPZhuBM8yWHxCWbobltqYO5M4XrUvQ=="], + + "scule": ["scule@1.3.0", "", {}, "sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g=="], + + "seemly": ["seemly@0.3.10", "", {}, "sha512-2+SMxtG1PcsL0uyhkumlOU6Qo9TAQ/WyH7tthnPIOQB05/12jz9naq6GZ6iZ6ApVsO3rr2gsnTf3++OV63kE1Q=="], + + "semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], + + "simple-peer": ["simple-peer@9.11.1", "", { "dependencies": { "buffer": "^6.0.3", "debug": "^4.3.2", "err-code": "^3.0.1", "get-browser-rtc": "^1.1.0", "queue-microtask": "^1.2.3", "randombytes": "^2.1.0", "readable-stream": "^3.6.0" } }, "sha512-D1SaWpOW8afq1CZGWB8xTfrT3FekjQmPValrqncJMX7QFl8YwhrPTZvMCANLtgBwwdS+7zURyqxDDEmY558tTw=="], + + "skulpt": ["skulpt@1.2.0", "", { "dependencies": { "jsbi": "^3.1.4" } }, "sha512-T0cv0sdSOXLlIJTuyXSeYJ3TFdWYSZfX2PcBLpRKrKZ3dTbVQXRMiYudSuko2xzcyMif47DS2ShatCwjCbsSsA=="], + + "slate": ["slate@0.124.1", "", {}, "sha512-ii7DwezgvbLAyKtHBIunjTR1kzbNfYLCUKLMzJELlbTZkvHzX4DzN7HKIwcakf6dPxO6AoeT/P7kHOcyTym/hA=="], + + "slate-history": ["slate-history@0.115.0", "", { "peerDependencies": { "slate": ">=0.114.3" } }, "sha512-QdUm9aVyQFz6JG4a84Z6Um+tJpZJmsh9bjXwTVTvYiN4rdKbqL6+/4HT94on1WYxe10Q4vY6mA6BCpoYxgF3tQ=="], + + "snabbdom": ["snabbdom@3.6.4", "", {}, "sha512-VmxEfuw1/Y/eFj5VtMhYnukExpYiPkNzoo3+N3qwAOUDMl8wXgbli5ebR+j0knE3lZ/0eYskLxNcX64uy10N9w=="], + + "source-map": ["source-map@0.6.1", "https://registry.npmjs.com/source-map/-/source-map-0.6.1.tgz", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="], + + "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], + + "source-map-support": ["source-map-support@0.5.21", "https://registry.npmjs.com/source-map-support/-/source-map-support-0.5.21.tgz", { "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" } }, "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w=="], + + "ssr-window": ["ssr-window@4.0.2", "", {}, "sha512-ISv/Ch+ig7SOtw7G2+qkwfVASzazUnvlDTwypdLoPoySv+6MqlOV10VwPSE6EWkGjhW50lUmghPmpYZXMu/+AQ=="], + + "string_decoder": ["string_decoder@1.3.0", "", { "dependencies": { "safe-buffer": "~5.2.0" } }, "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA=="], + + "strip-literal": ["strip-literal@4.0.0", "", { "dependencies": { "js-tokens": "^10.0.0" } }, "sha512-PaqAvfUZKBwc/SLmNZtHmzK+v19Z4O4eS3cKPeGvbIv/U3pnyEq4Tuw3/4v/FwfM8VQaEawsyCcOQ0P+kpwWWw=="], + + "style-mod": ["style-mod@4.1.3", "", {}, "sha512-i/n8VsZydrugj3Iuzll8+x/00GH2vnYsk1eomD8QiRrSAeW6ItbCQDtfXCeJHd0iwiNagqjQkvpvREEPtW3IoQ=="], + + "stylis": ["stylis@4.4.0", "", {}, "sha512-5Z9ZpRzfuH6l/UAvCPAPUo3665Nk2wLaZU3x+TLHKVzIz33+sbJqbtrYoC3KD4/uVOr2Zp+L0LySezP9OHV9yA=="], + + "supports-preserve-symlinks-flag": ["supports-preserve-symlinks-flag@1.0.0", "", {}, "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="], + + "systemjs": ["systemjs@6.15.1", "", {}, "sha512-Nk8c4lXvMB98MtbmjX7JwJRgJOL8fluecYCfCeYBznwmpOs8Bf15hLM6z4z71EDAhQVrQrI+wt1aLWSXZq+hXA=="], + + "terser": ["terser@5.49.2", "", { "dependencies": { "@jridgewell/source-map": "^0.3.3", "acorn": "^8.15.0", "commander": "^2.20.0", "source-map-support": "~0.5.20" }, "bin": { "terser": "bin/terser" } }, "sha512-rGbJiKeQ4WDe3EXlDAIaQcwftVfv2Q8o1awFNfvXolJYKkb1AuZY1RTOmqx4LJXZENbWZA7eIsYGHuEzHsi1nQ=="], + + "tinyexec": ["tinyexec@1.3.0", "", {}, "sha512-QKAl9m8gWWGHV8jZcPeym6j+XULi6tOf1mT83WYJ4Lk2ytW/uwAWkrP0uFsdoYMdueVJ0qs26wZ+23xeB4ibNQ=="], + + "tinyglobby": ["tinyglobby@0.2.17", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.4" } }, "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g=="], + + "treemate": ["treemate@0.3.11", "", {}, "sha512-M8RGFoKtZ8dF+iwJfAJTOH/SM4KluKOKRJpjCMhI8bG3qB74zrFoArKZ62ll0Fr3mqkMJiQOmWYkdYgDeITYQg=="], + + "ts-dedent": ["ts-dedent@2.3.0", "", {}, "sha512-JfJeIHke7y2egdGGgRAvpCwYFUsHlM2gPcrVOxFkznt/4uzQ7HFmvE63iFHVLBJNDuyDOQgijDK/tXH/f6Msjg=="], + + "tsx": ["tsx@4.23.9", "https://registry.npmjs.com/tsx/-/tsx-4.23.9.tgz", { "dependencies": { "esbuild": "~0.28.0" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "bin": { "tsx": "dist/cli.mjs" } }, "sha512-6q8uTORRGauQVjqMQnKUucLFoeXZAfw6zKvG35GLbdKWbLdeOtZ3H4mhyA5mxuUd2o2cRTskhj59nLLQseUvUw=="], + + "type": ["type@2.7.3", "", {}, "sha512-8j+1QmAbPvLZow5Qpi6NCaN8FB60p/6x8/vfNqOk/hC+HuvFZhL4+WfekuhQLiqFZXOgQdrs3B+XxEmCc6b3FQ=="], "typescript": ["typescript@5.9.3", "https://registry.npmjs.com/typescript/-/typescript-5.9.3.tgz", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], + "uc.micro": ["uc.micro@2.1.0", "", {}, "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A=="], + + "ufo": ["ufo@1.6.4", "", {}, "sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA=="], + "undici-types": ["undici-types@8.3.0", "https://registry.npmjs.com/undici-types/-/undici-types-8.3.0.tgz", {}, "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ=="], + "unicode-canonical-property-names-ecmascript": ["unicode-canonical-property-names-ecmascript@2.0.1", "", {}, "sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg=="], + + "unicode-match-property-ecmascript": ["unicode-match-property-ecmascript@2.0.0", "", { "dependencies": { "unicode-canonical-property-names-ecmascript": "^2.0.0", "unicode-property-aliases-ecmascript": "^2.0.0" } }, "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q=="], + + "unicode-match-property-value-ecmascript": ["unicode-match-property-value-ecmascript@2.2.1", "", {}, "sha512-JQ84qTuMg4nVkx8ga4A16a1epI9H6uTXAknqxkGF/aFfRLw1xC/Bp24HNLaZhHSkWd3+84t8iXnp1J0kYcZHhg=="], + + "unicode-property-aliases-ecmascript": ["unicode-property-aliases-ecmascript@2.2.0", "", {}, "sha512-hpbDzxUY9BFwX+UeBnxv3Sh1q7HFxj48DTmXchNgRa46lO8uj3/1iEn3MiNUYTg1g9ctIqXCCERn8gYZhHC5lQ=="], + + "unimport": ["unimport@6.4.0", "", { "dependencies": { "acorn": "^8.18.0", "escape-string-regexp": "^5.0.0", "estree-walker": "^3.0.3", "local-pkg": "^1.2.1", "magic-string": "^1.1.0", "mlly": "^1.8.2", "pathe": "^2.0.3", "picomatch": "^4.0.5", "pkg-types": "^2.3.1", "scule": "^1.3.0", "strip-literal": "^4.0.0", "tinyglobby": "^0.2.17", "unplugin": "^3.3.0", "unplugin-utils": "^0.3.2" }, "peerDependencies": { "oxc-parser": "*", "rolldown": "^1.0.0" }, "optionalPeers": ["oxc-parser", "rolldown"] }, "sha512-JJOOuNMFq8b4ZPBKwQUxEcba4MplskDzYI1Lvrf8rJfWphZTWvPNXWa493qsPngHUmub89w6C7j+SeLWTE/UIQ=="], + + "unplugin": ["unplugin@3.3.0", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "picomatch": "^4.0.4", "webpack-virtual-modules": "^0.6.2" }, "peerDependencies": { "@farmfe/core": "*", "@rspack/core": "*", "bun-types-no-globals": "*", "esbuild": "*", "rolldown": "*", "rollup": "*", "unloader": "*", "vite": "*", "webpack": "*" }, "optionalPeers": ["@farmfe/core", "@rspack/core", "bun-types-no-globals", "esbuild", "rolldown", "rollup", "unloader", "vite", "webpack"] }, "sha512-qa66K+crbfyE6JK10GjvbJeRrOsuC/JpbnHctfyp/i4oBTxWOzJfRZyDiOk1PtErMFRu8JhsU/wPvOdBNWe5Rg=="], + + "unplugin-auto-import": ["unplugin-auto-import@21.1.0", "", { "dependencies": { "local-pkg": "^1.2.1", "magic-string": "^1.1.0", "picomatch": "^4.0.5", "unimport": "^6.4.0", "unplugin": "^3.3.0", "unplugin-utils": "^0.3.2" }, "peerDependencies": { "@nuxt/kit": "^4.0.0", "@vueuse/core": "*" }, "optionalPeers": ["@nuxt/kit", "@vueuse/core"] }, "sha512-EzrSqWIBulEqCuP7idADXH+tVKYrbwKlR5+r/lOWik0o+Ksny1kitmtryHGNSv7puzzORlcIwT/x2JStiszlHA=="], + + "unplugin-utils": ["unplugin-utils@0.3.2", "", { "dependencies": { "pathe": "^2.0.3", "picomatch": "^4.0.4" } }, "sha512-xVToRh2CTmLk2HnEG7ac4rl1MJTT3RFkpS8B++/SnB0kXvuaavD+n3m/vrzyWQOdJNSZQACnbz01pnppbwV5BA=="], + + "unplugin-vue-components": ["unplugin-vue-components@32.1.0", "", { "dependencies": { "chokidar": "^5.0.0", "local-pkg": "^1.2.0", "magic-string": "^0.30.21", "mlly": "^1.8.2", "obug": "^2.1.1", "picomatch": "^4.0.4", "tinyglobby": "^0.2.16", "unplugin": "^3.0.0", "unplugin-utils": "^0.3.1" }, "peerDependencies": { "@nuxt/kit": "^3.2.2 || ^4.0.0", "vue": "^3.0.0" }, "optionalPeers": ["@nuxt/kit"] }, "sha512-YiUkSxuRjab18XFOrX5VsIxXzccrfmHVGsGeJgSgklb829DQmCy9E4vvDUE4tuvZZdxyFJZX0Oc4TPnnxiiMyg=="], + + "update-browserslist-db": ["update-browserslist-db@1.3.0", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-x/M6q3w4Ybp91CNaS4S69UnliqR3BzRpOT6LWbksjth0S/+jhfaPJsWjt/TewpT8j9eLIojUf5jr29WextHroA=="], + + "util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="], + + "uuid": ["uuid@14.0.1", "", { "bin": { "uuid": "dist-node/bin/uuid" } }, "sha512-6ZxzVpzDXDa3bJWaHilVayA+BH/1zmxCJoVgvmqJnid/gPoKHxUrS/aC/T6LGQtNHT+XHG9fXPJB4d+IrU30Ew=="], + + "vdirs": ["vdirs@0.1.8", "", { "dependencies": { "evtd": "^0.2.2" }, "peerDependencies": { "vue": "^3.0.11" } }, "sha512-H9V1zGRLQZg9b+GdMk8MXDN2Lva0zx72MPahDKc30v+DtwKjfyOSXWRIX4t2mhDubM1H09gPhWeth/BJWPHGUw=="], + + "vite": ["vite@8.2.1", "", { "dependencies": { "lightningcss": "^1.33.0", "picomatch": "^4.0.5", "postcss": "^8.5.25", "rolldown": "~1.2.1", "tinyglobby": "^0.2.17" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "@vitejs/devtools": "^0.4.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" }, "optionalPeers": ["@types/node", "@vitejs/devtools", "esbuild", "jiti", "less", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-EU/eS7BH3XROHh2YnBefjM6DBKA6ZeMZEYQbj7NLWg5wHYlhB8B/Mayd5XsgWq+NFYccDOTemRpdETWR6Ka/lw=="], + + "vooks": ["vooks@0.2.12", "", { "dependencies": { "evtd": "^0.2.2" }, "peerDependencies": { "vue": "^3.0.0" } }, "sha512-iox0I3RZzxtKlcgYaStQYKEzWWGAduMmq+jS7OrNdQo1FgGfPMubGL3uGHOU9n97NIvfFDBGnpSvkWyb/NSn/Q=="], + + "vue": ["vue@3.5.41", "", { "dependencies": { "@vue/compiler-dom": "3.5.41", "@vue/compiler-sfc": "3.5.41", "@vue/runtime-dom": "3.5.41", "@vue/server-renderer": "3.5.41", "@vue/shared": "3.5.41" }, "peerDependencies": { "typescript": "*" }, "optionalPeers": ["typescript"] }, "sha512-2laE0p+aK+/AOPG/XL/WepOs/GlK755LJ1XECi9kDUrz1FKNw8rb2Xzlw9JS1rqEV55nb0ttsKxVlTCcd+R5cg=="], + + "vue-chartjs": ["vue-chartjs@5.3.4", "", { "peerDependencies": { "chart.js": "^4.1.1", "vue": "^3.0.0-0 || ^2.7.0" } }, "sha512-x3Fqob8RQvrTdssfi9ecsCzEkFOd8JPmNwSkSQzdfKj/uBsRJs/Y88cZcZIEcPsTVfMGwMo4MOoihoDG2DoE/g=="], + + "vue-codemirror": ["vue-codemirror@6.1.1", "", { "dependencies": { "@codemirror/commands": "6.x", "@codemirror/language": "6.x", "@codemirror/state": "6.x", "@codemirror/view": "6.x" }, "peerDependencies": { "codemirror": "6.x", "vue": "3.x" } }, "sha512-rTAYo44owd282yVxKtJtnOi7ERAcXTeviwoPXjIc6K/IQYUsoDkzPvw/JDFtSP6T7Cz/2g3EHaEyeyaQCKoDMg=="], + + "vue-demi": ["vue-demi@0.14.10", "", { "peerDependencies": { "@vue/composition-api": "^1.0.0-rc.1", "vue": "^3.0.0-0 || ^2.6.0" }, "optionalPeers": ["@vue/composition-api"], "bin": { "vue-demi-fix": "bin/vue-demi-fix.js", "vue-demi-switch": "bin/vue-demi-switch.js" } }, "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg=="], + + "vue-router": ["vue-router@5.2.0", "", { "dependencies": { "@babel/generator": "^8.0.0", "@vue-macros/common": "^3.1.3", "@vue/devtools-api": "^8.1.5", "ast-walker-scope": "^0.9.0", "chokidar": "^5.0.0", "json5": "^2.2.3", "local-pkg": "^1.2.1", "magic-string": "^0.30.21", "mlly": "^1.8.2", "muggle-string": "^0.4.1", "nostics": "^1.1.4", "pathe": "^2.0.3", "picomatch": "^4.0.5", "scule": "^1.3.0", "tinyglobby": "^0.2.17", "unplugin": "^3.3.0", "unplugin-utils": "^0.3.2", "yaml": "^2.9.0" }, "peerDependencies": { "@pinia/colada": ">=0.21.2", "@vue/compiler-sfc": "^3.5.34 || ^4.0.0", "pinia": "^3.0.4 || ^4.0.2", "vite": "^7.3.0 || ^8.0.0", "vue": "^3.5.34 || ^4.0.0" }, "optionalPeers": ["@pinia/colada", "@vue/compiler-sfc", "pinia", "vite"] }, "sha512-QAC5i0LEb1GLG0LXDQmHu8L7FX12j0KwU/JTKmLQUJMrn04gQdKP6Du+p0QwpHb3iy71vBlqnHQ8WAfOSAWhqw=="], + + "vueuc": ["vueuc@0.4.65", "", { "dependencies": { "@css-render/vue3-ssr": "^0.15.10", "@juggle/resize-observer": "^3.3.1", "css-render": "^0.15.10", "evtd": "^0.2.4", "seemly": "^0.3.6", "vdirs": "^0.1.4", "vooks": "^0.2.4" }, "peerDependencies": { "vue": "^3.0.11" } }, "sha512-lXuMl+8gsBmruudfxnMF9HW4be8rFziylXFu1VHVNbLVhRTXXV4njvpRuJapD/8q+oFEMSfQMH16E/85VoWRyQ=="], + + "w3c-keyname": ["w3c-keyname@2.2.8", "", {}, "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ=="], + + "web-worker": ["web-worker@1.5.0", "", {}, "sha512-RiMReJrTAiA+mBjGONMnjVDP2u3p9R1vkcGz6gDIrOMT3oGuYwX2WRMYI9ipkphSuE5XKEhydbhNEJh4NY9mlw=="], + + "webpack-virtual-modules": ["webpack-virtual-modules@0.6.2", "", {}, "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ=="], + + "wildcard": ["wildcard@1.1.2", "", {}, "sha512-DXukZJxpHA8LuotRwL0pP1+rS6CS7FF2qStDDE1C7DDg2rLud2PXRMuEDYIPhgEezwnlHNL4c+N6MfMTjCGTng=="], + + "ws": ["ws@8.21.2", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-54dMVAo4WIe6SKy3vBgN+9bJZqqQ8IMRevAkOLQALhi49qkkQDQfWdAZ8KQlXiEabw88ARXXdUrlvtbKQX+aKw=="], + + "xss": ["xss@1.0.15", "", { "dependencies": { "commander": "^2.20.3", "cssfilter": "0.0.10" }, "bin": { "xss": "bin/xss" } }, "sha512-FVdlVVC67WOIPvfOwhoMETV72f6GbW7aOabBC3WxN/oUdoEMDyLz4OgRv5/gck2ZeNqEQu+Tb0kloovXOfpYVg=="], + + "y-codemirror.next": ["y-codemirror.next@0.3.5", "", { "dependencies": { "lib0": "^0.2.42" }, "peerDependencies": { "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.0.0", "yjs": "^13.5.6" } }, "sha512-VluNu3e5HfEXybnypnsGwKAj+fKLd4iAnR7JuX1Sfyydmn1jCBS5wwEL/uS04Ch2ib0DnMAOF6ZRR/8kK3wyGw=="], + + "y-protocols": ["y-protocols@1.0.7", "", { "dependencies": { "lib0": "^0.2.85" }, "peerDependencies": { "yjs": "^13.0.0" } }, "sha512-YSVsLoXxO67J6eE/nV4AtFtT3QEotZf5sK5BHxFBXso7VDUT3Tx07IfA6hsu5Q5OmBdMkQVmFZ9QOA7fikWvnw=="], + + "y-webrtc": ["y-webrtc@10.3.0", "", { "dependencies": { "lib0": "^0.2.42", "simple-peer": "^9.11.0", "y-protocols": "^1.0.6" }, "optionalDependencies": { "ws": "^8.14.2" }, "peerDependencies": { "yjs": "^13.6.8" }, "bin": { "y-webrtc-signaling": "bin/server.js" } }, "sha512-KalJr7dCgUgyVFxoG3CQYbpS0O2qybegD0vI4bYnYHI0MOwoVbucED3RZ5f2o1a5HZb1qEssUKS0H/Upc6p1lA=="], + + "yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="], + + "yaml": ["yaml@2.9.0", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA=="], + + "yjs": ["yjs@13.6.32", "", { "dependencies": { "lib0": "^0.2.99" } }, "sha512-lfiJIIC4Xayt5ItynE407ehlE03pCjeOc4hkR4yxxvvNJ4kuiN25B0g+Qp8XagYz361LLL7DCzR5bvFJ81QKtQ=="], + "zod": ["zod@4.4.3", "https://registry.npmjs.com/zod/-/zod-4.4.3.tgz", {}, "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ=="], - "@esbuild-kit/core-utils/esbuild": ["esbuild@0.18.20", "", { "optionalDependencies": { "@esbuild/android-arm": "0.18.20", "@esbuild/android-arm64": "0.18.20", "@esbuild/android-x64": "0.18.20", "@esbuild/darwin-arm64": "0.18.20", "@esbuild/darwin-x64": "0.18.20", "@esbuild/freebsd-arm64": "0.18.20", "@esbuild/freebsd-x64": "0.18.20", "@esbuild/linux-arm": "0.18.20", "@esbuild/linux-arm64": "0.18.20", "@esbuild/linux-ia32": "0.18.20", "@esbuild/linux-loong64": "0.18.20", "@esbuild/linux-mips64el": "0.18.20", "@esbuild/linux-ppc64": "0.18.20", "@esbuild/linux-riscv64": "0.18.20", "@esbuild/linux-s390x": "0.18.20", "@esbuild/linux-x64": "0.18.20", "@esbuild/netbsd-x64": "0.18.20", "@esbuild/openbsd-x64": "0.18.20", "@esbuild/sunos-x64": "0.18.20", "@esbuild/win32-arm64": "0.18.20", "@esbuild/win32-ia32": "0.18.20", "@esbuild/win32-x64": "0.18.20" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA=="], + "@babel/core/@babel/generator": ["@babel/generator@7.29.8", "", { "dependencies": { "@babel/parser": "^7.29.8", "@babel/types": "^7.29.8", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg=="], - "tsx/esbuild": ["esbuild@0.28.1", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.28.1", "@esbuild/android-arm": "0.28.1", "@esbuild/android-arm64": "0.28.1", "@esbuild/android-x64": "0.28.1", "@esbuild/darwin-arm64": "0.28.1", "@esbuild/darwin-x64": "0.28.1", "@esbuild/freebsd-arm64": "0.28.1", "@esbuild/freebsd-x64": "0.28.1", "@esbuild/linux-arm": "0.28.1", "@esbuild/linux-arm64": "0.28.1", "@esbuild/linux-ia32": "0.28.1", "@esbuild/linux-loong64": "0.28.1", "@esbuild/linux-mips64el": "0.28.1", "@esbuild/linux-ppc64": "0.28.1", "@esbuild/linux-riscv64": "0.28.1", "@esbuild/linux-s390x": "0.28.1", "@esbuild/linux-x64": "0.28.1", "@esbuild/netbsd-arm64": "0.28.1", "@esbuild/netbsd-x64": "0.28.1", "@esbuild/openbsd-arm64": "0.28.1", "@esbuild/openbsd-x64": "0.28.1", "@esbuild/openharmony-arm64": "0.28.1", "@esbuild/sunos-x64": "0.28.1", "@esbuild/win32-arm64": "0.28.1", "@esbuild/win32-ia32": "0.28.1", "@esbuild/win32-x64": "0.28.1" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw=="], + "@babel/generator/@babel/parser": ["@babel/parser@8.0.4", "", { "dependencies": { "@babel/types": "^8.0.4" }, "bin": "./bin/babel-parser.js" }, "sha512-srpptsAkEbbNIC/q8nT7o+m6CQe8CJUTV/t7MYc9NnWlgYVtHOb7JH6SorxMhN0kuRJjVqXbKClG6xSbPtzz+g=="], - "@esbuild-kit/core-utils/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.18.20", "", { "os": "android", "cpu": "arm" }, "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw=="], + "@babel/generator/@babel/types": ["@babel/types@8.0.4", "", { "dependencies": { "@babel/helper-string-parser": "^8.0.0", "@babel/helper-validator-identifier": "^8.0.4" } }, "sha512-eY+Yn3dCqTGmyiq2QRU66lA5FL8lqqqvecHt0fF3uHONIa7ToYsaCiWV8lOKqAs0Rb2SjixiKFROngnulPtt2g=="], - "@esbuild-kit/core-utils/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.18.20", "", { "os": "android", "cpu": "arm64" }, "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ=="], + "@babel/traverse/@babel/generator": ["@babel/generator@7.29.8", "", { "dependencies": { "@babel/parser": "^7.29.8", "@babel/types": "^7.29.8", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg=="], - "@esbuild-kit/core-utils/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.18.20", "", { "os": "android", "cpu": "x64" }, "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg=="], + "@esbuild-kit/core-utils/esbuild": ["esbuild@0.18.20", "https://registry.npmjs.com/esbuild/-/esbuild-0.18.20.tgz", { "optionalDependencies": { "@esbuild/android-arm": "0.18.20", "@esbuild/android-arm64": "0.18.20", "@esbuild/android-x64": "0.18.20", "@esbuild/darwin-arm64": "0.18.20", "@esbuild/darwin-x64": "0.18.20", "@esbuild/freebsd-arm64": "0.18.20", "@esbuild/freebsd-x64": "0.18.20", "@esbuild/linux-arm": "0.18.20", "@esbuild/linux-arm64": "0.18.20", "@esbuild/linux-ia32": "0.18.20", "@esbuild/linux-loong64": "0.18.20", "@esbuild/linux-mips64el": "0.18.20", "@esbuild/linux-ppc64": "0.18.20", "@esbuild/linux-riscv64": "0.18.20", "@esbuild/linux-s390x": "0.18.20", "@esbuild/linux-x64": "0.18.20", "@esbuild/netbsd-x64": "0.18.20", "@esbuild/openbsd-x64": "0.18.20", "@esbuild/sunos-x64": "0.18.20", "@esbuild/win32-arm64": "0.18.20", "@esbuild/win32-ia32": "0.18.20", "@esbuild/win32-x64": "0.18.20" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA=="], - "@esbuild-kit/core-utils/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.18.20", "", { "os": "darwin", "cpu": "arm64" }, "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA=="], + "@oj2/web/typescript": ["typescript@7.0.2", "", { "optionalDependencies": { "@typescript/typescript-aix-ppc64": "7.0.2", "@typescript/typescript-darwin-arm64": "7.0.2", "@typescript/typescript-darwin-x64": "7.0.2", "@typescript/typescript-freebsd-arm64": "7.0.2", "@typescript/typescript-freebsd-x64": "7.0.2", "@typescript/typescript-linux-arm": "7.0.2", "@typescript/typescript-linux-arm64": "7.0.2", "@typescript/typescript-linux-loong64": "7.0.2", "@typescript/typescript-linux-mips64el": "7.0.2", "@typescript/typescript-linux-ppc64": "7.0.2", "@typescript/typescript-linux-riscv64": "7.0.2", "@typescript/typescript-linux-s390x": "7.0.2", "@typescript/typescript-linux-x64": "7.0.2", "@typescript/typescript-netbsd-arm64": "7.0.2", "@typescript/typescript-netbsd-x64": "7.0.2", "@typescript/typescript-openbsd-arm64": "7.0.2", "@typescript/typescript-openbsd-x64": "7.0.2", "@typescript/typescript-sunos-x64": "7.0.2", "@typescript/typescript-win32-arm64": "7.0.2", "@typescript/typescript-win32-x64": "7.0.2" }, "bin": { "tsc": "bin/tsc" } }, "sha512-8FYau96o3NKOhbjKi/qNvG/W5jhzxkbdm5sj9AbZ/5T5sWqn3hJgLfGx27sRKZWTvyzCP8dLRBTf5tBTSRVUNA=="], - "@esbuild-kit/core-utils/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.18.20", "", { "os": "darwin", "cpu": "x64" }, "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ=="], + "@types/d3-cloud/@types/d3": ["@types/d3@3.5.53", "", {}, "sha512-8yKQA9cAS6+wGsJpBysmnhlaaxlN42Qizqkw+h2nILSlS+MAG2z4JdO6p+PJrJ+ACvimkmLJL281h157e52psQ=="], - "@esbuild-kit/core-utils/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.18.20", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw=="], + "@uppy/core/nanoid": ["nanoid@5.1.16", "", { "bin": { "nanoid": "bin/nanoid.js" } }, "sha512-kVrnsrJqMR8+oLJnGEmSWw9BivK5mt7H3FZatVRjrc5wGqFYuBxX1yG7+A7Gi5AefkX6t/oCkizcQgpu0cY1dQ=="], - "@esbuild-kit/core-utils/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.18.20", "", { "os": "freebsd", "cpu": "x64" }, "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ=="], + "@vue-flow/core/@vueuse/core": ["@vueuse/core@10.11.1", "", { "dependencies": { "@types/web-bluetooth": "^0.0.20", "@vueuse/metadata": "10.11.1", "@vueuse/shared": "10.11.1", "vue-demi": ">=0.14.8" } }, "sha512-guoy26JQktXPcz+0n3GukWIy/JDNKti9v6VEMu6kV2sYBsWuGiTU8OWdg+ADfUbHg3/3DlqySDe7JmdHrktiww=="], - "@esbuild-kit/core-utils/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.18.20", "", { "os": "linux", "cpu": "arm" }, "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg=="], + "@vue/compiler-core/entities": ["entities@7.0.1", "", {}, "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA=="], - "@esbuild-kit/core-utils/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.18.20", "", { "os": "linux", "cpu": "arm64" }, "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA=="], + "@vue/compiler-core/estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="], - "@esbuild-kit/core-utils/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.18.20", "", { "os": "linux", "cpu": "ia32" }, "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA=="], + "@vue/compiler-sfc/estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="], - "@esbuild-kit/core-utils/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.18.20", "", { "os": "linux", "cpu": "none" }, "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg=="], + "@vue/compiler-sfc/magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="], - "@esbuild-kit/core-utils/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.18.20", "", { "os": "linux", "cpu": "none" }, "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ=="], + "@wangeditor-next/basic-modules/nanoid": ["nanoid@5.1.16", "", { "bin": { "nanoid": "bin/nanoid.js" } }, "sha512-kVrnsrJqMR8+oLJnGEmSWw9BivK5mt7H3FZatVRjrc5wGqFYuBxX1yG7+A7Gi5AefkX6t/oCkizcQgpu0cY1dQ=="], - "@esbuild-kit/core-utils/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.18.20", "", { "os": "linux", "cpu": "ppc64" }, "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA=="], + "@wangeditor-next/core/nanoid": ["nanoid@5.1.16", "", { "bin": { "nanoid": "bin/nanoid.js" } }, "sha512-kVrnsrJqMR8+oLJnGEmSWw9BivK5mt7H3FZatVRjrc5wGqFYuBxX1yG7+A7Gi5AefkX6t/oCkizcQgpu0cY1dQ=="], - "@esbuild-kit/core-utils/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.18.20", "", { "os": "linux", "cpu": "none" }, "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A=="], + "@wangeditor-next/editor/nanoid": ["nanoid@5.1.16", "", { "bin": { "nanoid": "bin/nanoid.js" } }, "sha512-kVrnsrJqMR8+oLJnGEmSWw9BivK5mt7H3FZatVRjrc5wGqFYuBxX1yG7+A7Gi5AefkX6t/oCkizcQgpu0cY1dQ=="], - "@esbuild-kit/core-utils/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.18.20", "", { "os": "linux", "cpu": "s390x" }, "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ=="], + "@wangeditor-next/table-module/nanoid": ["nanoid@5.1.16", "", { "bin": { "nanoid": "bin/nanoid.js" } }, "sha512-kVrnsrJqMR8+oLJnGEmSWw9BivK5mt7H3FZatVRjrc5wGqFYuBxX1yG7+A7Gi5AefkX6t/oCkizcQgpu0cY1dQ=="], - "@esbuild-kit/core-utils/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.18.20", "", { "os": "linux", "cpu": "x64" }, "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w=="], + "@wangeditor-next/video-module/nanoid": ["nanoid@5.1.16", "", { "bin": { "nanoid": "bin/nanoid.js" } }, "sha512-kVrnsrJqMR8+oLJnGEmSWw9BivK5mt7H3FZatVRjrc5wGqFYuBxX1yG7+A7Gi5AefkX6t/oCkizcQgpu0cY1dQ=="], - "@esbuild-kit/core-utils/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.18.20", "", { "os": "none", "cpu": "x64" }, "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A=="], + "css-render/csstype": ["csstype@3.0.11", "", {}, "sha512-sa6P2wJ+CAbgyy4KFssIb/JNMLxFvKF1pCYCSXS8ZMuqZnMsrxqI2E5sPyoTpxoPU/gVZMzr2zjOfg8GIZOMsw=="], - "@esbuild-kit/core-utils/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.18.20", "", { "os": "openbsd", "cpu": "x64" }, "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg=="], + "cytoscape-fcose/cose-base": ["cose-base@2.2.0", "", { "dependencies": { "layout-base": "^2.0.0" } }, "sha512-AzlgcsCbUMymkADOJtQm3wO9S3ltPfYOFD5033keQn9NJzIbtnZj+UdBJe7DYml/8TdbtHJW3j58SOnKhWY/5g=="], - "@esbuild-kit/core-utils/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.18.20", "", { "os": "sunos", "cpu": "x64" }, "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ=="], + "d3-cloud/d3-dispatch": ["d3-dispatch@1.0.6", "", {}, "sha512-fVjoElzjhCEy+Hbn8KygnmMS7Or0a9sI2UzGwoB7cCtvI1XpVN9GpoYlnb3xt2YV66oXYb1fLJ8GMvP4hdU1RA=="], - "@esbuild-kit/core-utils/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.18.20", "", { "os": "win32", "cpu": "arm64" }, "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg=="], + "d3-dsv/commander": ["commander@7.2.0", "", {}, "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw=="], - "@esbuild-kit/core-utils/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.18.20", "", { "os": "win32", "cpu": "ia32" }, "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g=="], + "d3-sankey/d3-array": ["d3-array@2.12.1", "", { "dependencies": { "internmap": "^1.0.0" } }, "sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ=="], - "@esbuild-kit/core-utils/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.18.20", "", { "os": "win32", "cpu": "x64" }, "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ=="], + "d3-sankey/d3-shape": ["d3-shape@1.3.7", "", { "dependencies": { "d3-path": "1" } }, "sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw=="], - "tsx/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.28.1", "", { "os": "aix", "cpu": "ppc64" }, "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ=="], + "katex/commander": ["commander@8.3.0", "", {}, "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww=="], - "tsx/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.28.1", "", { "os": "android", "cpu": "arm" }, "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ=="], + "magic-string-ast/magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="], - "tsx/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.28.1", "", { "os": "android", "cpu": "arm64" }, "sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg=="], + "mermaid-legacy/@braintree/sanitize-url": ["@braintree/sanitize-url@6.0.4", "", {}, "sha512-s3jaWicZd0pkP0jf5ysyHUI/RE7MHos6qlToFcGWXVp+ykHOy77OUMrfbgJ9it2C5bow7OIQwYYaHjk9XlBQ2A=="], - "tsx/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.28.1", "", { "os": "android", "cpu": "x64" }, "sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng=="], + "mermaid-legacy/dagre-d3-es": ["dagre-d3-es@7.0.9", "", { "dependencies": { "d3": "^7.8.2", "lodash-es": "^4.17.21" } }, "sha512-rYR4QfVmy+sR44IBDvVtcAmOReGBvRCWDpO2QjYwqgh9yijw6eSHBqaPG/LIOEy7aBsniLvtMW6pg19qJhq60w=="], - "tsx/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.28.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q=="], + "mermaid-legacy/dompurify": ["dompurify@2.4.3", "", {}, "sha512-q6QaLcakcRjebxjg8/+NP+h0rPfatOgOzc46Fst9VAA3jF2ApfKBNKMzdP4DYTqtUMXSCd5pRS/8Po/OmoCHZQ=="], - "tsx/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.28.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ=="], + "mermaid-legacy/uuid": ["uuid@9.0.1", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA=="], - "tsx/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.28.1", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw=="], + "mlly/pkg-types": ["pkg-types@1.3.1", "", { "dependencies": { "confbox": "^0.1.8", "mlly": "^1.7.4", "pathe": "^2.0.1" } }, "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ=="], - "tsx/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.28.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ=="], + "postcss/nanoid": ["nanoid@3.3.17", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-xQLf0A3HOMlgHq0n247/LRuAOYmB7dXJ/DvAxGvsSBij45XtBSmQycu+F8ODbHwns/XyFZagyL1+J0Offw1E0g=="], - "tsx/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.28.1", "", { "os": "linux", "cpu": "arm" }, "sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ=="], + "strip-literal/js-tokens": ["js-tokens@10.0.0", "", {}, "sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q=="], - "tsx/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.28.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g=="], + "tsx/esbuild": ["esbuild@0.28.1", "https://registry.npmjs.com/esbuild/-/esbuild-0.28.1.tgz", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.28.1", "@esbuild/android-arm": "0.28.1", "@esbuild/android-arm64": "0.28.1", "@esbuild/android-x64": "0.28.1", "@esbuild/darwin-arm64": "0.28.1", "@esbuild/darwin-x64": "0.28.1", "@esbuild/freebsd-arm64": "0.28.1", "@esbuild/freebsd-x64": "0.28.1", "@esbuild/linux-arm": "0.28.1", "@esbuild/linux-arm64": "0.28.1", "@esbuild/linux-ia32": "0.28.1", "@esbuild/linux-loong64": "0.28.1", "@esbuild/linux-mips64el": "0.28.1", "@esbuild/linux-ppc64": "0.28.1", "@esbuild/linux-riscv64": "0.28.1", "@esbuild/linux-s390x": "0.28.1", "@esbuild/linux-x64": "0.28.1", "@esbuild/netbsd-arm64": "0.28.1", "@esbuild/netbsd-x64": "0.28.1", "@esbuild/openbsd-arm64": "0.28.1", "@esbuild/openbsd-x64": "0.28.1", "@esbuild/openharmony-arm64": "0.28.1", "@esbuild/sunos-x64": "0.28.1", "@esbuild/win32-arm64": "0.28.1", "@esbuild/win32-ia32": "0.28.1", "@esbuild/win32-x64": "0.28.1" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw=="], - "tsx/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.28.1", "", { "os": "linux", "cpu": "ia32" }, "sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w=="], + "unplugin-vue-components/magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="], - "tsx/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.28.1", "", { "os": "linux", "cpu": "none" }, "sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg=="], + "vue-router/magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="], - "tsx/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.28.1", "", { "os": "linux", "cpu": "none" }, "sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ=="], + "@babel/generator/@babel/types/@babel/helper-string-parser": ["@babel/helper-string-parser@8.0.0", "", {}, "sha512-6mJgmFFFIIO82vvoLt9XtRC7/TkzXfts1t/SpRX4IHSzMgqoPYCWesVu1udUPUWioAE/2fcG6WuI8zrkE1gwrg=="], - "tsx/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.28.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ=="], + "@babel/generator/@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@8.0.4", "", {}, "sha512-4wFaiLd0bVo4cIoTXI3zKI038NIWE/cr3jvBjejOVYVxV/m8Ltav1USiGzG1fmS5J2RhgEOgXNNK46cRPnRsrg=="], - "tsx/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.28.1", "", { "os": "linux", "cpu": "none" }, "sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ=="], + "@esbuild-kit/core-utils/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.18.20", "https://registry.npmjs.com/@esbuild/android-arm/-/android-arm-0.18.20.tgz", { "os": "android", "cpu": "arm" }, "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw=="], - "tsx/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.28.1", "", { "os": "linux", "cpu": "s390x" }, "sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag=="], + "@esbuild-kit/core-utils/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.18.20", "https://registry.npmjs.com/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz", { "os": "android", "cpu": "arm64" }, "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ=="], - "tsx/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.28.1", "", { "os": "linux", "cpu": "x64" }, "sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA=="], + "@esbuild-kit/core-utils/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.18.20", "https://registry.npmjs.com/@esbuild/android-x64/-/android-x64-0.18.20.tgz", { "os": "android", "cpu": "x64" }, "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg=="], - "tsx/esbuild/@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.28.1", "", { "os": "none", "cpu": "arm64" }, "sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw=="], + "@esbuild-kit/core-utils/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.18.20", "https://registry.npmjs.com/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz", { "os": "darwin", "cpu": "arm64" }, "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA=="], - "tsx/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.28.1", "", { "os": "none", "cpu": "x64" }, "sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg=="], + "@esbuild-kit/core-utils/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.18.20", "https://registry.npmjs.com/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz", { "os": "darwin", "cpu": "x64" }, "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ=="], - "tsx/esbuild/@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.28.1", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q=="], + "@esbuild-kit/core-utils/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.18.20", "https://registry.npmjs.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz", { "os": "freebsd", "cpu": "arm64" }, "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw=="], - "tsx/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.28.1", "", { "os": "openbsd", "cpu": "x64" }, "sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw=="], + "@esbuild-kit/core-utils/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.18.20", "https://registry.npmjs.com/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz", { "os": "freebsd", "cpu": "x64" }, "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ=="], - "tsx/esbuild/@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.28.1", "", { "os": "none", "cpu": "arm64" }, "sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg=="], + "@esbuild-kit/core-utils/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.18.20", "https://registry.npmjs.com/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz", { "os": "linux", "cpu": "arm" }, "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg=="], - "tsx/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.28.1", "", { "os": "sunos", "cpu": "x64" }, "sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ=="], + "@esbuild-kit/core-utils/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.18.20", "https://registry.npmjs.com/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz", { "os": "linux", "cpu": "arm64" }, "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA=="], - "tsx/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.28.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA=="], + "@esbuild-kit/core-utils/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.18.20", "https://registry.npmjs.com/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz", { "os": "linux", "cpu": "ia32" }, "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA=="], - "tsx/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.28.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg=="], + "@esbuild-kit/core-utils/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.18.20", "https://registry.npmjs.com/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz", { "os": "linux", "cpu": "none" }, "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg=="], - "tsx/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.28.1", "", { "os": "win32", "cpu": "x64" }, "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A=="], + "@esbuild-kit/core-utils/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.18.20", "https://registry.npmjs.com/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz", { "os": "linux", "cpu": "none" }, "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.18.20", "https://registry.npmjs.com/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz", { "os": "linux", "cpu": "ppc64" }, "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.18.20", "https://registry.npmjs.com/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz", { "os": "linux", "cpu": "none" }, "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.18.20", "https://registry.npmjs.com/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz", { "os": "linux", "cpu": "s390x" }, "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.18.20", "https://registry.npmjs.com/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz", { "os": "linux", "cpu": "x64" }, "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.18.20", "https://registry.npmjs.com/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz", { "os": "none", "cpu": "x64" }, "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.18.20", "https://registry.npmjs.com/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz", { "os": "openbsd", "cpu": "x64" }, "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.18.20", "https://registry.npmjs.com/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz", { "os": "sunos", "cpu": "x64" }, "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.18.20", "https://registry.npmjs.com/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz", { "os": "win32", "cpu": "arm64" }, "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.18.20", "https://registry.npmjs.com/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz", { "os": "win32", "cpu": "ia32" }, "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.18.20", "https://registry.npmjs.com/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz", { "os": "win32", "cpu": "x64" }, "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ=="], + + "@vue-flow/core/@vueuse/core/@types/web-bluetooth": ["@types/web-bluetooth@0.0.20", "", {}, "sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow=="], + + "@vue-flow/core/@vueuse/core/@vueuse/metadata": ["@vueuse/metadata@10.11.1", "", {}, "sha512-IGa5FXd003Ug1qAZmyE8wF3sJ81xGLSqTqtQ6jaVfkeZ4i5kS2mwQF61yhVqojRnenVew5PldLyRgvdl4YYuSw=="], + + "@vue-flow/core/@vueuse/core/@vueuse/shared": ["@vueuse/shared@10.11.1", "", { "dependencies": { "vue-demi": ">=0.14.8" } }, "sha512-LHpC8711VFZlDaYUXEBbFBCQ7GS3dVU9mjOhhMhXP6txTV4EhYQg/KGnQuvt/sPAtoUKq7VVUnL6mVtFoL42sA=="], + + "cytoscape-fcose/cose-base/layout-base": ["layout-base@2.0.1", "", {}, "sha512-dp3s92+uNI1hWIpPGH3jK2kxE2lMjdXdr+DH8ynZHpd6PUlH6x6cbuXnoMmiNumznqaNO31xu9e79F0uuZ0JFg=="], + + "d3-sankey/d3-array/internmap": ["internmap@1.0.1", "", {}, "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw=="], + + "d3-sankey/d3-shape/d3-path": ["d3-path@1.0.9", "", {}, "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg=="], + + "mlly/pkg-types/confbox": ["confbox@0.1.8", "", {}, "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w=="], + + "tsx/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.28.1", "https://registry.npmjs.com/@esbuild/aix-ppc64/-/aix-ppc64-0.28.1.tgz", { "os": "aix", "cpu": "ppc64" }, "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ=="], + + "tsx/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.28.1", "https://registry.npmjs.com/@esbuild/android-arm/-/android-arm-0.28.1.tgz", { "os": "android", "cpu": "arm" }, "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ=="], + + "tsx/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.28.1", "https://registry.npmjs.com/@esbuild/android-arm64/-/android-arm64-0.28.1.tgz", { "os": "android", "cpu": "arm64" }, "sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg=="], + + "tsx/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.28.1", "https://registry.npmjs.com/@esbuild/android-x64/-/android-x64-0.28.1.tgz", { "os": "android", "cpu": "x64" }, "sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng=="], + + "tsx/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.28.1", "https://registry.npmjs.com/@esbuild/darwin-arm64/-/darwin-arm64-0.28.1.tgz", { "os": "darwin", "cpu": "arm64" }, "sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q=="], + + "tsx/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.28.1", "https://registry.npmjs.com/@esbuild/darwin-x64/-/darwin-x64-0.28.1.tgz", { "os": "darwin", "cpu": "x64" }, "sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ=="], + + "tsx/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.28.1", "https://registry.npmjs.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.1.tgz", { "os": "freebsd", "cpu": "arm64" }, "sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw=="], + + "tsx/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.28.1", "https://registry.npmjs.com/@esbuild/freebsd-x64/-/freebsd-x64-0.28.1.tgz", { "os": "freebsd", "cpu": "x64" }, "sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ=="], + + "tsx/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.28.1", "https://registry.npmjs.com/@esbuild/linux-arm/-/linux-arm-0.28.1.tgz", { "os": "linux", "cpu": "arm" }, "sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ=="], + + "tsx/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.28.1", "https://registry.npmjs.com/@esbuild/linux-arm64/-/linux-arm64-0.28.1.tgz", { "os": "linux", "cpu": "arm64" }, "sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g=="], + + "tsx/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.28.1", "https://registry.npmjs.com/@esbuild/linux-ia32/-/linux-ia32-0.28.1.tgz", { "os": "linux", "cpu": "ia32" }, "sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w=="], + + "tsx/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.28.1", "https://registry.npmjs.com/@esbuild/linux-loong64/-/linux-loong64-0.28.1.tgz", { "os": "linux", "cpu": "none" }, "sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg=="], + + "tsx/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.28.1", "https://registry.npmjs.com/@esbuild/linux-mips64el/-/linux-mips64el-0.28.1.tgz", { "os": "linux", "cpu": "none" }, "sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ=="], + + "tsx/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.28.1", "https://registry.npmjs.com/@esbuild/linux-ppc64/-/linux-ppc64-0.28.1.tgz", { "os": "linux", "cpu": "ppc64" }, "sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ=="], + + "tsx/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.28.1", "https://registry.npmjs.com/@esbuild/linux-riscv64/-/linux-riscv64-0.28.1.tgz", { "os": "linux", "cpu": "none" }, "sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ=="], + + "tsx/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.28.1", "https://registry.npmjs.com/@esbuild/linux-s390x/-/linux-s390x-0.28.1.tgz", { "os": "linux", "cpu": "s390x" }, "sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag=="], + + "tsx/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.28.1", "https://registry.npmjs.com/@esbuild/linux-x64/-/linux-x64-0.28.1.tgz", { "os": "linux", "cpu": "x64" }, "sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA=="], + + "tsx/esbuild/@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.28.1", "https://registry.npmjs.com/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.1.tgz", { "os": "none", "cpu": "arm64" }, "sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw=="], + + "tsx/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.28.1", "https://registry.npmjs.com/@esbuild/netbsd-x64/-/netbsd-x64-0.28.1.tgz", { "os": "none", "cpu": "x64" }, "sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg=="], + + "tsx/esbuild/@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.28.1", "https://registry.npmjs.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.1.tgz", { "os": "openbsd", "cpu": "arm64" }, "sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q=="], + + "tsx/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.28.1", "https://registry.npmjs.com/@esbuild/openbsd-x64/-/openbsd-x64-0.28.1.tgz", { "os": "openbsd", "cpu": "x64" }, "sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw=="], + + "tsx/esbuild/@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.28.1", "https://registry.npmjs.com/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.1.tgz", { "os": "none", "cpu": "arm64" }, "sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg=="], + + "tsx/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.28.1", "https://registry.npmjs.com/@esbuild/sunos-x64/-/sunos-x64-0.28.1.tgz", { "os": "sunos", "cpu": "x64" }, "sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ=="], + + "tsx/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.28.1", "https://registry.npmjs.com/@esbuild/win32-arm64/-/win32-arm64-0.28.1.tgz", { "os": "win32", "cpu": "arm64" }, "sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA=="], + + "tsx/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.28.1", "https://registry.npmjs.com/@esbuild/win32-ia32/-/win32-ia32-0.28.1.tgz", { "os": "win32", "cpu": "ia32" }, "sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg=="], + + "tsx/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.28.1", "https://registry.npmjs.com/@esbuild/win32-x64/-/win32-x64-0.28.1.tgz", { "os": "win32", "cpu": "x64" }, "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A=="], } }