diff --git a/rust/spikes/leptos-tiptap-spike/Cargo.toml b/rust/spikes/leptos-tiptap-spike/Cargo.toml
index 437ee8e7..4aed8442 100644
--- a/rust/spikes/leptos-tiptap-spike/Cargo.toml
+++ b/rust/spikes/leptos-tiptap-spike/Cargo.toml
@@ -15,6 +15,7 @@ path = "src/main.rs"
[dependencies]
console_error_panic_hook = "0.1.7"
+radix-leptos-primitives = "0.9.0"
leptos = { version = "0.8.19", features = ["csr"] }
leptos_dom = "0.8.8"
serde = { version = "1", features = ["derive"] }
diff --git a/rust/spikes/leptos-tiptap-spike/src/editor_runtime/block_handle_menu_view.rs b/rust/spikes/leptos-tiptap-spike/src/editor_runtime/block_handle_menu_view.rs
index 8276ffda..a6b7be1d 100644
--- a/rust/spikes/leptos-tiptap-spike/src/editor_runtime/block_handle_menu_view.rs
+++ b/rust/spikes/leptos-tiptap-spike/src/editor_runtime/block_handle_menu_view.rs
@@ -13,6 +13,7 @@ use crate::editor_runtime::{
dom_events::trap_scroll_inside_menu,
dom_selection::runtime_block_id_from_index,
editor_focus::schedule_editor_focus,
+ icons::material_icon,
persistence::{persist_document_state, persisted_document_identity, PersistedDocumentIdentity},
slash_actions::{SlashActionKind, SLASH_ACTIONS},
};
@@ -34,25 +35,25 @@ const FOLDED_HEADING_ACTIONS: [FoldedHeadingAction; 4] = [
FoldedHeadingAction {
level: 1,
id: "1",
- icon: "H1",
+ icon: "looks_one",
label: "折叠主标题",
},
FoldedHeadingAction {
level: 2,
id: "2",
- icon: "H2",
+ icon: "looks_two",
label: "折叠大标题",
},
FoldedHeadingAction {
level: 3,
id: "3",
- icon: "H3",
+ icon: "looks_3",
label: "折叠中标题",
},
FoldedHeadingAction {
level: 4,
id: "4",
- icon: "H4",
+ icon: "looks_4",
label: "折叠小标题",
},
];
@@ -244,7 +245,7 @@ fn block_transform_submenu_view(
handle_turn_into_action_click(editor, block_index, kind, label, signals);
}
>
-
+
{label}
@@ -274,12 +275,12 @@ fn block_transform_submenu_view(
);
}
>
-
+
"页面"
@@ -294,21 +295,21 @@ fn block_transform_submenu_view(
signals.set_block_folded_title_open.set(true);
}
>
-
+
"折叠标题"
-
+
@@ -330,7 +331,7 @@ fn block_transform_submenu_view(
handle_folded_heading_click(editor, block_index, action, signals);
}
>
-
+
{action.label}
}
@@ -415,7 +416,7 @@ pub(crate) fn block_handle_menu_view(props: BlockHandleMenuViewProps) -> impl In
);
}
>
-
+
"AI 助理"
@@ -430,9 +431,9 @@ pub(crate) fn block_handle_menu_view(props: BlockHandleMenuViewProps) -> impl In
signals.set_block_turn_into_open.set(true);
}
>
-
+
"转换为"
-
+
@@ -462,7 +463,7 @@ pub(crate) fn block_handle_menu_view(props: BlockHandleMenuViewProps) -> impl In
}
}
>
-
+
"拷贝副本"
@@ -497,7 +498,7 @@ pub(crate) fn block_handle_menu_view(props: BlockHandleMenuViewProps) -> impl In
}
}
>
-
+
"删除"
@@ -538,9 +539,9 @@ pub(crate) fn block_handle_menu_view(props: BlockHandleMenuViewProps) -> impl In
});
}
>
-
+
"复制链接"
-
+
@@ -566,7 +567,7 @@ pub(crate) fn block_handle_menu_view(props: BlockHandleMenuViewProps) -> impl In
.set("块历史入口已记录,恢复流程后续接版本链路".to_string());
}
>
-
+
"块历史..."
@@ -596,9 +597,9 @@ pub(crate) fn block_handle_menu_view(props: BlockHandleMenuViewProps) -> impl In
.set("颜色子菜单后续按 Wolai 继续补齐".to_string());
}
>
-
+
"颜色"
-
+
diff --git a/rust/spikes/leptos-tiptap-spike/src/editor_runtime/icons.rs b/rust/spikes/leptos-tiptap-spike/src/editor_runtime/icons.rs
new file mode 100644
index 00000000..ff530d1a
--- /dev/null
+++ b/rust/spikes/leptos-tiptap-spike/src/editor_runtime/icons.rs
@@ -0,0 +1,83 @@
+//! Unified icon helpers — Material Symbols ligatures for all editor runtime icons.
+//!
+//! Every icon rendered in the leptos-tiptap editor shell should flow through
+//! `material_icon()` or consume `ICON_MAP` so that the set of Material Symbols
+//! ligatures stays centralised and we can swap font providers later.
+
+use leptos::prelude::*;
+
+/// Semantic name → Material Symbols ligature.
+///
+/// Keys are stable semantic identifiers; values are the literal text content
+/// that `material-symbols-outlined` will turn into a glyph via ligature.
+pub(crate) const ICON_MAP: &[(&str, &str)] = &[
+ ("heading_1", "looks_one"),
+ ("heading_2", "looks_two"),
+ ("heading_3", "looks_3"),
+ ("heading_4", "looks_4"),
+ ("chevron_right", "chevron_right"),
+ ("chevron_left", "chevron_left"),
+ ("check", "check"),
+ ("text_fields", "text_fields"),
+ ("auto_awesome", "auto_awesome"),
+ ("edit", "edit"),
+ ("redo", "redo"),
+ ("summarize", "auto_awesome"),
+ ("more_horiz", "more_horiz"),
+ ("checklist", "checklist"),
+ ("format_list_bulleted", "format_list_bulleted"),
+ ("format_list_numbered", "format_list_numbered"),
+ ("format_quote", "format_quote"),
+ ("code", "code"),
+ ("horizontal_rule", "horizontal_rule"),
+ ("table_chart", "table_chart"),
+ ("account_tree", "account_tree"),
+ ("image", "image"),
+ ("upload_file", "upload_file"),
+ ("toc", "toc"),
+ ("playlist_add", "playlist_add"),
+ ("view_column", "view_column"),
+ ("delete", "delete"),
+ ("backspace", "backspace"),
+ ("close", "close"),
+ ("settings", "settings"),
+ ("remove", "remove"),
+ ("fullscreen_exit", "fullscreen_exit"),
+ ("fullscreen", "fullscreen"),
+ ("lock", "lock"),
+ ("lock_open", "lock_open"),
+ ("autorenew", "autorenew"),
+ ("link", "link"),
+ ("arrow_outward", "arrow_outward"),
+ ("history", "history"),
+ ("chat", "chat"),
+ ("palette", "palette"),
+ ("format_align_center", "format_align_center"),
+ ("translate", "translate"),
+ ("function", "function"),
+ ("description", "description"),
+ ("content_copy", "content_copy"),
+ ("format_bold", "format_bold"),
+ ("dashboard", "dashboard"),
+ ("home", "home"),
+ ("search", "search"),
+ ("add", "add"),
+];
+
+/// Look up a Material Symbols ligature by semantic name.
+pub(crate) fn icon_ligature(name: &str) -> Option<&'static str> {
+ ICON_MAP
+ .iter()
+ .find(|(key, _)| *key == name)
+ .map(|(_, value)| *value)
+}
+
+/// Render a `` icon.
+///
+/// The `name` parameter must be a Material Symbols ligature name
+/// (e.g. `"auto_awesome"`, `"chevron_right"`, `"looks_one"`).
+pub(crate) fn material_icon(name: &str) -> impl IntoView {
+ view! {
+ {name}
+ }
+}
diff --git a/rust/spikes/leptos-tiptap-spike/src/editor_runtime/mindmap_node_view.rs b/rust/spikes/leptos-tiptap-spike/src/editor_runtime/mindmap_node_view.rs
index 34250df5..fd4f78a1 100644
--- a/rust/spikes/leptos-tiptap-spike/src/editor_runtime/mindmap_node_view.rs
+++ b/rust/spikes/leptos-tiptap-spike/src/editor_runtime/mindmap_node_view.rs
@@ -21,6 +21,7 @@ use wasm_bindgen::{JsCast, JsValue};
use web_sys::{CustomEvent, CustomEventInit, EventTarget, HtmlElement, HtmlInputElement};
use crate::editor_runtime::mount::next_mount_id;
+use crate::editor_runtime::icons::material_icon;
thread_local! {
static MOUNTED_MINDMAP_SHELLS: std::cell::RefCell> = std::cell::RefCell::new(HashMap::new());
@@ -493,7 +494,7 @@ pub(crate) fn MindmapShell(
);
}
>
- "⋯"
+ {material_icon("more_horiz")}
"更多"
{if toolbar_more_open {
@@ -557,7 +558,7 @@ pub(crate) fn MindmapShell(
}
}
>
- "›"
+ {material_icon("chevron_right")}
{sidebar_panels
.iter()
@@ -616,7 +617,7 @@ pub(crate) fn MindmapShell(
}
}
>
- "‹"
+ {material_icon("chevron_left")}
}
.into_any()
@@ -660,7 +661,7 @@ pub(crate) fn MindmapShell(
}
}
>
- "×"
+ {material_icon("close")}
}.into_any()
} else {
diff --git a/rust/spikes/leptos-tiptap-spike/src/editor_runtime/mod.rs b/rust/spikes/leptos-tiptap-spike/src/editor_runtime/mod.rs
index a3bfac38..35dc0c0d 100644
--- a/rust/spikes/leptos-tiptap-spike/src/editor_runtime/mod.rs
+++ b/rust/spikes/leptos-tiptap-spike/src/editor_runtime/mod.rs
@@ -2,6 +2,7 @@ pub(crate) mod attachment_links;
pub(crate) mod attachment_upload;
pub(crate) mod block_dnd;
pub(crate) mod block_hover_state;
+pub(crate) mod icons;
pub(crate) mod block_handle_menu_view;
pub(crate) mod block_menu_document;
pub(crate) mod block_menu_overlay;
diff --git a/rust/spikes/leptos-tiptap-spike/src/editor_runtime/slash_actions.rs b/rust/spikes/leptos-tiptap-spike/src/editor_runtime/slash_actions.rs
index 36e0b238..3e92fa41 100644
--- a/rust/spikes/leptos-tiptap-spike/src/editor_runtime/slash_actions.rs
+++ b/rust/spikes/leptos-tiptap-spike/src/editor_runtime/slash_actions.rs
@@ -40,7 +40,7 @@ pub(crate) const SLASH_ACTIONS: [SlashAction; 22] = [
kind: SlashActionKind::AiAssistant,
id: "ai-assistant",
category: "AI 助理",
- icon: "✦",
+ icon: "auto_awesome",
label: "AI 助理",
description: "按 Wolai 基线保留 AI 入口",
shortcut: "/ai",
@@ -49,7 +49,7 @@ pub(crate) const SLASH_ACTIONS: [SlashAction; 22] = [
kind: SlashActionKind::AiWrite,
id: "ai-write",
category: "AI 助理",
- icon: "✎",
+ icon: "edit",
label: "用 AI 写作",
description: "唤起写作辅助入口",
shortcut: "/yaixz",
@@ -58,7 +58,7 @@ pub(crate) const SLASH_ACTIONS: [SlashAction; 22] = [
kind: SlashActionKind::ContinueWriting,
id: "continue-writing",
category: "AI 助理",
- icon: "↪",
+ icon: "redo",
label: "续写",
description: "按当前上下文继续写作",
shortcut: "/xx",
@@ -67,7 +67,7 @@ pub(crate) const SLASH_ACTIONS: [SlashAction; 22] = [
kind: SlashActionKind::Summarize,
id: "summarize",
category: "AI 助理",
- icon: "≡",
+ icon: "auto_awesome",
label: "总结",
description: "对当前内容生成摘要",
shortcut: "/zj",
@@ -76,7 +76,7 @@ pub(crate) const SLASH_ACTIONS: [SlashAction; 22] = [
kind: SlashActionKind::MoreAi,
id: "more-ai",
category: "AI 助理",
- icon: "…",
+ icon: "more_horiz",
label: "更多",
description: "更多 AI 命令",
shortcut: "›",
@@ -85,7 +85,7 @@ pub(crate) const SLASH_ACTIONS: [SlashAction; 22] = [
kind: SlashActionKind::Paragraph,
id: "paragraph",
category: "基础块列表",
- icon: "Aa",
+ icon: "text_fields",
label: "文本",
description: "普通正文块",
shortcut: "/wb",
@@ -94,7 +94,7 @@ pub(crate) const SLASH_ACTIONS: [SlashAction; 22] = [
kind: SlashActionKind::Todo,
id: "todo",
category: "基础块列表",
- icon: "☑",
+ icon: "checklist",
label: "待办列表",
description: "创建可勾选任务",
shortcut: "/dblb",
@@ -103,7 +103,7 @@ pub(crate) const SLASH_ACTIONS: [SlashAction; 22] = [
kind: SlashActionKind::AdvancedTodo,
id: "advanced-todo",
category: "基础块列表",
- icon: "☑",
+ icon: "checklist",
label: "高级待办列表",
description: "保留 Wolai 高级待办入口",
shortcut: "/gjdblb",
@@ -112,7 +112,7 @@ pub(crate) const SLASH_ACTIONS: [SlashAction; 22] = [
kind: SlashActionKind::Heading1,
id: "heading-1",
category: "基础块列表",
- icon: "H1",
+ icon: "looks_one",
label: "主标题",
description: "一级标题",
shortcut: "/h1",
@@ -121,7 +121,7 @@ pub(crate) const SLASH_ACTIONS: [SlashAction; 22] = [
kind: SlashActionKind::Heading2,
id: "heading-2",
category: "基础块列表",
- icon: "H2",
+ icon: "looks_two",
label: "大标题",
description: "二级标题",
shortcut: "/h2",
@@ -130,7 +130,7 @@ pub(crate) const SLASH_ACTIONS: [SlashAction; 22] = [
kind: SlashActionKind::Heading3,
id: "heading-3",
category: "基础块列表",
- icon: "H3",
+ icon: "looks_3",
label: "中标题",
description: "三级标题",
shortcut: "/h3",
@@ -139,7 +139,7 @@ pub(crate) const SLASH_ACTIONS: [SlashAction; 22] = [
kind: SlashActionKind::Heading4,
id: "heading-4",
category: "基础块列表",
- icon: "H4",
+ icon: "looks_4",
label: "小标题",
description: "四级标题",
shortcut: "/h4",
@@ -148,7 +148,7 @@ pub(crate) const SLASH_ACTIONS: [SlashAction; 22] = [
kind: SlashActionKind::BulletList,
id: "bullet",
category: "基础块列表",
- icon: "•",
+ icon: "format_list_bulleted",
label: "列表",
description: "记录普通要点",
shortcut: "/lb",
@@ -157,7 +157,7 @@ pub(crate) const SLASH_ACTIONS: [SlashAction; 22] = [
kind: SlashActionKind::OrderedList,
id: "ordered",
category: "基础块列表",
- icon: "1.",
+ icon: "format_list_numbered",
label: "数字列表",
description: "记录步骤顺序",
shortcut: "/szlb",
@@ -166,7 +166,7 @@ pub(crate) const SLASH_ACTIONS: [SlashAction; 22] = [
kind: SlashActionKind::Quote,
id: "quote",
category: "基础块列表",
- icon: "❝",
+ icon: "format_quote",
label: "引述文字",
description: "包住注释与摘录",
shortcut: "/ys",
@@ -175,7 +175,7 @@ pub(crate) const SLASH_ACTIONS: [SlashAction; 22] = [
kind: SlashActionKind::CodeBlock,
id: "code-block",
category: "基础块列表",
- icon: "<> ",
+ icon: "code",
label: "代码片段",
description: "插入带语义的代码块",
shortcut: "/dm",
@@ -184,7 +184,7 @@ pub(crate) const SLASH_ACTIONS: [SlashAction; 22] = [
kind: SlashActionKind::Divider,
id: "divider",
category: "基础块列表",
- icon: "—",
+ icon: "horizontal_rule",
label: "分割线",
description: "插入分隔线",
shortcut: "/fgx",
@@ -193,7 +193,7 @@ pub(crate) const SLASH_ACTIONS: [SlashAction; 22] = [
kind: SlashActionKind::SimpleTable,
id: "simple-table",
category: "进阶块列表",
- icon: "▦",
+ icon: "table_chart",
label: "简单表格",
description: "插入基础表格",
shortcut: "/jdbg",
@@ -202,7 +202,7 @@ pub(crate) const SLASH_ACTIONS: [SlashAction; 22] = [
kind: SlashActionKind::Mindmap,
id: "mindmap",
category: "进阶块列表",
- icon: "⌘",
+ icon: "account_tree",
label: "思维导图",
description: "插入 kernel projection 导图块",
shortcut: "/dt",
@@ -211,7 +211,7 @@ pub(crate) const SLASH_ACTIONS: [SlashAction; 22] = [
kind: SlashActionKind::Image,
id: "image",
category: "媒体与附件",
- icon: "▧",
+ icon: "image",
label: "图片",
description: "上传并插入图片",
shortcut: "/tp",
@@ -220,7 +220,7 @@ pub(crate) const SLASH_ACTIONS: [SlashAction; 22] = [
kind: SlashActionKind::UploadAttachment,
id: "upload-attachment",
category: "媒体与附件",
- icon: "↥",
+ icon: "upload_file",
label: "上传附件",
description: "上传 Office、PDF 或其他文件",
shortcut: "/fj",
@@ -229,7 +229,7 @@ pub(crate) const SLASH_ACTIONS: [SlashAction; 22] = [
kind: SlashActionKind::Toc,
id: "toc",
category: "进阶块列表",
- icon: "☰",
+ icon: "toc",
label: "页面目录",
description: "根据当前标题生成目录",
shortcut: "/toc",
diff --git a/rust/spikes/leptos-tiptap-spike/src/editor_runtime/slash_menu_view.rs b/rust/spikes/leptos-tiptap-spike/src/editor_runtime/slash_menu_view.rs
index 70b4e533..fced2062 100644
--- a/rust/spikes/leptos-tiptap-spike/src/editor_runtime/slash_menu_view.rs
+++ b/rust/spikes/leptos-tiptap-spike/src/editor_runtime/slash_menu_view.rs
@@ -3,6 +3,7 @@ use crate::editor_runtime::{
block_transform::run_slash_action,
bridge_events::{ChangeMetaPayload, ChangePayload},
command_sync::read_editor_snapshot,
+ icons::material_icon,
overlays::toolbar_overlay_locked,
persistence::{persist_document_state, persisted_document_identity, PersistedDocumentIdentity},
runtime_bridge::dispatch_change_event_to_target,
@@ -59,6 +60,29 @@ fn runtime_persisted_identity(
persisted_document_identity(document_id.get_untracked(), workspace_id.get_untracked())
}
+fn render_slash_shortcut(shortcut: &str) -> impl IntoView {
+ // Hardcoded Unicode→Material Symbols mappings for shortcuts that use
+ // non-text glyphs (chevron, arrows, triangles, etc.).
+ match shortcut {
+ "›" => view! {
+ {material_icon("chevron_right")}
+ }
+ .into_any(),
+ "↻" => view! {
+ {material_icon("autorenew")}
+ }
+ .into_any(),
+ "◣" => view! {
+ {material_icon("arrow_drop_down")}
+ }
+ .into_any(),
+ _ => view! {
+ {shortcut}
+ }
+ .into_any(),
+ }
+}
+
fn filtered_slash_intro_view(
query: &str,
command_feedback: WriteSignal,
@@ -83,12 +107,12 @@ fn filtered_slash_intro_view(
}
>
- "↻"
+ {material_icon("autorenew")}
"转换为"
"打开块类型转换入口"
- "/zhw"
+ {render_slash_shortcut("/zhw")}
>
@@ -109,12 +133,12 @@ fn filtered_slash_intro_view(
}
>
- "▣"
+ {material_icon("description")}
"页面"
"创建或转换为页面块"
- "/ym"
+ {render_slash_shortcut("/ym")}
>
@@ -255,12 +279,12 @@ pub(crate) fn slash_menu_view(props: SlashMenuViewProps) -> impl IntoView {
}
>
- {icon}
+ {icon}
{label}
{description}
- {shortcut}
+ {render_slash_shortcut(shortcut)}
>
diff --git a/rust/spikes/leptos-tiptap-spike/src/editor_runtime/table_commands.rs b/rust/spikes/leptos-tiptap-spike/src/editor_runtime/table_commands.rs
index f0ed1a34..c4879e55 100644
--- a/rust/spikes/leptos-tiptap-spike/src/editor_runtime/table_commands.rs
+++ b/rust/spikes/leptos-tiptap-spike/src/editor_runtime/table_commands.rs
@@ -32,12 +32,12 @@ impl TableToolbarAction {
pub(crate) fn icon(self) -> &'static str {
match self {
- Self::AddRowAfter => "+R",
- Self::AddColumnAfter => "+C",
- Self::DeleteRow => "-R",
- Self::DeleteColumn => "-C",
- Self::ClearCell => "⌫",
- Self::DeleteTable => "×",
+ Self::AddRowAfter => "playlist_add",
+ Self::AddColumnAfter => "view_column",
+ Self::DeleteRow => "delete",
+ Self::DeleteColumn => "delete",
+ Self::ClearCell => "backspace",
+ Self::DeleteTable => "close",
}
}
diff --git a/rust/spikes/leptos-tiptap-spike/src/editor_runtime/table_toolbar_view.rs b/rust/spikes/leptos-tiptap-spike/src/editor_runtime/table_toolbar_view.rs
index 4ba169e2..b083f943 100644
--- a/rust/spikes/leptos-tiptap-spike/src/editor_runtime/table_toolbar_view.rs
+++ b/rust/spikes/leptos-tiptap-spike/src/editor_runtime/table_toolbar_view.rs
@@ -1,5 +1,6 @@
use crate::editor_runtime::{
command_sync::sync_persisted_editor_command,
+ icons::material_icon,
overlays::{
current_table_overlay_anchor, table_col_aux_style, table_row_aux_style,
table_selection_overlay_style, TableOverlayAnchor, TableSelectionKind,
@@ -141,7 +142,7 @@ pub(crate) fn table_toolbar_view(
Err(err) => set_command_feedback.set(err),
}
}
- >"+"
+ >{material_icon("add")}
}
}).collect_view()}
{(0..anchor.cols).map(|index| {
@@ -203,7 +204,7 @@ pub(crate) fn table_toolbar_view(
Err(err) => set_command_feedback.set(err),
}
}
- >"+"
+ >{material_icon("add")}
}
}).collect_view()}
@@ -287,7 +288,7 @@ pub(crate) fn table_toolbar_view(
}
}
>
- {action.icon()}
+ {material_icon(action.icon())}
>
}