refactor: split sidebar settings and slash menu view

This commit is contained in:
lix-2026
2026-05-26 04:17:52 +08:00
parent a8bc87173f
commit 85914cd4e1
9 changed files with 1082 additions and 853 deletions
+52 -186
View File
@@ -80,6 +80,9 @@ use editor_runtime::runtime_bridge::{
dispatch_status_event_to_target, register_runtime_listener, take_unmount_handle,
};
use editor_runtime::slash_actions::{SlashActionKind, SLASH_ACTIONS};
use editor_runtime::slash_menu_view::{
slash_menu_view, SlashMenuRuntimeContext, SlashMenuSignals, SlashMenuViewProps,
};
use editor_runtime::style::SPIKE_STYLE;
use editor_runtime::table_toolbar_view::table_toolbar_view;
@@ -2240,6 +2243,54 @@ fn App(mount_options: MountOptions) -> impl IntoView {
}
};
let slash_menu_runtime = SlashMenuRuntimeContext {
change_event_target: slash_change_event_target.clone(),
dispatch_runtime_state: std::sync::Arc::new(move |target: &EventTarget| {
dispatch_runtime_state_to_target(
target,
document_id.get_untracked(),
workspace_id.get_untracked(),
title.get_untracked(),
dirty_count.get_untracked(),
hovered_block.get_untracked(),
editor_focused.get_untracked(),
slash_open.get_untracked(),
turn_into_open.get_untracked(),
color_menu_open.get_untracked(),
more_menu_open.get_untracked(),
read_only.get_untracked(),
);
}),
};
let slash_menu_props = SlashMenuViewProps {
editor,
signals: SlashMenuSignals {
slash_open,
set_slash_open,
slash_index,
slash_query,
dirty_count,
set_dirty_count,
document_json: set_document_json,
html_output: set_html_output,
json_output: set_json_output,
document_id,
workspace_id,
title,
editor_focused,
hovered_block,
turn_into_open,
color_menu_open,
more_menu_open,
revision,
conflict_detection_key,
read_only,
command_feedback: set_command_feedback,
},
runtime: slash_menu_runtime,
anchor_style: std::sync::Arc::new(slash_menu_anchor_style),
};
view! {
<style>{SPIKE_STYLE}</style>
<main
@@ -3828,192 +3879,7 @@ fn App(mount_options: MountOptions) -> impl IntoView {
}
}}
{move || {
if slash_open.get() {
view! {
<div
class="slash-menu"
data-testid="mnote-leptos-tiptap-slash-menu"
style=slash_menu_anchor_style()
>
<div class="slash-list">
{move || {
let query = slash_query.get().trim().to_ascii_lowercase();
let show_turn_into = !query.is_empty()
&& ("zhw".contains(query.as_str())
|| "转换为".contains(query.as_str()));
let show_page = !query.is_empty()
&& ("ym".contains(query.as_str())
|| "页面".contains(query.as_str()));
if show_turn_into || show_page {
view! {
<>
{if show_turn_into {
view! {
<>
<div class="slash-section-label">"转换为"</div>
<button
class="slash-item"
data-active="false"
data-testid="slash-item-turn-into"
on:click=move |_| {
set_command_feedback.set("转换为入口请通过块菜单或选区工具栏执行".to_string());
}
>
<span class="slash-item-row">
<span class="slash-item-icon">""</span>
<span class="slash-item-copy">
<strong>"转换为"</strong>
<span>"打开块类型转换入口"</span>
</span>
<span class="slash-item-shortcut">"/zhw"</span>
</span>
</button>
</>
}.into_any()
} else {
().into_any()
}}
{if show_page {
view! {
<>
<div class="slash-section-label">"基础块列表"</div>
<button
class="slash-item"
data-active="false"
data-testid="slash-item-page"
on:click=move |_| {
set_command_feedback.set("页面入口请通过转换为页面真源链路执行".to_string());
}
>
<span class="slash-item-row">
<span class="slash-item-icon">""</span>
<span class="slash-item-copy">
<strong>"页面"</strong>
<span>"创建或转换为页面块"</span>
</span>
<span class="slash-item-shortcut">"/ym"</span>
</span>
</button>
</>
}.into_any()
} else {
().into_any()
}}
</>
}.into_any()
} else {
().into_any()
}
}}
{SLASH_ACTIONS.iter().enumerate().map(|(index, action)| {
let is_selected = move || slash_index.get() == index;
let kind = action.kind;
let label = action.label;
let description = action.description;
let shortcut = action.shortcut;
let icon = action.icon;
let category = action.category;
let show_category = index == 0
|| SLASH_ACTIONS[index - 1].category != category;
let testid = format!("slash-item-{}", action.id);
let slash_change_event_target = slash_change_event_target.clone();
view! {
<>
{if show_category {
view! { <div class="slash-section-label">{category}</div> }.into_any()
} else {
().into_any()
}}
<button
class="slash-item"
data-active=move || is_selected().to_string()
data-testid=testid
on:click=move |_| {
match run_slash_action(editor, kind) {
Ok(message) => {
set_slash_open.set(false);
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);
if let Some(target) = slash_change_event_target.as_ref() {
dispatch_change_event_to_target(
target,
&ChangePayload {
document_id: document_id.get_untracked(),
workspace_id: workspace_id.get_untracked(),
title: title.get_untracked(),
content: snapshot.clone(),
meta: ChangeMetaPayload {
dirty_count: dirty_count.get_untracked(),
editor_focused: editor_focused.get_untracked(),
slash_open: slash_open.get_untracked(),
toolbar_open: toolbar_overlay_locked(
turn_into_open.get_untracked(),
color_menu_open.get_untracked(),
more_menu_open.get_untracked(),
),
selected_block_index: hovered_block.get_untracked().map(|block| block.index),
revision: revision.get_untracked(),
conflict_detection_key: conflict_detection_key.get_untracked(),
read_only: read_only.get_untracked(),
},
},
);
dispatch_runtime_state_to_target(
target,
document_id.get_untracked(),
workspace_id.get_untracked(),
title.get_untracked(),
dirty_count.get_untracked(),
hovered_block.get_untracked(),
editor_focused.get_untracked(),
slash_open.get_untracked(),
turn_into_open.get_untracked(),
color_menu_open.get_untracked(),
more_menu_open.get_untracked(),
read_only.get_untracked(),
);
}
match persist_document_state(
&runtime_persisted_identity(document_id, workspace_id),
&title.get_untracked(),
&snapshot,
Some(html),
) {
Ok(()) => set_command_feedback.set(format!("{message},并已写入本地草稿")),
Err(err) => set_command_feedback.set(format!("{message},但本地保存失败:{err}")),
}
}
Err(err) => {
set_slash_open.set(false);
set_command_feedback.set(format!("命令执行失败:{err}"));
}
}
}
>
<span class="slash-item-row">
<span class="slash-item-icon">{icon}</span>
<span class="slash-item-copy">
<strong>{label}</strong>
<span>{description}</span>
</span>
<span class="slash-item-shortcut">{shortcut}</span>
</span>
</button>
</>
}
}).collect_view()}
</div>
</div>
}
.into_any()
} else {
().into_any()
}
}}
{slash_menu_view(slash_menu_props)}
<TiptapEditor
id=editor_instance_id.clone()