From 1e686bfa3c342b2e9761cdbbbf873ca6392c3090 Mon Sep 17 00:00:00 2001 From: lix-2026 Date: Sun, 19 Apr 2026 22:09:13 +0800 Subject: [PATCH] =?UTF-8?q?=E9=A2=9C=E8=89=B2=E4=BF=AE=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- rust/spikes/leptos-tiptap-spike/src/main.rs | 425 +++++++++++--------- 1 file changed, 225 insertions(+), 200 deletions(-) diff --git a/rust/spikes/leptos-tiptap-spike/src/main.rs b/rust/spikes/leptos-tiptap-spike/src/main.rs index 15b5f071..d145a7a9 100644 --- a/rust/spikes/leptos-tiptap-spike/src/main.rs +++ b/rust/spikes/leptos-tiptap-spike/src/main.rs @@ -1209,6 +1209,16 @@ fn target_element(target: web_sys::EventTarget) -> Option { }) } +fn event_target_matches_selector( + target: Option, + selector: &str, +) -> bool { + target + .and_then(target_element) + .and_then(|element| element.closest(selector).ok().flatten()) + .is_some() +} + fn current_target_html_element(event: &WheelEvent) -> Option { event.current_target()?.dyn_into::().ok() } @@ -2250,6 +2260,8 @@ fn App() -> impl IntoView { let (text_selection_active, set_text_selection_active) = signal(false); let (floating_toolbar_anchor, set_floating_toolbar_anchor) = signal(None::); + let (locked_toolbar_anchor, set_locked_toolbar_anchor) = + signal(None::); let (slash_open, set_slash_open) = signal(false); let (slash_index, set_slash_index) = signal(0_usize); let (turn_into_open, set_turn_into_open) = signal(false); @@ -2294,6 +2306,18 @@ fn App() -> impl IntoView { }); } + { + let turn_into_open = turn_into_open; + let color_menu_open = color_menu_open; + let more_menu_open = more_menu_open; + let set_locked_toolbar_anchor = set_locked_toolbar_anchor; + Effect::new(move |_| { + if !(turn_into_open.get() || color_menu_open.get() || more_menu_open.get()) { + set_locked_toolbar_anchor.set(None); + } + }); + } + { let editor_focused = editor_focused; let text_selection_active = text_selection_active; @@ -2311,8 +2335,7 @@ fn App() -> impl IntoView { color_menu_open.get_untracked(), more_menu_open.get_untracked(), ); - if !editor_focused.get() - || (!text_selection_active.get() && !toolbar_locked) + if ((!editor_focused.get() || !text_selection_active.get()) && !toolbar_locked) || slash_open.get() || block_menu_open.get() { @@ -2390,7 +2413,7 @@ fn App() -> impl IntoView { if let Some(target) = event.target() { if let Some(element) = target_element(target) { if element - .closest(HANDLE_SHELL_SELECTOR) + .closest(&format!("{HANDLE_SHELL_SELECTOR}, .floating-toolbar")) .ok() .flatten() .is_some() @@ -2808,14 +2831,20 @@ fn App() -> impl IntoView { } > {move || { - if let Some(anchor) = floating_toolbar_anchor.get() { - let toolbar_locked = toolbar_overlay_locked( - turn_into_open.get(), - color_menu_open.get(), - more_menu_open.get(), - ); - if !editor_focused.get() - || (!text_selection_active.get() && !toolbar_locked) + let toolbar_locked = toolbar_overlay_locked( + turn_into_open.get(), + color_menu_open.get(), + more_menu_open.get(), + ); + let toolbar_anchor = if toolbar_locked { + locked_toolbar_anchor + .get() + .or_else(|| floating_toolbar_anchor.get()) + } else { + floating_toolbar_anchor.get() + }; + if let Some(anchor) = toolbar_anchor { + if ((!editor_focused.get() || !text_selection_active.get()) && !toolbar_locked) || slash_open.get() || block_menu_open.get() || dragging_block_index.get().is_some() @@ -2829,7 +2858,12 @@ fn App() -> impl IntoView { style:top=format!("{}px", anchor.top) style:left=format!("{}px", anchor.left) on:mousedown=move |event: MouseEvent| { - event.prevent_default(); + if event_target_matches_selector( + event.target(), + ".toolbar-btn, .toolbar-menu-item, .turn-into-item", + ) { + event.prevent_default(); + } event.stop_propagation(); } on:click=move |event: MouseEvent| { @@ -2844,54 +2878,51 @@ fn App() -> impl IntoView { data-active=move || turn_into_open.get().to_string() data-testid="toolbar-turn-into" on:click=move |_| { - set_turn_into_open.update(|open| *open = !*open); + let next_open = !turn_into_open.get_untracked(); + if next_open { + set_locked_toolbar_anchor + .set(floating_toolbar_anchor.get_untracked()); + } + set_turn_into_open.set(next_open); set_block_menu_open.set(false); } > "Text" - {move || { - if turn_into_open.get() { +
+ {SLASH_ACTIONS.iter().map(|action| { + let kind = action.kind; + let label = action.label; + let description = action.description; + let testid = format!("turn-into-{}", action.id); view! { -
- {SLASH_ACTIONS.iter().map(|action| { - let kind = action.kind; - let label = action.label; - let description = action.description; - let testid = format!("turn-into-{}", action.id); - view! { - - } - }).collect_view()} -
- }.into_any() - } else { - ().into_any() - } - }} + {label} + {description} + + } + }).collect_view()} +
@@ -2959,113 +2990,110 @@ fn App() -> impl IntoView { || color_menu_open.get()).to_string() data-testid="toolbar-color" on:click=move |_| { - set_color_menu_open.update(|open| *open = !*open); + let next_open = !color_menu_open.get_untracked(); + if next_open { + set_locked_toolbar_anchor + .set(floating_toolbar_anchor.get_untracked()); + } + set_color_menu_open.set(next_open); set_more_menu_open.set(false); set_turn_into_open.set(false); } > "Color" - {move || { - if color_menu_open.get() { - view! { -
-
-
"文字颜色"
-
- {TOOLBAR_TEXT_COLORS.iter().map(|option| { - let value = option.value; - let label = option.label; - let testid = format!("toolbar-text-color-{}", option.id); - view! { - - } - }).collect_view()} -
-
- -
-
"背景颜色"
-
- {TOOLBAR_HIGHLIGHT_COLORS.iter().map(|option| { - let value = option.value; - let label = option.label; - let testid = format!("toolbar-highlight-color-{}", option.id); - view! { - - } - }).collect_view()} -
-
- -
+
+
+
"文字颜色"
+
+ {TOOLBAR_TEXT_COLORS.iter().map(|option| { + let value = option.value; + let label = option.label; + let testid = format!("toolbar-text-color-{}", option.id); + view! { -
-
- }.into_any() - } else { - ().into_any() - } - }} + } + }).collect_view()} +
+
+ +
+
"背景颜色"
+
+ {TOOLBAR_HIGHLIGHT_COLORS.iter().map(|option| { + let value = option.value; + let label = option.label; + let testid = format!("toolbar-highlight-color-{}", option.id); + view! { + + } + }).collect_view()} +
+
+ +
+ +
+
@@ -3082,69 +3110,66 @@ fn App() -> impl IntoView { || more_menu_open.get()).to_string() data-testid="toolbar-more" on:click=move |_| { - set_more_menu_open.update(|open| *open = !*open); + let next_open = !more_menu_open.get_untracked(); + if next_open { + set_locked_toolbar_anchor + .set(floating_toolbar_anchor.get_untracked()); + } + set_more_menu_open.set(next_open); set_color_menu_open.set(false); set_turn_into_open.set(false); } > "More" - {move || { - if more_menu_open.get() { - view! { -
-
-
"对齐方式"
-
- {TOOLBAR_ALIGN_OPTIONS.iter().map(|option| { - let label = option.label; - let alignment = option.alignment; - let testid = format!("toolbar-align-{}", option.id); - let active = match option.alignment { - TiptapTextAlign::Left => selection_state.get().align_left, - TiptapTextAlign::Center => selection_state.get().align_center, - TiptapTextAlign::Right => selection_state.get().align_right, - TiptapTextAlign::Justify => selection_state.get().align_justify, - }; - view! { - - } - }).collect_view()} -
-
-
- }.into_any() - } else { - ().into_any() +
+
+
"对齐方式"
+
+ {TOOLBAR_ALIGN_OPTIONS.iter().map(|option| { + let label = option.label; + let alignment = option.alignment; + let testid = format!("toolbar-align-{}", option.id); + let active = match option.alignment { + TiptapTextAlign::Left => selection_state.get().align_left, + TiptapTextAlign::Center => selection_state.get().align_center, + TiptapTextAlign::Right => selection_state.get().align_right, + TiptapTextAlign::Justify => selection_state.get().align_justify, + }; + view! { + + } + }).collect_view()} +
+
+