From 715ee4c6025a853557dbdb57651c84fbcb7c086d Mon Sep 17 00:00:00 2001 From: lix-2026 Date: Mon, 25 May 2026 01:52:11 +0800 Subject: [PATCH] refactor: extract media upload runtime helper --- ...0-browser-runtime-followup-checklist-v1.md | 4 ++ .../mnote-web/browser/local-upload-runtime.js | 21 ++++++++++ rust/crates/mnote-web/src/ssr/pages/layout.rs | 38 ++++++++++++------- 3 files changed, 50 insertions(+), 13 deletions(-) diff --git a/design/03-rust-web/process/3-20-browser-runtime-followup-checklist-v1.md b/design/03-rust-web/process/3-20-browser-runtime-followup-checklist-v1.md index c0076e64..a8afdcbd 100644 --- a/design/03-rust-web/process/3-20-browser-runtime-followup-checklist-v1.md +++ b/design/03-rust-web/process/3-20-browser-runtime-followup-checklist-v1.md @@ -93,3 +93,7 @@ UI / 浏览器可见项必须有真实浏览器截图或结构化 smoke 证据 - 已验证:`node --check rust/crates/mnote-web/browser/document-conflict-panel-runtime.js`、`cargo fmt --manifest-path rust/Cargo.toml --all --check`、`cargo check --manifest-path rust/Cargo.toml -p mnote-web`、`cargo test --manifest-path rust/Cargo.toml -p mnote-web document_conflict_panel_runtime_contains_dom_helpers -- --test-threads=1`、`cargo test --manifest-path rust/Cargo.toml -p mnote-web mnote_browser_runtime_assets_are_explicitly_mounted -- --test-threads=1`。 - 未完成:conflict merge / accept / keep 业务逻辑、document session lifecycle、secondary pane conflict smoke 仍未迁出;本轮未声称真实冲突 UI 全链路完成。 - 2026-05-25:根据用户纠正,后续实现 worker 不能在主工作区直接修改;每批最多 2 个 worker,且必须采用独立 worktree 候选 patch或明确只读任务。Codex 读取 Hindsight recall、`process-handoff.md/json`、`result.json`、diff 与验证证据后再选择性合入。 +- 2026-05-25:Codex 主控完成非本地 media upload API 小切片:`local-upload-runtime.js` 新增并导出 `uploadMediaAsset(file, plan, context)`,承接 `/api/media/upload` 的 FormData 构造、fetch 和响应解析;`layout.rs` 的 `uploadFileToMediaAsset` 仍负责 appendUploadedAssetRow、editor 插入和 `wolai:assets-changed` 事件分发,并保留完整 inline fallback。 + - 修改:`rust/crates/mnote-web/browser/local-upload-runtime.js`、`rust/crates/mnote-web/src/ssr/pages/layout.rs`。 + - 已验证:`node --check rust/crates/mnote-web/browser/local-upload-runtime.js`、`cargo fmt --manifest-path rust/Cargo.toml --all --check`、`cargo check --manifest-path rust/Cargo.toml -p mnote-web`、`cargo test --manifest-path rust/Cargo.toml -p mnote-web local_upload_runtime_contains_editor_upload_context_helpers -- --test-threads=1`、`cargo test --manifest-path rust/Cargo.toml -p mnote-web sidebar_upload_runtime_routes_local_markdown_assets_to_local_folder -- --test-threads=1`、`node scripts/task479-local-folder-markdown-resource-lifecycle-smoke.js`。 + - 未完成:`uploadFileToMediaAsset` / `uploadFilesWithResolvedTarget` 编排壳、conflict merge / accept / keep 业务逻辑、secondary pane conflict smoke 仍未迁出;本轮未改资源归属语义。 diff --git a/rust/crates/mnote-web/browser/local-upload-runtime.js b/rust/crates/mnote-web/browser/local-upload-runtime.js index 543c4fc2..f5983f3d 100644 --- a/rust/crates/mnote-web/browser/local-upload-runtime.js +++ b/rust/crates/mnote-web/browser/local-upload-runtime.js @@ -183,6 +183,26 @@ async function uploadLocalFolderAsset(file, plan, context) { }; } +async function uploadMediaAsset(file, plan, context) { + var form = new FormData(); + form.append('file', file); + form.append('workspaceId', plan && plan.workspaceId || ''); + form.append('documentId', plan && plan.targetDocumentId || ''); + if (plan && plan.targetMindmapId) form.append('mindmapId', plan.targetMindmapId); + var response = await fetchWithTimeout('/api/media/upload', { + method: 'POST', + credentials: 'include', + body: form + }, Number(context && context.timeoutMs) || 15000, '上传'); + var payload = await response.json().catch(function() { return null; }); + if (!response.ok || !payload || !payload.asset) { + throw new Error(payload && payload.error ? payload.error : '上传失败'); + } + return { + asset: payload.asset + }; +} + async function insertUploadedAssetIntoEditor(asset, targetRoot, deps) { deps = deps || {}; var editorRoot = targetRoot instanceof HTMLElement @@ -408,6 +428,7 @@ window.__mnoteLocalUploadRuntime = { openEditorUploadFilePicker: openEditorUploadFilePicker, fetchWithTimeout: fetchWithTimeout, uploadLocalFolderAsset: uploadLocalFolderAsset, + uploadMediaAsset: uploadMediaAsset, insertUploadedAssetIntoEditor: insertUploadedAssetIntoEditor, localAssetOpenUrl: localAssetOpenUrl, uploadedAssetType: uploadedAssetType, diff --git a/rust/crates/mnote-web/src/ssr/pages/layout.rs b/rust/crates/mnote-web/src/ssr/pages/layout.rs index 8b8f5e1f..8bc63345 100644 --- a/rust/crates/mnote-web/src/ssr/pages/layout.rs +++ b/rust/crates/mnote-web/src/ssr/pages/layout.rs @@ -4058,19 +4058,29 @@ const SIDEBAR_TREE_JS: &str = r##" })); return localPayload.asset; } - var form = new FormData(); - form.append('file', file); - form.append('workspaceId', plan.workspaceId); - form.append('documentId', plan.targetDocumentId); - if (plan.targetMindmapId) form.append('mindmapId', plan.targetMindmapId); - var response = await fetchWithTimeout('/api/media/upload', { - method: 'POST', - credentials: 'include', - body: form - }, 15000, '上传'); - var payload = await response.json().catch(function() { return null; }); - if (!response.ok || !payload || !payload.asset) { - throw new Error(payload && payload.error ? payload.error : '上传失败'); + var mediaUploadRuntime = localUploadRuntimeFunction('uploadMediaAsset'); + var payload = null; + if (mediaUploadRuntime) { + var mediaResult = await mediaUploadRuntime(file, plan, { timeoutMs: 15000 }); + payload = mediaResult && mediaResult.asset ? { asset: mediaResult.asset } : null; + if (!payload || !payload.asset) { + throw new Error('上传失败'); + } + } else { + var form = new FormData(); + form.append('file', file); + form.append('workspaceId', plan.workspaceId); + form.append('documentId', plan.targetDocumentId); + if (plan.targetMindmapId) form.append('mindmapId', plan.targetMindmapId); + var response = await fetchWithTimeout('/api/media/upload', { + method: 'POST', + credentials: 'include', + body: form + }, 15000, '上传'); + payload = await response.json().catch(function() { return null; }); + if (!response.ok || !payload || !payload.asset) { + throw new Error(payload && payload.error ? payload.error : '上传失败'); + } } appendUploadedAssetRow(payload.asset, plan.targetDocumentId); if (options && options.insertIntoEditor) { @@ -10954,6 +10964,7 @@ mod tests { assert!( SIDEBAR_TREE_JS.contains("localUploadRuntimeFunction('insertUploadedAssetIntoEditor')") ); + assert!(SIDEBAR_TREE_JS.contains("localUploadRuntimeFunction('uploadMediaAsset')")); assert!(SIDEBAR_TREE_JS.contains("__mnoteLastEditorUploadRoot")); assert!(SIDEBAR_TREE_JS.contains("data-pane-role=\"primary\"")); assert!(SIDEBAR_TREE_JS.contains("insertUploadedAssetIntoEditor(localPayload.asset, editorRootFromUploadOptions(options))")); @@ -10969,6 +10980,7 @@ mod tests { assert!(LOCAL_UPLOAD_RUNTIME_JS.contains("function openEditorUploadFilePicker")); assert!(LOCAL_UPLOAD_RUNTIME_JS.contains("function fetchWithTimeout")); assert!(LOCAL_UPLOAD_RUNTIME_JS.contains("function uploadLocalFolderAsset")); + assert!(LOCAL_UPLOAD_RUNTIME_JS.contains("function uploadMediaAsset")); assert!(LOCAL_UPLOAD_RUNTIME_JS.contains("function insertUploadedAssetIntoEditor")); assert!(LOCAL_UPLOAD_RUNTIME_JS.contains("data-mnote-last-upload-inserted")); assert!(LOCAL_UPLOAD_RUNTIME_JS.contains("uploadFilesWithResolvedTarget"));