refactor: extract editor dom selection runtime

This commit is contained in:
lix-2026
2026-05-25 00:48:37 +08:00
parent ec623c9a82
commit e20c0f95ff
7 changed files with 147 additions and 105 deletions
@@ -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: 504, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
// Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [NamedExternref("KeyboardEvent")], shim_idx: 312, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
const ret = makeMutClosure(arg0, arg1, wasm_bindgen__convert__closures_____invoke__hd90af689bc3e71bf);
return ret;
},
@@ -0,0 +1,105 @@
use leptos_tiptap::TiptapSelectionState;
use web_sys::{window, Element, Node};
use crate::editor_root_element;
pub(crate) fn node_is_within_root(node: Node, root: &Element) -> bool {
let mut current = Some(node);
while let Some(node) = current {
if node.is_same_node(Some(root)) {
return true;
}
current = node.parent_node();
}
false
}
pub(crate) fn active_editor_text_selection() -> Option<web_sys::Selection> {
let selection = window().and_then(|win| win.get_selection().ok().flatten())?;
if selection.is_collapsed() {
return None;
}
let root = editor_root_element()?;
let anchor_inside = selection
.anchor_node()
.map(|node| node_is_within_root(node, &root))
.unwrap_or(false);
let focus_inside = selection
.focus_node()
.map(|node| node_is_within_root(node, &root))
.unwrap_or(false);
if anchor_inside || focus_inside {
Some(selection)
} else {
None
}
}
pub(crate) fn has_active_text_selection() -> bool {
active_editor_text_selection().is_some()
}
pub(crate) fn selection_summary(selection: &TiptapSelectionState) -> String {
let block = if selection.h1 {
"一级标题"
} else if selection.h2 {
"二级标题"
} else if selection.h3 {
"三级标题"
} else if selection.task_list {
"Todo 列表"
} else if selection.ordered_list {
"有序列表"
} else if selection.bullet_list {
"无序列表"
} else if selection.blockquote {
"引用块"
} else if selection.paragraph {
"段落"
} else {
"未定位"
};
let mut marks = Vec::new();
if selection.bold {
marks.push("Bold");
}
if selection.italic {
marks.push("Italic");
}
if selection.underline {
marks.push("Underline");
}
if selection.strike {
marks.push("Strike");
}
if selection.highlight {
marks.push("Highlight");
}
if selection.text_style {
marks.push("Color");
}
if selection.link {
marks.push("Link");
}
let mark_text = if marks.is_empty() {
"无行内样式".to_string()
} else {
marks.join(" / ")
};
format!("当前块:{block} | 行内样式:{mark_text}")
}
pub(crate) fn selection_has_rich_marks(selection: &TiptapSelectionState) -> bool {
selection.bold
|| selection.italic
|| selection.underline
|| selection.strike
|| selection.highlight
|| selection.text_style
|| selection.link
}
@@ -1,5 +1,6 @@
pub(crate) mod attachment_links;
pub(crate) mod attachment_upload;
pub(crate) mod command_sync;
pub(crate) mod dom_selection;
pub(crate) mod history_safe_commands;
pub(crate) mod persistence;
+5 -102
View File
@@ -24,6 +24,10 @@ use editor_runtime::attachment_upload::dispatch_editor_upload_request;
use editor_runtime::command_sync::{
read_editor_snapshot, sync_editor_outputs, sync_persisted_editor_command,
};
use editor_runtime::dom_selection::{
active_editor_text_selection, has_active_text_selection, node_is_within_root,
selection_summary,
};
use editor_runtime::persistence::{
load_persisted_document, normalize_identity_value, persist_document_state,
persisted_document_identity, PersistedDocumentIdentity,
@@ -5015,7 +5019,7 @@ where
dispatch_runtime_event(event_name, payload);
}
fn editor_root_element() -> Option<Element> {
pub(crate) fn editor_root_element() -> Option<Element> {
window()
.and_then(|win| win.document())
.and_then(|document| document.query_selector(EDITOR_ROOT_SELECTOR).ok().flatten())
@@ -5334,40 +5338,6 @@ fn top_level_block_range(document: &Value, index: usize) -> Option<TiptapRange>
None
}
fn node_is_within_root(node: Node, root: &Element) -> bool {
let mut current = Some(node);
while let Some(node) = current {
if node.is_same_node(Some(root)) {
return true;
}
current = node.parent_node();
}
false
}
fn active_editor_text_selection() -> Option<web_sys::Selection> {
let selection = window().and_then(|win| win.get_selection().ok().flatten())?;
if selection.is_collapsed() {
return None;
}
let root = editor_root_element()?;
let anchor_inside = selection
.anchor_node()
.map(|node| node_is_within_root(node, &root))
.unwrap_or(false);
let focus_inside = selection
.focus_node()
.map(|node| node_is_within_root(node, &root))
.unwrap_or(false);
if anchor_inside || focus_inside {
Some(selection)
} else {
None
}
}
fn floating_toolbar_anchor_from_selection() -> Option<FloatingToolbarAnchor> {
let selection = active_editor_text_selection()?;
let range = selection.get_range_at(0).ok()?;
@@ -5866,10 +5836,6 @@ fn replace_top_level_block_kind(
Ok(())
}
fn has_active_text_selection() -> bool {
active_editor_text_selection().is_some()
}
fn toolbar_overlay_locked(
turn_into_open: bool,
color_menu_open: bool,
@@ -6534,59 +6500,6 @@ fn active_editor_stage() -> bool {
anchor_inside || focus_inside
}
fn selection_summary(selection: &TiptapSelectionState) -> String {
let block = if selection.h1 {
"一级标题"
} else if selection.h2 {
"二级标题"
} else if selection.h3 {
"三级标题"
} else if selection.task_list {
"Todo 列表"
} else if selection.ordered_list {
"有序列表"
} else if selection.bullet_list {
"无序列表"
} else if selection.blockquote {
"引用块"
} else if selection.paragraph {
"段落"
} else {
"未定位"
};
let mut marks = Vec::new();
if selection.bold {
marks.push("Bold");
}
if selection.italic {
marks.push("Italic");
}
if selection.underline {
marks.push("Underline");
}
if selection.strike {
marks.push("Strike");
}
if selection.highlight {
marks.push("Highlight");
}
if selection.text_style {
marks.push("Color");
}
if selection.link {
marks.push("Link");
}
let mark_text = if marks.is_empty() {
"无行内样式".to_string()
} else {
marks.join(" / ")
};
format!("当前块:{block} | 行内样式:{mark_text}")
}
fn current_block_info_from_index(index: Option<usize>) -> CurrentBlockInfo {
index
.map(|value| CurrentBlockInfo {
@@ -6860,16 +6773,6 @@ fn bridge_ready_payload() -> ReadyPayload {
}
}
fn selection_has_rich_marks(selection: &TiptapSelectionState) -> bool {
selection.bold
|| selection.italic
|| selection.underline
|| selection.strike
|| selection.highlight
|| selection.text_style
|| selection.link
}
fn apply_text_color(editor: TiptapEditorHandle, color: &str) -> Result<&'static str, String> {
editor
.set_color(TiptapColorAttributes {