refactor(write): address code review feedback

- remove duplicate `use dioxus::prelude::*` (keep #[allow] inline with comment)
- update write_editor doc comment: callback-driven init, EditorHandle::drop cleanup
  (no more polling/globals)
- restore empty-url check in make_upload_closure (match original JS `success && url`):
  empty url on success now rejects with clear message instead of inserting broken image
- update AGENTS.md tiptap section: write.rs uses tiptap_bridge, no eval/polling
This commit is contained in:
xfy 2026-06-23 13:32:34 +08:00
parent a3896c24a5
commit a6d30ed5d9
3 changed files with 13 additions and 8 deletions

View File

@ -122,7 +122,7 @@ Rich-text editor in `libs/tiptap-editor/`, built as an IIFE library exposing `wi
- Output: `public/tiptap/` - Output: `public/tiptap/`
- `make build` runs `npm install && npx vite build` inside `libs/tiptap-editor` - `make build` runs `npm install && npx vite build` inside `libs/tiptap-editor`
- `src/pages/admin/write.rs` initializes via `js_sys::eval`, polls `window.__tiptap_ready` - `src/pages/admin/write.rs` initializes via `src/tiptap_bridge.rs` (wasm-bindgen bindings): injects `Closure` callbacks (`onUpdate`/`onReady`/`onUploadEvent`/`onImageUpload`) into `TiptapEditor.create`, holds the instance + closures in `EditorHandle` (Drop calls `destroy()`). No `js_sys::eval`, no `window` globals, no polling.
Do not edit `public/tiptap/` — they are build artifacts. Do not edit `public/tiptap/` — they are build artifacts.

View File

@ -4,6 +4,9 @@
//! 编辑器通过 [`crate::tiptap_bridge`] 的 wasm-bindgen 绑定在 WASM 前端初始化, //! 编辑器通过 [`crate::tiptap_bridge`] 的 wasm-bindgen 绑定在 WASM 前端初始化,
//! 并与 `window.TiptapEditor` 实例交互,实现 Markdown 内容回填、图片上传与组件卸载时的清理。 //! 并与 `window.TiptapEditor` 实例交互,实现 Markdown 内容回填、图片上传与组件卸载时的清理。
// prelude 在 WASM 构建里直接使用use_signal/Signal/Element 等);
// server 构建里 #[component] 宏会重新导出这些符号导致 native 报 unused故 allow。
#[allow(unused_imports)]
use dioxus::prelude::*; use dioxus::prelude::*;
// 仅在 WASM 前端使用的类型转换与文章 API。 // 仅在 WASM 前端使用的类型转换与文章 API。
@ -21,9 +24,6 @@ use crate::router::Route;
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]
use wasm_bindgen::closure::Closure; use wasm_bindgen::closure::Closure;
#[allow(unused_imports)]
use dioxus::prelude::*;
/// 新建文章页面组件。 /// 新建文章页面组件。
/// ///
/// 内部委托给 `write_editor`,以 `None` 表示新建模式。 /// 内部委托给 `write_editor`,以 `None` 表示新建模式。
@ -46,10 +46,10 @@ pub fn WriteEdit(id: i32) -> Element {
/// ///
/// 负责: /// 负责:
/// - 编辑模式下通过 server function 拉取文章数据; /// - 编辑模式下通过 server function 拉取文章数据;
/// - 在 WASM 前端初始化 Tiptap 富文本编辑器并轮询就绪状态 /// - 在 WASM 前端通过 tiptap_bridge 的 closure 回调初始化 Tiptap 富文本编辑器;
/// - 编辑模式下将 Markdown 内容回填到编辑器; /// - 编辑模式下将 Markdown 内容回填到编辑器;
/// - 提交时读取编辑器 Markdown、校验并调用 create_post / update_post /// - 提交时读取编辑器 Markdown、校验并调用 create_post / update_post
/// - 组件卸载时销毁 Tiptap 实例并清理全局状态 /// - 组件卸载时销毁 Tiptap 实例EditorHandle::drop 自动 destroy + 释放 closure
#[allow(unused_mut, unused_variables)] #[allow(unused_mut, unused_variables)]
fn write_editor(post_id: Option<i32>) -> Element { fn write_editor(post_id: Option<i32>) -> Element {
let is_edit = post_id.is_some(); let is_edit = post_id.is_some();

View File

@ -281,8 +281,13 @@ pub mod wasm {
serde_json::from_str(&text).unwrap_or(serde_json::Value::Null); serde_json::from_str(&text).unwrap_or(serde_json::Value::Null);
if data["success"].as_bool() == Some(true) { if data["success"].as_bool() == Some(true) {
let url = data["url"].as_str().unwrap_or("").to_string(); let url = data["url"].as_str().unwrap_or("");
if !url.is_empty() {
Ok(js_sys::JsString::from(url).into()) Ok(js_sys::JsString::from(url).into())
} else {
// success=true 但 url 为空:服务端契约异常,按失败处理
Err(js_sys::Error::new("上传成功但未返回图片地址").into())
}
} else { } else {
// 失败:优先用服务端中文 error兜底用状态码 // 失败:优先用服务端中文 error兜底用状态码
let msg = data["error"] let msg = data["error"]