refactor: extract table toolbar view

This commit is contained in:
lix-2026
2026-05-25 20:40:55 +08:00
parent f8decfebd6
commit def143ea24
6 changed files with 418 additions and 277 deletions
@@ -406,14 +406,15 @@ UI / 浏览器可见项必须有真实浏览器截图或结构化 smoke 证据
| 文件 | 原大小 | 现大小 | 变化 |
|------|--------|--------|------|
| `lib.rs` | 9,873 行 | 8,189 行 | **-17%** |
| `lib.rs` | 9,873 行 | 7,951 行 | **-19%** |
| `editor_runtime/mindmap_node_view.rs` | — | 906 行 | 新建 |
| `editor_runtime/bridge_events.rs` | — | 154 行 | 新建 |
| `editor_runtime/slash_actions.rs` | — | 237 行 | 新建 |
| `editor_runtime/table_commands.rs` | — | 180 行 | 新建 |
| `editor_runtime/table_toolbar_view.rs` | — | 371 行 | 新建 |
| `editor_runtime/bridge_dispatch.rs` | — | 86 行 | 新建 |
### editor_runtime/ 最终模块清单(19 模块,另有 `mod.rs`
### editor_runtime/ 最终模块清单(20 模块,另有 `mod.rs`
| 模块 | 说明 | 来源 |
|------|------|------|
@@ -436,6 +437,7 @@ UI / 浏览器可见项必须有真实浏览器截图或结构化 smoke 证据
| `persistence.rs` | 持久化 | 5-28 P2 |
| `slash_actions.rs` | slash 菜单 action 数据 | **本批新建** |
| `table_commands.rs` | table toolbar / option 命令 | **本批新建** |
| `table_toolbar_view.rs` | table controls / toolbar / options view | **本批新建** |
### 未完成(后续独立任务)
@@ -464,3 +466,8 @@ UI / 浏览器可见项必须有真实浏览器截图或结构化 smoke 证据
-`should_auto_close_toolbar_overlays` 接回两个实际 auto-close 判断点,避免 helper 外置后仍有重复手写判定。
- 对必须持有生命周期但 Rust 编译器无法直接观察用途的 mount handle、host envelope 反序列化字段加局部 `#[allow(dead_code)]`,不扩大到模块级。
- 已验证:`cargo check --manifest-path rust/spikes/leptos-tiptap-spike/Cargo.toml` 无 warning`cargo test --manifest-path rust/spikes/leptos-tiptap-spike/Cargo.toml -- --test-threads=1` 13/13 通过。
- 2026-05-25Codex 完成 `table_toolbar_view.rs` 第一刀。
- 新增 `editor_runtime/table_toolbar_view.rs`,迁出 table controls、table toolbar 和 table options view`lib.rs` 保留 signal 创建与单一调用点。
- 边界:表格命令仍由 `table_commands.rs` 提供,持久化同步仍委托 `command_sync::sync_persisted_editor_command`,未修改 Tiptap table bridge 或保存语义。
- 已验证:`cargo fmt --manifest-path rust/Cargo.toml --all --check``cargo check --manifest-path rust/spikes/leptos-tiptap-spike/Cargo.toml``cargo test --manifest-path rust/spikes/leptos-tiptap-spike/Cargo.toml -- --test-threads=1` 13/13、wasm release build、`wasm-bindgen`
- 浏览器验证:`task158-e30-menu-state-smoke.js` 受旧 Convex mutation `127.0.0.1:3210` 503 阻塞;已用 Playwright 在 `http://127.0.0.1:3000` 登录测试账号后直接设置表格 fixture,确认 table toolbar、options menu、row aux、col aux 均可见。
@@ -1244,47 +1244,47 @@ function __wbg_get_imports() {
}
}, arguments); },
__wbindgen_cast_0000000000000001: function(arg0, arg1) {
// Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [Externref], shim_idx: 1143, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
// Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [Externref], shim_idx: 1148, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
const ret = makeMutClosure(arg0, arg1, wasm_bindgen__convert__closures_____invoke__h396914cf76a9e7a5);
return ret;
},
__wbindgen_cast_0000000000000002: function(arg0, arg1) {
// Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [Externref], shim_idx: 1195, ret: Result(Unit), inner_ret: Some(Result(Unit)) }, mutable: true }) -> Externref`.
// Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [Externref], shim_idx: 1200, ret: Result(Unit), inner_ret: Some(Result(Unit)) }, mutable: true }) -> Externref`.
const ret = makeMutClosure(arg0, arg1, wasm_bindgen__convert__closures_____invoke__h3a3182d847094e12);
return ret;
},
__wbindgen_cast_0000000000000003: function(arg0, arg1) {
// Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [Externref], shim_idx: 971, ret: Unit, inner_ret: Some(Unit) }, mutable: false }) -> Externref`.
// Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [Externref], shim_idx: 976, ret: Unit, inner_ret: Some(Unit) }, mutable: false }) -> Externref`.
const ret = makeClosure(arg0, arg1, wasm_bindgen__convert__closures_____invoke__had771ddc65647798);
return ret;
},
__wbindgen_cast_0000000000000004: function(arg0, arg1) {
// Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [NamedExternref("Event")], shim_idx: 1093, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
// Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [NamedExternref("Event")], shim_idx: 1098, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
const ret = makeMutClosure(arg0, arg1, wasm_bindgen__convert__closures_____invoke__h0abd2b2fe4652e2a);
return ret;
},
__wbindgen_cast_0000000000000005: function(arg0, arg1) {
// Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [NamedExternref("Event")], shim_idx: 1143, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
// Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [NamedExternref("Event")], shim_idx: 1148, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
const ret = makeMutClosure(arg0, arg1, wasm_bindgen__convert__closures_____invoke__h396914cf76a9e7a5_4);
return ret;
},
__wbindgen_cast_0000000000000006: function(arg0, arg1) {
// Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [NamedExternref("KeyboardEvent")], shim_idx: 879, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
// Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [NamedExternref("KeyboardEvent")], shim_idx: 747, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
const ret = makeMutClosure(arg0, arg1, wasm_bindgen__convert__closures_____invoke__hd90af689bc3e71bf);
return ret;
},
__wbindgen_cast_0000000000000007: function(arg0, arg1) {
// Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [], shim_idx: 1095, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
// Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [], shim_idx: 1100, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
const ret = makeMutClosure(arg0, arg1, wasm_bindgen__convert__closures_____invoke__h794babeffd4f821f);
return ret;
},
__wbindgen_cast_0000000000000008: function(arg0, arg1) {
// Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [], shim_idx: 1110, ret: Unit, inner_ret: Some(Unit) }, mutable: false }) -> Externref`.
// Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [], shim_idx: 1115, ret: Unit, inner_ret: Some(Unit) }, mutable: false }) -> Externref`.
const ret = makeClosure(arg0, arg1, wasm_bindgen__convert__closures_____invoke__h2b648e7ac8ec1fb5);
return ret;
},
__wbindgen_cast_0000000000000009: function(arg0, arg1) {
// Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [], shim_idx: 1146, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
// Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [], shim_idx: 1151, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
const ret = makeMutClosure(arg0, arg1, wasm_bindgen__convert__closures_____invoke__h0b7fb40a8610550c);
return ret;
},
@@ -17,3 +17,4 @@ pub(crate) mod overlays;
pub(crate) mod persistence;
pub(crate) mod slash_actions;
pub(crate) mod table_commands;
pub(crate) mod table_toolbar_view;
@@ -0,0 +1,371 @@
use crate::editor_runtime::{
command_sync::sync_persisted_editor_command,
overlays::{
current_table_overlay_anchor, table_col_aux_style, table_row_aux_style,
table_selection_overlay_style, TableOverlayAnchor, TableSelectionKind,
TableSelectionOverlayState,
},
persistence::{persisted_document_identity, PersistedDocumentIdentity},
table_commands::{
run_table_option_action, run_table_toolbar_action, table_option_checked,
TableToolbarAction, TABLE_OPTION_ACTIONS, TABLE_TOOLBAR_ACTIONS,
},
};
use leptos::prelude::*;
use leptos_tiptap::TiptapEditorHandle;
use serde_json::Value;
use web_sys::MouseEvent;
fn runtime_persisted_identity(
document_id: ReadSignal<Option<String>>,
workspace_id: ReadSignal<Option<String>>,
) -> PersistedDocumentIdentity {
persisted_document_identity(document_id.get_untracked(), workspace_id.get_untracked())
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn table_toolbar_view(
editor: TiptapEditorHandle,
table_toolbar_open: ReadSignal<bool>,
set_table_toolbar_open: WriteSignal<bool>,
table_options_open: ReadSignal<bool>,
set_table_options_open: WriteSignal<bool>,
table_overlay_anchor: ReadSignal<Option<TableOverlayAnchor>>,
set_table_overlay_anchor: WriteSignal<Option<TableOverlayAnchor>>,
table_selection_overlay: ReadSignal<Option<TableSelectionOverlayState>>,
set_table_selection_overlay: WriteSignal<Option<TableSelectionOverlayState>>,
slash_open: ReadSignal<bool>,
block_menu_open: ReadSignal<bool>,
dragging_block_index: ReadSignal<Option<usize>>,
document_json: ReadSignal<Value>,
set_document_json: WriteSignal<Value>,
set_dirty_count: WriteSignal<u32>,
set_html_output: WriteSignal<String>,
set_json_output: WriteSignal<String>,
title: ReadSignal<String>,
document_id: ReadSignal<Option<String>>,
workspace_id: ReadSignal<Option<String>>,
set_command_feedback: WriteSignal<String>,
) -> impl IntoView {
view! {
<>
{move || {
if !table_toolbar_open.get()
|| slash_open.get()
|| block_menu_open.get()
|| dragging_block_index.get().is_some()
{
return ().into_any();
}
let Some(anchor) = table_overlay_anchor.get() else {
return ().into_any();
};
let controls_style = format!(
"left:{:.1}px;top:{:.1}px;width:{:.1}px;height:{:.1}px;",
anchor.left, anchor.top, anchor.width, anchor.height
);
view! {
<div
class="table-controls"
data-testid="mnote-leptos-tiptap-table-controls"
style=controls_style
>
{move || {
table_selection_overlay
.get()
.map(|selection| {
view! {
<div
class="table-selection-overlay"
data-testid="table-selection-overlay"
data-selection-kind=selection.kind.id()
data-selection-index=selection.index.to_string()
style=table_selection_overlay_style(&anchor, &selection)
></div>
}
})
.into_any()
}}
{(0..anchor.rows).map(|index| {
let active = table_selection_overlay
.get()
.map(|selection| {
selection.kind == TableSelectionKind::Row && selection.index == index
})
.unwrap_or(false);
view! {
<button
type="button"
class="table-aux-btn table-row-aux"
data-testid=format!("table-row-aux-{index}")
data-active=active.to_string()
style=table_row_aux_style(&anchor, index)
title="点击插入一行"
aria-label="点击插入一行"
on:mousedown=move |event: MouseEvent| {
event.prevent_default();
event.stop_propagation();
}
on:click=move |event: MouseEvent| {
event.prevent_default();
event.stop_propagation();
set_table_toolbar_open.set(true);
set_table_options_open.set(false);
match run_table_toolbar_action(editor, TableToolbarAction::AddRowAfter) {
Ok(message) => {
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,
message,
);
if let Err(err) = editor.select_table_row(index as u32) {
set_command_feedback.set(format!(
"行已插入,但表格行选区同步失败:{err}"
));
}
set_table_overlay_anchor
.set(current_table_overlay_anchor());
set_table_selection_overlay.set(Some(
TableSelectionOverlayState {
kind: TableSelectionKind::Row,
index,
},
));
}
Err(err) => set_command_feedback.set(err),
}
}
>"+"</button>
}
}).collect_view()}
{(0..anchor.cols).map(|index| {
let active = table_selection_overlay
.get()
.map(|selection| {
selection.kind == TableSelectionKind::Column
&& selection.index == index
})
.unwrap_or(false);
view! {
<button
type="button"
class="table-aux-btn table-col-aux"
data-testid=format!("table-col-aux-{index}")
data-active=active.to_string()
style=table_col_aux_style(&anchor, index)
title="点击插入一列"
aria-label="点击插入一列"
on:mousedown=move |event: MouseEvent| {
event.prevent_default();
event.stop_propagation();
}
on:click=move |event: MouseEvent| {
event.prevent_default();
event.stop_propagation();
set_table_toolbar_open.set(true);
set_table_options_open.set(false);
match run_table_toolbar_action(
editor,
TableToolbarAction::AddColumnAfter,
) {
Ok(message) => {
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,
message,
);
if let Err(err) = editor.select_table_column(index as u32) {
set_command_feedback.set(format!(
"列已插入,但表格列选区同步失败:{err}"
));
}
set_table_overlay_anchor
.set(current_table_overlay_anchor());
set_table_selection_overlay.set(Some(
TableSelectionOverlayState {
kind: TableSelectionKind::Column,
index,
},
));
}
Err(err) => set_command_feedback.set(err),
}
}
>"+"</button>
}
}).collect_view()}
</div>
}
.into_any()
}}
{move || {
if !table_toolbar_open.get()
|| slash_open.get()
|| block_menu_open.get()
|| dragging_block_index.get().is_some()
{
return ().into_any();
}
view! {
<div
class="table-toolbar"
data-testid="mnote-leptos-tiptap-table-toolbar"
on:mousedown=move |event: MouseEvent| {
event.prevent_default();
event.stop_propagation();
}
on:click=move |event: MouseEvent| {
event.stop_propagation();
}
>
{TABLE_TOOLBAR_ACTIONS.iter().enumerate().map(|(index, action)| {
let action = *action;
let testid = format!("table-toolbar-{}", action.id());
view! {
<>
{if index == 3 {
view! {
<span
class="table-toolbar-separator"
aria-hidden="true"
></span>
}
.into_any()
} else {
().into_any()
}}
<button
type="button"
class="table-toolbar-btn"
data-testid=testid
title=action.title()
aria-label=action.title()
on:click=move |_| {
match run_table_toolbar_action(editor, action) {
Ok(message) => {
if matches!(action, TableToolbarAction::DeleteTable)
{
set_table_toolbar_open.set(false);
set_table_options_open.set(false);
set_table_overlay_anchor.set(None);
set_table_selection_overlay.set(None);
} else {
set_table_selection_overlay.set(None);
}
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,
message,
);
if !matches!(
action,
TableToolbarAction::DeleteTable
) {
set_table_overlay_anchor
.set(current_table_overlay_anchor());
}
}
Err(err) => set_command_feedback.set(err),
}
}
>
{action.icon()}
</button>
</>
}
}).collect_view()}
<span class="table-toolbar-separator" aria-hidden="true"></span>
<button
type="button"
class="table-toolbar-btn"
data-testid="table-toolbar-options"
title="选项"
aria-label="选项"
aria-expanded=move || table_options_open.get().to_string()
on:click=move |_| {
set_table_options_open.update(|open| *open = !*open);
}
>
"选项"
</button>
{move || {
if !table_options_open.get() {
return ().into_any();
}
view! {
<div
class="table-options-menu"
data-testid="table-toolbar-options-menu"
>
{TABLE_OPTION_ACTIONS.iter().map(|action| {
let action = *action;
let testid = format!("table-option-{}", action.id());
view! {
<button
type="button"
class="table-option-btn"
data-testid=testid
role="switch"
aria-checked=move || {
table_option_checked(&document_json.get(), action)
.to_string()
}
on:click=move |_| {
match run_table_option_action(editor, action) {
Ok(message) => {
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,
message,
);
}
Err(err) => set_command_feedback.set(err),
}
}
>
<span>{action.title()}</span>
<span
class="table-option-switch"
aria-hidden="true"
></span>
</button>
}
}).collect_view()}
</div>
}
.into_any()
}}
</div>
}
.into_any()
}}
</>
}
}
+22 -260
View File
@@ -58,23 +58,19 @@ use editor_runtime::mindmap_node_view::{
};
use editor_runtime::overlays::{
clamp_overlay_anchor,
close_editor_floating_overlays_if_escape, current_table_overlay_anchor,
close_editor_floating_overlays_if_escape,
image_element_from_target, image_toolbar_anchor_from_image, open_block_menu_overlay,
open_image_toolbar_overlay, open_slash_menu_overlay, selection_bounding_rect,
should_auto_close_toolbar_overlays, sync_overlays_on_selection_change, table_col_aux_style,
table_overlay_anchor_from_table, table_row_aux_style, table_selection_overlay_style,
toolbar_overlay_locked, try_sync_editor_overlay_state, FloatingToolbarAnchor, ImageToolbarAnchor,
TableOverlayAnchor, TableSelectionKind, TableSelectionOverlayState,
should_auto_close_toolbar_overlays, sync_overlays_on_selection_change,
table_overlay_anchor_from_table, toolbar_overlay_locked, try_sync_editor_overlay_state,
FloatingToolbarAnchor, ImageToolbarAnchor, TableOverlayAnchor, TableSelectionOverlayState,
};
use editor_runtime::persistence::{
load_persisted_document, normalize_identity_value, persist_document_state,
persisted_document_identity, PersistedDocumentIdentity,
};
use editor_runtime::slash_actions::{SlashActionKind, SLASH_ACTIONS};
use editor_runtime::table_commands::{
run_table_option_action, run_table_toolbar_action, table_option_checked, TableToolbarAction,
TABLE_OPTION_ACTIONS, TABLE_TOOLBAR_ACTIONS,
};
use editor_runtime::table_toolbar_view::table_toolbar_view;
#[cfg(test)]
use editor_runtime::persistence::persisted_document_storage_key;
@@ -6078,263 +6074,29 @@ fn App(mount_options: MountOptions) -> impl IntoView {
set_hovered_block.set(None);
}
>
{move || {
if !table_toolbar_open.get() || slash_open.get() || block_menu_open.get() || dragging_block_index.get().is_some() {
return ().into_any();
}
let Some(anchor) = table_overlay_anchor.get() else {
return ().into_any();
};
let controls_style = format!(
"left:{:.1}px;top:{:.1}px;width:{:.1}px;height:{:.1}px;",
anchor.left,
anchor.top,
anchor.width,
anchor.height
);
view! {
<div
class="table-controls"
data-testid="mnote-leptos-tiptap-table-controls"
style=controls_style
>
{move || {
table_selection_overlay.get().map(|selection| {
view! {
<div
class="table-selection-overlay"
data-testid="table-selection-overlay"
data-selection-kind=selection.kind.id()
data-selection-index=selection.index.to_string()
style=table_selection_overlay_style(&anchor, &selection)
></div>
}
}).into_any()
}}
{(0..anchor.rows).map(|index| {
let active = table_selection_overlay.get()
.map(|selection| selection.kind == TableSelectionKind::Row && selection.index == index)
.unwrap_or(false);
view! {
<button
type="button"
class="table-aux-btn table-row-aux"
data-testid=format!("table-row-aux-{index}")
data-active=active.to_string()
style=table_row_aux_style(&anchor, index)
title="点击插入一行"
aria-label="点击插入一行"
on:mousedown=move |event: MouseEvent| {
event.prevent_default();
event.stop_propagation();
}
on:click=move |event: MouseEvent| {
event.prevent_default();
event.stop_propagation();
set_table_toolbar_open.set(true);
set_table_options_open.set(false);
match run_table_toolbar_action(editor, TableToolbarAction::AddRowAfter) {
Ok(message) => {
sync_persisted_editor_command(
{table_toolbar_view(
editor,
&runtime_persisted_identity(document_id, workspace_id),
table_toolbar_open,
set_table_toolbar_open,
table_options_open,
set_table_options_open,
table_overlay_anchor,
set_table_overlay_anchor,
table_selection_overlay,
set_table_selection_overlay,
slash_open,
block_menu_open,
dragging_block_index,
document_json,
set_document_json,
set_dirty_count,
set_html_output,
set_document_json,
set_json_output,
title,
document_id,
workspace_id,
set_command_feedback,
message,
);
if let Err(err) = editor.select_table_row(index as u32) {
set_command_feedback.set(format!("行已插入,但表格行选区同步失败:{err}"));
}
set_table_overlay_anchor.set(current_table_overlay_anchor());
set_table_selection_overlay.set(Some(TableSelectionOverlayState { kind: TableSelectionKind::Row, index }));
}
Err(err) => set_command_feedback.set(err),
}
}
>"+"</button>
}
}).collect_view()}
{(0..anchor.cols).map(|index| {
let active = table_selection_overlay.get()
.map(|selection| selection.kind == TableSelectionKind::Column && selection.index == index)
.unwrap_or(false);
view! {
<button
type="button"
class="table-aux-btn table-col-aux"
data-testid=format!("table-col-aux-{index}")
data-active=active.to_string()
style=table_col_aux_style(&anchor, index)
title="点击插入一列"
aria-label="点击插入一列"
on:mousedown=move |event: MouseEvent| {
event.prevent_default();
event.stop_propagation();
}
on:click=move |event: MouseEvent| {
event.prevent_default();
event.stop_propagation();
set_table_toolbar_open.set(true);
set_table_options_open.set(false);
match run_table_toolbar_action(editor, TableToolbarAction::AddColumnAfter) {
Ok(message) => {
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,
message,
);
if let Err(err) = editor.select_table_column(index as u32) {
set_command_feedback.set(format!("列已插入,但表格列选区同步失败:{err}"));
}
set_table_overlay_anchor.set(current_table_overlay_anchor());
set_table_selection_overlay.set(Some(TableSelectionOverlayState { kind: TableSelectionKind::Column, index }));
}
Err(err) => set_command_feedback.set(err),
}
}
>"+"</button>
}
}).collect_view()}
</div>
}.into_any()
}}
{move || {
if !table_toolbar_open.get() || slash_open.get() || block_menu_open.get() || dragging_block_index.get().is_some() {
return ().into_any();
}
view! {
<div
class="table-toolbar"
data-testid="mnote-leptos-tiptap-table-toolbar"
on:mousedown=move |event: MouseEvent| {
event.prevent_default();
event.stop_propagation();
}
on:click=move |event: MouseEvent| {
event.stop_propagation();
}
>
{TABLE_TOOLBAR_ACTIONS.iter().enumerate().map(|(index, action)| {
let action = *action;
let testid = format!("table-toolbar-{}", action.id());
view! {
<>
{if index == 3 {
view! { <span class="table-toolbar-separator" aria-hidden="true"></span> }.into_any()
} else {
().into_any()
}}
<button
type="button"
class="table-toolbar-btn"
data-testid=testid
title=action.title()
aria-label=action.title()
on:click=move |_| {
match run_table_toolbar_action(editor, action) {
Ok(message) => {
if matches!(action, TableToolbarAction::DeleteTable) {
set_table_toolbar_open.set(false);
set_table_options_open.set(false);
set_table_overlay_anchor.set(None);
set_table_selection_overlay.set(None);
} else {
set_table_selection_overlay.set(None);
}
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,
message,
);
if !matches!(action, TableToolbarAction::DeleteTable) {
set_table_overlay_anchor.set(current_table_overlay_anchor());
}
}
Err(err) => set_command_feedback.set(err),
}
}
>
{action.icon()}
</button>
</>
}
}).collect_view()}
<span class="table-toolbar-separator" aria-hidden="true"></span>
<button
type="button"
class="table-toolbar-btn"
data-testid="table-toolbar-options"
title="选项"
aria-label="选项"
aria-expanded=move || table_options_open.get().to_string()
on:click=move |_| {
set_table_options_open.update(|open| *open = !*open);
}
>
"选项"
</button>
{move || {
if !table_options_open.get() {
return ().into_any();
}
view! {
<div class="table-options-menu" data-testid="table-toolbar-options-menu">
{TABLE_OPTION_ACTIONS.iter().map(|action| {
let action = *action;
let testid = format!("table-option-{}", action.id());
view! {
<button
type="button"
class="table-option-btn"
data-testid=testid
role="switch"
aria-checked=move || table_option_checked(&document_json.get(), action).to_string()
on:click=move |_| {
match run_table_option_action(editor, action) {
Ok(message) => {
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,
message,
);
}
Err(err) => set_command_feedback.set(err),
}
}
>
<span>{action.title()}</span>
<span class="table-option-switch" aria-hidden="true"></span>
</button>
}
}).collect_view()}
</div>
}.into_any()
}}
</div>
}.into_any()
}}
)}
{move || {
let Some(anchor) = image_toolbar_anchor.get() else {
return ().into_any();