From 928f742d5e54c49c5da0ee8161213ae1a87359eb Mon Sep 17 00:00:00 2001 From: Coldsmile_7 Date: Fri, 5 Jun 2026 23:21:41 +0800 Subject: [PATCH] feat: initialize VitePress CMS --- .gitignore | 32 + README.md | 112 + index.html | 12 + package-lock.json | 1422 +++++++++++++ package.json | 23 + server.mjs | 45 + server/auth.mjs | 96 + server/bootstrap.mjs | 47 + server/db.mjs | 86 + server/github.mjs | 577 +++++ server/http.mjs | 26 + server/index.mjs | 81 + server/security.mjs | 24 + server/session.mjs | 59 + server/settings.mjs | 70 + src/App.vue | 465 +++++ src/api/auth.ts | 47 + src/api/githubAuth.ts | 70 + src/api/githubSite.ts | 266 +++ src/api/localSite.ts | 64 + src/components/AppSidebar.vue | 67 + src/components/ConfigPanel.vue | 74 + src/components/ConnectPanel.vue | 206 ++ src/components/ContentPanel.vue | 108 + src/components/LoginView.vue | 58 + src/components/PublishPanel.vue | 29 + src/components/StructurePanel.vue | 49 + src/env.d.ts | 6 + src/main.ts | 5 + src/styles.css | 757 +++++++ src/types.ts | 71 + src/utils/markdown.ts | 27 + src/utils/siteContent.ts | 113 + test-vitepress/.vitepress/config.ts | 38 + test-vitepress/README.md | 24 + test-vitepress/changelog.md | 12 + test-vitepress/guide/config.md | 18 + test-vitepress/guide/getting-started.md | 17 + test-vitepress/guide/pages.md | 20 + test-vitepress/index.md | 20 + test-vitepress/package-lock.json | 2552 +++++++++++++++++++++++ test-vitepress/package.json | 15 + test-vitepress/public/logo.svg | 4 + tsconfig.json | 17 + vite.config.ts | 283 +++ 45 files changed, 8214 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 index.html create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 server.mjs create mode 100644 server/auth.mjs create mode 100644 server/bootstrap.mjs create mode 100644 server/db.mjs create mode 100644 server/github.mjs create mode 100644 server/http.mjs create mode 100644 server/index.mjs create mode 100644 server/security.mjs create mode 100644 server/session.mjs create mode 100644 server/settings.mjs create mode 100644 src/App.vue create mode 100644 src/api/auth.ts create mode 100644 src/api/githubAuth.ts create mode 100644 src/api/githubSite.ts create mode 100644 src/api/localSite.ts create mode 100644 src/components/AppSidebar.vue create mode 100644 src/components/ConfigPanel.vue create mode 100644 src/components/ConnectPanel.vue create mode 100644 src/components/ContentPanel.vue create mode 100644 src/components/LoginView.vue create mode 100644 src/components/PublishPanel.vue create mode 100644 src/components/StructurePanel.vue create mode 100644 src/env.d.ts create mode 100644 src/main.ts create mode 100644 src/styles.css create mode 100644 src/types.ts create mode 100644 src/utils/markdown.ts create mode 100644 src/utils/siteContent.ts create mode 100644 test-vitepress/.vitepress/config.ts create mode 100644 test-vitepress/README.md create mode 100644 test-vitepress/changelog.md create mode 100644 test-vitepress/guide/config.md create mode 100644 test-vitepress/guide/getting-started.md create mode 100644 test-vitepress/guide/pages.md create mode 100644 test-vitepress/index.md create mode 100644 test-vitepress/package-lock.json create mode 100644 test-vitepress/package.json create mode 100644 test-vitepress/public/logo.svg create mode 100644 tsconfig.json create mode 100644 vite.config.ts diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ac2508c --- /dev/null +++ b/.gitignore @@ -0,0 +1,32 @@ +# Dependencies +node_modules/ +test-vitepress/node_modules/ + +# Build output +dist/ +test-vitepress/.vitepress/dist/ +test-vitepress/.vitepress/.temp/ +test-vitepress/.vitepress/cache/ + +# Local data +data/ +*.sqlite +*.sqlite-shm +*.sqlite-wal + +# Environment +.env +.env.* +!.env.example + +# Logs +*.log +npm-debug.log* +pnpm-debug.log* +yarn-debug.log* + +# OS / editor +.DS_Store +Thumbs.db +.idea/ +.vscode/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..4d68755 --- /dev/null +++ b/README.md @@ -0,0 +1,112 @@ +# VitePress-CMS + +VitePress-CMS 是一个面向 VitePress 的可视化内容管理后台。它的目标是让用户通过网页界面管理 Markdown 页面、站点结构、主题配置与 GitHub 发布流程。 + +## 当前版本 + +当前项目已经从静态原型升级为 Vue 3 + TypeScript + Vite 工程。 + +已完成的原型能力: + +- 页面目录、搜索与新建页面 +- Markdown 编辑与预览切换 +- Frontmatter 标题和描述编辑 +- 导航栏与侧边栏管理界面 +- 站点基础配置界面 +- 发布中心与部署状态模拟 + +## 本地运行 + +```bash +npm install +npm run dev +``` + +Windows PowerShell 如果拦截 `npm.ps1`,可以使用: + +```bash +npm.cmd install +npm.cmd run dev +``` + +## 初始账户 + +项目启动时会自动创建 SQLite 数据库: + +```text +data/vitepress-cms.sqlite +``` + +如果数据库里还没有系统管理员,会自动创建初始账户: + +```text +邮箱:admin@example.com +密码:admin123456 +角色:system_admin +``` + +可以通过环境变量修改初始账户: + +```bash +CMS_ADMIN_EMAIL=admin@your-domain.com +CMS_ADMIN_PASSWORD=change-me +``` + +## 构建验证 + +```bash +npm run build +``` + +## 测试站点 + +VitePress 测试站点放在项目内的独立子目录: + +```text +test-vitepress/ +``` + +它有自己的 `package.json` 和依赖,不会让 VitePress 成为 CMS 主项目的一部分。这样既方便开发测试,也能保持主项目依赖干净。 + +运行测试站点: + +```bash +cd test-vitepress +npm install +npm run dev +``` + +## GitHub 连接 + +当前版本支持两种 GitHub 连接方式: + +1. GitHub OAuth 登录后选择仓库。 +2. 手动输入 token、owner、repo、branch 和站点目录。 + +使用 OAuth 登录前,需要创建 GitHub OAuth App,并配置环境变量: + +```bash +GITHUB_CLIENT_ID=xxx +GITHUB_CLIENT_SECRET=xxx +``` + +开发环境 callback URL: + +```text +http://localhost:5173/api/github/callback +``` + +Token 或 OAuth 授权至少需要仓库内容读写权限。保存页面或配置时,CMS 会通过 GitHub Contents API 创建 commit。 + +站点目录示例: + +- VitePress 在仓库根目录:留空 +- VitePress 在 `docs/`:填写 `docs` + +## 下一步 + +1. 建立 GitHub OAuth 登录流程。 +2. 读取仓库中的 `docs/` 页面文件。 +3. 解析 `.vitepress/config.ts` 中的常用配置。 +4. 将页面和配置修改提交为 GitHub commit。 +5. 显示 GitHub Pages 或 Actions 部署状态。 diff --git a/index.html b/index.html new file mode 100644 index 0000000..8f81bdc --- /dev/null +++ b/index.html @@ -0,0 +1,12 @@ + + + + + + VitePress-CMS + + +
+ + + diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..f0febae --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1422 @@ +{ + "name": "vitepress-cms", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "vitepress-cms", + "version": "0.1.0", + "dependencies": { + "vue": "^3.5.0" + }, + "devDependencies": { + "@vitejs/plugin-vue": "^5.1.0", + "typescript": "^5.5.0", + "vite": "^5.4.0", + "vue-tsc": "^2.1.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz", + "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.7" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz", + "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.61.1.tgz", + "integrity": "sha512-JnBB8MdXj45cajvTuO5FmPlvFVJRQgvrz1uSEl3NwqFnReAPGwb8EanbGi4z2nRaqLzjJSv5/JmycoTKlRZxHA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.61.1.tgz", + "integrity": "sha512-Jx2g7iSjw4AOT0HDPHM9RV3GNjRXwybWtSFZiZAYUTjUwjVrYIwq3kBf+LnhqJlzXFAqTAh2F7IGI+O568exPw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.61.1.tgz", + "integrity": "sha512-0F1L/Z3Eqv8mT2n3dCpeO8GcTvHvVqkP5/t6DMsn0KzhYVcg+s7Ncl5DS8qjKYEeio6Az0Gt6nyBORay5qIlCA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.61.1.tgz", + "integrity": "sha512-qLttcH871ujY4YcVfUSShhOw+CsoTatYz8gRbHO7Bb92QH059/P0y5do1KMs41fY0BpD2x4AJH/gID0zFiqVKQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.61.1.tgz", + "integrity": "sha512-fUI4RapGE0Oh3mb8mgfvC1O2nU1RpDZUKnDQm3xB1Ipg7C2wTs5Kstz7G2uWK99a8S2yTMq8/P4uycwNa0nJyw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.61.1.tgz", + "integrity": "sha512-H5YrdvJaDtI/U9/emrD4b++xkvp3y/JvOe4rizHbxvkyMfRS/CiRYdji+Pl8D0brEaNFWUh1drQxgAGIl6Xudw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.61.1.tgz", + "integrity": "sha512-Q8CBCCQtDFrYtXoeUXSrnFXKOnyUhx6bz+SkL6A0E7V8kAiCJ5pamq1WtbfpVGhR5TSpXY6ak3avmDc5fHTyJA==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.61.1.tgz", + "integrity": "sha512-nwnhk1581l0FBVellGcVCAT0Oi06onEA3WB53sf01VO3I0UPBkMH9sXONYME2K0ovXcNayJfNtHfm6mpJElatQ==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.61.1.tgz", + "integrity": "sha512-x5Xr49hwt3hdW75UOZm3395YwwzPyauktslv29KpWL/T+vVAzoT3azLcTWv0eMciBNrx+DYjH4paehHoLpPvpg==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.61.1.tgz", + "integrity": "sha512-unMS3H73DpaoPyyEVPjGKleM/s0mkmsauTENpw4INQY8y4+IuLNjkueQ5QCtC0D3N38Y38yhAU8OoZ20S2Tm6w==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.61.1.tgz", + "integrity": "sha512-zNZzGRnAhwjFEYmvphJRV5XaQGjs62cCmeYYHUT//NbvEnHauw+I85nGG+SiVg5ld4GX8D1IbKIX+ozITQnhMQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.61.1.tgz", + "integrity": "sha512-LdpWGL8X209B2SIvWjqlc8VZgM6PKfontSerGepuldQmHYrAOtnMCXeJkxXGbC+PPZVOuu5czJo7fNV6aeW8rQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.61.1.tgz", + "integrity": "sha512-EC5kTtNaNGOmbMGqar8dvJy6y/hg99GAwjfBz++pxZhQATXGcRjd6c5en5wcbru0vkRmiMGsQKdMJOOf6sza4g==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.61.1.tgz", + "integrity": "sha512-8hiwp6D4acEcNK78I4rP0/XtS1sknWIAMJBPdR4l6zUtyTm5KiTDr5bXmWt4foY7nAN7AThDHgkLIEZOWKbzWw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.61.1.tgz", + "integrity": "sha512-10dh/h/BqA7DuMPWSxkR8uks18FRwnwOEqr5zOTEl+NOwP/OMzKX8OFR/Of9xxDA7D5qef1Nzar5WDD2kCCr1g==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.61.1.tgz", + "integrity": "sha512-YKJ5lg35DP17gcAOggnihe+APw9HLyj1Xn7gsmGumBJAUDa6NGXNixJzmkWLhcK9TOuuyQjdamzvJefkO7qHZQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.61.1.tgz", + "integrity": "sha512-Mlil5G2Jj6a7B3LWGctg+XPL9vdXYuzCtNXfxOQ0nPjc2m6ueUktocPGH9bnAM0bNRKb/bAWTujUU7IJQdQA+g==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.61.1.tgz", + "integrity": "sha512-bVWIOIk6pV01p4CdUbPP7CJ/434z+OooYjDuFcR+44N35YvKUC66G8MGnvcWx5mWKW3g61J+t74l3Kj15Kwn2Q==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.61.1.tgz", + "integrity": "sha512-qy5pBvZbqNFheBz61R1rzsezjm0J7O2oNGoWtGoY89SZYLUfxAJTBAqDChqAIdB4rCiIbi9nF7yZ83GnNiLwSw==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.61.1.tgz", + "integrity": "sha512-E83TXjI4zm0+5f2qO+UOudaCYIhYwpJ5jq6YCZNIZ+6CbfhKrkAGezeiASBL9ElxAxFsRS9ZhESv8mfnj6TKeg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.61.1.tgz", + "integrity": "sha512-fbWnKqVkjrJN38vNe3ahkbk6iejS/3b0Nt7EEtPpE6RBacZcGXNKbzfHN3GUUlXOPghUg0j6XUGrtjX9z1sIvA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.61.1.tgz", + "integrity": "sha512-ArMl38iVAbk0New1ogihQNY6iphLi4ZaRsa037gUzv5yeKPY8TD3Dmy4x2RNC1VztU/uqm+G+/RwFrSka3Oy2g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.61.1.tgz", + "integrity": "sha512-0mYtjHS9ucAbcATycCNK9IGBk/cCe/ma7EmSLGZdsxnOA8cjRIyU04wDpVAD9NiOfLUR9KTxdiO53uOkherqjQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.61.1.tgz", + "integrity": "sha512-gK1iCEPfpoSG9wfBihXxvBMi8ZfcWffYkEsC/Eih+iFENTaewvNcrEQ69lIOWYO5pePHKLHHO7nq5AILGO/HQQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.61.1.tgz", + "integrity": "sha512-X+zaP2x+j4RXGfbp/seSoRHWnPxzApilDszisZxbYH5C/jTxFhCtDNdPGZb9lJyYPs24wGxruPF7Y+sIXt9Gzw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@vitejs/plugin-vue": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.2.4.tgz", + "integrity": "sha512-7Yx/SXSOcQq5HiiV3orevHUFn+pmMB4cgbEkDYgnkUWb0WfeQ/wa2yFv6D5ICiCQOVpjA7vYDXrC7AGO8yjDHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "peerDependencies": { + "vite": "^5.0.0 || ^6.0.0", + "vue": "^3.2.25" + } + }, + "node_modules/@volar/language-core": { + "version": "2.4.15", + "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.4.15.tgz", + "integrity": "sha512-3VHw+QZU0ZG9IuQmzT68IyN4hZNd9GchGPhbD9+pa8CVv7rnoOZwo7T8weIbrRmihqy3ATpdfXFnqRrfPVK6CA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/source-map": "2.4.15" + } + }, + "node_modules/@volar/source-map": { + "version": "2.4.15", + "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-2.4.15.tgz", + "integrity": "sha512-CPbMWlUN6hVZJYGcU/GSoHu4EnCHiLaXI9n8c9la6RaI9W5JHX+NqG+GSQcB0JdC2FIBLdZJwGsfKyBB71VlTg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@volar/typescript": { + "version": "2.4.15", + "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-2.4.15.tgz", + "integrity": "sha512-2aZ8i0cqPGjXb4BhkMsPYDkkuc2ZQ6yOpqwAuNwUoncELqoy5fRgOQtLR9gB0g902iS0NAkvpIzs27geVyVdPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/language-core": "2.4.15", + "path-browserify": "^1.0.1", + "vscode-uri": "^3.0.8" + } + }, + "node_modules/@vue/compiler-core": { + "version": "3.5.35", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.35.tgz", + "integrity": "sha512-BUmHaR1J+O+CKZ9uJucdVTEr1LHsdyvv7vG3eNRhK3CczEHeMd/LtsHAuD7PbrxvI2envCY2v7HI1vC1aBRzKw==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.3", + "@vue/shared": "3.5.35", + "entities": "^7.0.1", + "estree-walker": "^2.0.2", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-dom": { + "version": "3.5.35", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.35.tgz", + "integrity": "sha512-k+bprkXxuqhVajgTx5mUHuir7TwQzUKOWR40ng1ncAqQRPnrLngGGgqVEEhOnTMlc8btHYVKmrP8s5Qyg0hvYA==", + "license": "MIT", + "dependencies": { + "@vue/compiler-core": "3.5.35", + "@vue/shared": "3.5.35" + } + }, + "node_modules/@vue/compiler-sfc": { + "version": "3.5.35", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.35.tgz", + "integrity": "sha512-G5VPMcXTSywXBgtFOZOnHKBxKSrwXUcvY1iaF5/hRcy7t0J6CH/d8ha9F4nzi00Fax1eLV0QHM7v4mQu68jydw==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.3", + "@vue/compiler-core": "3.5.35", + "@vue/compiler-dom": "3.5.35", + "@vue/compiler-ssr": "3.5.35", + "@vue/shared": "3.5.35", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.21", + "postcss": "^8.5.15", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-ssr": { + "version": "3.5.35", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.35.tgz", + "integrity": "sha512-rGhAeXgdM7/ffTJGXT69rCCdTmjDewnFuUZfBQQHTdcEBeWdT5HCGY60y2ytLJr9/Dsu7IntUi5z/w0h6Rjnzw==", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.35", + "@vue/shared": "3.5.35" + } + }, + "node_modules/@vue/compiler-vue2": { + "version": "2.7.16", + "resolved": "https://registry.npmjs.org/@vue/compiler-vue2/-/compiler-vue2-2.7.16.tgz", + "integrity": "sha512-qYC3Psj9S/mfu9uVi5WvNZIzq+xnXMhOwbTFKKDD7b1lhpnn71jXSFdTQ+WsIEk0ONCd7VV2IMm7ONl6tbQ86A==", + "dev": true, + "license": "MIT", + "dependencies": { + "de-indent": "^1.0.2", + "he": "^1.2.0" + } + }, + "node_modules/@vue/language-core": { + "version": "2.2.12", + "resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-2.2.12.tgz", + "integrity": "sha512-IsGljWbKGU1MZpBPN+BvPAdr55YPkj2nB/TBNGNC32Vy2qLG25DYu/NBN2vNtZqdRbTRjaoYrahLrToim2NanA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/language-core": "2.4.15", + "@vue/compiler-dom": "^3.5.0", + "@vue/compiler-vue2": "^2.7.16", + "@vue/shared": "^3.5.0", + "alien-signals": "^1.0.3", + "minimatch": "^9.0.3", + "muggle-string": "^0.4.1", + "path-browserify": "^1.0.1" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@vue/reactivity": { + "version": "3.5.35", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.35.tgz", + "integrity": "sha512-tVc+SsHConvh/Lz64qq1pP3rYArBmK42xonovEcxY74SQtvctZodG/zhq54P5dr38cVuw25d27cPNRdlMidpGQ==", + "license": "MIT", + "dependencies": { + "@vue/shared": "3.5.35" + } + }, + "node_modules/@vue/runtime-core": { + "version": "3.5.35", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.35.tgz", + "integrity": "sha512-A/xFNX9loIcWDygeQuNCfKuh0CoYBzxhqEMNah5TSFg9Z53DrFYEN2qi5CU9necjM1OWYegYREUTHmXTmhfXtg==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.35", + "@vue/shared": "3.5.35" + } + }, + "node_modules/@vue/runtime-dom": { + "version": "3.5.35", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.35.tgz", + "integrity": "sha512-odrJ1C391dbGnyDRh8U+rnP7J2amIEzfmRk5vXy7xi3aZhEXofTvpi0T4HJb6jlNqQZTNPR5MPHSB3RHNkIORA==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.35", + "@vue/runtime-core": "3.5.35", + "@vue/shared": "3.5.35", + "csstype": "^3.2.3" + } + }, + "node_modules/@vue/server-renderer": { + "version": "3.5.35", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.35.tgz", + "integrity": "sha512-NkebSOYdB97wi8OQcO3HqzZSlymJi/aWsN/7h74OSVhRTm6qGs3Jp3e0rCXynmWwSlKeRrnlIug+ilYoHBmQDA==", + "license": "MIT", + "dependencies": { + "@vue/compiler-ssr": "3.5.35", + "@vue/shared": "3.5.35" + }, + "peerDependencies": { + "vue": "3.5.35" + } + }, + "node_modules/@vue/shared": { + "version": "3.5.35", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.35.tgz", + "integrity": "sha512-zSbjL7gRXwks2ZQLRGCajBtBXEOXW9Ddhn/HvSdrGkE2dqGnumzW8XtusRrxrE9LvqtiqDXQ+A60Hp6mvdYxfA==", + "license": "MIT" + }, + "node_modules/alien-signals": { + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/alien-signals/-/alien-signals-1.0.13.tgz", + "integrity": "sha512-OGj9yyTnJEttvzhTUWuscOvtqxq5vrhF7vL9oS0xJ2mK0ItPYP1/y+vCFebfxoEyAz0++1AIwJ5CMr+Fk3nDmg==", + "dev": true, + "license": "MIT" + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/brace-expansion": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.1.tgz", + "integrity": "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "license": "MIT" + }, + "node_modules/de-indent": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz", + "integrity": "sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==", + "dev": true, + "license": "MIT" + }, + "node_modules/entities": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz", + "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "license": "MIT" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true, + "license": "MIT", + "bin": { + "he": "bin/he" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/muggle-string": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/muggle-string/-/muggle-string-0.4.1.tgz", + "integrity": "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.12", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", + "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/path-browserify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", + "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/postcss": { + "version": "8.5.15", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", + "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.12", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/rollup": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.61.1.tgz", + "integrity": "sha512-I4KW6iuRpuu2uHBLraZ1wNZe0DP7lnRha+VJ9tNaYVaVgKhW0aI3h4RYnoRPeql0flHm/Co55b7snEDcOfOJrA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.9" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.61.1", + "@rollup/rollup-android-arm64": "4.61.1", + "@rollup/rollup-darwin-arm64": "4.61.1", + "@rollup/rollup-darwin-x64": "4.61.1", + "@rollup/rollup-freebsd-arm64": "4.61.1", + "@rollup/rollup-freebsd-x64": "4.61.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.61.1", + "@rollup/rollup-linux-arm-musleabihf": "4.61.1", + "@rollup/rollup-linux-arm64-gnu": "4.61.1", + "@rollup/rollup-linux-arm64-musl": "4.61.1", + "@rollup/rollup-linux-loong64-gnu": "4.61.1", + "@rollup/rollup-linux-loong64-musl": "4.61.1", + "@rollup/rollup-linux-ppc64-gnu": "4.61.1", + "@rollup/rollup-linux-ppc64-musl": "4.61.1", + "@rollup/rollup-linux-riscv64-gnu": "4.61.1", + "@rollup/rollup-linux-riscv64-musl": "4.61.1", + "@rollup/rollup-linux-s390x-gnu": "4.61.1", + "@rollup/rollup-linux-x64-gnu": "4.61.1", + "@rollup/rollup-linux-x64-musl": "4.61.1", + "@rollup/rollup-openbsd-x64": "4.61.1", + "@rollup/rollup-openharmony-arm64": "4.61.1", + "@rollup/rollup-win32-arm64-msvc": "4.61.1", + "@rollup/rollup-win32-ia32-msvc": "4.61.1", + "@rollup/rollup-win32-x64-gnu": "4.61.1", + "@rollup/rollup-win32-x64-msvc": "4.61.1", + "fsevents": "~2.3.2" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "devOptional": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/vite": { + "version": "5.4.21", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", + "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/vscode-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.1.0.tgz", + "integrity": "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/vue": { + "version": "3.5.35", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.35.tgz", + "integrity": "sha512-cx89fnr+0kVGHiNFG6y6s0bdjypJRFNZn6x3WPstNdQR1bi1mbB7h4v5IBGTsPJU3nK1+0Iqj3Zf+hZWMieR4Q==", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.35", + "@vue/compiler-sfc": "3.5.35", + "@vue/runtime-dom": "3.5.35", + "@vue/server-renderer": "3.5.35", + "@vue/shared": "3.5.35" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/vue-tsc": { + "version": "2.2.12", + "resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-2.2.12.tgz", + "integrity": "sha512-P7OP77b2h/Pmk+lZdJ0YWs+5tJ6J2+uOQPo7tlBnY44QqQSPYvS0qVT4wqDJgwrZaLe47etJLLQRFia71GYITw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/typescript": "2.4.15", + "@vue/language-core": "2.2.12" + }, + "bin": { + "vue-tsc": "bin/vue-tsc.js" + }, + "peerDependencies": { + "typescript": ">=5.0.0" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..696772c --- /dev/null +++ b/package.json @@ -0,0 +1,23 @@ +{ + "name": "vitepress-cms", + "version": "0.1.0", + "private": true, + "description": "A visual CMS prototype for managing VitePress sites.", + "type": "module", + "scripts": { + "dev": "node server/index.mjs", + "dev:vite": "vite --host 0.0.0.0", + "build": "vue-tsc --noEmit && vite build", + "start": "node server/index.mjs", + "preview": "vite preview --host 0.0.0.0" + }, + "dependencies": { + "vue": "^3.5.0" + }, + "devDependencies": { + "@vitejs/plugin-vue": "^5.1.0", + "typescript": "^5.5.0", + "vite": "^5.4.0", + "vue-tsc": "^2.1.0" + } +} diff --git a/server.mjs b/server.mjs new file mode 100644 index 0000000..933f95d --- /dev/null +++ b/server.mjs @@ -0,0 +1,45 @@ +import { createReadStream, existsSync } from "node:fs"; +import { extname, join, normalize } from "node:path"; +import { createServer } from "node:http"; + +const port = Number(process.env.PORT || 5173); +const root = process.cwd(); + +const mimeTypes = { + ".html": "text/html; charset=utf-8", + ".js": "text/javascript; charset=utf-8", + ".css": "text/css; charset=utf-8", + ".json": "application/json; charset=utf-8", + ".svg": "image/svg+xml", + ".png": "image/png", + ".jpg": "image/jpeg", + ".jpeg": "image/jpeg", +}; + +function resolveRequest(url) { + const pathname = decodeURIComponent(new URL(url, `http://localhost:${port}`).pathname); + const requestedPath = normalize(join(root, pathname === "/" ? "index.html" : pathname)); + + if (!requestedPath.startsWith(root)) { + return null; + } + + return requestedPath; +} + +createServer((request, response) => { + const filePath = resolveRequest(request.url || "/"); + + if (!filePath || !existsSync(filePath)) { + response.writeHead(404, { "Content-Type": "text/plain; charset=utf-8" }); + response.end("Not found"); + return; + } + + response.writeHead(200, { + "Content-Type": mimeTypes[extname(filePath)] || "application/octet-stream", + }); + createReadStream(filePath).pipe(response); +}).listen(port, () => { + console.log(`VitePress-CMS prototype running at http://localhost:${port}`); +}); diff --git a/server/auth.mjs b/server/auth.mjs new file mode 100644 index 0000000..c0567f6 --- /dev/null +++ b/server/auth.mjs @@ -0,0 +1,96 @@ +import { db } from "./db.mjs"; +import { json, readBody } from "./http.mjs"; +import { clearSessionCookie, createSession, deleteSession, getSession, parseCookies, sessionCookie } from "./session.mjs"; +import { verifyPassword } from "./security.mjs"; + +function publicUser(user) { + if (!user) return null; + + return { + id: user.id, + email: user.email, + name: user.name, + avatarUrl: user.avatar_url, + role: user.role, + }; +} + +export function getRequestSession(request) { + const cookies = parseCookies(request.headers.cookie); + const sessionId = cookies.vpc_session; + const session = getSession(sessionId); + return { sessionId, session }; +} + +export function requireCmsUser(request, response) { + const { session } = getRequestSession(request); + + if (!session?.user?.id) { + json(response, 401, { error: "Not signed in" }); + return undefined; + } + + return session.user; +} + +async function handleMe(request, response) { + const { session } = getRequestSession(request); + json(response, 200, { user: session?.user ?? null }); +} + +async function handleLogin(request, response) { + const body = JSON.parse(await readBody(request) || "{}"); + const email = String(body.email || "").trim().toLowerCase(); + const password = String(body.password || ""); + + if (!email || !password) { + json(response, 400, { error: "Email and password are required" }); + return; + } + + const user = db.prepare("SELECT * FROM users WHERE email = ?").get(email); + + if (!user?.password_hash || !verifyPassword(password, user.password_hash)) { + json(response, 401, { error: "Invalid email or password" }); + return; + } + + const sessionUser = publicUser(user); + const sessionId = createSession({ + user: sessionUser, + }); + + json(response, 200, { user: sessionUser }, { "Set-Cookie": sessionCookie(sessionId) }); +} + +async function handleLogout(request, response) { + const { sessionId } = getRequestSession(request); + deleteSession(sessionId); + json(response, 200, { ok: true }, { "Set-Cookie": clearSessionCookie() }); +} + +export async function handleAuthApi(request, response, url) { + try { + if (url.pathname === "/api/auth/me" && request.method === "GET") { + await handleMe(request, response); + return true; + } + + if (url.pathname === "/api/auth/login" && request.method === "POST") { + await handleLogin(request, response); + return true; + } + + if (url.pathname === "/api/auth/logout" && request.method === "POST") { + await handleLogout(request, response); + return true; + } + + return false; + } catch (error) { + json(response, 500, { + error: error instanceof Error ? error.message : "Unknown auth API error", + }); + return true; + } +} diff --git a/server/bootstrap.mjs b/server/bootstrap.mjs new file mode 100644 index 0000000..00aa47b --- /dev/null +++ b/server/bootstrap.mjs @@ -0,0 +1,47 @@ +import { db, getSetting, migrate, setSetting } from "./db.mjs"; +import { hashPassword } from "./security.mjs"; + +const defaultAdminEmail = process.env.CMS_ADMIN_EMAIL || "admin@example.com"; +const defaultAdminPassword = process.env.CMS_ADMIN_PASSWORD || "admin123456"; + +function seedAdmin() { + const admin = db.prepare("SELECT id FROM users WHERE role = 'system_admin' LIMIT 1").get(); + + if (admin) return; + + db.prepare(` + INSERT INTO users (email, password_hash, name, role, email_verified) + VALUES (?, ?, ?, 'system_admin', 1) + `).run(defaultAdminEmail, hashPassword(defaultAdminPassword), "Administrator"); +} + +function seedSettings() { + const seeds = { + "site.title": "VitePress-CMS", + "auth.allow_email_login": "true", + "auth.allow_github_login": "true", + "auth.allow_registration": "false", + "smtp.host": "", + "smtp.port": "587", + "smtp.username": "", + "smtp.password": "", + "smtp.sender_email": "", + "github.api_base_url": "https://api.github.com", + "github.web_base_url": "https://github.com", + "github.oauth_client_id": "", + "github.oauth_client_secret": "", + }; + + Object.entries(seeds).forEach(([key, value]) => { + if (getSetting(key, undefined) === undefined) { + setSetting(key, value, key.includes("secret") || key.includes("password")); + } + }); +} + +export function bootstrap() { + migrate(); + seedAdmin(); + seedSettings(); + console.log(`Initial admin: ${defaultAdminEmail}`); +} diff --git a/server/db.mjs b/server/db.mjs new file mode 100644 index 0000000..ad0cf90 --- /dev/null +++ b/server/db.mjs @@ -0,0 +1,86 @@ +import { mkdirSync } from "node:fs"; +import { dirname, join } from "node:path"; +import { DatabaseSync } from "node:sqlite"; + +const dbPath = join(process.cwd(), "data", "vitepress-cms.sqlite"); +mkdirSync(dirname(dbPath), { recursive: true }); + +export const db = new DatabaseSync(dbPath); +db.exec("PRAGMA foreign_keys = ON"); + +export function migrate() { + db.exec(` + CREATE TABLE IF NOT EXISTS users ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + email TEXT UNIQUE, + password_hash TEXT, + name TEXT NOT NULL, + avatar_url TEXT, + role TEXT NOT NULL DEFAULT 'user', + email_verified INTEGER NOT NULL DEFAULT 0, + created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP + ); + + CREATE TABLE IF NOT EXISTS oauth_accounts ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + user_id INTEGER NOT NULL, + provider TEXT NOT NULL, + provider_user_id TEXT NOT NULL, + provider_login TEXT NOT NULL, + access_token TEXT, + avatar_url TEXT, + created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP, + UNIQUE(provider, provider_user_id), + FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE + ); + + CREATE TABLE IF NOT EXISTS projects ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + owner_user_id INTEGER NOT NULL, + name TEXT NOT NULL, + github_owner TEXT, + github_repo TEXT, + github_branch TEXT NOT NULL DEFAULT 'main', + site_root TEXT NOT NULL DEFAULT '', + created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY(owner_user_id) REFERENCES users(id) ON DELETE CASCADE + ); + + CREATE TABLE IF NOT EXISTS project_members ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + project_id INTEGER NOT NULL, + user_id INTEGER NOT NULL, + role TEXT NOT NULL DEFAULT 'editor', + created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP, + UNIQUE(project_id, user_id), + FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE, + FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE + ); + + CREATE TABLE IF NOT EXISTS system_settings ( + key TEXT PRIMARY KEY, + value TEXT NOT NULL, + encrypted INTEGER NOT NULL DEFAULT 0, + updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP + ); + `); +} + +export function getSetting(key, fallback = "") { + const row = db.prepare("SELECT value FROM system_settings WHERE key = ?").get(key); + return row?.value ?? fallback; +} + +export function setSetting(key, value, encrypted = false) { + db.prepare(` + INSERT INTO system_settings (key, value, encrypted, updated_at) + VALUES (?, ?, ?, CURRENT_TIMESTAMP) + ON CONFLICT(key) DO UPDATE SET + value = excluded.value, + encrypted = excluded.encrypted, + updated_at = CURRENT_TIMESTAMP + `).run(key, value, encrypted ? 1 : 0); +} diff --git a/server/github.mjs b/server/github.mjs new file mode 100644 index 0000000..a30180a --- /dev/null +++ b/server/github.mjs @@ -0,0 +1,577 @@ +import { randomBytes } from "node:crypto"; +import { db } from "./db.mjs"; +import { json, readBody, redirect } from "./http.mjs"; +import { getRequestSession } from "./auth.mjs"; +import { createSession, sessionCookie, updateSession } from "./session.mjs"; + +const oauthStates = new Map(); + +function getBaseUrl(request) { + const configuredBaseUrl = process.env.GITHUB_OAUTH_REDIRECT_BASE_URL; + if (configuredBaseUrl) return configuredBaseUrl.replace(/\/$/, ""); + + const host = request.headers.host ?? "localhost:5173"; + const protocol = host.startsWith("localhost") || host.startsWith("127.0.0.1") ? "http" : "https"; + return `${protocol}://${host}`; +} + +function getOAuthConfig(request) { + const clientId = process.env.GITHUB_CLIENT_ID; + const clientSecret = process.env.GITHUB_CLIENT_SECRET; + const baseUrl = getBaseUrl(request); + + if (!clientId || !clientSecret) { + throw new Error("请先配置 GITHUB_CLIENT_ID 和 GITHUB_CLIENT_SECRET"); + } + + return { + clientId, + clientSecret, + redirectUri: `${baseUrl}/api/github/callback`, + }; +} + +async function githubFetch(session, path, init = {}) { + const response = await fetch(`https://api.github.com${path}`, { + ...init, + headers: { + Accept: "application/vnd.github+json", + Authorization: `Bearer ${session.accessToken}`, + "X-GitHub-Api-Version": "2022-11-28", + ...init.headers, + }, + }); + + if (!response.ok) { + const details = await response.text(); + throw new Error(`GitHub API error ${response.status}: ${details}`); + } + + return response.json(); +} + +function requireSession(request, response) { + const { session } = getRequestSession(request); + + if (!session?.github?.accessToken) { + json(response, 401, { error: "Not signed in" }); + return undefined; + } + + return { + ...session, + accessToken: session.github.accessToken, + }; +} + +function publicCmsUser(user) { + return { + id: user.id, + email: user.email, + name: user.name, + avatarUrl: user.avatar_url, + role: user.role, + }; +} + +function findOrCreateGithubUser(githubUser, accessToken, currentSession) { + const providerUserId = String(githubUser.id); + const linkedAccount = db + .prepare("SELECT user_id FROM oauth_accounts WHERE provider = 'github' AND provider_user_id = ?") + .get(providerUserId); + + let user; + + if (linkedAccount) { + user = db.prepare("SELECT * FROM users WHERE id = ?").get(linkedAccount.user_id); + } else if (currentSession?.user?.id) { + user = db.prepare("SELECT * FROM users WHERE id = ?").get(currentSession.user.id); + } else { + const email = githubUser.email ? String(githubUser.email).toLowerCase() : null; + const existingByEmail = email ? db.prepare("SELECT * FROM users WHERE email = ?").get(email) : null; + + if (existingByEmail) { + user = existingByEmail; + } else { + const result = db + .prepare("INSERT INTO users (email, name, avatar_url, role, email_verified) VALUES (?, ?, ?, 'user', ?)") + .run(email, githubUser.name || githubUser.login, githubUser.avatar_url, email ? 1 : 0); + user = db.prepare("SELECT * FROM users WHERE id = ?").get(result.lastInsertRowid); + } + } + + db.prepare(` + INSERT INTO oauth_accounts (user_id, provider, provider_user_id, provider_login, access_token, avatar_url, updated_at) + VALUES (?, 'github', ?, ?, ?, ?, CURRENT_TIMESTAMP) + ON CONFLICT(provider, provider_user_id) DO UPDATE SET + user_id = excluded.user_id, + provider_login = excluded.provider_login, + access_token = excluded.access_token, + avatar_url = excluded.avatar_url, + updated_at = CURRENT_TIMESTAMP + `).run(user.id, providerUserId, githubUser.login, accessToken, githubUser.avatar_url); + + db.prepare("UPDATE users SET avatar_url = COALESCE(avatar_url, ?), updated_at = CURRENT_TIMESTAMP WHERE id = ?").run( + githubUser.avatar_url, + user.id, + ); + + return db.prepare("SELECT * FROM users WHERE id = ?").get(user.id); +} + +async function handleLogin(request, response) { + const { clientId, redirectUri } = getOAuthConfig(request); + const state = randomBytes(16).toString("hex"); + oauthStates.set(state, Date.now()); + + const params = new URLSearchParams({ + client_id: clientId, + redirect_uri: redirectUri, + scope: "repo", + state, + }); + + redirect(response, `https://github.com/login/oauth/authorize?${params.toString()}`); +} + +async function handleCallback(request, response, url) { + const code = url.searchParams.get("code"); + const state = url.searchParams.get("state"); + + if (!code || !state || !oauthStates.has(state)) { + redirect(response, "/?github=oauth_error"); + return; + } + + oauthStates.delete(state); + const { clientId, clientSecret, redirectUri } = getOAuthConfig(request); + const tokenResponse = await fetch("https://github.com/login/oauth/access_token", { + method: "POST", + headers: { + Accept: "application/json", + "Content-Type": "application/json", + }, + body: JSON.stringify({ + client_id: clientId, + client_secret: clientSecret, + code, + redirect_uri: redirectUri, + }), + }); + const tokenPayload = await tokenResponse.json(); + + if (!tokenPayload.access_token) { + redirect(response, "/?github=token_error"); + return; + } + + const userResponse = await fetch("https://api.github.com/user", { + headers: { + Accept: "application/vnd.github+json", + Authorization: `Bearer ${tokenPayload.access_token}`, + "X-GitHub-Api-Version": "2022-11-28", + }, + }); + const user = await userResponse.json(); + const { session: currentSession } = getRequestSession(request); + const cmsUser = findOrCreateGithubUser(user, tokenPayload.access_token, currentSession); + const sessionId = createSession({ + user: publicCmsUser(cmsUser), + github: { + accessToken: tokenPayload.access_token, + login: user.login, + avatarUrl: user.avatar_url, + name: user.name, + }, + }); + + redirect(response, "/?github=connected", { + "Set-Cookie": sessionCookie(sessionId), + }); +} + +async function handleMe(request, response) { + const { session } = getRequestSession(request); + + if (!session) { + json(response, 200, { user: null }); + return; + } + + json(response, 200, { user: session.github ?? null }); +} + +async function handleLogout(request, response) { + const { sessionId, session } = getRequestSession(request); + if (session) { + const { github, ...restSession } = session; + updateSession(sessionId, restSession); + } + json(response, 200, { ok: true }); +} + +async function handleRepos(request, response) { + const session = requireSession(request, response); + if (!session) return; + + const repos = await githubFetch( + session, + "/user/repos?per_page=100&sort=updated&affiliation=owner,collaborator,organization_member", + ); + json(response, 200, { + repos: repos.map((repo) => ({ + id: repo.id, + fullName: repo.full_name, + owner: repo.owner.login, + name: repo.name, + private: repo.private, + defaultBranch: repo.default_branch, + updatedAt: repo.updated_at, + })), + }); +} + +async function handleCreateRepo(request, response) { + const session = requireSession(request, response); + if (!session) return; + + const body = JSON.parse(await readBody(request) || "{}"); + + if (!body.name) { + json(response, 400, { error: "Repository name is required" }); + return; + } + + const repo = await githubFetch(session, "/user/repos", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + name: body.name, + private: Boolean(body.private), + auto_init: true, + description: body.description || "Created by VitePress-CMS", + }), + }); + + json(response, 201, { + repo: { + id: repo.id, + fullName: repo.full_name, + owner: repo.owner.login, + name: repo.name, + private: repo.private, + defaultBranch: repo.default_branch, + updatedAt: repo.updated_at, + }, + }); +} + +function normalizeSiteRoot(siteRoot = "") { + return siteRoot.trim().replace(/^\/+|\/+$/g, ""); +} + +function toRepoPath(siteRoot, path) { + const normalizedRoot = normalizeSiteRoot(siteRoot); + return normalizedRoot ? `${normalizedRoot}/${path}` : path; +} + +function fromRepoPath(siteRoot, path) { + const normalizedRoot = normalizeSiteRoot(siteRoot); + return normalizedRoot && path.startsWith(`${normalizedRoot}/`) ? path.slice(normalizedRoot.length + 1) : path; +} + +function decodeBase64Content(content) { + return Buffer.from(content.replace(/\n/g, ""), "base64").toString("utf-8"); +} + +function encodeBase64Content(content) { + return Buffer.from(content, "utf-8").toString("base64"); +} + +function parseFrontmatter(source) { + const match = source.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?/); + + if (!match) { + return { + title: "", + description: "", + content: source, + }; + } + + const frontmatter = match[1]; + const title = frontmatter.match(/^title:\s*(.+)$/m)?.[1]?.trim().replace(/^["']|["']$/g, "") ?? ""; + const description = + frontmatter.match(/^description:\s*(.+)$/m)?.[1]?.trim().replace(/^["']|["']$/g, "") ?? ""; + + return { + title, + description, + content: source.slice(match[0].length), + }; +} + +function stringifyMarkdownPage(page) { + return [ + "---", + `title: ${page.title || "未命名页面"}`, + `description: ${page.description || ""}`, + "---", + "", + page.content.trimStart(), + ].join("\n"); +} + +function readStringValue(source, key, fallback = "") { + const pattern = new RegExp(`${key}:\\s*["'\`]([^"'\`]+)["'\`]`); + return source.match(pattern)?.[1] ?? fallback; +} + +function readBooleanValue(source, key, fallback = false) { + const pattern = new RegExp(`${key}:\\s*(true|false)`); + const value = source.match(pattern)?.[1]; + return value ? value === "true" : fallback; +} + +function parseSiteSettings(source) { + const socialLinksSource = source.match(/socialLinks:\s*\[([\s\S]*?)\]/)?.[1] ?? ""; + + return { + title: readStringValue(source, "title", "VitePress Site"), + description: readStringValue(source, "description"), + logo: readStringValue(source, "logo", "/logo.svg"), + lastUpdated: readBooleanValue(source, "lastUpdated", true), + localSearch: /search:\s*{[\s\S]*?provider:\s*["']local["'][\s\S]*?}/.test(source), + outline: readBooleanValue(source, "outline", true), + socialKind: readStringValue(socialLinksSource, "icon", "github"), + socialLink: readStringValue(socialLinksSource, "link", "https://github.com/example/vitepress-cms"), + }; +} + +function stringifySiteConfig(settings) { + const searchConfig = settings.localSearch + ? ` search: { + provider: "local", + },` + : ""; + + return `import { defineConfig } from "vitepress"; + +export default defineConfig({ + title: ${JSON.stringify(settings.title)}, + description: ${JSON.stringify(settings.description)}, + lastUpdated: ${settings.lastUpdated}, + themeConfig: { + logo: ${JSON.stringify(settings.logo)}, + outline: ${settings.outline}, + nav: [ + { text: "首页", link: "/" }, + { text: "指南", link: "/guide/getting-started" }, + { text: "配置", link: "/guide/config" }, + { text: "更新日志", link: "/changelog" }, + ], + sidebar: { + "/guide/": [ + { + text: "指南", + items: [ + { text: "快速开始", link: "/guide/getting-started" }, + { text: "页面管理", link: "/guide/pages" }, + { text: "配置说明", link: "/guide/config" }, + ], + }, + ], + }, + socialLinks: [ + { icon: ${JSON.stringify(settings.socialKind)}, link: ${JSON.stringify(settings.socialLink)} }, + ], + footer: { + message: "Powered by VitePress-CMS", + copyright: "Copyright 2026", + }, +${searchConfig} + }, +}); +`; +} + +function getSiteQuery(url) { + const owner = url.searchParams.get("owner"); + const repo = url.searchParams.get("repo"); + const branch = url.searchParams.get("branch") || "main"; + const siteRoot = url.searchParams.get("siteRoot") || ""; + + if (!owner || !repo) { + throw new Error("owner and repo are required"); + } + + return { owner, repo, branch, siteRoot }; +} + +async function getRepoContent(session, site, path) { + const repoPath = toRepoPath(site.siteRoot, path); + const encodedPath = repoPath.split("/").map(encodeURIComponent).join("/"); + return githubFetch( + session, + `/repos/${site.owner}/${site.repo}/contents/${encodedPath}?ref=${encodeURIComponent(site.branch)}`, + ); +} + +async function handleSitePages(request, response, url) { + const session = requireSession(request, response); + if (!session) return; + + if (request.method === "GET") { + const site = getSiteQuery(url); + const tree = await githubFetch( + session, + `/repos/${site.owner}/${site.repo}/git/trees/${encodeURIComponent(site.branch)}?recursive=1`, + ); + const siteRoot = normalizeSiteRoot(site.siteRoot); + const prefix = siteRoot ? `${siteRoot}/` : ""; + const markdownFiles = tree.tree + .filter((entry) => entry.type === "blob") + .filter((entry) => entry.path.endsWith(".md")) + .filter((entry) => !entry.path.includes("/node_modules/")) + .filter((entry) => entry.path !== `${prefix}README.md`) + .filter((entry) => !prefix || entry.path.startsWith(prefix)); + const pages = await Promise.all( + markdownFiles.sort((a, b) => a.path.localeCompare(b.path)).map(async (entry) => { + const file = await getRepoContent(session, site, fromRepoPath(site.siteRoot, entry.path)); + const source = decodeBase64Content(file.content); + const parsed = parseFrontmatter(source); + const path = fromRepoPath(site.siteRoot, entry.path); + + return { + id: path, + path, + sha: file.sha, + title: parsed.title || path.replace(/\.md$/, ""), + description: parsed.description, + content: parsed.content, + }; + }), + ); + + json(response, 200, { pages }); + return; + } + + if (request.method === "PUT") { + const body = JSON.parse(await readBody(request) || "{}"); + const site = body.site; + const page = body.page; + const repoPath = toRepoPath(site.siteRoot, page.path); + const encodedPath = repoPath.split("/").map(encodeURIComponent).join("/"); + const result = await githubFetch(session, `/repos/${site.owner}/${site.repo}/contents/${encodedPath}`, { + method: "PUT", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + message: `docs: update ${page.path} from VitePress-CMS`, + content: encodeBase64Content(stringifyMarkdownPage(page)), + branch: site.branch, + sha: page.sha, + }), + }); + + json(response, 200, { sha: result.content.sha }); + return; + } + + json(response, 405, { error: "Method not allowed" }); +} + +async function handleSiteSettings(request, response, url) { + const session = requireSession(request, response); + if (!session) return; + + if (request.method === "GET") { + const site = getSiteQuery(url); + const file = await getRepoContent(session, site, ".vitepress/config.ts"); + json(response, 200, { + settings: parseSiteSettings(decodeBase64Content(file.content)), + sha: file.sha, + }); + return; + } + + if (request.method === "PUT") { + const body = JSON.parse(await readBody(request) || "{}"); + const site = body.site; + const settings = body.settings; + const repoPath = toRepoPath(site.siteRoot, ".vitepress/config.ts"); + const encodedPath = repoPath.split("/").map(encodeURIComponent).join("/"); + const result = await githubFetch(session, `/repos/${site.owner}/${site.repo}/contents/${encodedPath}`, { + method: "PUT", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + message: "docs: update VitePress config from VitePress-CMS", + content: encodeBase64Content(stringifySiteConfig(settings)), + branch: site.branch, + sha: body.sha, + }), + }); + + json(response, 200, { sha: result.content.sha }); + return; + } + + json(response, 405, { error: "Method not allowed" }); +} + +export async function handleGithubApi(request, response, url) { + try { + if (url.pathname === "/api/github/login" && request.method === "GET") { + await handleLogin(request, response); + return true; + } + + if (url.pathname === "/api/github/callback" && request.method === "GET") { + await handleCallback(request, response, url); + return true; + } + + if (url.pathname === "/api/github/me" && request.method === "GET") { + await handleMe(request, response); + return true; + } + + if (url.pathname === "/api/github/logout" && request.method === "POST") { + await handleLogout(request, response); + return true; + } + + if (url.pathname === "/api/github/repos" && request.method === "GET") { + await handleRepos(request, response); + return true; + } + + if (url.pathname === "/api/github/repos" && request.method === "POST") { + await handleCreateRepo(request, response); + return true; + } + + if (url.pathname === "/api/github/site/pages") { + await handleSitePages(request, response, url); + return true; + } + + if (url.pathname === "/api/github/site/settings") { + await handleSiteSettings(request, response, url); + return true; + } + + return false; + } catch (error) { + json(response, 500, { + error: error instanceof Error ? error.message : "Unknown GitHub API error", + }); + return true; + } +} diff --git a/server/http.mjs b/server/http.mjs new file mode 100644 index 0000000..0200b1a --- /dev/null +++ b/server/http.mjs @@ -0,0 +1,26 @@ +export function json(response, status, payload, headers = {}) { + response.writeHead(status, { + "Content-Type": "application/json; charset=utf-8", + ...headers, + }); + response.end(JSON.stringify(payload)); +} + +export function redirect(response, location, headers = {}) { + response.writeHead(302, { + Location: location, + ...headers, + }); + response.end(); +} + +export function readBody(request) { + return new Promise((resolve, reject) => { + let body = ""; + request.on("data", (chunk) => { + body += chunk; + }); + request.on("end", () => resolve(body)); + request.on("error", reject); + }); +} diff --git a/server/index.mjs b/server/index.mjs new file mode 100644 index 0000000..96b96d9 --- /dev/null +++ b/server/index.mjs @@ -0,0 +1,81 @@ +import { createReadStream, existsSync } from "node:fs"; +import { extname, join, normalize } from "node:path"; +import { createServer } from "node:http"; +import { createServer as createViteServer } from "vite"; +import { bootstrap } from "./bootstrap.mjs"; +import { handleAuthApi } from "./auth.mjs"; +import { handleGithubApi } from "./github.mjs"; +import { handleSettingsApi } from "./settings.mjs"; + +const isProduction = process.env.NODE_ENV === "production"; +const port = Number(process.env.PORT || 5173); +const root = process.cwd(); + +bootstrap(); + +const mimeTypes = { + ".html": "text/html; charset=utf-8", + ".js": "text/javascript; charset=utf-8", + ".css": "text/css; charset=utf-8", + ".json": "application/json; charset=utf-8", + ".svg": "image/svg+xml", +}; + +const vite = isProduction + ? undefined + : await createViteServer({ + server: { + middlewareMode: true, + host: "0.0.0.0", + }, + appType: "spa", + }); + +function sendNotFound(response) { + response.writeHead(404, { "Content-Type": "text/plain; charset=utf-8" }); + response.end("Not found"); +} + +function serveStatic(request, response) { + const pathname = decodeURIComponent(new URL(request.url ?? "/", `http://localhost:${port}`).pathname); + const requestedPath = normalize(join(root, "dist", pathname === "/" ? "index.html" : pathname)); + const fallbackPath = join(root, "dist", "index.html"); + const filePath = existsSync(requestedPath) ? requestedPath : fallbackPath; + + if (!filePath.startsWith(join(root, "dist")) || !existsSync(filePath)) { + sendNotFound(response); + return; + } + + response.writeHead(200, { + "Content-Type": mimeTypes[extname(filePath)] || "application/octet-stream", + }); + createReadStream(filePath).pipe(response); +} + +const server = createServer(async (request, response) => { + const url = new URL(request.url ?? "/", `http://localhost:${port}`); + + if (await handleAuthApi(request, response, url)) { + return; + } + + if (await handleSettingsApi(request, response, url)) { + return; + } + + if (await handleGithubApi(request, response, url)) { + return; + } + + if (vite) { + vite.middlewares(request, response, () => sendNotFound(response)); + return; + } + + serveStatic(request, response); +}); + +server.listen(port, "0.0.0.0", () => { + console.log(`VitePress-CMS running at http://localhost:${port}`); +}); diff --git a/server/security.mjs b/server/security.mjs new file mode 100644 index 0000000..666f478 --- /dev/null +++ b/server/security.mjs @@ -0,0 +1,24 @@ +import { pbkdf2Sync, randomBytes, timingSafeEqual } from "node:crypto"; + +const iterations = 120000; +const keyLength = 32; +const digest = "sha256"; + +export function hashPassword(password) { + const salt = randomBytes(16).toString("hex"); + const hash = pbkdf2Sync(password, salt, iterations, keyLength, digest).toString("hex"); + return `pbkdf2:${iterations}:${salt}:${hash}`; +} + +export function verifyPassword(password, storedHash) { + const [scheme, storedIterations, salt, hash] = storedHash.split(":"); + + if (scheme !== "pbkdf2" || !storedIterations || !salt || !hash) { + return false; + } + + const candidate = pbkdf2Sync(password, salt, Number(storedIterations), keyLength, digest); + const expected = Buffer.from(hash, "hex"); + + return candidate.length === expected.length && timingSafeEqual(candidate, expected); +} diff --git a/server/session.mjs b/server/session.mjs new file mode 100644 index 0000000..5111fef --- /dev/null +++ b/server/session.mjs @@ -0,0 +1,59 @@ +import { randomBytes } from "node:crypto"; + +const sessions = new Map(); + +export function createSession(data) { + const id = randomBytes(24).toString("hex"); + sessions.set(id, { + ...data, + createdAt: Date.now(), + }); + return id; +} + +export function getSession(id) { + if (!id) return undefined; + return sessions.get(id); +} + +export function updateSession(id, data) { + if (!id || !sessions.has(id)) return undefined; + const nextSession = { + ...sessions.get(id), + ...data, + }; + sessions.set(id, nextSession); + return nextSession; +} + +export function deleteSession(id) { + if (!id) return; + sessions.delete(id); +} + +export function parseCookies(cookieHeader = "") { + return Object.fromEntries( + cookieHeader + .split(";") + .map((item) => item.trim()) + .filter(Boolean) + .map((item) => { + const index = item.indexOf("="); + return [item.slice(0, index), decodeURIComponent(item.slice(index + 1))]; + }), + ); +} + +export function sessionCookie(id) { + return [ + `vpc_session=${encodeURIComponent(id)}`, + "Path=/", + "HttpOnly", + "SameSite=Lax", + "Max-Age=2592000", + ].join("; "); +} + +export function clearSessionCookie() { + return "vpc_session=; Path=/; HttpOnly; SameSite=Lax; Max-Age=0"; +} diff --git a/server/settings.mjs b/server/settings.mjs new file mode 100644 index 0000000..15e3661 --- /dev/null +++ b/server/settings.mjs @@ -0,0 +1,70 @@ +import { db, setSetting } from "./db.mjs"; +import { requireCmsUser } from "./auth.mjs"; +import { json, readBody } from "./http.mjs"; + +function requireAdmin(request, response) { + const user = requireCmsUser(request, response); + + if (!user) return undefined; + + if (user.role !== "system_admin") { + json(response, 403, { error: "System admin permission is required" }); + return undefined; + } + + return user; +} + +function publicSetting(row) { + return { + key: row.key, + value: row.encrypted ? "" : row.value, + encrypted: Boolean(row.encrypted), + updatedAt: row.updated_at, + }; +} + +async function handleGetSettings(request, response) { + if (!requireAdmin(request, response)) return; + + const rows = db + .prepare("SELECT key, value, encrypted, updated_at FROM system_settings ORDER BY key") + .all(); + + json(response, 200, { settings: rows.map(publicSetting) }); +} + +async function handleSaveSettings(request, response) { + if (!requireAdmin(request, response)) return; + + const body = JSON.parse(await readBody(request) || "{}"); + const settings = Array.isArray(body.settings) ? body.settings : []; + + settings.forEach((setting) => { + if (!setting.key) return; + setSetting(String(setting.key), String(setting.value ?? ""), Boolean(setting.encrypted)); + }); + + json(response, 200, { ok: true }); +} + +export async function handleSettingsApi(request, response, url) { + try { + if (url.pathname === "/api/admin/settings" && request.method === "GET") { + await handleGetSettings(request, response); + return true; + } + + if (url.pathname === "/api/admin/settings" && request.method === "PUT") { + await handleSaveSettings(request, response); + return true; + } + + return false; + } catch (error) { + json(response, 500, { + error: error instanceof Error ? error.message : "Unknown settings API error", + }); + return true; + } +} diff --git a/src/App.vue b/src/App.vue new file mode 100644 index 0000000..1e7c8ee --- /dev/null +++ b/src/App.vue @@ -0,0 +1,465 @@ + + + diff --git a/src/api/auth.ts b/src/api/auth.ts new file mode 100644 index 0000000..4090194 --- /dev/null +++ b/src/api/auth.ts @@ -0,0 +1,47 @@ +import type { CmsUser } from "../types"; + +interface MeResponse { + user: CmsUser | null; +} + +interface LoginResponse { + user: CmsUser; +} + +export async function loadCurrentUser() { + const response = await fetch("/api/auth/me"); + + if (!response.ok) { + throw new Error(`读取当前用户失败:${response.status}`); + } + + const data = (await response.json()) as MeResponse; + return data.user; +} + +export async function loginWithEmail(email: string, password: string) { + const response = await fetch("/api/auth/login", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ email, password }), + }); + + if (!response.ok) { + throw new Error("邮箱或密码错误"); + } + + const data = (await response.json()) as LoginResponse; + return data.user; +} + +export async function logoutCms() { + const response = await fetch("/api/auth/logout", { + method: "POST", + }); + + if (!response.ok) { + throw new Error(`退出登录失败:${response.status}`); + } +} diff --git a/src/api/githubAuth.ts b/src/api/githubAuth.ts new file mode 100644 index 0000000..24bc878 --- /dev/null +++ b/src/api/githubAuth.ts @@ -0,0 +1,70 @@ +import type { GitHubRepository, GitHubUser } from "../types"; + +interface MeResponse { + user: GitHubUser | null; +} + +interface ReposResponse { + repos: GitHubRepository[]; +} + +interface CreateRepoResponse { + repo: GitHubRepository; +} + +export function startGitHubLogin() { + window.location.href = "/api/github/login"; +} + +export async function loadGitHubUser() { + const response = await fetch("/api/github/me"); + + if (!response.ok) { + throw new Error(`读取 GitHub 登录状态失败:${response.status}`); + } + + const data = (await response.json()) as MeResponse; + return data.user; +} + +export async function logoutGitHub() { + const response = await fetch("/api/github/logout", { + method: "POST", + }); + + if (!response.ok) { + throw new Error(`退出 GitHub 登录失败:${response.status}`); + } +} + +export async function loadGitHubRepositories() { + const response = await fetch("/api/github/repos"); + + if (!response.ok) { + throw new Error(`读取 GitHub 仓库列表失败:${response.status}`); + } + + const data = (await response.json()) as ReposResponse; + return data.repos; +} + +export async function createGitHubRepository(payload: { + name: string; + private: boolean; + description?: string; +}) { + const response = await fetch("/api/github/repos", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(payload), + }); + + if (!response.ok) { + throw new Error(`创建 GitHub 仓库失败:${response.status}`); + } + + const data = (await response.json()) as CreateRepoResponse; + return data.repo; +} diff --git a/src/api/githubSite.ts b/src/api/githubSite.ts new file mode 100644 index 0000000..47cde21 --- /dev/null +++ b/src/api/githubSite.ts @@ -0,0 +1,266 @@ +import type { DocPage, GitHubConnection, SiteSettings } from "../types"; +import { parseFrontmatter, parseSiteSettings, stringifyMarkdownPage, stringifySiteConfig } from "../utils/siteContent"; + +interface GitTreeResponse { + tree: Array<{ + path: string; + type: "blob" | "tree"; + sha: string; + }>; +} + +interface GitHubContentResponse { + content: string; + encoding: string; + sha: string; + path: string; +} + +function githubHeaders(connection: GitHubConnection) { + const headers: Record = { + Accept: "application/vnd.github+json", + "X-GitHub-Api-Version": "2022-11-28", + }; + + if (connection.token.trim()) { + headers.Authorization = `Bearer ${connection.token.trim()}`; + } + + return headers; +} + +function usesSessionProxy(connection: GitHubConnection) { + return !connection.token.trim(); +} + +function siteParams(connection: GitHubConnection) { + return new URLSearchParams({ + owner: connection.owner, + repo: connection.repo, + branch: connection.branch, + siteRoot: connection.siteRoot, + }); +} + +function normalizeSiteRoot(siteRoot: string) { + return siteRoot.trim().replace(/^\/+|\/+$/g, ""); +} + +function toRepoPath(connection: GitHubConnection, path: string) { + const siteRoot = normalizeSiteRoot(connection.siteRoot); + return siteRoot ? `${siteRoot}/${path}` : path; +} + +function fromRepoPath(connection: GitHubConnection, path: string) { + const siteRoot = normalizeSiteRoot(connection.siteRoot); + return siteRoot && path.startsWith(`${siteRoot}/`) ? path.slice(siteRoot.length + 1) : path; +} + +function decodeBase64Content(content: string) { + const binary = atob(content.replace(/\n/g, "")); + const bytes = Uint8Array.from(binary, (char) => char.charCodeAt(0)); + return new TextDecoder().decode(bytes); +} + +function encodeBase64Content(content: string) { + const bytes = new TextEncoder().encode(content); + let binary = ""; + bytes.forEach((byte) => { + binary += String.fromCharCode(byte); + }); + return btoa(binary); +} + +async function githubFetch(connection: GitHubConnection, path: string, init?: RequestInit) { + const response = await fetch(`https://api.github.com${path}`, { + ...init, + headers: { + ...githubHeaders(connection), + ...init?.headers, + }, + }); + + if (!response.ok) { + const details = await response.text(); + throw new Error(`GitHub 请求失败:${response.status} ${details}`); + } + + return (await response.json()) as T; +} + +async function getContent(connection: GitHubConnection, repoPath: string) { + const encodedPath = repoPath.split("/").map(encodeURIComponent).join("/"); + return githubFetch( + connection, + `/repos/${connection.owner}/${connection.repo}/contents/${encodedPath}?ref=${encodeURIComponent(connection.branch)}`, + ); +} + +export async function loadGitHubPages(connection: GitHubConnection) { + if (usesSessionProxy(connection)) { + const response = await fetch(`/api/github/site/pages?${siteParams(connection).toString()}`); + + if (!response.ok) { + throw new Error(`读取 GitHub 页面失败:${response.status}`); + } + + const data = (await response.json()) as { pages: DocPage[] }; + return data.pages; + } + + const tree = await githubFetch( + connection, + `/repos/${connection.owner}/${connection.repo}/git/trees/${encodeURIComponent(connection.branch)}?recursive=1`, + ); + const siteRoot = normalizeSiteRoot(connection.siteRoot); + const prefix = siteRoot ? `${siteRoot}/` : ""; + const markdownFiles = tree.tree + .filter((entry) => entry.type === "blob") + .filter((entry) => entry.path.endsWith(".md")) + .filter((entry) => !entry.path.includes("/node_modules/")) + .filter((entry) => entry.path !== `${prefix}README.md`) + .filter((entry) => !prefix || entry.path.startsWith(prefix)); + + return Promise.all( + markdownFiles.sort((a, b) => a.path.localeCompare(b.path)).map(async (entry) => { + const file = await getContent(connection, entry.path); + const source = decodeBase64Content(file.content); + const parsed = parseFrontmatter(source); + const path = fromRepoPath(connection, entry.path); + + return { + id: path, + path, + sha: file.sha, + title: parsed.title || path.replace(/\.md$/, ""), + description: parsed.description, + content: parsed.content, + } satisfies DocPage; + }), + ); +} + +export async function saveGitHubPage(connection: GitHubConnection, page: DocPage) { + if (usesSessionProxy(connection)) { + const response = await fetch("/api/github/site/pages", { + method: "PUT", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + site: { + owner: connection.owner, + repo: connection.repo, + branch: connection.branch, + siteRoot: connection.siteRoot, + }, + page, + }), + }); + + if (!response.ok) { + throw new Error(`保存 GitHub 页面失败:${response.status}`); + } + + const data = (await response.json()) as { sha: string }; + return data.sha; + } + + const repoPath = toRepoPath(connection, page.path); + const encodedPath = repoPath.split("/").map(encodeURIComponent).join("/"); + const response = await githubFetch<{ content: { sha: string } }>( + connection, + `/repos/${connection.owner}/${connection.repo}/contents/${encodedPath}`, + { + method: "PUT", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + message: `docs: update ${page.path} from VitePress-CMS`, + content: encodeBase64Content(stringifyMarkdownPage(page)), + branch: connection.branch, + sha: page.sha, + }), + }, + ); + + return response.content.sha; +} + +export async function loadGitHubSettings(connection: GitHubConnection) { + if (usesSessionProxy(connection)) { + const response = await fetch(`/api/github/site/settings?${siteParams(connection).toString()}`); + + if (!response.ok) { + throw new Error(`读取 GitHub 配置失败:${response.status}`); + } + + return (await response.json()) as { + settings: SiteSettings; + sha: string; + }; + } + + const configPath = toRepoPath(connection, ".vitepress/config.ts"); + const file = await getContent(connection, configPath); + const source = decodeBase64Content(file.content); + + return { + settings: parseSiteSettings(source), + sha: file.sha, + }; +} + +export async function saveGitHubSettings( + connection: GitHubConnection, + settings: SiteSettings, + sha?: string, +) { + if (usesSessionProxy(connection)) { + const response = await fetch("/api/github/site/settings", { + method: "PUT", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + site: { + owner: connection.owner, + repo: connection.repo, + branch: connection.branch, + siteRoot: connection.siteRoot, + }, + settings, + sha, + }), + }); + + if (!response.ok) { + throw new Error(`保存 GitHub 配置失败:${response.status}`); + } + + const data = (await response.json()) as { sha: string }; + return data.sha; + } + + const repoPath = toRepoPath(connection, ".vitepress/config.ts"); + const encodedPath = repoPath.split("/").map(encodeURIComponent).join("/"); + const response = await githubFetch<{ content: { sha: string } }>( + connection, + `/repos/${connection.owner}/${connection.repo}/contents/${encodedPath}`, + { + method: "PUT", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + message: "docs: update VitePress config from VitePress-CMS", + content: encodeBase64Content(stringifySiteConfig(settings)), + branch: connection.branch, + sha, + }), + }, + ); + + return response.content.sha; +} diff --git a/src/api/localSite.ts b/src/api/localSite.ts new file mode 100644 index 0000000..493824c --- /dev/null +++ b/src/api/localSite.ts @@ -0,0 +1,64 @@ +import type { DocPage, SiteSettings } from "../types"; + +interface PagesResponse { + pages: DocPage[]; +} + +interface SettingsResponse { + settings: SiteSettings; +} + +export async function loadLocalPages() { + const response = await fetch("/api/local-site/pages"); + + if (!response.ok) { + throw new Error(`读取测试站点失败:${response.status}`); + } + + const data = (await response.json()) as PagesResponse; + return data.pages; +} + +export async function saveLocalPage(page: DocPage) { + const response = await fetch("/api/local-site/pages", { + method: "PUT", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + path: page.path, + title: page.title, + description: page.description, + content: page.content, + }), + }); + + if (!response.ok) { + throw new Error(`保存测试站点失败:${response.status}`); + } +} + +export async function loadLocalSettings() { + const response = await fetch("/api/local-site/settings"); + + if (!response.ok) { + throw new Error(`读取测试站点配置失败:${response.status}`); + } + + const data = (await response.json()) as SettingsResponse; + return data.settings; +} + +export async function saveLocalSettings(settings: SiteSettings) { + const response = await fetch("/api/local-site/settings", { + method: "PUT", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(settings), + }); + + if (!response.ok) { + throw new Error(`保存测试站点配置失败:${response.status}`); + } +} diff --git a/src/components/AppSidebar.vue b/src/components/AppSidebar.vue new file mode 100644 index 0000000..0da8b35 --- /dev/null +++ b/src/components/AppSidebar.vue @@ -0,0 +1,67 @@ + + + diff --git a/src/components/ConfigPanel.vue b/src/components/ConfigPanel.vue new file mode 100644 index 0000000..e7bd5d7 --- /dev/null +++ b/src/components/ConfigPanel.vue @@ -0,0 +1,74 @@ + + + diff --git a/src/components/ConnectPanel.vue b/src/components/ConnectPanel.vue new file mode 100644 index 0000000..da12cf9 --- /dev/null +++ b/src/components/ConnectPanel.vue @@ -0,0 +1,206 @@ + + + diff --git a/src/components/ContentPanel.vue b/src/components/ContentPanel.vue new file mode 100644 index 0000000..ed1cdbf --- /dev/null +++ b/src/components/ContentPanel.vue @@ -0,0 +1,108 @@ + + + diff --git a/src/components/LoginView.vue b/src/components/LoginView.vue new file mode 100644 index 0000000..6fd72f6 --- /dev/null +++ b/src/components/LoginView.vue @@ -0,0 +1,58 @@ + + + diff --git a/src/components/PublishPanel.vue b/src/components/PublishPanel.vue new file mode 100644 index 0000000..75f516d --- /dev/null +++ b/src/components/PublishPanel.vue @@ -0,0 +1,29 @@ + + + diff --git a/src/components/StructurePanel.vue b/src/components/StructurePanel.vue new file mode 100644 index 0000000..a823d83 --- /dev/null +++ b/src/components/StructurePanel.vue @@ -0,0 +1,49 @@ + + + diff --git a/src/env.d.ts b/src/env.d.ts new file mode 100644 index 0000000..1f6de9a --- /dev/null +++ b/src/env.d.ts @@ -0,0 +1,6 @@ +declare module "*.vue" { + import type { DefineComponent } from "vue"; + + const component: DefineComponent, Record, unknown>; + export default component; +} diff --git a/src/main.ts b/src/main.ts new file mode 100644 index 0000000..8d6a5ff --- /dev/null +++ b/src/main.ts @@ -0,0 +1,5 @@ +import { createApp } from "vue"; +import App from "./App.vue"; +import "./styles.css"; + +createApp(App).mount("#app"); diff --git a/src/styles.css b/src/styles.css new file mode 100644 index 0000000..b0e0e3f --- /dev/null +++ b/src/styles.css @@ -0,0 +1,757 @@ +:root { + color-scheme: light; + --bg: #eef1f5; + --surface: #ffffff; + --surface-soft: #f7f9fb; + --ink: #18212f; + --muted: #687386; + --line: #dfe5ed; + --accent: #117a72; + --accent-strong: #0b625d; + --accent-soft: #e1f4f1; + --warning: #c07a21; + --radius: 8px; + font-family: + Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", + sans-serif; +} + +* { + box-sizing: border-box; +} + +body { + margin: 0; + min-width: 320px; + min-height: 100vh; + background: var(--bg); + color: var(--ink); +} + +button, +input, +select, +textarea { + font: inherit; +} + +button { + cursor: pointer; +} + +button:disabled { + cursor: not-allowed; + opacity: 0.55; +} + +#app-shell { + display: grid; + grid-template-columns: 280px minmax(0, 1fr); + min-height: 100vh; +} + +.login-screen { + display: grid; + min-height: 100vh; + place-items: center; + padding: 20px; + background: var(--bg); +} + +.login-panel { + display: grid; + width: min(420px, 100%); + gap: 16px; + border: 1px solid var(--line); + border-radius: var(--radius); + background: var(--surface); + padding: 22px; +} + +.login-form { + display: grid; + gap: 12px; +} + +.workspace-sidebar { + display: flex; + flex-direction: column; + gap: 18px; + padding: 22px; + border-right: 1px solid var(--line); + background: #fbfcfd; +} + +.brand-block { + display: flex; + align-items: center; + gap: 12px; +} + +.brand-mark { + display: grid; + width: 44px; + height: 44px; + place-items: center; + border-radius: var(--radius); + background: var(--accent); + color: #fff; + font-weight: 800; +} + +.brand-block h1, +.brand-block p, +.topbar h2, +.topbar p, +.section-head h3, +.section-head p, +.management-card h3, +.management-card p { + margin: 0; +} + +.brand-block h1 { + font-size: 18px; + letter-spacing: 0; +} + +.brand-block p, +.eyebrow { + color: var(--muted); + font-size: 12px; +} + +.eyebrow { + margin-bottom: 4px; + font-weight: 700; + text-transform: uppercase; +} + +.connect-button, +.primary-button, +.secondary-button, +.ghost-button, +.icon-button, +.nav-item, +.page-item, +.sidebar-preview button { + border: 1px solid transparent; + border-radius: var(--radius); +} + +.connect-button { + display: flex; + align-items: center; + justify-content: space-between; + padding: 13px 14px; + background: #171b23; + color: #fff; + text-align: left; +} + +.connect-button span { + color: #bfc7d1; +} + +.main-nav { + display: grid; + gap: 8px; +} + +.nav-item { + display: flex; + align-items: center; + gap: 10px; + width: 100%; + padding: 11px 12px; + background: transparent; + color: var(--muted); + text-align: left; +} + +.nav-item.is-active { + background: var(--accent-soft); + color: var(--accent-strong); + font-weight: 700; +} + +.nav-icon { + display: grid; + width: 24px; + height: 24px; + place-items: center; + border-radius: 6px; + background: #e8edf3; + font-size: 12px; + font-weight: 800; +} + +.repo-card, +.management-card, +.document-tree, +.editor-pane { + border: 1px solid var(--line); + border-radius: var(--radius); + background: var(--surface); +} + +.repo-card { + margin-top: auto; + padding: 16px; +} + +.repo-card h2 { + margin: 0 0 14px; + font-size: 15px; +} + +.repo-card dl { + display: grid; + gap: 10px; + margin: 0; +} + +.repo-card dl div { + display: flex; + justify-content: space-between; + gap: 12px; +} + +.repo-card dt { + color: var(--muted); +} + +.repo-card dd { + margin: 0; + font-weight: 700; +} + +.status-dot::before { + display: inline-block; + width: 8px; + height: 8px; + margin-right: 6px; + border-radius: 999px; + background: #21a36e; + content: ""; +} + +.app-shell { + min-width: 0; + padding: 22px; +} + +.topbar { + display: flex; + align-items: center; + justify-content: space-between; + gap: 16px; + margin-bottom: 18px; +} + +.topbar h2 { + font-size: 28px; + letter-spacing: 0; +} + +.topbar-actions { + display: flex; + gap: 10px; +} + +.primary-button, +.secondary-button, +.ghost-button, +.icon-button { + min-height: 38px; + padding: 0 14px; + font-weight: 700; +} + +.primary-button { + background: var(--accent); + color: #fff; +} + +.secondary-button, +.ghost-button, +.icon-button { + border-color: var(--line); + background: var(--surface); + color: var(--ink); +} + +.panel { + display: none; +} + +.panel.is-visible { + display: block; +} + +.content-layout { + display: grid; + grid-template-columns: 320px minmax(0, 1fr); + gap: 16px; + min-height: calc(100vh - 112px); +} + +.document-tree, +.editor-pane, +.management-card { + padding: 16px; +} + +.section-head { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + margin-bottom: 14px; +} + +.section-head h3, +.management-card h3 { + font-size: 18px; +} + +.search-box input, +label input, +label select, +.sort-row input, +.commit-message { + width: 100%; + border: 1px solid var(--line); + border-radius: var(--radius); + background: var(--surface-soft); + color: var(--ink); + outline: none; +} + +.search-box input, +label input, +label select, +.sort-row input { + height: 40px; + padding: 0 12px; +} + +.page-list { + display: grid; + gap: 8px; + max-height: calc(100vh - 226px); + margin: 14px 0 0; + padding: 0; + overflow: auto; + list-style: none; +} + +.page-item { + display: grid; + gap: 4px; + width: 100%; + padding: 12px; + background: transparent; + text-align: left; +} + +.page-item span { + overflow: hidden; + color: var(--muted); + font-size: 12px; + text-overflow: ellipsis; + white-space: nowrap; +} + +.page-item.is-active { + border-color: #b9d8d4; + background: var(--accent-soft); +} + +.editor-pane { + display: grid; + grid-template-rows: auto auto minmax(0, 1fr); + gap: 14px; + min-width: 0; +} + +.editor-header { + display: flex; + align-items: center; + justify-content: space-between; + gap: 14px; +} + +.editor-actions { + display: flex; + align-items: center; + gap: 10px; +} + +.editor-header h3 { + margin: 0; + font-size: 24px; +} + +.segmented-control { + display: flex; + padding: 3px; + border: 1px solid var(--line); + border-radius: var(--radius); + background: var(--surface-soft); +} + +.segmented-control button { + min-width: 64px; + height: 32px; + border: 0; + border-radius: 6px; + background: transparent; + color: var(--muted); + font-weight: 700; +} + +.segmented-control button.is-active { + background: var(--surface); + color: var(--ink); + box-shadow: 0 1px 4px rgba(23, 33, 46, 0.12); +} + +.frontmatter-grid, +.settings-grid { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 12px; +} + +.settings-toolbar { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + margin-bottom: 12px; +} + +label { + display: grid; + gap: 7px; + color: var(--muted); + font-size: 13px; + font-weight: 700; +} + +.helper-text, +.save-status { + margin: 10px 0 0; + color: var(--muted); + font-size: 13px; +} + +.save-status { + margin: -4px 0 0; +} + +.save-status.is-saved { + color: var(--accent-strong); + font-weight: 700; +} + +.save-status.is-error { + color: #a64038; + font-weight: 700; +} + +.empty-state { + color: var(--muted); +} + +.editor-pane > textarea, +.markdown-preview { + min-height: 460px; + overflow: auto; + border: 1px solid var(--line); + border-radius: var(--radius); + background: var(--surface-soft); +} + +.editor-pane > textarea { + resize: none; + padding: 16px; + line-height: 1.65; +} + +.markdown-preview { + padding: 18px 22px; + line-height: 1.7; +} + +.markdown-preview h1, +.markdown-preview h2, +.markdown-preview h3 { + letter-spacing: 0; +} + +.markdown-preview blockquote { + margin: 12px 0; + padding: 10px 14px; + border-left: 4px solid var(--accent); + background: #edf7f5; + color: #30514e; +} + +.preview-list { + margin: 6px 0; +} + +.two-column, +.publish-layout, +.connect-layout { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 16px; +} + +.sortable-list, +.sidebar-preview, +.settings-grid, +.publish-card, +.form-grid { + display: grid; + gap: 12px; +} + +.form-grid { + grid-template-columns: repeat(2, minmax(0, 1fr)); + margin-top: 14px; +} + +.wide-field { + grid-column: 1 / -1; +} + +.connect-actions { + display: flex; + gap: 10px; + margin-top: 14px; +} + +.github-user { + display: grid; + grid-template-columns: 42px minmax(0, 1fr) auto; + gap: 12px; + align-items: center; + margin-top: 14px; + padding: 12px; + border: 1px solid var(--line); + border-radius: var(--radius); + background: var(--surface-soft); +} + +.github-user img { + width: 42px; + height: 42px; + border-radius: 999px; +} + +.github-user strong, +.github-user span { + display: block; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.github-user span { + color: var(--muted); + font-size: 13px; +} + +.new-repo-box { + margin-top: 18px; + padding-top: 16px; + border-top: 1px solid var(--line); +} + +.source-summary { + display: grid; + gap: 12px; + margin: 14px 0 0; +} + +.source-summary div { + display: flex; + justify-content: space-between; + gap: 12px; + border-bottom: 1px solid var(--line); + padding-bottom: 10px; +} + +.source-summary dt { + color: var(--muted); +} + +.source-summary dd { + margin: 0; + overflow-wrap: anywhere; + font-weight: 700; + text-align: right; +} + +.sort-row { + display: grid; + grid-template-columns: 28px 1fr 1.4fr; + gap: 10px; + align-items: center; +} + +.drag-handle { + color: var(--muted); + font-weight: 800; + text-align: center; +} + +.sidebar-preview { + align-content: start; +} + +.sidebar-preview strong { + margin-top: 6px; +} + +.sidebar-preview button { + min-height: 34px; + border-color: var(--line); + background: var(--surface-soft); + text-align: left; +} + +.settings-grid { + align-items: start; +} + +.wide-card { + grid-column: 1 / -1; +} + +.toggle-row { + display: flex; + align-items: center; + justify-content: space-between; + min-height: 42px; + border-bottom: 1px solid var(--line); +} + +.toggle-row input { + width: 18px; + height: 18px; +} + +.commit-message { + min-height: 140px; + padding: 12px; + resize: vertical; +} + +.activity-list { + display: grid; + gap: 12px; + margin: 0; + padding-left: 0; + list-style: none; +} + +.activity-list li { + display: flex; + align-items: center; + gap: 10px; + color: var(--muted); +} + +.activity-list span { + width: 10px; + height: 10px; + border-radius: 999px; + background: var(--accent); +} + +@media (max-width: 980px) { + #app-shell { + grid-template-columns: 1fr; + } + + .workspace-sidebar { + position: static; + border-right: 0; + border-bottom: 1px solid var(--line); + } + + .main-nav { + grid-template-columns: repeat(4, minmax(0, 1fr)); + } + + .nav-item { + justify-content: center; + } + + .repo-card { + margin-top: 0; + } + + .content-layout, + .two-column, + .publish-layout, + .connect-layout, + .settings-grid, + .frontmatter-grid, + .form-grid { + grid-template-columns: 1fr; + } +} + +@media (max-width: 640px) { + .app-shell, + .workspace-sidebar { + padding: 14px; + } + + .topbar, + .editor-header, + .settings-toolbar { + align-items: stretch; + flex-direction: column; + } + + .editor-actions { + align-items: stretch; + flex-direction: column; + } + + .topbar-actions, + .segmented-control, + .connect-actions { + width: 100%; + } + + .topbar-actions button, + .segmented-control button, + .connect-actions button { + flex: 1; + } + + .main-nav { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } + + .nav-icon { + display: none; + } + + .sort-row { + grid-template-columns: 1fr; + } + + .editor-pane > textarea, + .markdown-preview { + min-height: 360px; + } +} diff --git a/src/types.ts b/src/types.ts new file mode 100644 index 0000000..f524171 --- /dev/null +++ b/src/types.ts @@ -0,0 +1,71 @@ +export type PanelKey = "connect" | "content" | "structure" | "config" | "publish"; +export type EditorMode = "write" | "preview"; +export type DataSource = "local" | "github"; + +export interface DocPage { + id: string; + title: string; + path: string; + description: string; + content: string; + sha?: string; +} + +export interface NavItem { + id: string; + label: string; + link: string; +} + +export interface SidebarGroup { + id: string; + title: string; + items: Array<{ + id: string; + label: string; + link: string; + }>; +} + +export interface SiteSettings { + title: string; + description: string; + logo: string; + lastUpdated: boolean; + localSearch: boolean; + outline: boolean; + socialKind: string; + socialLink: string; +} + +export interface GitHubConnection { + token: string; + owner: string; + repo: string; + branch: string; + siteRoot: string; +} + +export interface GitHubUser { + login: string; + avatarUrl?: string; + name?: string; +} + +export interface CmsUser { + id: number; + email?: string; + name: string; + avatarUrl?: string; + role: "system_admin" | "user" | string; +} + +export interface GitHubRepository { + id: number; + fullName: string; + owner: string; + name: string; + private: boolean; + defaultBranch: string; + updatedAt: string; +} diff --git a/src/utils/markdown.ts b/src/utils/markdown.ts new file mode 100644 index 0000000..cb2f503 --- /dev/null +++ b/src/utils/markdown.ts @@ -0,0 +1,27 @@ +function escapeHtml(value: string) { + return value + .replace(/&/g, "&") + .replace(//g, ">") + .replace(/"/g, """) + .replace(/'/g, "'"); +} + +export function markdownToHtml(markdown: string) { + return markdown + .split("\n") + .map((rawLine) => { + const line = escapeHtml(rawLine); + + if (line.startsWith("### ")) return `

${line.slice(4)}

`; + if (line.startsWith("## ")) return `

${line.slice(3)}

`; + if (line.startsWith("# ")) return `

${line.slice(2)}

`; + if (line.startsWith("- ")) return `

• ${line.slice(2)}

`; + if (/^\d+\.\s/.test(line)) return `

${line}

`; + if (line.startsWith("> ")) return `
${line.slice(5)}
`; + if (!line.trim()) return ""; + + return `

${line}

`; + }) + .join(""); +} diff --git a/src/utils/siteContent.ts b/src/utils/siteContent.ts new file mode 100644 index 0000000..a44a14b --- /dev/null +++ b/src/utils/siteContent.ts @@ -0,0 +1,113 @@ +import type { SiteSettings } from "../types"; + +export function parseFrontmatter(source: string) { + const match = source.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?/); + + if (!match) { + return { + title: "", + description: "", + content: source, + }; + } + + const frontmatter = match[1]; + const title = frontmatter.match(/^title:\s*(.+)$/m)?.[1]?.trim().replace(/^["']|["']$/g, "") ?? ""; + const description = + frontmatter.match(/^description:\s*(.+)$/m)?.[1]?.trim().replace(/^["']|["']$/g, "") ?? ""; + + return { + title, + description, + content: source.slice(match[0].length), + }; +} + +export function stringifyMarkdownPage(page: { + title: string; + description: string; + content: string; +}) { + const frontmatter = [ + "---", + `title: ${page.title || "未命名页面"}`, + `description: ${page.description || ""}`, + "---", + "", + ].join("\n"); + + return `${frontmatter}${page.content.trimStart()}`; +} + +function readStringValue(source: string, key: string, fallback = "") { + const pattern = new RegExp(`${key}:\\s*["'\`]([^"'\`]+)["'\`]`); + return source.match(pattern)?.[1] ?? fallback; +} + +function readBooleanValue(source: string, key: string, fallback = false) { + const pattern = new RegExp(`${key}:\\s*(true|false)`); + const value = source.match(pattern)?.[1]; + return value ? value === "true" : fallback; +} + +export function parseSiteSettings(source: string): SiteSettings { + const socialLinksSource = source.match(/socialLinks:\s*\[([\s\S]*?)\]/)?.[1] ?? ""; + + return { + title: readStringValue(source, "title", "VitePress Site"), + description: readStringValue(source, "description"), + logo: readStringValue(source, "logo", "/logo.svg"), + lastUpdated: readBooleanValue(source, "lastUpdated", true), + localSearch: /search:\s*{[\s\S]*?provider:\s*["']local["'][\s\S]*?}/.test(source), + outline: readBooleanValue(source, "outline", true), + socialKind: readStringValue(socialLinksSource, "icon", "github"), + socialLink: readStringValue(socialLinksSource, "link", "https://github.com/example/vitepress-cms"), + }; +} + +export function stringifySiteConfig(settings: SiteSettings) { + const searchConfig = settings.localSearch + ? ` search: { + provider: "local", + },` + : ""; + + return `import { defineConfig } from "vitepress"; + +export default defineConfig({ + title: ${JSON.stringify(settings.title)}, + description: ${JSON.stringify(settings.description)}, + lastUpdated: ${settings.lastUpdated}, + themeConfig: { + logo: ${JSON.stringify(settings.logo)}, + outline: ${settings.outline}, + nav: [ + { text: "首页", link: "/" }, + { text: "指南", link: "/guide/getting-started" }, + { text: "配置", link: "/guide/config" }, + { text: "更新日志", link: "/changelog" }, + ], + sidebar: { + "/guide/": [ + { + text: "指南", + items: [ + { text: "快速开始", link: "/guide/getting-started" }, + { text: "页面管理", link: "/guide/pages" }, + { text: "配置说明", link: "/guide/config" }, + ], + }, + ], + }, + socialLinks: [ + { icon: ${JSON.stringify(settings.socialKind)}, link: ${JSON.stringify(settings.socialLink)} }, + ], + footer: { + message: "Powered by VitePress-CMS", + copyright: "Copyright 2026", + }, +${searchConfig} + }, +}); +`; +} diff --git a/test-vitepress/.vitepress/config.ts b/test-vitepress/.vitepress/config.ts new file mode 100644 index 0000000..22cb010 --- /dev/null +++ b/test-vitepress/.vitepress/config.ts @@ -0,0 +1,38 @@ +import { defineConfig } from "vitepress"; + +export default defineConfig({ + title: "VitePress CMS Test", + description: "A local VitePress site used by VitePress-CMS for integration tests.", + lastUpdated: true, + themeConfig: { + logo: "/logo.svg", + nav: [ + { text: "首页", link: "/" }, + { text: "指南", link: "/guide/getting-started" }, + { text: "配置", link: "/guide/config" }, + { text: "更新日志", link: "/changelog" }, + ], + sidebar: { + "/guide/": [ + { + text: "指南", + items: [ + { text: "快速开始", link: "/guide/getting-started" }, + { text: "页面管理", link: "/guide/pages" }, + { text: "配置说明", link: "/guide/config" }, + ], + }, + ], + }, + socialLinks: [ + { icon: "github", link: "https://github.com/example/vitepress-cms" }, + ], + footer: { + message: "Powered by VitePress-CMS", + copyright: "Copyright 2026", + }, + search: { + provider: "local", + }, + }, +}); diff --git a/test-vitepress/README.md b/test-vitepress/README.md new file mode 100644 index 0000000..1ac1f3e --- /dev/null +++ b/test-vitepress/README.md @@ -0,0 +1,24 @@ +# VitePress CMS Test Site + +这是一个独立的 VitePress 测试站点,用来模拟真实用户项目。 + +VitePress-CMS 主项目不依赖 VitePress。后续 CMS 会通过文件系统或 GitHub API 读取这个站点的 Markdown 页面和 `.vitepress/config.ts`。 + +## 本地运行 + +```bash +npm install +npm run dev +``` + +默认开发地址: + +```text +http://localhost:5175 +``` + +## 构建 + +```bash +npm run build +``` diff --git a/test-vitepress/changelog.md b/test-vitepress/changelog.md new file mode 100644 index 0000000..6569134 --- /dev/null +++ b/test-vitepress/changelog.md @@ -0,0 +1,12 @@ +--- +title: 更新日志 +description: 测试站点更新记录 +--- + +# 更新日志 + +## v0.1.0 + +- 创建 VitePress 测试站点 +- 添加导航栏、侧边栏和多篇 Markdown 页面 +- 准备给 VitePress-CMS 做本地读取测试 diff --git a/test-vitepress/guide/config.md b/test-vitepress/guide/config.md new file mode 100644 index 0000000..55ba4c5 --- /dev/null +++ b/test-vitepress/guide/config.md @@ -0,0 +1,18 @@ +--- +title: 配置说明 +description: 测试 VitePress 配置解析 +--- + +# 配置说明 + +这个页面用于测试 `.vitepress/config.ts` 的读取与回写。 + +## 第一版优先支持 + +- `title` +- `description` +- `themeConfig.logo` +- `themeConfig.nav` +- `themeConfig.sidebar` +- `themeConfig.socialLinks` +- `themeConfig.search` diff --git a/test-vitepress/guide/getting-started.md b/test-vitepress/guide/getting-started.md new file mode 100644 index 0000000..79c00d3 --- /dev/null +++ b/test-vitepress/guide/getting-started.md @@ -0,0 +1,17 @@ +--- +title: 快速开始 +description: 测试站点的快速开始页面 +--- + +# 快速开始 + +这个页面用于测试 VitePress-CMS 的 Markdown 编辑体验。 + +## 编辑流程 + +1. CMS 读取这个文件。 +2. 用户在后台修改标题、描述和正文。 +3. CMS 将修改保存回 Markdown 文件。 +4. VitePress 重新构建站点。 + +> 这条链路跑通后,项目就从界面原型进入真实可用阶段。 diff --git a/test-vitepress/guide/pages.md b/test-vitepress/guide/pages.md new file mode 100644 index 0000000..c400b02 --- /dev/null +++ b/test-vitepress/guide/pages.md @@ -0,0 +1,20 @@ +--- +title: 页面管理 +description: 测试页面目录、搜索和新增页面 +--- + +# 页面管理 + +这个页面用于测试页面树和文件路径管理。 + +## 需要覆盖的场景 + +- 读取嵌套目录 +- 新建 Markdown 页面 +- 重命名页面 +- 删除页面 +- 移动页面位置 + +## 文件路径 + +当前文件路径是 `test-vitepress/guide/pages.md`。 diff --git a/test-vitepress/index.md b/test-vitepress/index.md new file mode 100644 index 0000000..73f16eb --- /dev/null +++ b/test-vitepress/index.md @@ -0,0 +1,20 @@ +--- +title: 首页 +description: VitePress-CMS 测试站点首页 +--- + +# VitePress CMS Test + +这是一个给 VitePress-CMS 使用的本地测试站点。 + +它用来验证这些能力: + +- 扫描 Markdown 页面 +- 解析 frontmatter +- 读取和修改 `.vitepress/config.ts` +- 管理导航栏和侧边栏 +- 构建真实 VitePress 站点 + +## 测试入口 + +从 [快速开始](/guide/getting-started) 开始查看测试内容。 diff --git a/test-vitepress/package-lock.json b/test-vitepress/package-lock.json new file mode 100644 index 0000000..255fb7c --- /dev/null +++ b/test-vitepress/package-lock.json @@ -0,0 +1,2552 @@ +{ + "name": "vitepress-cms-test-site", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "vitepress-cms-test-site", + "version": "0.1.0", + "devDependencies": { + "vitepress": "^1.6.0" + } + }, + "node_modules/@algolia/abtesting": { + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/@algolia/abtesting/-/abtesting-1.19.0.tgz", + "integrity": "sha512-Lhnez3hhXHk25lfxLAMxvkP4fmN3+1RgADhD2ssMDBYuAsDVReeyP+3SGRx+ntq8ijMrLqUyfvO72TB6jsTteQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.53.0", + "@algolia/requester-browser-xhr": "5.53.0", + "@algolia/requester-fetch": "5.53.0", + "@algolia/requester-node-http": "5.53.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/autocomplete-core": { + "version": "1.17.7", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-core/-/autocomplete-core-1.17.7.tgz", + "integrity": "sha512-BjiPOW6ks90UKl7TwMv7oNQMnzU+t/wk9mgIDi6b1tXpUek7MW0lbNOUHpvam9pe3lVCf4xPFT+lK7s+e+fs7Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/autocomplete-plugin-algolia-insights": "1.17.7", + "@algolia/autocomplete-shared": "1.17.7" + } + }, + "node_modules/@algolia/autocomplete-plugin-algolia-insights": { + "version": "1.17.7", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-plugin-algolia-insights/-/autocomplete-plugin-algolia-insights-1.17.7.tgz", + "integrity": "sha512-Jca5Ude6yUOuyzjnz57og7Et3aXjbwCSDf/8onLHSQgw1qW3ALl9mrMWaXb5FmPVkV3EtkD2F/+NkT6VHyPu9A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/autocomplete-shared": "1.17.7" + }, + "peerDependencies": { + "search-insights": ">= 1 < 3" + } + }, + "node_modules/@algolia/autocomplete-preset-algolia": { + "version": "1.17.7", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-preset-algolia/-/autocomplete-preset-algolia-1.17.7.tgz", + "integrity": "sha512-ggOQ950+nwbWROq2MOCIL71RE0DdQZsceqrg32UqnhDz8FlO9rL8ONHNsI2R1MH0tkgVIDKI/D0sMiUchsFdWA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/autocomplete-shared": "1.17.7" + }, + "peerDependencies": { + "@algolia/client-search": ">= 4.9.1 < 6", + "algoliasearch": ">= 4.9.1 < 6" + } + }, + "node_modules/@algolia/autocomplete-shared": { + "version": "1.17.7", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-shared/-/autocomplete-shared-1.17.7.tgz", + "integrity": "sha512-o/1Vurr42U/qskRSuhBH+VKxMvkkUVTLU6WZQr+L5lGZZLYWyhdzWjW0iGXY7EkwRTjBqvN2EsR81yCTGV/kmg==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@algolia/client-search": ">= 4.9.1 < 6", + "algoliasearch": ">= 4.9.1 < 6" + } + }, + "node_modules/@algolia/client-abtesting": { + "version": "5.53.0", + "resolved": "https://registry.npmjs.org/@algolia/client-abtesting/-/client-abtesting-5.53.0.tgz", + "integrity": "sha512-0ZjA5Hcmaoz5Lj6OG0zhfIyeqzJZnLW2CRJA1W17UwMFGRtZAJ9yJKRvPEDA6gkpsIoQxORTSW6sWFiuYncPNQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.53.0", + "@algolia/requester-browser-xhr": "5.53.0", + "@algolia/requester-fetch": "5.53.0", + "@algolia/requester-node-http": "5.53.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/client-analytics": { + "version": "5.53.0", + "resolved": "https://registry.npmjs.org/@algolia/client-analytics/-/client-analytics-5.53.0.tgz", + "integrity": "sha512-kWNodP75iiEaOtemC9F/hlxNBG5E2QUjN1BusnE6m2b4l7Qh/BUO3fGCVsmKJI65VO4VKGGmT43ICvHtTcJ2JQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.53.0", + "@algolia/requester-browser-xhr": "5.53.0", + "@algolia/requester-fetch": "5.53.0", + "@algolia/requester-node-http": "5.53.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/client-common": { + "version": "5.53.0", + "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-5.53.0.tgz", + "integrity": "sha512-YPN45TXD9Wrse185t/Ta7nktZsqpv97oOjCzp2sblHnCL6rBc9TDeJAg1IGl2UpdwnSD05Zu/5wLB4watOUMyg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/client-insights": { + "version": "5.53.0", + "resolved": "https://registry.npmjs.org/@algolia/client-insights/-/client-insights-5.53.0.tgz", + "integrity": "sha512-qAcYTDJE6m924FDDUQvdD6vh7DYaqOeSpFS74IP37/JRV0v4cGBauyxTF2WzDnokUylQDbqreoFIJZfg0Fitmw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.53.0", + "@algolia/requester-browser-xhr": "5.53.0", + "@algolia/requester-fetch": "5.53.0", + "@algolia/requester-node-http": "5.53.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/client-personalization": { + "version": "5.53.0", + "resolved": "https://registry.npmjs.org/@algolia/client-personalization/-/client-personalization-5.53.0.tgz", + "integrity": "sha512-fQaY+DkSJOpuUVUe8MQTwrdiKAqkJGhpDarB08duBn/sUv7Bkib6MDRQauCcWTWTe4HIW+EbwQP9R4kci1V/Yw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.53.0", + "@algolia/requester-browser-xhr": "5.53.0", + "@algolia/requester-fetch": "5.53.0", + "@algolia/requester-node-http": "5.53.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/client-query-suggestions": { + "version": "5.53.0", + "resolved": "https://registry.npmjs.org/@algolia/client-query-suggestions/-/client-query-suggestions-5.53.0.tgz", + "integrity": "sha512-o72tsiEZGfeS/dxL9IADfzcZWGEwKDEe5CvtrBuT//3JR+SHuTtHRI2ZTf7D7bcKagcbojvO8hnkHdfoakSlYg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.53.0", + "@algolia/requester-browser-xhr": "5.53.0", + "@algolia/requester-fetch": "5.53.0", + "@algolia/requester-node-http": "5.53.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/client-search": { + "version": "5.53.0", + "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-5.53.0.tgz", + "integrity": "sha512-Ds16IyPm/dNJPCU8OzApo2gwGrgWT5BYHhE3NFwZbpCveqyvPDB9sZDDkJ5DsdOGT2aC+R3i0/M1OVXF2qdgPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.53.0", + "@algolia/requester-browser-xhr": "5.53.0", + "@algolia/requester-fetch": "5.53.0", + "@algolia/requester-node-http": "5.53.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/ingestion": { + "version": "1.53.0", + "resolved": "https://registry.npmjs.org/@algolia/ingestion/-/ingestion-1.53.0.tgz", + "integrity": "sha512-oNbT6z4NwD8Pou9VPINGlN/tlG1afESh2EbxqnP6rwl95xKVD/Zlciis1PpNeO/9U/rrajc1+7DcfKi03tX1KQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.53.0", + "@algolia/requester-browser-xhr": "5.53.0", + "@algolia/requester-fetch": "5.53.0", + "@algolia/requester-node-http": "5.53.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/monitoring": { + "version": "1.53.0", + "resolved": "https://registry.npmjs.org/@algolia/monitoring/-/monitoring-1.53.0.tgz", + "integrity": "sha512-G+KZb/yd+qAOFn/cEvTGeLxQm8aP3a0od50l3z/ylccY+/o4YG3TNcjU1tFQHW4mXC137GPyR7W70R0kRQDLnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.53.0", + "@algolia/requester-browser-xhr": "5.53.0", + "@algolia/requester-fetch": "5.53.0", + "@algolia/requester-node-http": "5.53.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/recommend": { + "version": "5.53.0", + "resolved": "https://registry.npmjs.org/@algolia/recommend/-/recommend-5.53.0.tgz", + "integrity": "sha512-6aVfYd55Un6IUgPLbo84WfgFZlS3L0vA1ttzXL5vahHewUJ8jYgd89TzlWRTeej7w70mb9RWsVlFYGmJ/diQww==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.53.0", + "@algolia/requester-browser-xhr": "5.53.0", + "@algolia/requester-fetch": "5.53.0", + "@algolia/requester-node-http": "5.53.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/requester-browser-xhr": { + "version": "5.53.0", + "resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-5.53.0.tgz", + "integrity": "sha512-ke27DqgzCOlt+RbeEdCxtXxMQOnAOi8ujr2wid0DmDKzR95Kw/f9sBsuhBxtjevCqJRJszfRTLY0B1pbO6IhkA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.53.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/requester-fetch": { + "version": "5.53.0", + "resolved": "https://registry.npmjs.org/@algolia/requester-fetch/-/requester-fetch-5.53.0.tgz", + "integrity": "sha512-GngiOqt2Gq4oLno6yXQVj9om+qSO9SWAoduoTOEg79dKZ62brB8OOIvSJG/vDNoanYi6a7Al9uDZwXvi+bcVTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.53.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/requester-node-http": { + "version": "5.53.0", + "resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-5.53.0.tgz", + "integrity": "sha512-6mF9LZMUk0QqWvrnxkxBqhswwz6Xfiwy6/gmTzL5HrlhdVG3ITAqGV2k3XmVThP1h0Ulc3VQwiNCD7/Nr4JNlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.53.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz", + "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.7" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz", + "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@docsearch/css": { + "version": "3.8.2", + "resolved": "https://registry.npmjs.org/@docsearch/css/-/css-3.8.2.tgz", + "integrity": "sha512-y05ayQFyUmCXze79+56v/4HpycYF3uFqB78pLPrSV5ZKAlDuIAAJNhaRi8tTdRNXh05yxX/TyNnzD6LwSM89vQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@docsearch/js": { + "version": "3.8.2", + "resolved": "https://registry.npmjs.org/@docsearch/js/-/js-3.8.2.tgz", + "integrity": "sha512-Q5wY66qHn0SwA7Taa0aDbHiJvaFJLOJyHmooQ7y8hlwwQLQ/5WwCcoX0g7ii04Qi2DJlHsd0XXzJ8Ypw9+9YmQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@docsearch/react": "3.8.2", + "preact": "^10.0.0" + } + }, + "node_modules/@docsearch/react": { + "version": "3.8.2", + "resolved": "https://registry.npmjs.org/@docsearch/react/-/react-3.8.2.tgz", + "integrity": "sha512-xCRrJQlTt8N9GU0DG4ptwHRkfnSnD/YpdeaXe02iKfqs97TkZJv60yE+1eq/tjPcVnTW8dP5qLP7itifFVV5eg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/autocomplete-core": "1.17.7", + "@algolia/autocomplete-preset-algolia": "1.17.7", + "@docsearch/css": "3.8.2", + "algoliasearch": "^5.14.2" + }, + "peerDependencies": { + "@types/react": ">= 16.8.0 < 19.0.0", + "react": ">= 16.8.0 < 19.0.0", + "react-dom": ">= 16.8.0 < 19.0.0", + "search-insights": ">= 1 < 3" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + }, + "search-insights": { + "optional": true + } + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@iconify-json/simple-icons": { + "version": "1.2.85", + "resolved": "https://registry.npmjs.org/@iconify-json/simple-icons/-/simple-icons-1.2.85.tgz", + "integrity": "sha512-Hp5LXvd3LRk+e+1558wtonA7c1Z0/Phmi7xCqpgtb8bs8cuyGnP34GDbt5uhhUXxKlzacnnhAcXgcDxe9bUa1w==", + "dev": true, + "license": "CC0-1.0", + "dependencies": { + "@iconify/types": "*" + } + }, + "node_modules/@iconify/types": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@iconify/types/-/types-2.0.0.tgz", + "integrity": "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.61.1.tgz", + "integrity": "sha512-JnBB8MdXj45cajvTuO5FmPlvFVJRQgvrz1uSEl3NwqFnReAPGwb8EanbGi4z2nRaqLzjJSv5/JmycoTKlRZxHA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.61.1.tgz", + "integrity": "sha512-Jx2g7iSjw4AOT0HDPHM9RV3GNjRXwybWtSFZiZAYUTjUwjVrYIwq3kBf+LnhqJlzXFAqTAh2F7IGI+O568exPw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.61.1.tgz", + "integrity": "sha512-0F1L/Z3Eqv8mT2n3dCpeO8GcTvHvVqkP5/t6DMsn0KzhYVcg+s7Ncl5DS8qjKYEeio6Az0Gt6nyBORay5qIlCA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.61.1.tgz", + "integrity": "sha512-qLttcH871ujY4YcVfUSShhOw+CsoTatYz8gRbHO7Bb92QH059/P0y5do1KMs41fY0BpD2x4AJH/gID0zFiqVKQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.61.1.tgz", + "integrity": "sha512-fUI4RapGE0Oh3mb8mgfvC1O2nU1RpDZUKnDQm3xB1Ipg7C2wTs5Kstz7G2uWK99a8S2yTMq8/P4uycwNa0nJyw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.61.1.tgz", + "integrity": "sha512-H5YrdvJaDtI/U9/emrD4b++xkvp3y/JvOe4rizHbxvkyMfRS/CiRYdji+Pl8D0brEaNFWUh1drQxgAGIl6Xudw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.61.1.tgz", + "integrity": "sha512-Q8CBCCQtDFrYtXoeUXSrnFXKOnyUhx6bz+SkL6A0E7V8kAiCJ5pamq1WtbfpVGhR5TSpXY6ak3avmDc5fHTyJA==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.61.1.tgz", + "integrity": "sha512-nwnhk1581l0FBVellGcVCAT0Oi06onEA3WB53sf01VO3I0UPBkMH9sXONYME2K0ovXcNayJfNtHfm6mpJElatQ==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.61.1.tgz", + "integrity": "sha512-x5Xr49hwt3hdW75UOZm3395YwwzPyauktslv29KpWL/T+vVAzoT3azLcTWv0eMciBNrx+DYjH4paehHoLpPvpg==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.61.1.tgz", + "integrity": "sha512-unMS3H73DpaoPyyEVPjGKleM/s0mkmsauTENpw4INQY8y4+IuLNjkueQ5QCtC0D3N38Y38yhAU8OoZ20S2Tm6w==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.61.1.tgz", + "integrity": "sha512-zNZzGRnAhwjFEYmvphJRV5XaQGjs62cCmeYYHUT//NbvEnHauw+I85nGG+SiVg5ld4GX8D1IbKIX+ozITQnhMQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.61.1.tgz", + "integrity": "sha512-LdpWGL8X209B2SIvWjqlc8VZgM6PKfontSerGepuldQmHYrAOtnMCXeJkxXGbC+PPZVOuu5czJo7fNV6aeW8rQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.61.1.tgz", + "integrity": "sha512-EC5kTtNaNGOmbMGqar8dvJy6y/hg99GAwjfBz++pxZhQATXGcRjd6c5en5wcbru0vkRmiMGsQKdMJOOf6sza4g==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.61.1.tgz", + "integrity": "sha512-8hiwp6D4acEcNK78I4rP0/XtS1sknWIAMJBPdR4l6zUtyTm5KiTDr5bXmWt4foY7nAN7AThDHgkLIEZOWKbzWw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.61.1.tgz", + "integrity": "sha512-10dh/h/BqA7DuMPWSxkR8uks18FRwnwOEqr5zOTEl+NOwP/OMzKX8OFR/Of9xxDA7D5qef1Nzar5WDD2kCCr1g==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.61.1.tgz", + "integrity": "sha512-YKJ5lg35DP17gcAOggnihe+APw9HLyj1Xn7gsmGumBJAUDa6NGXNixJzmkWLhcK9TOuuyQjdamzvJefkO7qHZQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.61.1.tgz", + "integrity": "sha512-Mlil5G2Jj6a7B3LWGctg+XPL9vdXYuzCtNXfxOQ0nPjc2m6ueUktocPGH9bnAM0bNRKb/bAWTujUU7IJQdQA+g==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.61.1.tgz", + "integrity": "sha512-bVWIOIk6pV01p4CdUbPP7CJ/434z+OooYjDuFcR+44N35YvKUC66G8MGnvcWx5mWKW3g61J+t74l3Kj15Kwn2Q==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.61.1.tgz", + "integrity": "sha512-qy5pBvZbqNFheBz61R1rzsezjm0J7O2oNGoWtGoY89SZYLUfxAJTBAqDChqAIdB4rCiIbi9nF7yZ83GnNiLwSw==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.61.1.tgz", + "integrity": "sha512-E83TXjI4zm0+5f2qO+UOudaCYIhYwpJ5jq6YCZNIZ+6CbfhKrkAGezeiASBL9ElxAxFsRS9ZhESv8mfnj6TKeg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.61.1.tgz", + "integrity": "sha512-fbWnKqVkjrJN38vNe3ahkbk6iejS/3b0Nt7EEtPpE6RBacZcGXNKbzfHN3GUUlXOPghUg0j6XUGrtjX9z1sIvA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.61.1.tgz", + "integrity": "sha512-ArMl38iVAbk0New1ogihQNY6iphLi4ZaRsa037gUzv5yeKPY8TD3Dmy4x2RNC1VztU/uqm+G+/RwFrSka3Oy2g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.61.1.tgz", + "integrity": "sha512-0mYtjHS9ucAbcATycCNK9IGBk/cCe/ma7EmSLGZdsxnOA8cjRIyU04wDpVAD9NiOfLUR9KTxdiO53uOkherqjQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.61.1.tgz", + "integrity": "sha512-gK1iCEPfpoSG9wfBihXxvBMi8ZfcWffYkEsC/Eih+iFENTaewvNcrEQ69lIOWYO5pePHKLHHO7nq5AILGO/HQQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.61.1.tgz", + "integrity": "sha512-X+zaP2x+j4RXGfbp/seSoRHWnPxzApilDszisZxbYH5C/jTxFhCtDNdPGZb9lJyYPs24wGxruPF7Y+sIXt9Gzw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@shikijs/core": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-2.5.0.tgz", + "integrity": "sha512-uu/8RExTKtavlpH7XqnVYBrfBkUc20ngXiX9NSrBhOVZYv/7XQRKUyhtkeflY5QsxC0GbJThCerruZfsUaSldg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/engine-javascript": "2.5.0", + "@shikijs/engine-oniguruma": "2.5.0", + "@shikijs/types": "2.5.0", + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4", + "hast-util-to-html": "^9.0.4" + } + }, + "node_modules/@shikijs/engine-javascript": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-2.5.0.tgz", + "integrity": "sha512-VjnOpnQf8WuCEZtNUdjjwGUbtAVKuZkVQ/5cHy/tojVVRIRtlWMYVjyWhxOmIq05AlSOv72z7hRNRGVBgQOl0w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/types": "2.5.0", + "@shikijs/vscode-textmate": "^10.0.2", + "oniguruma-to-es": "^3.1.0" + } + }, + "node_modules/@shikijs/engine-oniguruma": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-2.5.0.tgz", + "integrity": "sha512-pGd1wRATzbo/uatrCIILlAdFVKdxImWJGQ5rFiB5VZi2ve5xj3Ax9jny8QvkaV93btQEwR/rSz5ERFpC5mKNIw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/types": "2.5.0", + "@shikijs/vscode-textmate": "^10.0.2" + } + }, + "node_modules/@shikijs/langs": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-2.5.0.tgz", + "integrity": "sha512-Qfrrt5OsNH5R+5tJ/3uYBBZv3SuGmnRPejV9IlIbFH3HTGLDlkqgHymAlzklVmKBjAaVmkPkyikAV/sQ1wSL+w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/types": "2.5.0" + } + }, + "node_modules/@shikijs/themes": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-2.5.0.tgz", + "integrity": "sha512-wGrk+R8tJnO0VMzmUExHR+QdSaPUl/NKs+a4cQQRWyoc3YFbUzuLEi/KWK1hj+8BfHRKm2jNhhJck1dfstJpiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/types": "2.5.0" + } + }, + "node_modules/@shikijs/transformers": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@shikijs/transformers/-/transformers-2.5.0.tgz", + "integrity": "sha512-SI494W5X60CaUwgi8u4q4m4s3YAFSxln3tzNjOSYqq54wlVgz0/NbbXEb3mdLbqMBztcmS7bVTaEd2w0qMmfeg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/core": "2.5.0", + "@shikijs/types": "2.5.0" + } + }, + "node_modules/@shikijs/types": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-2.5.0.tgz", + "integrity": "sha512-ygl5yhxki9ZLNuNpPitBWvcy9fsSKKaRuO4BAlMyagszQidxcpLAr0qiW/q43DtSIDxO6hEbtYLiFZNXO/hdGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4" + } + }, + "node_modules/@shikijs/vscode-textmate": { + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/@shikijs/vscode-textmate/-/vscode-textmate-10.0.2.tgz", + "integrity": "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/linkify-it": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/markdown-it": { + "version": "14.1.2", + "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-14.1.2.tgz", + "integrity": "sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/linkify-it": "^5", + "@types/mdurl": "^2" + } + }, + "node_modules/@types/mdast": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/mdurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/web-bluetooth": { + "version": "0.0.21", + "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.21.tgz", + "integrity": "sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@ungap/structured-clone": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.1.tgz", + "integrity": "sha512-mUFwbeTqrVgDQxFveS+df2yfap6iuP20NAKAsBt5jDEoOTDew+zwLAOilHCeQJOVSvmgCX4ogqIrA0mnyr08yQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/@vitejs/plugin-vue": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.2.4.tgz", + "integrity": "sha512-7Yx/SXSOcQq5HiiV3orevHUFn+pmMB4cgbEkDYgnkUWb0WfeQ/wa2yFv6D5ICiCQOVpjA7vYDXrC7AGO8yjDHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "peerDependencies": { + "vite": "^5.0.0 || ^6.0.0", + "vue": "^3.2.25" + } + }, + "node_modules/@vue/compiler-core": { + "version": "3.5.35", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.35.tgz", + "integrity": "sha512-BUmHaR1J+O+CKZ9uJucdVTEr1LHsdyvv7vG3eNRhK3CczEHeMd/LtsHAuD7PbrxvI2envCY2v7HI1vC1aBRzKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.3", + "@vue/shared": "3.5.35", + "entities": "^7.0.1", + "estree-walker": "^2.0.2", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-dom": { + "version": "3.5.35", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.35.tgz", + "integrity": "sha512-k+bprkXxuqhVajgTx5mUHuir7TwQzUKOWR40ng1ncAqQRPnrLngGGgqVEEhOnTMlc8btHYVKmrP8s5Qyg0hvYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/compiler-core": "3.5.35", + "@vue/shared": "3.5.35" + } + }, + "node_modules/@vue/compiler-sfc": { + "version": "3.5.35", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.35.tgz", + "integrity": "sha512-G5VPMcXTSywXBgtFOZOnHKBxKSrwXUcvY1iaF5/hRcy7t0J6CH/d8ha9F4nzi00Fax1eLV0QHM7v4mQu68jydw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.3", + "@vue/compiler-core": "3.5.35", + "@vue/compiler-dom": "3.5.35", + "@vue/compiler-ssr": "3.5.35", + "@vue/shared": "3.5.35", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.21", + "postcss": "^8.5.15", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-ssr": { + "version": "3.5.35", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.35.tgz", + "integrity": "sha512-rGhAeXgdM7/ffTJGXT69rCCdTmjDewnFuUZfBQQHTdcEBeWdT5HCGY60y2ytLJr9/Dsu7IntUi5z/w0h6Rjnzw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.35", + "@vue/shared": "3.5.35" + } + }, + "node_modules/@vue/devtools-api": { + "version": "7.7.9", + "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-7.7.9.tgz", + "integrity": "sha512-kIE8wvwlcZ6TJTbNeU2HQNtaxLx3a84aotTITUuL/4bzfPxzajGBOoqjMhwZJ8L9qFYDU/lAYMEEm11dnZOD6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/devtools-kit": "^7.7.9" + } + }, + "node_modules/@vue/devtools-kit": { + "version": "7.7.9", + "resolved": "https://registry.npmjs.org/@vue/devtools-kit/-/devtools-kit-7.7.9.tgz", + "integrity": "sha512-PyQ6odHSgiDVd4hnTP+aDk2X4gl2HmLDfiyEnn3/oV+ckFDuswRs4IbBT7vacMuGdwY/XemxBoh302ctbsptuA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/devtools-shared": "^7.7.9", + "birpc": "^2.3.0", + "hookable": "^5.5.3", + "mitt": "^3.0.1", + "perfect-debounce": "^1.0.0", + "speakingurl": "^14.0.1", + "superjson": "^2.2.2" + } + }, + "node_modules/@vue/devtools-shared": { + "version": "7.7.9", + "resolved": "https://registry.npmjs.org/@vue/devtools-shared/-/devtools-shared-7.7.9.tgz", + "integrity": "sha512-iWAb0v2WYf0QWmxCGy0seZNDPdO3Sp5+u78ORnyeonS6MT4PC7VPrryX2BpMJrwlDeaZ6BD4vP4XKjK0SZqaeA==", + "dev": true, + "license": "MIT", + "dependencies": { + "rfdc": "^1.4.1" + } + }, + "node_modules/@vue/reactivity": { + "version": "3.5.35", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.35.tgz", + "integrity": "sha512-tVc+SsHConvh/Lz64qq1pP3rYArBmK42xonovEcxY74SQtvctZodG/zhq54P5dr38cVuw25d27cPNRdlMidpGQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/shared": "3.5.35" + } + }, + "node_modules/@vue/runtime-core": { + "version": "3.5.35", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.35.tgz", + "integrity": "sha512-A/xFNX9loIcWDygeQuNCfKuh0CoYBzxhqEMNah5TSFg9Z53DrFYEN2qi5CU9necjM1OWYegYREUTHmXTmhfXtg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.35", + "@vue/shared": "3.5.35" + } + }, + "node_modules/@vue/runtime-dom": { + "version": "3.5.35", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.35.tgz", + "integrity": "sha512-odrJ1C391dbGnyDRh8U+rnP7J2amIEzfmRk5vXy7xi3aZhEXofTvpi0T4HJb6jlNqQZTNPR5MPHSB3RHNkIORA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.35", + "@vue/runtime-core": "3.5.35", + "@vue/shared": "3.5.35", + "csstype": "^3.2.3" + } + }, + "node_modules/@vue/server-renderer": { + "version": "3.5.35", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.35.tgz", + "integrity": "sha512-NkebSOYdB97wi8OQcO3HqzZSlymJi/aWsN/7h74OSVhRTm6qGs3Jp3e0rCXynmWwSlKeRrnlIug+ilYoHBmQDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/compiler-ssr": "3.5.35", + "@vue/shared": "3.5.35" + }, + "peerDependencies": { + "vue": "3.5.35" + } + }, + "node_modules/@vue/shared": { + "version": "3.5.35", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.35.tgz", + "integrity": "sha512-zSbjL7gRXwks2ZQLRGCajBtBXEOXW9Ddhn/HvSdrGkE2dqGnumzW8XtusRrxrE9LvqtiqDXQ+A60Hp6mvdYxfA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@vueuse/core": { + "version": "12.8.2", + "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-12.8.2.tgz", + "integrity": "sha512-HbvCmZdzAu3VGi/pWYm5Ut+Kd9mn1ZHnn4L5G8kOQTPs/IwIAmJoBrmYk2ckLArgMXZj0AW3n5CAejLUO+PhdQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/web-bluetooth": "^0.0.21", + "@vueuse/metadata": "12.8.2", + "@vueuse/shared": "12.8.2", + "vue": "^3.5.13" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/integrations": { + "version": "12.8.2", + "resolved": "https://registry.npmjs.org/@vueuse/integrations/-/integrations-12.8.2.tgz", + "integrity": "sha512-fbGYivgK5uBTRt7p5F3zy6VrETlV9RtZjBqd1/HxGdjdckBgBM4ugP8LHpjolqTj14TXTxSK1ZfgPbHYyGuH7g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vueuse/core": "12.8.2", + "@vueuse/shared": "12.8.2", + "vue": "^3.5.13" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "async-validator": "^4", + "axios": "^1", + "change-case": "^5", + "drauu": "^0.4", + "focus-trap": "^7", + "fuse.js": "^7", + "idb-keyval": "^6", + "jwt-decode": "^4", + "nprogress": "^0.2", + "qrcode": "^1.5", + "sortablejs": "^1", + "universal-cookie": "^7" + }, + "peerDependenciesMeta": { + "async-validator": { + "optional": true + }, + "axios": { + "optional": true + }, + "change-case": { + "optional": true + }, + "drauu": { + "optional": true + }, + "focus-trap": { + "optional": true + }, + "fuse.js": { + "optional": true + }, + "idb-keyval": { + "optional": true + }, + "jwt-decode": { + "optional": true + }, + "nprogress": { + "optional": true + }, + "qrcode": { + "optional": true + }, + "sortablejs": { + "optional": true + }, + "universal-cookie": { + "optional": true + } + } + }, + "node_modules/@vueuse/metadata": { + "version": "12.8.2", + "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-12.8.2.tgz", + "integrity": "sha512-rAyLGEuoBJ/Il5AmFHiziCPdQzRt88VxR+Y/A/QhJ1EWtWqPBBAxTAFaSkviwEuOEZNtW8pvkPgoCZQ+HxqW1A==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/shared": { + "version": "12.8.2", + "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-12.8.2.tgz", + "integrity": "sha512-dznP38YzxZoNloI0qpEfpkms8knDtaoQ6Y/sfS0L7Yki4zh40LFHEhur0odJC6xTHG5dxWVPiUWBXn+wCG2s5w==", + "dev": true, + "license": "MIT", + "dependencies": { + "vue": "^3.5.13" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/algoliasearch": { + "version": "5.53.0", + "resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-5.53.0.tgz", + "integrity": "sha512-OGW1q6b91CRSSeiOnM8LxuR5NYJ2esvw66jUZ4IIvdv+ItNkx3pwLuyR+jaCdbGee4ov5WgUnyPryyh11xvByQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/abtesting": "1.19.0", + "@algolia/client-abtesting": "5.53.0", + "@algolia/client-analytics": "5.53.0", + "@algolia/client-common": "5.53.0", + "@algolia/client-insights": "5.53.0", + "@algolia/client-personalization": "5.53.0", + "@algolia/client-query-suggestions": "5.53.0", + "@algolia/client-search": "5.53.0", + "@algolia/ingestion": "1.53.0", + "@algolia/monitoring": "1.53.0", + "@algolia/recommend": "5.53.0", + "@algolia/requester-browser-xhr": "5.53.0", + "@algolia/requester-fetch": "5.53.0", + "@algolia/requester-node-http": "5.53.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/birpc": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/birpc/-/birpc-2.9.0.tgz", + "integrity": "sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/ccount": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", + "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-html4": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", + "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-legacy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", + "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/comma-separated-tokens": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", + "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/copy-anything": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-4.0.5.tgz", + "integrity": "sha512-7Vv6asjS4gMOuILabD3l739tsaxFQmC+a7pLZm02zyvs8p977bL3zEgq3yDk5rn9B0PbYgIv++jmHcuUab4RhA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-what": "^5.2.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/devlop": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", + "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", + "dev": true, + "license": "MIT", + "dependencies": { + "dequal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/emoji-regex-xs": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex-xs/-/emoji-regex-xs-1.0.0.tgz", + "integrity": "sha512-LRlerrMYoIDrT6jgpeZ2YYl/L8EulRTt5hQcYjy5AInh7HWXKimpqx68aknBFpGL2+/IcogTcaydJEgaTmOpDg==", + "dev": true, + "license": "MIT" + }, + "node_modules/entities": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz", + "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "dev": true, + "license": "MIT" + }, + "node_modules/focus-trap": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/focus-trap/-/focus-trap-7.8.0.tgz", + "integrity": "sha512-/yNdlIkpWbM0ptxno3ONTuf+2g318kh2ez3KSeZN5dZ8YC6AAmgeWz+GasYYiBJPFaYcSAPeu4GfhUaChzIJXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "tabbable": "^6.4.0" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/hast-util-to-html": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-9.0.5.tgz", + "integrity": "sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-whitespace": "^3.0.0", + "html-void-elements": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "stringify-entities": "^4.0.0", + "zwitch": "^2.0.4" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-whitespace": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", + "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hookable": { + "version": "5.5.3", + "resolved": "https://registry.npmjs.org/hookable/-/hookable-5.5.3.tgz", + "integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/html-void-elements": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz", + "integrity": "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-what": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/is-what/-/is-what-5.5.0.tgz", + "integrity": "sha512-oG7cgbmg5kLYae2N5IVd3jm2s+vldjxJzK1pcu9LfpGuQ93MQSzo0okvRna+7y5ifrD+20FE8FvjusyGaz14fw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/mark.js": { + "version": "8.11.1", + "resolved": "https://registry.npmjs.org/mark.js/-/mark.js-8.11.1.tgz", + "integrity": "sha512-1I+1qpDt4idfgLQG+BNWmrqku+7/2bi5nLf4YwF8y8zXvmfiTBY3PV3ZibfrjBueCByROpuBjLLFCajqkgYoLQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/mdast-util-to-hast": { + "version": "13.2.1", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.1.tgz", + "integrity": "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@ungap/structured-clone": "^1.0.0", + "devlop": "^1.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "trim-lines": "^3.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-encode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", + "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-sanitize-uri": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", + "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-types": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz", + "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/minisearch": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/minisearch/-/minisearch-7.2.0.tgz", + "integrity": "sha512-dqT2XBYUOZOiC5t2HRnwADjhNS2cecp9u+TJRiJ1Qp/f5qjkeT5APcGPjHw+bz89Ms8Jp+cG4AlE+QZ/QnDglg==", + "dev": true, + "license": "MIT" + }, + "node_modules/mitt": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", + "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.12", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", + "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/oniguruma-to-es": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/oniguruma-to-es/-/oniguruma-to-es-3.1.1.tgz", + "integrity": "sha512-bUH8SDvPkH3ho3dvwJwfonjlQ4R80vjyvrU8YpxuROddv55vAEJrTuCuCVUhhsHbtlD9tGGbaNApGQckXhS8iQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex-xs": "^1.0.0", + "regex": "^6.0.1", + "regex-recursion": "^6.0.2" + } + }, + "node_modules/perfect-debounce": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz", + "integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/postcss": { + "version": "8.5.15", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", + "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.12", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/preact": { + "version": "10.29.2", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.29.2.tgz", + "integrity": "sha512-7tNmwg/7mzzAoB/8kSg6Hl37JraAZw3Z3A0JSY7VXlZwo82Xn0G7wKbNNs2qoF4ZEEsQGTwDAroNdqKs1ofJxQ==", + "dev": true, + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/preact" + } + }, + "node_modules/property-information": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.2.0.tgz", + "integrity": "sha512-IAtzIB6sUiWaJYrX9smp3V46pBGbBeLFRGdh25kg1334VcBlD8HzhPeNIWQH9zhGmo2itIe25EHt9dQP7G5hmg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/regex/-/regex-6.1.0.tgz", + "integrity": "sha512-6VwtthbV4o/7+OaAF9I5L5V3llLEsoPyq9P1JVXkedTP33c7MfCG0/5NOPcSJn0TzXcG9YUrR0gQSWioew3LDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "regex-utilities": "^2.3.0" + } + }, + "node_modules/regex-recursion": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/regex-recursion/-/regex-recursion-6.0.2.tgz", + "integrity": "sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==", + "dev": true, + "license": "MIT", + "dependencies": { + "regex-utilities": "^2.3.0" + } + }, + "node_modules/regex-utilities": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/regex-utilities/-/regex-utilities-2.3.0.tgz", + "integrity": "sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==", + "dev": true, + "license": "MIT" + }, + "node_modules/rfdc": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", + "dev": true, + "license": "MIT" + }, + "node_modules/rollup": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.61.1.tgz", + "integrity": "sha512-I4KW6iuRpuu2uHBLraZ1wNZe0DP7lnRha+VJ9tNaYVaVgKhW0aI3h4RYnoRPeql0flHm/Co55b7snEDcOfOJrA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.9" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.61.1", + "@rollup/rollup-android-arm64": "4.61.1", + "@rollup/rollup-darwin-arm64": "4.61.1", + "@rollup/rollup-darwin-x64": "4.61.1", + "@rollup/rollup-freebsd-arm64": "4.61.1", + "@rollup/rollup-freebsd-x64": "4.61.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.61.1", + "@rollup/rollup-linux-arm-musleabihf": "4.61.1", + "@rollup/rollup-linux-arm64-gnu": "4.61.1", + "@rollup/rollup-linux-arm64-musl": "4.61.1", + "@rollup/rollup-linux-loong64-gnu": "4.61.1", + "@rollup/rollup-linux-loong64-musl": "4.61.1", + "@rollup/rollup-linux-ppc64-gnu": "4.61.1", + "@rollup/rollup-linux-ppc64-musl": "4.61.1", + "@rollup/rollup-linux-riscv64-gnu": "4.61.1", + "@rollup/rollup-linux-riscv64-musl": "4.61.1", + "@rollup/rollup-linux-s390x-gnu": "4.61.1", + "@rollup/rollup-linux-x64-gnu": "4.61.1", + "@rollup/rollup-linux-x64-musl": "4.61.1", + "@rollup/rollup-openbsd-x64": "4.61.1", + "@rollup/rollup-openharmony-arm64": "4.61.1", + "@rollup/rollup-win32-arm64-msvc": "4.61.1", + "@rollup/rollup-win32-ia32-msvc": "4.61.1", + "@rollup/rollup-win32-x64-gnu": "4.61.1", + "@rollup/rollup-win32-x64-msvc": "4.61.1", + "fsevents": "~2.3.2" + } + }, + "node_modules/search-insights": { + "version": "2.17.3", + "resolved": "https://registry.npmjs.org/search-insights/-/search-insights-2.17.3.tgz", + "integrity": "sha512-RQPdCYTa8A68uM2jwxoY842xDhvx3E5LFL1LxvxCNMev4o5mLuokczhzjAgGwUZBAmOKZknArSxLKmXtIi2AxQ==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/shiki": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/shiki/-/shiki-2.5.0.tgz", + "integrity": "sha512-mI//trrsaiCIPsja5CNfsyNOqgAZUb6VpJA+340toL42UpzQlXpwRV9nch69X6gaUxrr9kaOOa6e3y3uAkGFxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/core": "2.5.0", + "@shikijs/engine-javascript": "2.5.0", + "@shikijs/engine-oniguruma": "2.5.0", + "@shikijs/langs": "2.5.0", + "@shikijs/themes": "2.5.0", + "@shikijs/types": "2.5.0", + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/space-separated-tokens": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", + "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/speakingurl": { + "version": "14.0.1", + "resolved": "https://registry.npmjs.org/speakingurl/-/speakingurl-14.0.1.tgz", + "integrity": "sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stringify-entities": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", + "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", + "dev": true, + "license": "MIT", + "dependencies": { + "character-entities-html4": "^2.0.0", + "character-entities-legacy": "^3.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/superjson": { + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/superjson/-/superjson-2.2.6.tgz", + "integrity": "sha512-H+ue8Zo4vJmV2nRjpx86P35lzwDT3nItnIsocgumgr0hHMQ+ZGq5vrERg9kJBo5AWGmxZDhzDo+WVIJqkB0cGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "copy-anything": "^4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/tabbable": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.4.0.tgz", + "integrity": "sha512-05PUHKSNE8ou2dwIxTngl4EzcnsCDZGJ/iCLtDflR/SHB/ny14rXc+qU5P4mG9JkusiV7EivzY9Mhm55AzAvCg==", + "dev": true, + "license": "MIT" + }, + "node_modules/trim-lines": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", + "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/unist-util-is": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.1.tgz", + "integrity": "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-position": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz", + "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.1.0.tgz", + "integrity": "sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit-parents": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.2.tgz", + "integrity": "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", + "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-message": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.3.tgz", + "integrity": "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vite": { + "version": "5.4.21", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", + "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/vitepress": { + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/vitepress/-/vitepress-1.6.4.tgz", + "integrity": "sha512-+2ym1/+0VVrbhNyRoFFesVvBvHAVMZMK0rw60E3X/5349M1GuVdKeazuksqopEdvkKwKGs21Q729jX81/bkBJg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@docsearch/css": "3.8.2", + "@docsearch/js": "3.8.2", + "@iconify-json/simple-icons": "^1.2.21", + "@shikijs/core": "^2.1.0", + "@shikijs/transformers": "^2.1.0", + "@shikijs/types": "^2.1.0", + "@types/markdown-it": "^14.1.2", + "@vitejs/plugin-vue": "^5.2.1", + "@vue/devtools-api": "^7.7.0", + "@vue/shared": "^3.5.13", + "@vueuse/core": "^12.4.0", + "@vueuse/integrations": "^12.4.0", + "focus-trap": "^7.6.4", + "mark.js": "8.11.1", + "minisearch": "^7.1.1", + "shiki": "^2.1.0", + "vite": "^5.4.14", + "vue": "^3.5.13" + }, + "bin": { + "vitepress": "bin/vitepress.js" + }, + "peerDependencies": { + "markdown-it-mathjax3": "^4", + "postcss": "^8" + }, + "peerDependenciesMeta": { + "markdown-it-mathjax3": { + "optional": true + }, + "postcss": { + "optional": true + } + } + }, + "node_modules/vue": { + "version": "3.5.35", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.35.tgz", + "integrity": "sha512-cx89fnr+0kVGHiNFG6y6s0bdjypJRFNZn6x3WPstNdQR1bi1mbB7h4v5IBGTsPJU3nK1+0Iqj3Zf+hZWMieR4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.35", + "@vue/compiler-sfc": "3.5.35", + "@vue/runtime-dom": "3.5.35", + "@vue/server-renderer": "3.5.35", + "@vue/shared": "3.5.35" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/zwitch": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", + "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + } + } +} diff --git a/test-vitepress/package.json b/test-vitepress/package.json new file mode 100644 index 0000000..8e739d6 --- /dev/null +++ b/test-vitepress/package.json @@ -0,0 +1,15 @@ +{ + "name": "vitepress-cms-test-site", + "version": "0.1.0", + "private": true, + "type": "module", + "description": "A standalone VitePress site used to test VitePress-CMS integrations.", + "scripts": { + "dev": "vitepress dev . --host 0.0.0.0 --port 5175", + "build": "vitepress build .", + "preview": "vitepress preview . --host 0.0.0.0 --port 5176" + }, + "devDependencies": { + "vitepress": "^1.6.0" + } +} diff --git a/test-vitepress/public/logo.svg b/test-vitepress/public/logo.svg new file mode 100644 index 0000000..fa2b2ec --- /dev/null +++ b/test-vitepress/public/logo.svg @@ -0,0 +1,4 @@ + + + + diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..415d281 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "module": "ESNext", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "skipLibCheck": true, + "moduleResolution": "Bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "preserve", + "strict": true + }, + "include": ["src/**/*.ts", "src/**/*.vue"] +} diff --git a/vite.config.ts b/vite.config.ts new file mode 100644 index 0000000..3abb7c1 --- /dev/null +++ b/vite.config.ts @@ -0,0 +1,283 @@ +import { createReadStream, promises as fs } from "node:fs"; +import { extname, join, normalize, relative, sep } from "node:path"; +import type { IncomingMessage, ServerResponse } from "node:http"; +import { defineConfig, type Plugin } from "vite"; +import vue from "@vitejs/plugin-vue"; + +const siteRoot = join(process.cwd(), "test-vitepress"); + +interface LocalPagePayload { + path: string; + title: string; + description: string; + content: string; +} + +interface LocalSiteSettingsPayload { + title: string; + description: string; + logo: string; + lastUpdated: boolean; + localSearch: boolean; + outline: boolean; + socialKind: string; + socialLink: string; +} + +const configPath = join(siteRoot, ".vitepress", "config.ts"); + +function isMarkdownPath(filePath: string) { + return filePath.endsWith(".md") && !filePath.includes(`${sep}node_modules${sep}`); +} + +function toPosixPath(filePath: string) { + return filePath.split(sep).join("/"); +} + +function resolveSitePath(relativePath: string) { + const normalizedPath = normalize(join(siteRoot, relativePath)); + + if (!normalizedPath.startsWith(siteRoot) || !normalizedPath.endsWith(".md")) { + throw new Error("Invalid site path"); + } + + return normalizedPath; +} + +function parseFrontmatter(source: string) { + const match = source.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?/); + + if (!match) { + return { + title: "", + description: "", + content: source, + }; + } + + const frontmatter = match[1]; + const title = frontmatter.match(/^title:\s*(.+)$/m)?.[1]?.trim().replace(/^["']|["']$/g, "") ?? ""; + const description = + frontmatter.match(/^description:\s*(.+)$/m)?.[1]?.trim().replace(/^["']|["']$/g, "") ?? ""; + + return { + title, + description, + content: source.slice(match[0].length), + }; +} + +function stringifyPage(page: LocalPagePayload) { + const frontmatter = [ + "---", + `title: ${page.title || "未命名页面"}`, + `description: ${page.description || ""}`, + "---", + "", + ].join("\n"); + + return `${frontmatter}${page.content.trimStart()}`; +} + +function readStringValue(source: string, key: string, fallback = "") { + const pattern = new RegExp(`${key}:\\s*["'\`]([^"'\`]+)["'\`]`); + return source.match(pattern)?.[1] ?? fallback; +} + +function readBooleanValue(source: string, key: string, fallback = false) { + const pattern = new RegExp(`${key}:\\s*(true|false)`); + const value = source.match(pattern)?.[1]; + return value ? value === "true" : fallback; +} + +function parseSiteSettings(source: string): LocalSiteSettingsPayload { + const socialLinksSource = source.match(/socialLinks:\s*\[([\s\S]*?)\]/)?.[1] ?? ""; + + return { + title: readStringValue(source, "title", "VitePress Site"), + description: readStringValue(source, "description"), + logo: readStringValue(source, "logo", "/logo.svg"), + lastUpdated: readBooleanValue(source, "lastUpdated", true), + localSearch: /search:\s*{[\s\S]*?provider:\s*["']local["'][\s\S]*?}/.test(source), + outline: readBooleanValue(source, "outline", true), + socialKind: readStringValue(socialLinksSource, "icon", "github"), + socialLink: readStringValue(socialLinksSource, "link", "https://github.com/example/vitepress-cms"), + }; +} + +function stringifySiteConfig(settings: LocalSiteSettingsPayload) { + const searchConfig = settings.localSearch + ? ` search: { + provider: "local", + },` + : ""; + + return `import { defineConfig } from "vitepress"; + +export default defineConfig({ + title: ${JSON.stringify(settings.title)}, + description: ${JSON.stringify(settings.description)}, + lastUpdated: ${settings.lastUpdated}, + themeConfig: { + logo: ${JSON.stringify(settings.logo)}, + outline: ${settings.outline}, + nav: [ + { text: "首页", link: "/" }, + { text: "指南", link: "/guide/getting-started" }, + { text: "配置", link: "/guide/config" }, + { text: "更新日志", link: "/changelog" }, + ], + sidebar: { + "/guide/": [ + { + text: "指南", + items: [ + { text: "快速开始", link: "/guide/getting-started" }, + { text: "页面管理", link: "/guide/pages" }, + { text: "配置说明", link: "/guide/config" }, + ], + }, + ], + }, + socialLinks: [ + { icon: ${JSON.stringify(settings.socialKind)}, link: ${JSON.stringify(settings.socialLink)} }, + ], + footer: { + message: "Powered by VitePress-CMS", + copyright: "Copyright 2026", + }, +${searchConfig} + }, +}); +`; +} + +async function collectMarkdownFiles(dir: string): Promise { + const entries = await fs.readdir(dir, { withFileTypes: true }); + const nestedFiles = await Promise.all( + entries.map(async (entry) => { + const entryPath = join(dir, entry.name); + + if (entry.isDirectory()) { + if (entry.name === "node_modules" || entry.name === ".vitepress") { + return []; + } + + return collectMarkdownFiles(entryPath); + } + + return isMarkdownPath(entryPath) ? [entryPath] : []; + }), + ); + + return nestedFiles.flat(); +} + +function readRequestBody(request: IncomingMessage) { + return new Promise((resolve, reject) => { + let body = ""; + request.on("data", (chunk) => { + body += chunk; + }); + request.on("end", () => resolve(body)); + request.on("error", reject); + }); +} + +function sendJson(response: ServerResponse, status: number, payload: unknown) { + response.statusCode = status; + response.setHeader("Content-Type", "application/json; charset=utf-8"); + response.end(JSON.stringify(payload)); +} + +function localSiteApi(): Plugin { + return { + name: "vitepress-cms-local-site-api", + configureServer(server) { + server.middlewares.use("/api/local-site/pages", async (request, response) => { + try { + if (request.method === "GET") { + const files = await collectMarkdownFiles(siteRoot); + const contentFiles = files.filter((filePath) => { + const sitePath = toPosixPath(relative(siteRoot, filePath)); + return sitePath !== "README.md"; + }); + const pages = await Promise.all( + contentFiles.sort().map(async (filePath) => { + const source = await fs.readFile(filePath, "utf-8"); + const parsed = parseFrontmatter(source); + const path = toPosixPath(relative(siteRoot, filePath)); + + return { + id: path, + path, + title: parsed.title || path.replace(/\.md$/, ""), + description: parsed.description, + content: parsed.content, + }; + }), + ); + + sendJson(response, 200, { pages }); + return; + } + + if (request.method === "PUT") { + const payload = JSON.parse(await readRequestBody(request)) as LocalPagePayload; + const filePath = resolveSitePath(payload.path); + await fs.writeFile(filePath, stringifyPage(payload), "utf-8"); + + sendJson(response, 200, { ok: true }); + return; + } + + sendJson(response, 405, { error: "Method not allowed" }); + } catch (error) { + sendJson(response, 500, { + error: error instanceof Error ? error.message : "Unknown local site API error", + }); + } + }); + + server.middlewares.use("/api/local-site/settings", async (request, response) => { + try { + if (request.method === "GET") { + const source = await fs.readFile(configPath, "utf-8"); + sendJson(response, 200, { settings: parseSiteSettings(source) }); + return; + } + + if (request.method === "PUT") { + const settings = JSON.parse(await readRequestBody(request)) as LocalSiteSettingsPayload; + await fs.writeFile(configPath, stringifySiteConfig(settings), "utf-8"); + sendJson(response, 200, { ok: true }); + return; + } + + sendJson(response, 405, { error: "Method not allowed" }); + } catch (error) { + sendJson(response, 500, { + error: error instanceof Error ? error.message : "Unknown local site settings API error", + }); + } + }); + + server.middlewares.use("/api/local-site/assets/", (request, response, next) => { + const url = request.url ?? ""; + const filePath = normalize(join(siteRoot, "public", decodeURIComponent(url))); + + if (!filePath.startsWith(join(siteRoot, "public"))) { + sendJson(response, 403, { error: "Forbidden" }); + return; + } + + response.setHeader("Content-Type", extname(filePath) === ".svg" ? "image/svg+xml" : "application/octet-stream"); + createReadStream(filePath).on("error", next).pipe(response); + }); + }, + }; +} + +export default defineConfig({ + plugins: [localSiteApi(), vue()], +});