Repository Wiki
LYOfficial/OneDocs

发现与浏览界面

发现页从项目的 GitHub Releases 接口读取版本发布记录,提供按标签选择、发布日期展示及 Markdown 正文浏览;桌面和移动端使用不同的列表/详情布局。

Purpose and Scope

本文聚焦发现页的入口、远程数据加载、版本选择、正文渲染和移动端导航。归档页虽然复用部分布局类名和移动端列表/详情模式,但管理的是另一类结果,不在本文展开;分析、设置和关于页面也各有独立职责。发现页的远程发布记录不经过归档存储:页面组件直接调用 fetch,将响应保存在组件状态中。Discover.tsx

Overview

App 以 currentPage === "discover" 为条件挂载 Discover,并依据宽度 768 像素的断点决定是否传递移动子标签状态。移动端进入发现页时,App 将子标签设为 list;MobileNav 则提供发现页的 list、detail 子标签。发现组件请求固定仓库的 releases 列表,默认选择响应数组第一项的 tag_name,左侧/列表展示标签与日期,详情展示名称、标签、日期和渲染后的 body。App.tsx · App.tsx · App.tsx · MobileNav.tsx · Discover.tsx

Architecture

Loading diagram...

Sources: App.tsx, MobileNav.tsx, Discover.tsx

App 管理页面和移动子标签;Discover 自己管理请求、选择和显示状态;MarkdownRenderer 是共享的 Markdown 转换工具,而非发现页专属服务。接口 URL 在页面内直接给定,并未在已读实现中经过代理或配置注入。Discover.tsx · markdownRenderer.ts

实现与核心流程

获取与选择版本

组件初始化 releases=[]、activeTag=null、loading=true、error=null。useEffect 内开始请求前再次置 loading=true;非成功 HTTP 状态抛出带状态码的错误,成功则解析 JSON 并保存数组;仅在数组非空时把首条的 tag_name 设为选中标签。无论成功失败最终关闭加载状态。详情不是复制一份对象状态,而是用 releases.find 按 tag_name 从最新状态推导;同名标签将匹配首个。依赖数组为 [t]:翻译函数引用变化时 effect 会重新请求,并非显式的定时刷新。Discover.tsx

tsx
1useEffect(() => { 2 const fetchReleases = async () => { 3 try { 4 setLoading(true); 5 const res = await fetch( 6 "https://api.github.com/repos/LYOfficial/OneDocs/releases" 7 ); 8 if (!res.ok) throw new Error(`HTTP ${res.status}`); 9 const data: GitHubRelease[] = await res.json(); 10 setReleases(data); 11 if (data.length > 0) setActiveTag(data[0].tag_name); 12 } catch (err: any) { 13 setError(err.message || t("discover.fetchError")); 14 } finally { 15 setLoading(false); 16 } 17 }; 18 fetchReleases(); 19}, [t]);

Source: Discover.tsx

桌面与移动呈现

桌面使用 aside.tools-sidebar 与 section.tools-content 同屏展示列表和详情。移动端则依据 mobileSubTab === 'list' 仅显示列表,否则仅显示详情;选择某个版本时更新 activeTag,若提供了移动切换回调则切换到 detail。App 的 handleMobileSubTabChange 对发现页没有 store 同步分支,子标签只在 App 的本地状态中变化;重进发现页且处于移动模式会复位为列表。Discover.tsx · Discover.tsx · App.tsx

tsx
1const handleReleaseSelect = (tag: string) => { 2 setActiveTag(tag); 3 if (isMobile && onMobileSubTabChange) { 4 onMobileSubTabChange('detail'); 5 } 6};

Source: Discover.tsx

MobileNav 提供显式的列表、详情切换,详情可在无选择时显示空状态。加载/错误/无记录的提示仅出现在列表区域;详情区通过 activeRelease 决定显示发布内容还是 discover.emptyTitle、discover.emptyBody。桌面列表按钮仅更新选中标签,移动列表按钮则调用上述处理函数;两者最终共用 activeRelease 的查找结果。MobileNav.tsx · Discover.tsx · Discover.tsx

