Upload articles by parsing urls - #5
Conversation
CeitherNSW
commented
Sep 12, 2025
longsizhuo
left a comment
There was a problem hiding this comment.
提个建议,css单独创一个和你新增的东西同名的css,不然找不到了
There was a problem hiding this comment.
Pull Request Overview
This PR adds a new upload feature for parsing and uploading articles via URLs, introducing a dedicated upload page with URL input functionality. The changes also refactor existing styles by extracting inline styles into CSS classes for better maintainability.
- Creates a new upload page with URL input field and upload button
- Refactors inline styles to CSS classes for the docs page
- Adds comprehensive CSS variables and utility classes for consistent styling
Reviewed Changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 9 comments.
| File | Description |
|---|---|
| app/upload/page.tsx | New upload page with URL input form for article parsing |
| app/page.tsx | Refactored to use CSS classes instead of inline styles |
| app/globals.css | Added CSS variables and utility classes for consistent styling |
Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.
| const [value, setValue] = useState('') | ||
|
|
||
| const handleClick = () => { | ||
| console.log('输入内容:', value) |
There was a problem hiding this comment.
The console.log message contains Chinese characters '输入内容:'. Consider using English for consistency with the codebase, such as 'Input content:' or 'URL input:'.
| console.log('输入内容:', value) | |
| console.log('Input content:', value) |
| <div className="doc-card" style={{ display: 'flex', gap: 8 }}> | ||
| <input | ||
| type="text" | ||
| placeholder="公众号url" |
There was a problem hiding this comment.
The placeholder text '公众号url' is in Chinese. Consider using English for consistency, such as 'Enter article URL' or 'WeChat article URL'.
| placeholder="公众号url" | |
| placeholder="Enter article URL" |
| <button | ||
| onClick={handleClick} | ||
| > | ||
| 上传 |
There was a problem hiding this comment.
The button text '上传' is in Chinese. Consider using English for consistency with the codebase, such as 'Upload' or 'Submit'.
| 上传 | |
| Upload |
| } | ||
| } | ||
|
|
||
| /* globals.css */ |
There was a problem hiding this comment.
[nitpick] This comment is redundant since it's already clear from the filename. Consider removing it or replacing with a more descriptive comment about the purpose of the following CSS rules.
| /* globals.css */ | |
| /* Custom properties for layout and design */ |
| --list-gap: 12px; | ||
| } | ||
|
|
||
| /* 全局 main 容器 */ |
There was a problem hiding this comment.
The comment '全局 main 容器' is in Chinese. Consider using English for consistency, such as '/* Global main container */'.
| /* 全局 main 容器 */ | |
| /* Global main container */ |
| padding: var(--page-padding); | ||
| } | ||
|
|
||
| /* 标题 */ |
There was a problem hiding this comment.
The comment '标题' is in Chinese. Consider using English for consistency, such as '/* Page title /' or '/ Heading */'.
| /* 标题 */ | |
| /* Page title */ |
| margin-bottom: var(--heading-margin-bottom); | ||
| } | ||
|
|
||
| /* 列表 */ |
There was a problem hiding this comment.
The comment '列表' is in Chinese. Consider using English for consistency, such as '/* Document list /' or '/ List styles */'.
| /* 列表 */ | |
| /* Document list */ |
| padding: 0; | ||
| } | ||
|
|
||
| /* 卡片 */ |
There was a problem hiding this comment.
The comment '卡片' is in Chinese. Consider using English for consistency, such as '/* Card styles /' or '/ Document card */'.
| /* 卡片 */ | |
| /* Card styles */ |
| padding: var(--card-padding); | ||
| } | ||
|
|
||
| /* 链接 */ |
There was a problem hiding this comment.
The comment '链接' is in Chinese. Consider using English for consistency, such as '/* Link styles /' or '/ Document links */'.
| /* 链接 */ | |
| /* Link styles */ |
- UserPaperItem 加 itemKey 字段(可选) - page.tsx 提取所有 itemKey 批量调 /api/user-center/zotero/items, Zotero 拉到的元信息作为主数据源,手填字段作为离线 fallback - EditProfileForm papers 区增加 itemKey 输入框(提示可选),保存条件放宽为 itemKey 或 title 至少有一个 - 完全向后兼容:历史 pinned_papers(只有 title/authors 的)照常渲染 配套后端 commit involutionhell-backend 1fb697f(Zotero API 代理 + Caffeine 1h) V2 全部完成:#1 编辑页 / #2 docs/history 迁 Java / #3 活跃度热力图 / #4 关注系统 / #5 GitHub repos 同步 / #6 保留 analyticsEvent / #7 Zotero itemKey
* feat(profile): 个人主页 /u/[username] SSR + Bento 布局
MVP 范围(UX + PM 对齐后砍刀):
- 路由:/u/[username] SSR(ISR 300s)
- 数据源:后端 GET /api/user-center/profile/{username}
- 布局:12-col bento,左 col-span-5 Identity,右 col-span-7 小卡网格
- 间距:gap-8 统一,用户明确要求松散
- 三种卡片:PROJ / PAPER / DOC,前两者读 preferences,DOC 读 leaderboard
- hover 展开用 max-height 原地展开;mobile 改 tap toggle
砍掉(V2):
- sticky 左大块(滚动竞争)
- 绝对定位浮层(移动端 mess)
- 编辑页(复用后端 PATCH /preferences)
- Zotero 实时关联(pinned_papers 直接存 title/author/year)
配套后端改动见 involutionhell-backend main commit 8efae54
(新增 GET /api/user-center/profile/{username} + SaToken 白名单)
同步新增:
- docs/architecture/frontend-backend-separation.md 固化前后端分离约定
* feat(profile): 入口收口 + URL 支持 github_id 双路径
- UserMenu 登录态增加"我的主页"链接(基于 githubId)
- AuthNav 透传 githubId 到 UserMenu
- ContributorRow 弹窗加"VIEW DOSSIER"跳站内个人主页,GitHub 外链保留
- page.tsx 改按 user.githubId 从 leaderboard JSON 匹配贡献数据
(之前按 name 字符串匹配踩坑:leaderboard.name="longsizhuo" vs user.username="github_114939201")
- 新增 EditLinkIfOwner 客户端小组件,只在访问者 == 主页主人时渲染编辑入口
* feat(profile): 编辑页 /u/[username]/edit 支持 bio/tagline/links/projects/papers
- 新 page.tsx 壳 + EditProfileForm 客户端表单
- useAuth 读当前用户,URL username 与 githubId / username 都不匹配时显示"只能编辑自己"
- GET /api/user-center/preferences 拉现有值 → 表单 state
- 提交走 PATCH /api/user-center/preferences(后端合并 JSONB 顶层 key 保留其他)
- RepeatableList 通用泛型组件支持 links/projects/papers 增删
- editorial 风格:border 无圆角 / SEC 编号 / 硬阴影 submit 按钮
* refactor(api): /api/docs/history 迁到 Java 后端
删 Next API Route,next.config 加 rewrite 把 /api/docs/history 透传到 Java,
Vercel 不再跑 Node function 去调 GitHub API。后端带 Caffeine 10min 缓存 +
GITHUB_TOKEN。配套后端 commit 见 involutionhell-backend#main。
* perf(build): leaderboard 用 git log 反推 login + 加 dailyCounts 字段
- 从 noreply 邮箱(1234567+alice@users.noreply.github.com) 离线提取 id→login 映射,
14/21 命中直接拿到 login,剩下 7 才走 GitHub API。规避限流风险。
- DB 聚合时同时按日分桶贡献次数写进 dailyCounts: { "YYYY-MM-DD": count },
供前端活跃度热力图渲染,零运行时 DB 查询。
generated/site-leaderboard.json 一并重新生成带 dailyCounts。
* feat(profile): 活跃度热力图 52 周 GitHub 风格
- 新增 ActivityHeatmap server component,无 JS
- 52 列 × 7 行小格子,色阶分 5 档(0 / 1-2 / 3-5 / 6-10 / 10+),硬红(#CC0000)系列
- 数据走 leaderboard.json 的 dailyCounts(零运行时 DB 查询,和 docs git-based 一致)
- 月份刻度 + 周几标签(周一/四/六)
- page.tsx Bento grid 下方独立一行展示,仅当有贡献数据时渲染
* feat(follows): 个人主页关注按钮 + 粉丝/关注数
- FollowButton 客户端组件:拉 /api/user-center/follows/stats 填初始值,
点击走 POST/DELETE /api/user-center/follows/{id},乐观更新失败回滚
- 自己访问自己主页只显示统计,不显示按钮
- 匿名用户可见数字,按钮显示"登录后关注"置灰
- prisma/schema.prisma 加 user_follows model 同步(DB 已在 Neon 手动建表)
配套后端端点见 involutionhell-backend#main 30daf9d
* feat(profile): GitHub repos 小卡区 SEC. REPOS · 006
- GithubRepos server component,fetch /api/user-center/github/repos/{identifier}
- 2 列 grid,每卡显示 name / stars / description / language / 更新时间
- 数据为空时返回 null 不渲染 section
配套后端 commit involutionhell-backend b6b48b2(Caffeine 1h,不需要 OAuth scope 扩展)
* feat(profile): pinned_papers 支持 Zotero itemKey 运行时填充
- UserPaperItem 加 itemKey 字段(可选)
- page.tsx 提取所有 itemKey 批量调 /api/user-center/zotero/items,
Zotero 拉到的元信息作为主数据源,手填字段作为离线 fallback
- EditProfileForm papers 区增加 itemKey 输入框(提示可选),保存条件放宽为 itemKey 或 title 至少有一个
- 完全向后兼容:历史 pinned_papers(只有 title/authors 的)照常渲染
配套后端 commit involutionhell-backend 1fb697f(Zotero API 代理 + Caffeine 1h)
V2 全部完成:#1 编辑页 / #2 docs/history 迁 Java / #3 活跃度热力图 /
#4 关注系统 / #5 GitHub repos 同步 / #6 保留 analyticsEvent / #7 Zotero itemKey
* fix(ts): 修复 typecheck 两处报错
1. leaderboard as Row[] → leaderboard as unknown as Row[]
JSON 字面量的 dailyCounts 各自是不同 literal 类型,和 Row 的 Record<string, number>
索引签名不兼容,CI tsc --noEmit 报 TS2352。先经 unknown 绕开。
2. ProfileCard title prop 必填 string,但 UserPaperItem 引入 itemKey 后 title 变可选。
兜底为 p.title || p.itemKey || "(untitled paper)"。
* fix(review): 修复 Copilot PR CR 8 条
安全修复(P0):
- fetchProfile 只在后端真返 404 或 success=false 时 notFound();其他非 2xx 抛错进 error boundary
(避免后端故障伪装成"用户不存在")
- links/projects/papers 的 URL 在渲染前过 sanitizeExternalUrl 白名单
(仅 http/https/mailto + 相对路径,拦 javascript:/data: 等 XSS 向量)
- ProfileCard 内部再加 safeHref 二次防御
UX / a11y(P1):
- ProfileCard 的 click toggle 用 matchMedia('(hover: none)') 限定触屏设备
(桌面端仍走 group-hover,避免"离开 hover 后仍保持展开"幽灵状态)
- 补 role="button" / tabIndex=0 / onKeyDown(Enter|Space) / aria-expanded,
键盘 / 读屏用户可用;只在有 detail 时才挂这些属性
文档(P2):
- docs/architecture/frontend-backend-separation.md
环境变量章节改成"生产禁 fallback,开发态过渡允许",与现状(next.config.mjs/upload 仍用 fallback)自洽
- fetchProfile 注释说"直连后端(BACKEND_URL)",不再错写"走 rewrite"
* refactor(profile): 调整 profile 页版式,避免文档多的用户下滑过久
- 右侧 Bento 小卡区只保留 projects / papers,DOC 卡片抽出去
- 活跃度热力图从页底提到 Bento 之后立即显示(视觉重点前置)
- 新增 SEC. DOCS 独立 section 放页尾,紧凑列表(每行 ~48px 而非 180px 小卡)
- 默认只显示前 10 篇,超过的用 <details> 折叠"展开剩余 N 篇"
旧版顺序:Identity(bio/stats)+DOC 卡(8 条) → Heatmap → Repos
新版顺序:Identity(bio/stats)+projects/papers → Heatmap → Repos → Docs 列表(折叠)
文档特别多的用户(本作者 44 条)现在热力图和 repos 一屏可见,docs 列表放最后折叠。
* refactor(header): 更新顶部导航以对齐当前信息架构
- 去掉"首页":BrandMark 点击已回首页,避免冗余
- 去掉"特点":旧 Features 组件 2026-04 重构时删除,/#features 已失效
- 新增"文档"→ /docs 和"排行榜"→ /rank,把主要产品路由提到顶
- 保留"社区"→ /#community(DispatchNetwork bar)
- "联系我们"缩写为"联系"→ /#contact(Footer 还在)
* fix(profile-edit): 改善编辑页文案,让普通用户知道每个字段填什么
用户反馈"编辑个人主页这地方不知道 edit 什么,不懂 papers 是什么意思"。
根因是字段名是工程师视角(bio/tagline/pinned_papers),普通用户不理解。
改动:
- 编辑页顶部加一段"About this page"总说明:填的东西去哪里显示、全都可选
- 每个 Section 从只有 SEC 编号,扩成「SEC 编号 + 中文大标题 + 一句话描述」
- PAPERS 块重命名为"最近在读 / 推荐的论文"(papers 太学术,降低理解门槛)
- placeholder 从抽象("项目名")换成具体示例("involutionhell.com")
- IDENTITY/LINKS/PROJECTS/PAPERS 4 块都加 heading + description 说明作用
主页空态文案:
- 旧:"该用户还没有填写 projects / papers"(用户反馈看不懂)
- 新:"Ta 还没填个人项目和最近在读的论文" + 小字补充"仍会显示 GitHub repos 和文档贡献"
* feat(i18n): 个人主页相关 UI 接入真 i18n(cookie 驱动 zh/en 切换)
基建:
- lib/i18n/messages.ts:扁平 key 字典,zh/en 同结构,{param} 占位填充
- lib/i18n/server.ts:server-only,getServerLocale + getServerT
- lib/i18n/client.tsx:LocaleProvider + useT hook
- app/layout.tsx:LocaleProvider 包在 ThemeProvider 外,locale 服务端读 cookie
注入客户端 Context,避免 SSR/CSR 水合抖动
接入(全部 profile 相关 UI):
- page.tsx:dossier / stats / empty state / docs 列表全部走 t(key)
- ActivityHeatmap:改 async server component,月份 Jan..Dec 走 activity.month.{1..12}
- GithubRepos:heading / subtitle / count 走 t
- FollowButton / EditLinkIfOwner / ProfileCard:client hook useT
- edit/page.tsx + EditProfileForm:全套(intro / 4 个 section heading+description
+ placeholders + CTA + auth gate + RepeatableList 增删按钮文字)
- RepeatableList 新增 addLabel/removeLabel props,调用方翻译好后传入
验证:
- pnpm run typecheck 通过
- locale=zh → "活跃度/文档贡献/粉丝/积分/贡献过的文档/GitHub 仓库"
- locale=en → "Activity/Docs/Followers/Points/Docs Contributed/GitHub Repositories"
Copilot 提了 6 条 + CodeQL 2 条正则告警,全部修复: **lib/rate-limit.ts** - 文档头 usage 示例 API 改对(CR #1) - getClientIp 防伪造(CR #2,**安全修复**): 优先 x-real-ip(Vercel 等 CDN 写的是可信值);降级用 XFF 时取**最后一个** 而非首个,避免客户端伪造 `x-forwarded-for: fakeip` 绕过 rate-limit - Upstash 缺失 warn 改用 module-scoped flag,整个实例生命周期只打一次, 不再按 NODE_ENV 区分 —— dev 也得看到提示(CR #3) **app/api/chat/route.ts** - POST 入口预读 body 判定 hasImage,true 时触发 5 req/60s 严限流; 预读失败不阻塞,保持原有容错(CR #4) - 新增 messagesHaveImage helper:识别 type=image / image_url / file+image 媒体 - mapUpstreamError 不再把 err.stack 拼进匹配文本:stack 里的 `:429:` 行号 会误触发 rate-limited 分类(CR #5,**真实 bug**) - JSON.stringify 加 try/catch 兜底 String(err),避免循环引用再抛错(CR #6) - 所有业务码正则里的 `.*` 改成 `[^\s]{0,10}?`,限死回溯深度防 ReDoS (CodeQL polynomial regex 告警)
|
已实现类似功能, 详细见 feed 和 share 路由 |