diff --git a/.gitignore b/.gitignore
index 2a11596..d38d5af 100644
--- a/.gitignore
+++ b/.gitignore
@@ -12,4 +12,5 @@ deploy_versions/
node_modules/
.DS_Store
.swc
+.vsix
end/
\ No newline at end of file
diff --git a/README.md b/README.md
index 50843d3..01989f8 100644
--- a/README.md
+++ b/README.md
@@ -1,259 +1,151 @@
-
+
- VS Code Boilerplate
+ AtCoder Helper
- 现代化 VS Code 插件开发模板
- Turborepo · TypeScript · React WebView · Tailwind CSS
+ VS Code 插件 — AtCoder 题目浏览 & 报名
+ Turborepo · TypeScript · React WebView · Tailwind CSS · KaTeX
-[核心优势](#核心优势) • [特性](#特性) • [项目结构](#项目结构) • [快速开始](#快速开始) • [开发指南](#开发指南) • [常见问题](#常见问题) • [许可证](#许可证)
+[特性](#特性) • [项目结构](#项目结构) • [快速开始](#快速开始) • [开发指南](#开发指南) • [常见问题](#常见问题) • [许可证](#许可证)
-
-一个现代化的 VS Code 插件开发模板,基于 [YAAC](https://github.com/cs-magic/YAAC) 项目的技术栈,集成了:
-
-- 🏃 Turborepo - 高性能的构建系统
-- 📝 TypeScript - 类型安全的开发体验
-- 🖼️ WebView - VS Code 自定义界面
-- ⚛️ React - 组件化开发
-- 🎨 Tailwind CSS - 现代化 UI 设计
-- 🛠️ VS Code API - 深度扩展集成
-- 📦 Webpack - 优化的打包工具
-
-## 核心优势
-
-- 🔍 优化的调试体验
-
- - 预配置的 F5 调试设置
- - 智能的断点和变量监控
- - 集成的开发者工具支持
-
-- 📦 完善的依赖管理
-
- - 基于 pnpm 的高效依赖管理
- - 智能的构建缓存策略
- - 自动化的依赖更新
-
-- 🎨 增强的 WebView 体验
- - 响应式设计支持
- - 主题自适应
- - 优化的状态管理
+VS Code 插件,支持浏览 AtCoder 竞赛题目、LaTeX 数学公式渲染、DeepL 翻译、评级/非评级报名。
## 特性
-- ⚡️ 快速开发启动
-- 📦 Monorepo 工作区管理
-- 🔄 开发环境热重载
-- 🎯 TypeScript 严格模式
-- 🎨 现代化 UI 框架
-- 🛠️ 完整的工具链集成
+- 📋 **题目浏览** — 输入比赛代号,一键加载所有题目列表
+- 📐 **LaTeX 渲染** — 服务端 KaTeX 预渲染,WebView 直接展示
+- 🌐 **DeepL 翻译** — 选中题面段落,自动翻译为中文
+- 📝 **报名比赛** — 支持评级(Rated)/ 非评级报名
+- 🔑 **Cookie 登录** — 安全存储 REVEL_SESSION,自动注入请求
+- 🎨 **主题适配** — WebView 自动跟随 VS Code 主题
## 项目结构
```
.
├── apps/
-│ └── vscode-extension/ # VS Code 插件主程序
-│ ├── src/ # 插件源码
-│ └── webpack.config.js # Webpack 构建配置
+│ └── vscode-extension/ # VS Code 插件主程序
+│ ├── src/
+│ │ ├── extension.ts # 插件入口,消息路由
+│ │ ├── atcoder.ts # AtCoder 爬虫 & 题面解析
+│ │ ├── SelectContest.ts # 报名逻辑(fetchContest / signedUpContest)
+│ │ ├── atcoder.test.ts # 单元测试
+│ │ └── tools/
+│ │ ├── fetch.ts # HTTP 请求工具 & 网络错误类
+│ │ └── deepl.ts # DeepL 翻译 API
+│ ├── webpack.config.js # Webpack 构建配置
+│ └── package.json # 扩展清单
│
├── packages/
-│ ├── core/ # 核心业务逻辑
-│ │ └── src/ # 共享的核心功能
-│ │
-│ ├── ui/ # UI 组件库
-│ │ ├── src/ # React 组件
-│ │ └── tailwind.config.js # Tailwind 配置
-│ │
-│ └── webview/ # WebView 前端应用
-│ └── src/ # WebView 源码
+│ ├── core/ # @template/core — 核心工具
+│ ├── ui/ # @template/ui — React 组件库
+│ └── webview/ # @template/webview — WebView 应用
+│
+├── docs/
+│ ├── images/
+│ └── Standard.md # 编码规范文档
+│
+├── .github/workflows/
+│ └── pr-review.yml # PR 自动审查流程
│
-├── pnpm-workspace.yaml # 工作区配置
-└── turbo.json # Turborepo 配置
+├── pnpm-workspace.yaml # 工作区配置
+└── turbo.json # Turborepo 配置
```
### 目录说明
-- `apps/vscode-extension`: VS Code 插件主程序
-
- - 负责插件生命周期管理
- - 处理 VS Code 命令和事件
- - 集成 WebView 和核心功能
-
-- `packages/core`: 核心业务逻辑
-
- - 提供共享的业务功能
- - 实现数据处理和状态管理
- - 定义核心 API 和接口
-
-- `packages/ui`: UI 组件库
-
- - 基于 React 的可复用组件
- - 集成 Tailwind CSS 样式系统
- - 支持主题定制和响应式设计
-
-- `packages/webview`: WebView 前端应用
- - 独立的 React 应用
- - 负责展示和用户交互
- - 与插件主程序通信
+| 目录 | 职责 |
+|------|------|
+| `apps/vscode-extension` | 插件入口,消息处理,AtCoder 爬虫,HTTP 工具,报名逻辑 |
+| `packages/core` | 核心业务逻辑(MessageBus, StateManager) |
+| `packages/ui` | React 组件库(Button, Card, Input, Spinner) |
+| `packages/webview` | WebView 前端应用(React + Tailwind + KaTeX) |
+| `docs/Standard.md` | 编码规范(命名、行数限制、导入顺序等) |
## 快速开始
-1. 克隆项目:
-
- ```bash
- git clone [your-template-url]
- cd vscode-boilerplate
- ```
-
-2. 安装依赖:
-
- ```bash
- # 安装 pnpm(如果未安装)
- npm install -g pnpm@8.10.0
-
- # 安装项目依赖
- pnpm install
- ```
+### 安装
-3. 开发模式:
-
- ```bash
- # 启动所有相关包的开发模式
- pnpm dev
- ```
-
-4. VS Code 插件调试:
- - 在 VS Code 中打开项目根目录
- - 按 F5 启动调试(已预配置调试设置)
- - 在新窗口中使用 `Cmd/Ctrl + Shift + P` 运行 "Show WebView" 命令
-
-## 开发指南
-
-### 项目开发
-
-1. **开发工作流**
-
- ```bash
- # 启动开发模式(监听所有相关包的变更)
- pnpm dev
-
- # 构建所有包
- pnpm build
-
- # 运行代码检查
- pnpm lint
-
- # 格式化代码
- pnpm format
- ```
-
-2. **清理项目**
- ```bash
- # 清理所有构建缓存和依赖
- pnpm clean
- ```
-
-### 包开发指南
-
-1. **Core 包开发**
-
- - 位置:`packages/core`
- - 职责:实现核心业务逻辑
- - 开发建议:
- - 保持良好的类型定义
- - 编写单元测试
- - 遵循模块化原则
-
-2. **UI 包开发**
+```bash
+pnpm install
+```
- - 位置:`packages/ui`
- - 职责:提供可复用的 UI 组件
- - 开发建议:
- - 使用 Storybook 开发和测试组件
- - 遵循 Tailwind 设计规范
- - 确保组件的可访问性
+### 开发
-3. **WebView 开发**
+```bash
+# 启动监听模式(自动编译)
+pnpm dev
+```
- - 位置:`packages/webview`
- - 职责:实现 VS Code WebView 界面
- - 开发建议:
- - 使用 React DevTools 调试
- - 注意状态管理和性能优化
- - 遵循 VS Code 的设计规范
+### 调试
-4. **插件开发**
- - 位置:`apps/vscode-extension`
- - 职责:实现 VS Code 插件功能
- - 开发建议:
- - 使用 VS Code 调试工具
- - 查看开发者工具的控制台输出
- - 测试不同的 VS Code 主题兼容性
+1. VS Code 中按 `F5`(已预配置 launch.json)
+2. 新窗口中 `Ctrl+Shift+P` → `Show WebView`
+3. 输入比赛代号(如 `abc345`)→ 加载题目 → 浏览/翻译/报名
-### 调试技巧
+### 设置 Cookie(用于报名 & 登录后题目)
-1. **WebView 调试**
+1. 浏览器登录 https://atcoder.jp
+2. F12 → Application → Cookies → 复制 `REVEL_SESSION` 的 Value
+3. VS Code 中 `Ctrl+Shift+P` → `Set AtCoder Login Cookie`
- - 在 VS Code 中打开开发者工具(帮助 > 切换开发人员工具)
- - 使用 React DevTools 检查组件
- - 查看控制台输出和网络请求
+### 设置 DeepL API Key(用于翻译)
-2. **插件调试**
+`Ctrl+Shift+P` → `Set DeepL API Key`
- - 使用断点和日志调试
- - 查看 VS Code 输出面板
- - 使用 VS Code 的调试控制台
+## 开发指南
-3. **性能优化**
- - 使用 React Profiler 分析性能
- - 检查 Webpack 构建配置
- - 优化状态更新和渲染
+### 脚本
-### 发布准备
+```bash
+pnpm build # 编译所有包
+pnpm dev # 开发监听模式
+pnpm lint # ESLint 检查
+pnpm format # Prettier 格式化
+pnpm test:unit # 运行单元测试(apps/vscode-extension)
+pnpm clean # 清理构建产物
+```
-1. **版本发布**
+### 编码规范
- ```bash
- # 构建生产版本
- pnpm build
+详见 [docs/Standard.md](docs/Standard.md),包括:
- # 运行测试
- pnpm test
+- 文件名:组件 PascalCase,工具 camelCase
+- TypeScript:优先 `interface`,避免 `any`
+- 函数不超过 **120 行**(可适当放宽至 130)
+- 导入顺序:外部 → 内部包 → 相对路径
- # 打包插件(在 apps/vscode-extension 目录下)
- cd apps/vscode-extension
- vsce package
- ```
+## 发布
-2. **发布检查清单**
- - 确保所有测试通过
- - 检查构建产物
- - 在不同版本的 VS Code 中测试
- - 更新版本号和更新日志
- - 检查发布配置
+```bash
+cd apps/vscode-extension
+pnpm package
+# 输出: release/extension.vsix
+```
## 常见问题
-1. WebView 不显示?
+**WebView 不显示?**
+- 确保已运行 `pnpm build`
+- 检查 `dist/webview.js` 是否存在
- - 确保已运行 `pnpm build` 构建 WebView
- - 检查 WebView 的构建输出
- - 查看 VS Code 的开发者工具
+**报名失败?**
+- 确认已设置 AtCoder Cookie
+- Cookie 可能过期,重新登录获取
-2. 热重载不工作?
- - 确保开发服务器正在运行
- - 检查控制台错误
- - 重新加载 VS Code 窗口
+**LaTeX 不渲染?**
+- KaTeX 在扩展端预渲染,WebView 只需 CSS
+- 检查控制台是否有字体加载错误
## 许可证
diff --git a/apps/vscode-extension/package.json b/apps/vscode-extension/package.json
index bab76b0..b2ab1e0 100644
--- a/apps/vscode-extension/package.json
+++ b/apps/vscode-extension/package.json
@@ -17,7 +17,7 @@
"commands": [
{
"command": "extension.showWebview",
- "title": "Show WebView"
+ "title": "AtCoder"
},
{
"command": "extension.setDeeplApiKey",
diff --git a/apps/vscode-extension/src/atcoder.ts b/apps/vscode-extension/src/atcoder.ts
index 62bd094..29295d4 100644
--- a/apps/vscode-extension/src/atcoder.ts
+++ b/apps/vscode-extension/src/atcoder.ts
@@ -206,12 +206,19 @@ export async function fetchAtCoderTasks(contest: string): Promise]*>([^<]+)<\\/a>`,
"gi"
);
- const tasks = Array.from(html.matchAll(taskRegex));
- return tasks
- .filter(([, task]) => task && task !== "tasks_print")
- .map(([, task, label]) => ({
- label: cleanText(label),
- value: task,
- url: `https://atcoder.jp/contests/${contest}/tasks/${task}`,
- }));
-}
+ const best = new Map();
+ Array.from(html.matchAll(taskRegex))
+ .filter(([, task]) => task && task !== "tasks_print" && !task.includes("/"))
+ .forEach(([, task, label]) => {
+ const cleaned = cleanText(label);
+ const prev = best.get(task);
+ if (!prev || cleaned.length > prev.length) {
+ best.set(task, cleaned);
+ }
+ });
+ return Array.from(best.entries()).map(([task, label]) => {
+ const suffix = (task.split("_").pop() || "").toUpperCase();
+ const title = label.replace(/^[A-Za-z0-9]+\s*[-–—.]\s*/, "").trim();
+ return { label: `${suffix} - ${title}`, value: task, url: `https://atcoder.jp/contests/${contest}/tasks/${task}` };
+ });
+}
\ No newline at end of file
diff --git a/apps/vscode-extension/src/extension.ts b/apps/vscode-extension/src/extension.ts
index ceea4ca..b8b95e5 100644
--- a/apps/vscode-extension/src/extension.ts
+++ b/apps/vscode-extension/src/extension.ts
@@ -226,7 +226,7 @@ function createShowWebview(context: vscode.ExtensionContext) {
return () => {
const panel = vscode.window.createWebviewPanel(
"templateWebview",
- "AtCoder 题目浏览器",
+ "AtCoder",
vscode.ViewColumn.One,
{
enableScripts: true,
diff --git a/apps/vscode-extension/src/tools/fetch.ts b/apps/vscode-extension/src/tools/fetch.ts
index 871552b..a4ff846 100644
--- a/apps/vscode-extension/src/tools/fetch.ts
+++ b/apps/vscode-extension/src/tools/fetch.ts
@@ -233,11 +233,16 @@ function handleResponse(
restoreProxyEnv(restoreSaved);
- if (res.statusCode === 403 || isCfChallenge(body)) {
+ if (isCfChallenge(body)) {
console.log(`${logPrefix} 检测到 Cloudflare 挑战`);
reject(new CfError(url));
return;
}
+ if (res.statusCode === 403) {
+ console.log(`${logPrefix} 403 但非 CF,可能 Cookie 无效`);
+ reject(new Error(`访问被拒绝 (403)。Cookie 可能无效或已过期,请重新登录 AtCoder 获取新的 REVEL_SESSION`));
+ return;
+ }
if (isLoginPage(body)) {
console.log(`${logPrefix} 检测到登录页面`);
reject(new LoginRequiredError(url));
diff --git a/packages/webview/src/WebviewApp.tsx b/packages/webview/src/WebviewApp.tsx
index 6c9806e..78c9b46 100644
--- a/packages/webview/src/WebviewApp.tsx
+++ b/packages/webview/src/WebviewApp.tsx
@@ -122,6 +122,10 @@ const WebviewApp: React.FC = ({
if (message.statusMessage) {
setStatus(message.statusMessage);
}
+ if (message.hasCookie) {
+ setCfUrl(null);
+ setShowSettings(false);
+ }
}
if (message.type === "registrationStatus") {
setSigned(message.signed ?? false);
@@ -239,17 +243,6 @@ const WebviewApp: React.FC = ({
- {Rated && (
-
- )}
+ {Rated && (
+
+ )}
{registrationMessage && (
{registrationMessage}
diff --git a/release/extension.vsix b/release/extension.vsix
index 14499c1..4b2a7f8 100644
Binary files a/release/extension.vsix and b/release/extension.vsix differ