Loading diagram...

Sources: App.tsx, Discover.tsx, Discover.tsx

上图最后的渲染步骤只在有 activeRelease 且详情区域被呈现时发生;失败时页面使用错误提示,而不会执行成功分支。Discover.tsx

数据结构与正文处理

GitHubRelease 是页面本地接口:id: number 用作列表项 key,tag_name: string 用于选择与回查,name 用作标题(为空时回退到标签),body 是 Markdown 文本,published_at 转换为浏览器区域设置的短月日期,html_url 虽在类型中声明但当前组件没有使用。请求后不做字段校验、排序或持久化;界面直接按 API 返回的数组顺序排列。Discover.tsx · Discover.tsx · Discover.tsx

renderReleaseBody 遇到空 body 返回空字符串;正常路径调用共享 MarkdownRenderer.render,如果调用抛出异常则先转义 &、<、>,再把换行改为 <br/>。详情通过 dangerouslySetInnerHTML 插入 HTML。须注意共享渲染器自身也捕获顶层异常并返回错误提示与 <pre> 内容,因此外层 catch 不一定能处理渲染器内部的失败。Discover.tsx · Discover.tsx · markdownRenderer.ts

tsx
1const renderReleaseBody = (body: string): string => { 2 if (!body) return ""; 3 try { 4 return MarkdownRenderer.render(body); 5 } catch { 6 // Fallback to basic rendering if MarkdownRenderer fails 7 return body 8 .replace(/&/g, "&amp;") 9 .replace(/</g, "&lt;") 10 .replace(/>/g, "&gt;") 11 .replace(/\n/g, "<br/>"); 12 } 13};

Source: Discover.tsx

共享渲染器开启 marked 的 breaks 与 gfm;渲染前提取数学表达式,渲染后以 KaTeX 替换占位符,并将本地图片源转换为 Tauri 可访问地址、给图片追加懒加载属性、处理外部链接。这些能力来自共享工具而不是发现页独立的发布内容规则。markdownRenderer.ts · markdownRenderer.ts · markdownRenderer.ts

Usage Examples

上述请求代码和移动选中处理函数就是应用中实际运行的使用方式。以下进一步展示页面如何把派生的版本详情写入 DOM;它依赖成功加载后得到的 activeRelease,不是独立的外部组件 API。

tsx
1{activeRelease ? ( 2 <div className="discover-release"> 3 <div className="discover-release-header"> 4 <h2>{activeRelease.name || activeRelease.tag_name}</h2> 5 <div className="discover-release-meta"> 6 <span className="discover-tag-badge">{activeRelease.tag_name}</span> 7 <span>{formatDate(activeRelease.published_at)}</span> 8 </div> 9 </div> 10 <div 11 className="discover-release-body markdown-body" 12 dangerouslySetInnerHTML={{ __html: renderReleaseBody(activeRelease.body) }} 13 /> 14 </div> 15) : ( 16 <div className="result-empty-card"> 17 <h3>{t("discover.emptyTitle")}</h3> 18 <p>{t("discover.emptyBody")}</p> 19 </div> 20)}

Source: Discover.tsx

配置与组件契约

项类型/值默认或来源作用
isMobileboolean组件默认 false;App 用 window.innerWidth < 768 设置选择单栏或双栏布局,并控制选择版本后是否跳转详情。
mobileSubTabstring,界面使用 list/detail移动端发现页由 App 初始化为 list决定显示列表还是详情;非 list 值走详情分支。
onMobileSubTabChange(subTab: string) => void可选;由移动端 App 传入从选择版本动作切换到详情。
Releases URL固定字符串https://api.github.com/repos/LYOfficial/OneDocs/releases浏览器端直接获取记录;当前代码没有可覆盖的配置项。
markedbreaks: true、gfm: true共享渲染器全局设置控制 Markdown 的换行及 GitHub 风格语法。

依据:Discover.tsx · App.tsx · App.tsx · markdownRenderer.ts。这里的表格记录的是组件输入和写死的实现参数,不意味着存在独立的发现页配置文件。

API Reference

  • Discover: React.FC<DiscoverProps>:渲染发现页。isMobile?: boolean 缺省为 false;mobileSubTab?: string 决定移动端视图;onMobileSubTabChange?: (subTab: string) => void 在移动端点击版本时以 detail 调用。返回 React 元素。组件没有暴露手动重新加载方法。Discover.tsx · Discover.tsx
  • MarkdownRenderer.render(content: string): string:将正文转换为 HTML;空输入直接返回 "",顶层异常在方法内部捕获并返回错误 HTML。发现页只调用这个静态方法,不参与其数学公式及图片解析内部逻辑。markdownRenderer.ts · markdownRenderer.ts
  • 本地辅助函数 formatDate(iso: string):用 new Date(iso).toLocaleDateString(undefined, {year: "numeric", month: "short", day: "numeric"}) 格式化日期;它不是对外导出 API。Discover.tsx

Failure Modes、边界与并发

  • 请求失败:fetch 拒绝、非 2xx HTTP 响应、JSON 解析异常都进入 catch;内部保存 err.message 或翻译的兜底信息,列表区域显示翻译后的错误标题及实际错误小字。没有重试按钮或自动退避逻辑。Discover.tsx · Discover.tsx
  • 空结果/无选中项:列表在 releases.length === 0 时显示 discover.noReleases;详情在找不到与 activeTag 匹配的记录时显示空卡片。API 响应是由 TypeScript 断言为 GitHubRelease[] 的 JSON,当前实现未做运行时结构验证。Discover.tsx · Discover.tsx · Discover.tsx
  • 重入与状态一致性:effect 无 AbortController 和请求序号守卫;如果 [t] 触发多次请求,代码没有阻止较慢的旧请求覆盖较新的结果。请求前不会清除既有 error;因此一次失败之后,即使随后成功更新 releases,列表仍因 error 分支优先于列表分支而呈现错误。它们是从当前分支顺序和状态写入推导的行为,而非额外的恢复机制。Discover.tsx · Discover.tsx
  • HTML 信任边界:发布正文作为远程文本经过 MarkdownRenderer.render 后送入 dangerouslySetInnerHTML;阅读到的转换流程包含 marked.parse,但没有在所读路径看到明确的 HTML 清洗调用。维护时不能把外层 catch 的转义回退误认为正常路径的安全保证,发布正文的 HTML 安全策略需要单独审查。Discover.tsx · Discover.tsx · markdownRenderer.ts

性能、运维与扩展点

页面挂载时发起一次列表请求;页面由 App 条件渲染,离开发现页时不再挂载组件,因此本地列表状态不是跨页面持久缓存。版本列表使用 map 全量生成按钮,当前组件没有分页、搜索或客户端筛选;releases.find 按标签线性查找选中详情。共享渲染器为图片追加 loading="lazy" 以降低多图片同时加载带来的 UI 压力。App.tsx · Discover.tsx · Discover.tsx · markdownRenderer.ts

如需更换发布源或加入重试,应从 fetchReleases 的固定 URL 与状态分支着手;如需改移动端切换规则,应同时检查 App 的子标签重置/回调和 Discover 的 handleReleaseSelect;如需改正文能力,应留意 MarkdownRenderer 是共享工具,修改可能影响其他调用方。Discover.tsx · App.tsx · Discover.tsx

  • 归档页入口:与发现页并列的页面入口;归档内容请见归档相关目录页。
  • 移动导航:发现页列表/详情子标签定义。
  • 共享 Markdown 渲染:发布正文所用的通用渲染能力,内部规则不属于发现页专属配置。

Sources

(4 files)
src/components
src/pages