fix: preserve undo for attachment link deletion

This commit is contained in:
lix-2026
2026-05-24 19:55:37 +08:00
parent 9c4bb58257
commit cafd29a963
9 changed files with 401 additions and 82 deletions
+104 -68
View File
@@ -9,6 +9,7 @@ use leptos_tiptap::{
TiptapNodeName, TiptapRange, TiptapSchemaTarget, TiptapSelectionState, TiptapTextAlign,
TiptapTocNodeAttrs,
};
use send_wrapper::SendWrapper;
use serde::{Deserialize, Serialize};
use serde_json::{json, Map, Value};
use std::{any::Any, cell::Cell, collections::HashMap, fmt::Display};
@@ -5261,14 +5262,19 @@ fn hovered_block_from_selection() -> Option<HoveredBlockState> {
block_state_from_index(index)
}
fn selected_local_attachment_block_index() -> Option<usize> {
let selection = window().and_then(|win| win.get_selection().ok().flatten())?;
fn selected_local_attachment_link_text() -> bool {
let selection = match window().and_then(|win| win.get_selection().ok().flatten()) {
Some(selection) if !selection.is_collapsed() => selection,
_ => return false,
};
let selected_text = String::from(selection.to_string()).trim().to_string();
if selected_text.is_empty() {
return None;
return false;
}
let root = editor_root_element()?;
let Some(root) = editor_root_element() else {
return false;
};
let links = root.get_elements_by_tag_name("a");
for index in 0..links.length() {
let Some(link) = links.item(index) else {
@@ -5278,15 +5284,49 @@ fn selected_local_attachment_block_index() -> Option<usize> {
if !href.contains("/api/local-folder/files/open") {
continue;
}
let text = link.text_content().unwrap_or_default();
if text.trim() != selected_text {
continue;
if link.text_content().unwrap_or_default().trim() == selected_text {
return true;
}
let block = direct_block_from_element(link, &root)?;
return top_level_block_index(&root, &block);
}
None
false
}
fn selected_local_attachment_link_in_editor_state() -> bool {
let Some(root) = editor_root_element() else {
return false;
};
let Ok(editor_value) = js_sys::Reflect::get(root.as_ref(), &JsValue::from_str("editor"))
else {
return false;
};
let Ok(state_value) = js_sys::Reflect::get(&editor_value, &JsValue::from_str("state")) else {
return false;
};
let Ok(selection_value) = js_sys::Reflect::get(&state_value, &JsValue::from_str("selection"))
else {
return false;
};
let is_empty = js_sys::Reflect::get(&selection_value, &JsValue::from_str("empty"))
.ok()
.and_then(|value| value.as_bool())
.unwrap_or(true);
if is_empty {
return false;
}
let Some(get_attributes) = js_sys::Reflect::get(&editor_value, &JsValue::from_str("getAttributes"))
.ok()
.and_then(|value| value.dyn_into::<js_sys::Function>().ok())
else {
return false;
};
let Ok(attributes) = get_attributes.call1(&editor_value, &JsValue::from_str("link")) else {
return false;
};
js_sys::Reflect::get(&attributes, &JsValue::from_str("href"))
.ok()
.and_then(|value| value.as_string())
.is_some_and(|href| href.contains("/api/local-folder/files/open"))
}
fn block_state_from_index(index: usize) -> Option<HoveredBlockState> {
@@ -8736,9 +8776,7 @@ fn App(mount_options: MountOptions) -> impl IntoView {
let editor = editor;
let document_id = document_id;
let workspace_id = workspace_id;
let dirty_count = dirty_count;
let title = title;
let editor_focused = editor_focused;
let set_dirty_count = set_dirty_count;
let set_html_output = set_html_output;
let set_document_json = set_document_json;
@@ -8759,9 +8797,6 @@ fn App(mount_options: MountOptions) -> impl IntoView {
let set_color_menu_open = set_color_menu_open;
let more_menu_open = more_menu_open;
let set_more_menu_open = set_more_menu_open;
let revision = revision;
let conflict_detection_key = conflict_detection_key;
let read_only = read_only;
let set_locked_toolbar_anchor = set_locked_toolbar_anchor;
let set_text_selection_active = set_text_selection_active;
let set_floating_toolbar_anchor = set_floating_toolbar_anchor;
@@ -8779,65 +8814,66 @@ fn App(mount_options: MountOptions) -> impl IntoView {
let set_block_turn_into_open = set_block_turn_into_open;
let set_block_folded_title_open = set_block_folded_title_open;
let set_command_feedback = set_command_feedback;
let keydown_capture_closure =
SendWrapper::new(Closure::wrap(Box::new(move |event: ev::KeyboardEvent| {
if !active_editor_stage()
|| event.ctrl_key()
|| event.meta_key()
|| event.alt_key()
|| !matches!(event.key().as_str(), "Backspace" | "Delete")
|| !(selected_local_attachment_link_in_editor_state()
|| selected_local_attachment_link_text())
{
return;
}
event.prevent_default();
event.stop_immediate_propagation();
match editor.delete_selection() {
Ok(()) => {
sync_persisted_editor_command(
editor,
&runtime_persisted_identity(document_id, workspace_id),
set_dirty_count,
set_html_output,
set_document_json,
set_json_output,
title,
set_command_feedback,
"已删除附件引用",
);
let _ = set_hovered_block.try_set(None);
let _ = set_block_menu_open.try_set(false);
let _ = set_block_menu_anchor.try_set(None);
}
Err(err) => {
let _ = set_command_feedback.try_set(format!("删除附件引用失败:{err}"));
}
}
}) as Box<dyn FnMut(_)>));
if let Some(win) = window() {
let _ = win.add_event_listener_with_callback_and_bool(
"keydown",
keydown_capture_closure.as_ref().unchecked_ref(),
true,
);
}
on_cleanup(move || {
if let Some(win) = window() {
let _ = win.remove_event_listener_with_callback_and_bool(
"keydown",
keydown_capture_closure.as_ref().unchecked_ref(),
true,
);
}
drop(keydown_capture_closure);
});
let keydown_handle = window_event_listener(ev::keydown, move |event| {
let focused = active_editor_stage();
if !focused {
return;
}
if !event.ctrl_key()
&& !event.meta_key()
&& !event.alt_key()
&& matches!(event.key().as_str(), "Backspace" | "Delete")
{
if let Some(block_index) = selected_local_attachment_block_index() {
event.prevent_default();
match editor.get_html() {
Ok(current_html) => {
match delete_top_level_block_html(&current_html, block_index) {
Ok(next_html) => {
apply_html_update(
editor,
&runtime_persisted_identity(document_id, workspace_id),
next_html,
document_id,
workspace_id,
dirty_count,
set_dirty_count,
set_html_output,
set_document_json,
set_json_output,
title,
hovered_block,
editor_focused,
slash_open,
turn_into_open,
color_menu_open,
more_menu_open,
revision,
conflict_detection_key,
read_only,
set_command_feedback,
"已删除附件引用",
);
let _ = set_hovered_block.try_set(None);
let _ = set_block_menu_open.try_set(false);
let _ = set_block_menu_anchor.try_set(None);
}
Err(err) => {
let _ = set_command_feedback.try_set(err);
}
}
}
Err(err) => {
let _ =
set_command_feedback.try_set(format!("读取当前 HTML 失败:{err}"));
}
}
return;
}
}
if (event.ctrl_key() || event.meta_key())
&& event.shift_key()
&& event.key().eq_ignore_ascii_case("u")