docs: design spec for HTML preview/source toggle in workspace dialog
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,67 @@
|
|||||||
|
# HTML 文件 Preview/Source 切换 — 设计
|
||||||
|
|
||||||
|
日期:2026-08-08
|
||||||
|
|
||||||
|
## 背景
|
||||||
|
|
||||||
|
Workspace 文件对话框(`WorkspaceFileDialog.tsx`)打开 `.html` 文件时只显示源码(SyntaxHighlighter)。服务端有意以 `text/plain` + `Content-Security-Policy: sandbox` + `nosniff` 返回 HTML(`src/app/api/workspace/file/route.ts:53`),防止直接打开时渲染执行。
|
||||||
|
|
||||||
|
## 需求
|
||||||
|
|
||||||
|
1. HTML 文件增加 **Preview | Source** 切换,默认 Preview。
|
||||||
|
2. Preview 渲染页面(允许 JavaScript,plotly/echarts 交互图表可用)。
|
||||||
|
3. Source 为现有源码高亮视图;现有 Edit(textarea 编辑 + Save)保持不变。
|
||||||
|
4. 同目录相对资源(CSS/JS/图片)在 Preview 中可解析。
|
||||||
|
|
||||||
|
## 设计
|
||||||
|
|
||||||
|
### 1. 服务端:`?render=1` 渲染模式
|
||||||
|
|
||||||
|
修改 `src/app/api/workspace/file/route.ts` 的 GET:
|
||||||
|
|
||||||
|
- 新参数 `render=1`,仅当解析出的扩展名为 `html` 时生效;其他扩展名忽略该参数。
|
||||||
|
- render 模式下:
|
||||||
|
- `Content-Type: text/html; charset=utf-8`
|
||||||
|
- `Content-Security-Policy: sandbox allow-scripts`(允许脚本;不授予 `allow-same-origin`,脚本运行在 opaque origin,无法读取应用 cookie/DOM,其 fetch 会被 API 的跨源与会话检查拒绝)
|
||||||
|
- `Content-Disposition: inline`
|
||||||
|
- 保留 `Cache-Control: no-store`;不加 `X-Content-Type-Options: nosniff` 以外的变化
|
||||||
|
- `?download=1` 优先于 `render=1`(仍为 attachment、text/plain)。
|
||||||
|
- 非 render 模式行为完全不变(HTML 仍 text/plain + `sandbox`)。
|
||||||
|
|
||||||
|
**相对资源 MIME 修正**:同目录 `.css`/`.js` 当前以 `text/plain` + `nosniff` 返回,浏览器在渲染页面中会拒绝应用/执行。`CONTENT_TYPES` 中:
|
||||||
|
|
||||||
|
- `css`: `text/plain` → `text/css; charset=utf-8`
|
||||||
|
- `js`: `text/plain` → `text/javascript; charset=utf-8`
|
||||||
|
|
||||||
|
这两个扩展仍受 CSP `sandbox`(直接打开时脚本不执行)与既有同源检查保护;图片扩展(png/jpg/…)已是正确 MIME,无需改动。
|
||||||
|
|
||||||
|
### 2. 对话框 UI(`src/app/components/WorkspaceFileDialog.tsx`)
|
||||||
|
|
||||||
|
- 仅对 `ext === "html"` 且 `kind === "text"` 且未超大小限制的文件启用。
|
||||||
|
- 新 state:`htmlView: "preview" | "source"`,默认 `"preview"`;`useEffect` 在 `path`/`threadId` 变化时重置为 `"preview"`。
|
||||||
|
- 头部工具栏(现有 Edit/Download/Delete 之前)增加分段切换:Preview | Source 两个 ghost Button,激活态用 `bg-accent` 类区分;进入编辑模式时隐藏该切换(编辑已有 Cancel/Save)。
|
||||||
|
- **Preview 视图**:`<iframe src={workspaceFileUrl(threadId, path) + "&render=1"} sandbox="allow-scripts" title={name} className="h-full w-full rounded-md border border-border" />`
|
||||||
|
- **Source 视图**:现有 SyntaxHighlighter 分支(对 html 文件从默认改为仅 `htmlView === "source"` 时显示)。
|
||||||
|
- 保存成功后 `setEditing(false)` 且 `htmlView` 回到 `"preview"`,iframe 以 `key={content}`(或保存计数)强制重新加载以显示新内容。
|
||||||
|
- 切换状态不持久化,每次打开文件重置为 Preview。
|
||||||
|
|
||||||
|
### 3. 安全边界
|
||||||
|
|
||||||
|
- `render=1` 仅限 GET、仅 `.html`;`isCrossOrigin` 检查已有。
|
||||||
|
- iframe `sandbox="allow-scripts"`(不含 `allow-same-origin`/`allow-forms`/`allow-modals`/`allow-popups`),与服务端 CSP 双重限制。
|
||||||
|
- 渲染的 HTML 来自用户/agent 自己的工作区文件,与现有图片/PDF iframe 预览的信任级别一致。
|
||||||
|
|
||||||
|
## 错误处理
|
||||||
|
|
||||||
|
- render 模式读取失败沿用现有错误 JSON(400/403/404)。
|
||||||
|
- Preview iframe 加载失败(文件被删等)由浏览器显示空 iframe;Source 视图的错误展示不变。
|
||||||
|
|
||||||
|
## 测试
|
||||||
|
|
||||||
|
- `src/app/api/workspace/file/route.test.ts`(新建或扩展,参照现有路由测试模式):
|
||||||
|
- `render=1` + `.html` → 200、`text/html`、CSP `sandbox allow-scripts`、inline
|
||||||
|
- `render=1` + `.md` → 忽略 render,行为不变
|
||||||
|
- `render=1&download=1` + `.html` → download 优先(attachment、text/plain)
|
||||||
|
- 不带 render 的 `.html` → 仍 `text/plain` + CSP `sandbox`
|
||||||
|
- `.css`/`.js` 新 MIME
|
||||||
|
- UI 手动验证:打开含 plotly 的 HTML → 默认渲染、交互可用;切 Source → 源码;Edit 保存 → 回到 Preview 且内容更新;相对 CSS/JS/图片正常加载。
|
||||||
Reference in New Issue
Block a user