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); } > - {icon} + {icon} {label} {shortcut} @@ -274,12 +275,12 @@ fn block_transform_submenu_view( ); } > - "▣" + {material_icon("description")} "页面" {page_current_marker} @@ -294,21 +295,21 @@ fn block_transform_submenu_view( signals.set_block_folded_title_open.set(true); } > - "H1" + {material_icon("looks_one")} "折叠标题" - "›" + {material_icon("chevron_right")} @@ -330,7 +331,7 @@ fn block_transform_submenu_view( handle_folded_heading_click(editor, block_index, action, signals); } > - {action.icon} + {action.icon} {action.label} } @@ -415,7 +416,7 @@ pub(crate) fn block_handle_menu_view(props: BlockHandleMenuViewProps) -> impl In ); } > - "✦" + {material_icon("auto_awesome")} "AI 助理" "Ctrl+J" @@ -430,9 +431,9 @@ pub(crate) fn block_handle_menu_view(props: BlockHandleMenuViewProps) -> impl In signals.set_block_turn_into_open.set(true); } > - "↻" + {material_icon("autorenew")} "转换为" - "›" + {material_icon("chevron_right")}
@@ -462,7 +463,7 @@ pub(crate) fn block_handle_menu_view(props: BlockHandleMenuViewProps) -> impl In } } > - "▣" + {material_icon("content_copy")} "拷贝副本" "Ctrl+D" @@ -497,7 +498,7 @@ pub(crate) fn block_handle_menu_view(props: BlockHandleMenuViewProps) -> impl In } } > - "⌫" + {material_icon("delete")} "删除" "Del" @@ -538,9 +539,9 @@ pub(crate) fn block_handle_menu_view(props: BlockHandleMenuViewProps) -> impl In }); } > - "🔗" + {material_icon("link")} "复制链接" - "›" + {material_icon("chevron_right")} @@ -566,7 +567,7 @@ pub(crate) fn block_handle_menu_view(props: BlockHandleMenuViewProps) -> impl In .set("块历史入口已记录,恢复流程后续接版本链路".to_string()); } > - "◴" + {material_icon("history")} "块历史..." @@ -596,9 +597,9 @@ pub(crate) fn block_handle_menu_view(props: BlockHandleMenuViewProps) -> impl In .set("颜色子菜单后续按 Wolai 继续补齐".to_string()); } > - "▧" + {material_icon("palette")} "颜色" - "›" + {material_icon("chevron_right")} 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! { + + } +} 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( ); } > - + "更多" {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")}
@@ -711,7 +712,7 @@ pub(crate) fn MindmapShell( compat_path: None, source: None, }) - }> + }>
+ }> {if search_open { view! { + }> + }>
+ }> + }> + }> + }>
}.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())} }