diff --git a/design/05-editor-mainline/process/5-28-leptos-tiptap-editor-runtime-module-extraction-v1.md b/design/05-editor-mainline/process/5-28-leptos-tiptap-editor-runtime-module-extraction-v1.md index 810c4c1f..d3f0fcf8 100644 --- a/design/05-editor-mainline/process/5-28-leptos-tiptap-editor-runtime-module-extraction-v1.md +++ b/design/05-editor-mainline/process/5-28-leptos-tiptap-editor-runtime-module-extraction-v1.md @@ -120,13 +120,16 @@ rust/spikes/leptos-tiptap-spike/src/ ### P2:抽出 command sync / persistence -- [ ] 新增 `editor_runtime/command_sync.rs`。 -- [ ] 迁出: +- [x] 新增 `editor_runtime/command_sync.rs`。 +- [x] 新增 `editor_runtime/persistence.rs`。 +- [x] 迁出: - `read_editor_snapshot` - `sync_editor_outputs` - `sync_persisted_editor_command` - - change/status event dispatch 的编辑器侧包装 -- [ ] 明确 `set_content` 只允许用于: + - `apply_document_update`(当前保持零调用者现状) +- [ ] 迁出 change/status event dispatch 的编辑器侧包装。 + - 2026-05-24 复核:`apply_html_update` 仍依赖 `ChangePayload` / `ChangeMetaPayload`、`dispatch_change_event`、`dispatch_runtime_state`、`HoveredBlockState` 和 toolbar overlay state,暂留 `lib.rs`,等待 P4 `bridge_events.rs` / `overlays.rs` 后再迁。 +- [x] 明确 `set_content` 只允许用于: - 初始加载 - host 替换文档 - 外部同步/恢复 @@ -193,6 +196,8 @@ rust/spikes/leptos-tiptap-spike/src/ 允许读取/修改: - rust/spikes/leptos-tiptap-spike/src/editor_runtime/attachment_links.rs - rust/spikes/leptos-tiptap-spike/src/editor_runtime/history_safe_commands.rs +- rust/spikes/leptos-tiptap-spike/src/editor_runtime/command_sync.rs +- rust/spikes/leptos-tiptap-spike/src/editor_runtime/persistence.rs - scripts/task488-local-attachment-link-delete-undo-smoke.js 禁止: @@ -290,6 +295,12 @@ Codex 主控复核修正: - `cargo check --manifest-path rust/spikes/leptos-tiptap-spike/Cargo.toml`(35 个既有 warning) - `cargo build --manifest-path rust/spikes/leptos-tiptap-spike/Cargo.toml --target wasm32-unknown-unknown --release` - `wasm-bindgen rust/spikes/leptos-tiptap-spike/target/wasm32-unknown-unknown/release/mnote_leptos_tiptap_spike.wasm --target web --out-dir rust/spikes/leptos-tiptap-spike/generated/island --out-name mnote-leptos-tiptap-spike-island` +- `node scripts/task488-local-attachment-link-delete-undo-smoke.js` + - 结果:`ok=true` + - 证据:`tmp/task488-local-attachment-link-delete-undo-smoke/result.json` +- `node scripts/task479-local-folder-markdown-resource-lifecycle-smoke.js` + - 结果:`ok=true` + - 证据:命令 stdout JSON,覆盖 `editor-upload-to-page-resource-dir`、`filetree-folder-drop-to-target`、`broken-link-after-real-file-delete`、`direct-attach-open-without-active-md`、`editor-delete-one-attachment-preserves-adjacent` 等检查。 - `node scripts/task488-local-attachment-link-delete-undo-smoke.js` - 结果:`ok=true` - 步骤:`uploaded -> selected -> deleted -> undo -> redo` @@ -301,6 +312,41 @@ Codex 主控复核修正: - 结果:索引 318 个文件,状态 up to date。 - 复核:`delete_selected_attachment_link_with_history`、`selected_local_attachment_link_text`、`is_local_attachment_link_selected` 均可通过 CodeGraph 搜索定位到 `editor_runtime/*`。 +### 10.3 Batch 2 执行记录(2026-05-24) + +Reasonix read-only audit: + +- run id: `reasonix-2026-05-24T15-01-33-138Z-dc32246a` +- worker worktree: `/mnt/Data1T/mnote-wt-5-28-command-sync-b2` +- handoff: `/home/lix/.codex/runtime/reasonix-coding-worker/reasonix-2026-05-24T15-01-33-138Z-dc32246a/process-handoff.md` +- 类型:只读审计,主控已读取 Hindsight recall、`process-handoff.md`、`process-handoff.json`。 + +采纳内容: + +- 新增 `editor_runtime/command_sync.rs`,迁出 `read_editor_snapshot`、`sync_editor_outputs`、`sync_persisted_editor_command`。 +- 新增 `editor_runtime/persistence.rs`,迁出 `PersistedSpikeDocument`、`PersistedDocumentIdentity`、identity normalization、storage key、localStorage load/persist。 +- `apply_document_update` 迁入 `command_sync.rs`,保持迁移前零调用者现状。 +- `apply_html_update` 暂留 `lib.rs`:它仍强依赖 host change/status event、`HoveredBlockState` 和 toolbar overlay state;后续等 `bridge_events.rs` / `overlays.rs` 拆出后再迁。 + +Codex 主控复核: + +- `persist_document_state` 的 payload、storage key、`localStorage` 写入错误文案保持不变。 +- `sync_persisted_editor_command` 现在委托 `persist_current_editor_snapshot(...)`,dirty count、自身快照读取、HTML/JSON output 更新和反馈文案顺序保持不变。 +- `normalize_identity_value` 改为 `pub(crate)`,用于 mindmap draft identity;`persisted_document_storage_key` 只在 `#[cfg(test)]` 下导入。 + +已通过验证: + +- `cargo fmt --manifest-path rust/Cargo.toml --all --check` +- `cargo check --manifest-path rust/spikes/leptos-tiptap-spike/Cargo.toml`(35 个既有 warning) +- `cargo build --manifest-path rust/spikes/leptos-tiptap-spike/Cargo.toml --target wasm32-unknown-unknown --release`(35 个既有 warning) +- `wasm-bindgen rust/spikes/leptos-tiptap-spike/target/wasm32-unknown-unknown/release/mnote_leptos_tiptap_spike.wasm --target web --out-dir rust/spikes/leptos-tiptap-spike/generated/island --out-name mnote-leptos-tiptap-spike-island` + +受阻验证: + +- `cargo test --manifest-path rust/spikes/leptos-tiptap-spike/Cargo.toml persisted_document_storage_key_isolated_per_document_identity` +- `cargo test --manifest-path rust/spikes/leptos-tiptap-spike/Cargo.toml standalone_mindmap_object_uses_isolated_draft_identity` +- 阻塞原因:lib test 编译阶段命中既有 `slash_menu_css_uses_viewport_overlay_layer` 中 `STYLE` 未定义;HEAD 中该测试已引用 `STYLE`,而实际常量名为 `SPIKE_STYLE`。该问题不属于本批拆分,已记录为后续单独处理。 + ## 11. 测试与验收 ### Rust 检查 diff --git a/rust/spikes/leptos-tiptap-spike/generated/island/mnote-leptos-tiptap-spike-island.js b/rust/spikes/leptos-tiptap-spike/generated/island/mnote-leptos-tiptap-spike-island.js index 4aed409a..14ea47e7 100644 --- a/rust/spikes/leptos-tiptap-spike/generated/island/mnote-leptos-tiptap-spike-island.js +++ b/rust/spikes/leptos-tiptap-spike/generated/island/mnote-leptos-tiptap-spike-island.js @@ -1284,7 +1284,7 @@ function __wbg_get_imports() { return ret; }, __wbindgen_cast_0000000000000006: function(arg0, arg1) { - // Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [NamedExternref("KeyboardEvent")], shim_idx: 745, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`. + // Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [NamedExternref("KeyboardEvent")], shim_idx: 758, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`. const ret = makeMutClosure(arg0, arg1, wasm_bindgen__convert__closures_____invoke__hd90af689bc3e71bf); return ret; }, diff --git a/rust/spikes/leptos-tiptap-spike/generated/island/mnote-leptos-tiptap-spike-island_bg.wasm b/rust/spikes/leptos-tiptap-spike/generated/island/mnote-leptos-tiptap-spike-island_bg.wasm index d385e0f2..efdf6d05 100644 Binary files a/rust/spikes/leptos-tiptap-spike/generated/island/mnote-leptos-tiptap-spike-island_bg.wasm and b/rust/spikes/leptos-tiptap-spike/generated/island/mnote-leptos-tiptap-spike-island_bg.wasm differ diff --git a/rust/spikes/leptos-tiptap-spike/src/editor_runtime/command_sync.rs b/rust/spikes/leptos-tiptap-spike/src/editor_runtime/command_sync.rs new file mode 100644 index 00000000..904870b4 --- /dev/null +++ b/rust/spikes/leptos-tiptap-spike/src/editor_runtime/command_sync.rs @@ -0,0 +1,122 @@ +use crate::editor_runtime::persistence::{persist_document_state, PersistedDocumentIdentity}; +use leptos::prelude::{GetUntracked, ReadSignal, Set, Update, WriteSignal}; +use leptos_tiptap::{TiptapContent, TiptapEditorHandle}; +use serde_json::{json, Value}; + +pub(crate) fn read_editor_snapshot(editor: TiptapEditorHandle) -> (String, Value, String) { + let html = editor + .get_html() + .unwrap_or_else(|err| format!("读取 HTML 失败:{err}")); + let json_value = editor.get_json().unwrap_or_else(|err| { + json!({ + "type": "error", + "message": format!("读取 JSON 失败:{err}"), + }) + }); + let json_text = serde_json::to_string_pretty(&json_value) + .unwrap_or_else(|err| format!("格式化 JSON 失败:{err}")); + (html, json_value, json_text) +} + +pub(crate) fn sync_editor_outputs( + editor: TiptapEditorHandle, + set_html_output: WriteSignal, + set_document_json: WriteSignal, + set_json_output: WriteSignal, +) -> Value { + let (html, json_value, json_text) = read_editor_snapshot(editor); + set_html_output.set(html); + set_document_json.set(json_value.clone()); + set_json_output.set(json_text); + json_value +} + +pub(crate) fn apply_document_update( + editor: TiptapEditorHandle, + persisted_identity: &PersistedDocumentIdentity, + next_document: Value, + set_dirty_count: WriteSignal, + set_html_output: WriteSignal, + set_document_json: WriteSignal, + set_json_output: WriteSignal, + title: ReadSignal, + set_command_feedback: WriteSignal, + success_message: impl Into, +) { + let success_message = success_message.into(); + let next_payload = next_document + .get("content") + .and_then(Value::as_array) + .cloned() + .map(Value::Array) + .unwrap_or_else(|| next_document.clone()); + match editor.set_content(TiptapContent::json(next_payload)) { + Ok(()) => { + persist_current_editor_snapshot( + editor, + persisted_identity, + set_dirty_count, + set_html_output, + set_document_json, + set_json_output, + title, + set_command_feedback, + success_message, + ); + } + Err(err) => { + set_command_feedback.set(format!("更新文档失败:{err}")); + } + } +} + +pub(crate) fn sync_persisted_editor_command( + editor: TiptapEditorHandle, + persisted_identity: &PersistedDocumentIdentity, + set_dirty_count: WriteSignal, + set_html_output: WriteSignal, + set_document_json: WriteSignal, + set_json_output: WriteSignal, + title: ReadSignal, + set_command_feedback: WriteSignal, + success_message: impl Into, +) { + persist_current_editor_snapshot( + editor, + persisted_identity, + set_dirty_count, + set_html_output, + set_document_json, + set_json_output, + title, + set_command_feedback, + success_message.into(), + ); +} + +fn persist_current_editor_snapshot( + editor: TiptapEditorHandle, + persisted_identity: &PersistedDocumentIdentity, + set_dirty_count: WriteSignal, + set_html_output: WriteSignal, + set_document_json: WriteSignal, + set_json_output: WriteSignal, + title: ReadSignal, + set_command_feedback: WriteSignal, + success_message: String, +) { + set_dirty_count.update(|count| *count += 1); + let (html, snapshot, json_text) = read_editor_snapshot(editor); + set_html_output.set(html.clone()); + set_document_json.set(snapshot.clone()); + set_json_output.set(json_text); + match persist_document_state( + persisted_identity, + &title.get_untracked(), + &snapshot, + Some(html), + ) { + Ok(()) => set_command_feedback.set(format!("{success_message},并已写入本地草稿")), + Err(err) => set_command_feedback.set(format!("{success_message},但本地保存失败:{err}")), + } +} diff --git a/rust/spikes/leptos-tiptap-spike/src/editor_runtime/mod.rs b/rust/spikes/leptos-tiptap-spike/src/editor_runtime/mod.rs index 96fe433c..2f2e0c27 100644 --- a/rust/spikes/leptos-tiptap-spike/src/editor_runtime/mod.rs +++ b/rust/spikes/leptos-tiptap-spike/src/editor_runtime/mod.rs @@ -1,2 +1,4 @@ pub(crate) mod attachment_links; +pub(crate) mod command_sync; pub(crate) mod history_safe_commands; +pub(crate) mod persistence; diff --git a/rust/spikes/leptos-tiptap-spike/src/editor_runtime/persistence.rs b/rust/spikes/leptos-tiptap-spike/src/editor_runtime/persistence.rs new file mode 100644 index 00000000..8c3a8917 --- /dev/null +++ b/rust/spikes/leptos-tiptap-spike/src/editor_runtime/persistence.rs @@ -0,0 +1,96 @@ +use serde::{Deserialize, Serialize}; +use serde_json::Value; +use web_sys::{window, Storage}; + +const SPIKE_STORAGE_KEY: &str = "mnote.leptos-tiptap-spike.document"; + +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub(crate) struct PersistedSpikeDocument { + #[serde(default)] + pub(crate) document_id: Option, + #[serde(default)] + pub(crate) workspace_id: Option, + pub(crate) title: String, + pub(crate) content: Value, + #[serde(default)] + pub(crate) html: Option, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub(crate) struct PersistedDocumentIdentity { + pub(crate) document_id: Option, + pub(crate) workspace_id: Option, +} + +fn local_storage() -> Option { + window().and_then(|win| win.local_storage().ok().flatten()) +} + +pub(crate) fn normalize_identity_value(value: Option) -> Option { + value.and_then(|value| { + let trimmed = value.trim(); + if trimmed.is_empty() { + None + } else { + Some(trimmed.to_string()) + } + }) +} + +pub(crate) fn persisted_document_identity( + document_id: Option, + workspace_id: Option, +) -> PersistedDocumentIdentity { + PersistedDocumentIdentity { + document_id: normalize_identity_value(document_id), + workspace_id: normalize_identity_value(workspace_id), + } +} + +pub(crate) fn persisted_document_storage_key(identity: &PersistedDocumentIdentity) -> String { + match (&identity.workspace_id, &identity.document_id) { + (Some(workspace_id), Some(document_id)) => { + format!("{SPIKE_STORAGE_KEY}:{workspace_id}:{document_id}") + } + (None, Some(document_id)) => format!("{SPIKE_STORAGE_KEY}:{document_id}"), + _ => SPIKE_STORAGE_KEY.to_string(), + } +} + +pub(crate) fn persist_document_state( + identity: &PersistedDocumentIdentity, + title: &str, + content: &Value, + html: Option, +) -> Result<(), String> { + let payload = PersistedSpikeDocument { + document_id: identity.document_id.clone(), + workspace_id: identity.workspace_id.clone(), + title: title.to_string(), + content: content.clone(), + html, + }; + let raw = serde_json::to_string(&payload).map_err(|err| format!("序列化草稿失败:{err}"))?; + local_storage() + .ok_or_else(|| "浏览器 localStorage 不可用".to_string())? + .set_item(&persisted_document_storage_key(identity), &raw) + .map_err(|err| format!("写入 localStorage 失败:{err:?}")) +} + +pub(crate) fn load_persisted_document( + identity: &PersistedDocumentIdentity, +) -> Option { + let raw = local_storage()? + .get_item(&persisted_document_storage_key(identity)) + .ok() + .flatten()?; + let document = serde_json::from_str::(&raw).ok()?; + let stored_identity = + persisted_document_identity(document.document_id.clone(), document.workspace_id.clone()); + if stored_identity == *identity || identity.document_id.is_none() { + Some(document) + } else { + None + } +} diff --git a/rust/spikes/leptos-tiptap-spike/src/lib.rs b/rust/spikes/leptos-tiptap-spike/src/lib.rs index 5194fb0d..fba668ea 100644 --- a/rust/spikes/leptos-tiptap-spike/src/lib.rs +++ b/rust/spikes/leptos-tiptap-spike/src/lib.rs @@ -17,13 +17,22 @@ use wasm_bindgen::{closure::Closure, prelude::*, JsCast, JsValue}; use wasm_bindgen_futures::{spawn_local, JsFuture}; use web_sys::{ window, CustomEvent, CustomEventInit, DragEvent, Element, Event, EventTarget, HtmlElement, - HtmlInputElement, MouseEvent, Node, RequestInit, RequestMode, Response, Storage, WheelEvent, + HtmlInputElement, MouseEvent, Node, RequestInit, RequestMode, Response, WheelEvent, }; +use editor_runtime::command_sync::{ + read_editor_snapshot, sync_editor_outputs, sync_persisted_editor_command, +}; +use editor_runtime::persistence::{ + load_persisted_document, normalize_identity_value, persist_document_state, + persisted_document_identity, PersistedDocumentIdentity, +}; +#[cfg(test)] +use editor_runtime::persistence::persisted_document_storage_key; + const EDITOR_STAGE_SELECTOR: &str = "[data-testid=\"mnote-leptos-tiptap-editor-stage\"]"; const EDITOR_ROOT_SELECTOR: &str = ".editor-surface .ProseMirror"; const HANDLE_SHELL_SELECTOR: &str = ".block-handle-shell"; -const SPIKE_STORAGE_KEY: &str = "mnote.leptos-tiptap-spike.document"; const HANDLE_MULTILINE_HEIGHT: f64 = 40.0; const CONTENT_COLUMN_MAX_WIDTH: f64 = 708.0; const CONTENT_COLUMN_HORIZONTAL_PADDING: f64 = 48.0; @@ -2996,25 +3005,6 @@ const SLASH_ACTIONS: [SlashAction; 22] = [ }, ]; -#[derive(Clone, Debug, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -struct PersistedSpikeDocument { - #[serde(default)] - document_id: Option, - #[serde(default)] - workspace_id: Option, - title: String, - content: Value, - #[serde(default)] - html: Option, -} - -#[derive(Clone, Debug, PartialEq, Eq)] -struct PersistedDocumentIdentity { - document_id: Option, - workspace_id: Option, -} - #[derive(Clone, Debug, Serialize)] #[serde(rename_all = "camelCase")] struct BridgeEnvelope @@ -4863,10 +4853,6 @@ struct PendingDragState { start_y: i32, } -fn local_storage() -> Option { - window().and_then(|win| win.local_storage().ok().flatten()) -} - fn default_title() -> String { "Leptos Tiptap 主编辑器 P0".to_string() } @@ -4887,27 +4873,6 @@ fn current_query_param(key: &str) -> Option { }) } -fn normalize_identity_value(value: Option) -> Option { - value.and_then(|value| { - let trimmed = value.trim(); - if trimmed.is_empty() { - None - } else { - Some(trimmed.to_string()) - } - }) -} - -fn persisted_document_identity( - document_id: Option, - workspace_id: Option, -) -> PersistedDocumentIdentity { - PersistedDocumentIdentity { - document_id: normalize_identity_value(document_id), - workspace_id: normalize_identity_value(workspace_id), - } -} - fn resolve_persisted_document_identity( explicit_document_id: Option, explicit_workspace_id: Option, @@ -4918,16 +4883,6 @@ fn resolve_persisted_document_identity( ) } -fn persisted_document_storage_key(identity: &PersistedDocumentIdentity) -> String { - match (&identity.workspace_id, &identity.document_id) { - (Some(workspace_id), Some(document_id)) => { - format!("{SPIKE_STORAGE_KEY}:{workspace_id}:{document_id}") - } - (None, Some(document_id)) => format!("{SPIKE_STORAGE_KEY}:{document_id}"), - _ => SPIKE_STORAGE_KEY.to_string(), - } -} - fn persisted_mindmap_object_identity( object: Option<&RuntimeStandaloneObject>, ) -> Option { @@ -4950,41 +4905,6 @@ fn runtime_persisted_identity( persisted_document_identity(document_id.get_untracked(), workspace_id.get_untracked()) } -fn persist_document_state( - identity: &PersistedDocumentIdentity, - title: &str, - content: &Value, - html: Option, -) -> Result<(), String> { - let payload = PersistedSpikeDocument { - document_id: identity.document_id.clone(), - workspace_id: identity.workspace_id.clone(), - title: title.to_string(), - content: content.clone(), - html, - }; - let raw = serde_json::to_string(&payload).map_err(|err| format!("序列化草稿失败:{err}"))?; - local_storage() - .ok_or_else(|| "浏览器 localStorage 不可用".to_string())? - .set_item(&persisted_document_storage_key(identity), &raw) - .map_err(|err| format!("写入 localStorage 失败:{err:?}")) -} - -fn load_persisted_document(identity: &PersistedDocumentIdentity) -> Option { - let raw = local_storage()? - .get_item(&persisted_document_storage_key(identity)) - .ok() - .flatten()?; - let document = serde_json::from_str::(&raw).ok()?; - let stored_identity = - persisted_document_identity(document.document_id.clone(), document.workspace_id.clone()); - if stored_identity == *identity || identity.document_id.is_none() { - Some(document) - } else { - None - } -} - fn url_requests_embedded_mode() -> bool { window() .and_then(|win| win.location().search().ok()) @@ -5094,34 +5014,6 @@ where dispatch_runtime_event(event_name, payload); } -fn read_editor_snapshot(editor: TiptapEditorHandle) -> (String, Value, String) { - let html = editor - .get_html() - .unwrap_or_else(|err| format!("读取 HTML 失败:{err}")); - let json_value = editor.get_json().unwrap_or_else(|err| { - json!({ - "type": "error", - "message": format!("读取 JSON 失败:{err}"), - }) - }); - let json_text = serde_json::to_string_pretty(&json_value) - .unwrap_or_else(|err| format!("格式化 JSON 失败:{err}")); - (html, json_value, json_text) -} - -fn sync_editor_outputs( - editor: TiptapEditorHandle, - set_html_output: WriteSignal, - set_document_json: WriteSignal, - set_json_output: WriteSignal, -) -> Value { - let (html, json_value, json_text) = read_editor_snapshot(editor); - set_html_output.set(html); - set_document_json.set(json_value.clone()); - set_json_output.set(json_text); - json_value -} - fn editor_root_element() -> Option { window() .and_then(|win| win.document()) @@ -6317,52 +6209,6 @@ fn try_sync_editor_overlay_state( true } -fn apply_document_update( - editor: TiptapEditorHandle, - persisted_identity: &PersistedDocumentIdentity, - next_document: Value, - set_dirty_count: WriteSignal, - set_html_output: WriteSignal, - set_document_json: WriteSignal, - set_json_output: WriteSignal, - title: ReadSignal, - set_command_feedback: WriteSignal, - success_message: impl Into, -) { - let success_message = success_message.into(); - let next_payload = next_document - .get("content") - .and_then(Value::as_array) - .cloned() - .map(Value::Array) - .unwrap_or_else(|| next_document.clone()); - match editor.set_content(TiptapContent::json(next_payload)) { - Ok(()) => { - set_dirty_count.update(|count| *count += 1); - let (html, snapshot, json_text) = read_editor_snapshot(editor); - set_html_output.set(html.clone()); - set_document_json.set(snapshot.clone()); - set_json_output.set(json_text); - match persist_document_state( - persisted_identity, - &title.get_untracked(), - &snapshot, - Some(html), - ) { - Ok(()) => { - set_command_feedback.set(format!("{success_message},并已写入本地草稿")); - } - Err(err) => { - set_command_feedback.set(format!("{success_message},但本地保存失败:{err}")); - } - } - } - Err(err) => { - set_command_feedback.set(format!("更新文档失败:{err}")); - } - } -} - fn apply_html_update( editor: TiptapEditorHandle, persisted_identity: &PersistedDocumentIdentity, @@ -6524,34 +6370,6 @@ fn insert_editor_paragraph_relative_to_block( Ok(new_index) } -fn sync_persisted_editor_command( - editor: TiptapEditorHandle, - persisted_identity: &PersistedDocumentIdentity, - set_dirty_count: WriteSignal, - set_html_output: WriteSignal, - set_document_json: WriteSignal, - set_json_output: WriteSignal, - title: ReadSignal, - set_command_feedback: WriteSignal, - success_message: impl Into, -) { - let success_message = success_message.into(); - set_dirty_count.update(|count| *count += 1); - let (html, snapshot, json_text) = read_editor_snapshot(editor); - set_html_output.set(html.clone()); - set_document_json.set(snapshot.clone()); - set_json_output.set(json_text); - match persist_document_state( - persisted_identity, - &title.get_untracked(), - &snapshot, - Some(html), - ) { - Ok(()) => set_command_feedback.set(format!("{success_message},并已写入本地草稿")), - Err(err) => set_command_feedback.set(format!("{success_message},但本地保存失败:{err}")), - } -} - fn focus_top_level_block_start(editor: TiptapEditorHandle, index: usize) -> Result<(), String> { editor .focus()