refactor: split tiptap block transform helpers
This commit is contained in:
+1
-1
@@ -252,7 +252,7 @@ cargo test --manifest-path rust/Cargo.toml -p mnote-web --lib routes::tree -- --
|
|||||||
|
|
||||||
验收标准:
|
验收标准:
|
||||||
|
|
||||||
- [ ] `lib.rs` 少于 5,000 行。
|
- [x] `lib.rs` 少于 5,000 行。
|
||||||
- [ ] `block_handle_menu_view.rs` 可以超过 1,500 行,但必须有清晰入参 struct,避免二十多个 signal 直接散落。
|
- [ ] `block_handle_menu_view.rs` 可以超过 1,500 行,但必须有清晰入参 struct,避免二十多个 signal 直接散落。
|
||||||
- [ ] 不改变 Tiptap command 行为,不新增编辑器功能。
|
- [ ] 不改变 Tiptap command 行为,不新增编辑器功能。
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,273 @@
|
|||||||
|
use leptos_tiptap::{
|
||||||
|
TiptapCodeBlockAttributes, TiptapColorAttributes, TiptapContent, TiptapEditorHandle,
|
||||||
|
TiptapHeadingLevel, TiptapHighlightAttributes, TiptapNodeName, TiptapSchemaTarget,
|
||||||
|
TiptapTextAlign, TiptapTocNodeAttrs,
|
||||||
|
};
|
||||||
use serde_json::{json, Value};
|
use serde_json::{json, Value};
|
||||||
|
|
||||||
|
use crate::editor_runtime::attachment_upload::dispatch_editor_upload_request;
|
||||||
|
use crate::editor_runtime::block_menu_document::mindmap_paragraph_node;
|
||||||
|
use crate::editor_runtime::slash_actions::SlashActionKind;
|
||||||
|
|
||||||
|
pub(crate) fn apply_text_color(
|
||||||
|
editor: TiptapEditorHandle,
|
||||||
|
color: &str,
|
||||||
|
) -> Result<&'static str, String> {
|
||||||
|
editor
|
||||||
|
.set_color(TiptapColorAttributes {
|
||||||
|
color: color.to_string(),
|
||||||
|
})
|
||||||
|
.map(|_| "已更新文字颜色")
|
||||||
|
.map_err(|err| format!("命令执行失败:{err}"))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn clear_text_color(editor: TiptapEditorHandle) -> Result<&'static str, String> {
|
||||||
|
editor
|
||||||
|
.unset_color()
|
||||||
|
.map(|_| "已清除文字颜色")
|
||||||
|
.map_err(|err| format!("命令执行失败:{err}"))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn apply_highlight_color(
|
||||||
|
editor: TiptapEditorHandle,
|
||||||
|
color: Option<&str>,
|
||||||
|
) -> Result<&'static str, String> {
|
||||||
|
match color {
|
||||||
|
Some(color) => editor
|
||||||
|
.set_highlight(Some(TiptapHighlightAttributes {
|
||||||
|
color: Some(color.to_string()),
|
||||||
|
}))
|
||||||
|
.map(|_| "已更新背景色")
|
||||||
|
.map_err(|err| format!("命令执行失败:{err}")),
|
||||||
|
None => editor
|
||||||
|
.unset_highlight()
|
||||||
|
.map(|_| "已清除背景色")
|
||||||
|
.map_err(|err| format!("命令执行失败:{err}")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn apply_text_align(
|
||||||
|
editor: TiptapEditorHandle,
|
||||||
|
alignment: TiptapTextAlign,
|
||||||
|
) -> Result<&'static str, String> {
|
||||||
|
let message = match alignment {
|
||||||
|
TiptapTextAlign::Left => "已切到左对齐",
|
||||||
|
TiptapTextAlign::Center => "已切到居中",
|
||||||
|
TiptapTextAlign::Right => "已切到右对齐",
|
||||||
|
TiptapTextAlign::Justify => "已切到两端对齐",
|
||||||
|
};
|
||||||
|
editor
|
||||||
|
.set_text_align(alignment)
|
||||||
|
.map(|_| message)
|
||||||
|
.map_err(|err| format!("命令执行失败:{err}"))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn apply_image_align(
|
||||||
|
editor: TiptapEditorHandle,
|
||||||
|
align: &'static str,
|
||||||
|
) -> Result<&'static str, String> {
|
||||||
|
let _ = editor.focus();
|
||||||
|
let mut attrs = leptos_tiptap::TiptapAttributes::new();
|
||||||
|
attrs.insert("data-align", align);
|
||||||
|
editor
|
||||||
|
.update_attributes(TiptapSchemaTarget::Node(TiptapNodeName::Image), attrs)
|
||||||
|
.map(|_| "已更新图片对齐")
|
||||||
|
.map_err(|err| format!("命令执行失败:{err}"))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn run_slash_action(
|
||||||
|
editor: TiptapEditorHandle,
|
||||||
|
action: SlashActionKind,
|
||||||
|
) -> Result<&'static str, String> {
|
||||||
|
let result = match action {
|
||||||
|
SlashActionKind::AiAssistant
|
||||||
|
| SlashActionKind::AiWrite
|
||||||
|
| SlashActionKind::ContinueWriting
|
||||||
|
| SlashActionKind::Summarize
|
||||||
|
| SlashActionKind::MoreAi => return Ok("AI 命令入口已打开"),
|
||||||
|
SlashActionKind::Paragraph => editor.set_paragraph(),
|
||||||
|
SlashActionKind::Heading1 => editor.toggle_heading(TiptapHeadingLevel::H1),
|
||||||
|
SlashActionKind::Heading2 => editor.toggle_heading(TiptapHeadingLevel::H2),
|
||||||
|
SlashActionKind::Heading3 => editor.toggle_heading(TiptapHeadingLevel::H3),
|
||||||
|
SlashActionKind::Heading4 => editor.toggle_heading(TiptapHeadingLevel::H4),
|
||||||
|
SlashActionKind::BulletList => editor.toggle_bullet_list(),
|
||||||
|
SlashActionKind::OrderedList => editor.toggle_ordered_list(),
|
||||||
|
SlashActionKind::Todo | SlashActionKind::AdvancedTodo => editor.toggle_task_list(),
|
||||||
|
SlashActionKind::Quote => editor.toggle_blockquote(),
|
||||||
|
SlashActionKind::CodeBlock => editor.toggle_code_block(Some(TiptapCodeBlockAttributes {
|
||||||
|
language: Some("rust".into()),
|
||||||
|
})),
|
||||||
|
SlashActionKind::Divider => editor.set_horizontal_rule(),
|
||||||
|
SlashActionKind::SimpleTable => {
|
||||||
|
let _ = editor.focus();
|
||||||
|
editor.insert_table(4, 3, false)
|
||||||
|
}
|
||||||
|
SlashActionKind::Mindmap => {
|
||||||
|
editor.insert_content(TiptapContent::json(mindmap_paragraph_node(next_mindmap_id)), None)
|
||||||
|
}
|
||||||
|
SlashActionKind::Image => {
|
||||||
|
dispatch_editor_upload_request("image", "image/*")?;
|
||||||
|
return Ok("已打开图片上传");
|
||||||
|
}
|
||||||
|
SlashActionKind::UploadAttachment => {
|
||||||
|
dispatch_editor_upload_request("attachment", "")?;
|
||||||
|
return Ok("已打开附件上传");
|
||||||
|
}
|
||||||
|
SlashActionKind::Toc => editor.insert_toc_node(TiptapTocNodeAttrs {
|
||||||
|
top_offset: Some(0),
|
||||||
|
max_show_count: Some(20),
|
||||||
|
show_title: Some(true),
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
result
|
||||||
|
.map(|_| match action {
|
||||||
|
SlashActionKind::AiAssistant
|
||||||
|
| SlashActionKind::AiWrite
|
||||||
|
| SlashActionKind::ContinueWriting
|
||||||
|
| SlashActionKind::Summarize
|
||||||
|
| SlashActionKind::MoreAi => "AI 命令入口已打开",
|
||||||
|
SlashActionKind::Paragraph => "已切到文本",
|
||||||
|
SlashActionKind::Heading1 => "已切到一级标题",
|
||||||
|
SlashActionKind::Heading2 => "已切到二级标题",
|
||||||
|
SlashActionKind::Heading3 => "已切到三级标题",
|
||||||
|
SlashActionKind::Heading4 => "已切到四级标题",
|
||||||
|
SlashActionKind::BulletList => "已切到无序列表",
|
||||||
|
SlashActionKind::OrderedList => "已切到有序列表",
|
||||||
|
SlashActionKind::Todo | SlashActionKind::AdvancedTodo => "已切到待办列表",
|
||||||
|
SlashActionKind::Quote => "已切到引述文字",
|
||||||
|
SlashActionKind::CodeBlock => "已切到代码块",
|
||||||
|
SlashActionKind::Divider => "已插入分割线",
|
||||||
|
SlashActionKind::SimpleTable => "已插入简单表格",
|
||||||
|
SlashActionKind::Mindmap => "已插入思维导图",
|
||||||
|
SlashActionKind::Image => "已打开图片上传",
|
||||||
|
SlashActionKind::UploadAttachment => "已打开附件上传",
|
||||||
|
SlashActionKind::Toc => "已插入页面目录",
|
||||||
|
})
|
||||||
|
.map_err(|err| format!("命令执行失败:{err}"))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn top_level_block_is_page(
|
||||||
|
editor: TiptapEditorHandle,
|
||||||
|
block_index: usize,
|
||||||
|
) -> bool {
|
||||||
|
editor
|
||||||
|
.get_json()
|
||||||
|
.ok()
|
||||||
|
.and_then(|document| {
|
||||||
|
document
|
||||||
|
.get("content")
|
||||||
|
.and_then(Value::as_array)
|
||||||
|
.and_then(|content| content.get(block_index).cloned())
|
||||||
|
})
|
||||||
|
.map(|block| inline_has_page_block_link(&block))
|
||||||
|
.unwrap_or(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn top_level_block_matches_action(
|
||||||
|
editor: TiptapEditorHandle,
|
||||||
|
block_index: usize,
|
||||||
|
action: SlashActionKind,
|
||||||
|
) -> bool {
|
||||||
|
let Some(block) = editor.get_json().ok().and_then(|document| {
|
||||||
|
document
|
||||||
|
.get("content")
|
||||||
|
.and_then(Value::as_array)
|
||||||
|
.and_then(|content| content.get(block_index).cloned())
|
||||||
|
}) else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
let node_type = block
|
||||||
|
.get("type")
|
||||||
|
.and_then(Value::as_str)
|
||||||
|
.unwrap_or_default();
|
||||||
|
match action {
|
||||||
|
SlashActionKind::Paragraph => {
|
||||||
|
node_type == "paragraph" && !inline_has_page_block_link(&block)
|
||||||
|
}
|
||||||
|
SlashActionKind::Heading1
|
||||||
|
| SlashActionKind::Heading2
|
||||||
|
| SlashActionKind::Heading3
|
||||||
|
| SlashActionKind::Heading4 => {
|
||||||
|
let expected_level = match action {
|
||||||
|
SlashActionKind::Heading1 => 1,
|
||||||
|
SlashActionKind::Heading2 => 2,
|
||||||
|
SlashActionKind::Heading3 => 3,
|
||||||
|
SlashActionKind::Heading4 => 4,
|
||||||
|
_ => 0,
|
||||||
|
};
|
||||||
|
node_type == "heading"
|
||||||
|
&& block
|
||||||
|
.get("attrs")
|
||||||
|
.and_then(|attrs| attrs.get("level"))
|
||||||
|
.and_then(Value::as_i64)
|
||||||
|
.map(|level| level == expected_level)
|
||||||
|
.unwrap_or(false)
|
||||||
|
}
|
||||||
|
SlashActionKind::BulletList => node_type == "bulletList",
|
||||||
|
SlashActionKind::OrderedList => node_type == "orderedList",
|
||||||
|
SlashActionKind::Todo | SlashActionKind::AdvancedTodo => node_type == "taskList",
|
||||||
|
SlashActionKind::Quote => node_type == "blockquote",
|
||||||
|
SlashActionKind::CodeBlock => node_type == "codeBlock",
|
||||||
|
SlashActionKind::Divider => node_type == "horizontalRule",
|
||||||
|
SlashActionKind::SimpleTable => node_type == "table",
|
||||||
|
SlashActionKind::Mindmap => {
|
||||||
|
node_type == "paragraph"
|
||||||
|
&& block
|
||||||
|
.get("attrs")
|
||||||
|
.and_then(|attrs| attrs.get("mnoteBlockType"))
|
||||||
|
.and_then(Value::as_str)
|
||||||
|
== Some("mindmap")
|
||||||
|
}
|
||||||
|
SlashActionKind::Image => node_type == "image",
|
||||||
|
SlashActionKind::UploadAttachment => false,
|
||||||
|
SlashActionKind::Toc => node_type == "tocNode",
|
||||||
|
SlashActionKind::AiAssistant
|
||||||
|
| SlashActionKind::AiWrite
|
||||||
|
| SlashActionKind::ContinueWriting
|
||||||
|
| SlashActionKind::Summarize
|
||||||
|
| SlashActionKind::MoreAi => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn next_mindmap_id() -> String {
|
||||||
|
format!(
|
||||||
|
"mindmap-{}",
|
||||||
|
(js_sys::Math::random() * 1_000_000_000.0).round() as u64
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn inline_has_page_block_link(node: &Value) -> bool {
|
||||||
|
if node
|
||||||
|
.get("marks")
|
||||||
|
.and_then(Value::as_array)
|
||||||
|
.map(|marks| {
|
||||||
|
marks.iter().any(|mark| {
|
||||||
|
mark.get("type").and_then(Value::as_str) == Some("link")
|
||||||
|
&& mark
|
||||||
|
.get("attrs")
|
||||||
|
.and_then(|attrs| attrs.get("class"))
|
||||||
|
.and_then(Value::as_str)
|
||||||
|
.map(|class_name| {
|
||||||
|
class_name
|
||||||
|
.split_whitespace()
|
||||||
|
.any(|part| part == "mnote-page-block-link")
|
||||||
|
})
|
||||||
|
.unwrap_or(false)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.unwrap_or(false)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
node.get("content")
|
||||||
|
.and_then(Value::as_array)
|
||||||
|
.map(|children| children.iter().any(inline_has_page_block_link))
|
||||||
|
.unwrap_or(false)
|
||||||
|
}
|
||||||
|
|
||||||
/// 将 delta operations 应用到 Tiptap JSON 树(原地修改)。
|
/// 将 delta operations 应用到 Tiptap JSON 树(原地修改)。
|
||||||
/// 返回 true 表示树发生了变更。
|
/// 返回 true 表示树发生了变更。
|
||||||
pub(crate) fn apply_block_delta_to_json(content: &mut Value, operations: &[Value]) -> bool {
|
pub(crate) fn apply_block_delta_to_json(content: &mut Value, operations: &[Value]) -> bool {
|
||||||
|
|||||||
@@ -3,8 +3,7 @@ use leptos::mount::{mount_to, mount_to_body};
|
|||||||
use leptos::prelude::*;
|
use leptos::prelude::*;
|
||||||
use leptos_dom::helpers::window_event_listener;
|
use leptos_dom::helpers::window_event_listener;
|
||||||
use leptos_tiptap::{
|
use leptos_tiptap::{
|
||||||
TiptapCodeBlockAttributes, TiptapColorAttributes, TiptapContent, TiptapEditor,
|
TiptapContent, TiptapEditor, TiptapEditorHandle, TiptapExtension, TiptapHeadingLevel,
|
||||||
TiptapEditorHandle, TiptapExtension, TiptapHeadingLevel, TiptapHighlightAttributes,
|
|
||||||
TiptapInsertContentOptions, TiptapLinkResource, TiptapMarkName, TiptapNodeName, TiptapRange,
|
TiptapInsertContentOptions, TiptapLinkResource, TiptapMarkName, TiptapNodeName, TiptapRange,
|
||||||
TiptapSchemaTarget, TiptapSelectionState, TiptapTextAlign, TiptapTocNodeAttrs,
|
TiptapSchemaTarget, TiptapSelectionState, TiptapTextAlign, TiptapTocNodeAttrs,
|
||||||
};
|
};
|
||||||
@@ -19,7 +18,6 @@ use web_sys::{
|
|||||||
MouseEvent, Node, RequestInit, RequestMode, Response, WheelEvent,
|
MouseEvent, Node, RequestInit, RequestMode, Response, WheelEvent,
|
||||||
};
|
};
|
||||||
|
|
||||||
use editor_runtime::attachment_upload::dispatch_editor_upload_request;
|
|
||||||
use editor_runtime::block_dnd::{
|
use editor_runtime::block_dnd::{
|
||||||
block_state_from_index as block_state_from_index_runtime,
|
block_state_from_index as block_state_from_index_runtime,
|
||||||
drop_indicator_from_point as drop_indicator_from_point_runtime,
|
drop_indicator_from_point as drop_indicator_from_point_runtime,
|
||||||
@@ -33,7 +31,11 @@ use editor_runtime::block_menu_document::{
|
|||||||
collect_plain_text, mindmap_paragraph_node, paragraph_node,
|
collect_plain_text, mindmap_paragraph_node, paragraph_node,
|
||||||
};
|
};
|
||||||
use editor_runtime::block_menu_overlay;
|
use editor_runtime::block_menu_overlay;
|
||||||
use editor_runtime::block_transform::apply_block_delta_to_json;
|
use editor_runtime::block_transform::{
|
||||||
|
apply_block_delta_to_json, apply_highlight_color, apply_image_align, apply_text_align,
|
||||||
|
apply_text_color, clear_text_color, run_slash_action, top_level_block_is_page,
|
||||||
|
top_level_block_matches_action,
|
||||||
|
};
|
||||||
use editor_runtime::bridge_dispatch::HostCommandKind;
|
use editor_runtime::bridge_dispatch::HostCommandKind;
|
||||||
use editor_runtime::bridge_events::{
|
use editor_runtime::bridge_events::{
|
||||||
BridgeSelectorsPayload, ChangeMetaPayload, ChangePayload, HeightPayload, HostCommandEnvelope,
|
BridgeSelectorsPayload, ChangeMetaPayload, ChangePayload, HeightPayload, HostCommandEnvelope,
|
||||||
@@ -1315,142 +1317,6 @@ fn apply_host_document_payload(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn apply_text_color(editor: TiptapEditorHandle, color: &str) -> Result<&'static str, String> {
|
|
||||||
editor
|
|
||||||
.set_color(TiptapColorAttributes {
|
|
||||||
color: color.to_string(),
|
|
||||||
})
|
|
||||||
.map(|_| "已更新文字颜色")
|
|
||||||
.map_err(|err| format!("命令执行失败:{err}"))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn clear_text_color(editor: TiptapEditorHandle) -> Result<&'static str, String> {
|
|
||||||
editor
|
|
||||||
.unset_color()
|
|
||||||
.map(|_| "已清除文字颜色")
|
|
||||||
.map_err(|err| format!("命令执行失败:{err}"))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn apply_highlight_color(
|
|
||||||
editor: TiptapEditorHandle,
|
|
||||||
color: Option<&str>,
|
|
||||||
) -> Result<&'static str, String> {
|
|
||||||
match color {
|
|
||||||
Some(color) => editor
|
|
||||||
.set_highlight(Some(TiptapHighlightAttributes {
|
|
||||||
color: Some(color.to_string()),
|
|
||||||
}))
|
|
||||||
.map(|_| "已更新背景色")
|
|
||||||
.map_err(|err| format!("命令执行失败:{err}")),
|
|
||||||
None => editor
|
|
||||||
.unset_highlight()
|
|
||||||
.map(|_| "已清除背景色")
|
|
||||||
.map_err(|err| format!("命令执行失败:{err}")),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn apply_text_align(
|
|
||||||
editor: TiptapEditorHandle,
|
|
||||||
alignment: TiptapTextAlign,
|
|
||||||
) -> Result<&'static str, String> {
|
|
||||||
let message = match alignment {
|
|
||||||
TiptapTextAlign::Left => "已切到左对齐",
|
|
||||||
TiptapTextAlign::Center => "已切到居中",
|
|
||||||
TiptapTextAlign::Right => "已切到右对齐",
|
|
||||||
TiptapTextAlign::Justify => "已切到两端对齐",
|
|
||||||
};
|
|
||||||
editor
|
|
||||||
.set_text_align(alignment)
|
|
||||||
.map(|_| message)
|
|
||||||
.map_err(|err| format!("命令执行失败:{err}"))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn apply_image_align(
|
|
||||||
editor: TiptapEditorHandle,
|
|
||||||
align: &'static str,
|
|
||||||
) -> Result<&'static str, String> {
|
|
||||||
let _ = editor.focus();
|
|
||||||
let mut attrs = leptos_tiptap::TiptapAttributes::new();
|
|
||||||
attrs.insert("data-align", align);
|
|
||||||
editor
|
|
||||||
.update_attributes(TiptapSchemaTarget::Node(TiptapNodeName::Image), attrs)
|
|
||||||
.map(|_| "已更新图片对齐")
|
|
||||||
.map_err(|err| format!("命令执行失败:{err}"))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn run_slash_action(
|
|
||||||
editor: TiptapEditorHandle,
|
|
||||||
action: SlashActionKind,
|
|
||||||
) -> Result<&'static str, String> {
|
|
||||||
let result = match action {
|
|
||||||
SlashActionKind::AiAssistant
|
|
||||||
| SlashActionKind::AiWrite
|
|
||||||
| SlashActionKind::ContinueWriting
|
|
||||||
| SlashActionKind::Summarize
|
|
||||||
| SlashActionKind::MoreAi => return Ok("AI 命令入口已打开"),
|
|
||||||
SlashActionKind::Paragraph => editor.set_paragraph(),
|
|
||||||
SlashActionKind::Heading1 => editor.toggle_heading(TiptapHeadingLevel::H1),
|
|
||||||
SlashActionKind::Heading2 => editor.toggle_heading(TiptapHeadingLevel::H2),
|
|
||||||
SlashActionKind::Heading3 => editor.toggle_heading(TiptapHeadingLevel::H3),
|
|
||||||
SlashActionKind::Heading4 => editor.toggle_heading(TiptapHeadingLevel::H4),
|
|
||||||
SlashActionKind::BulletList => editor.toggle_bullet_list(),
|
|
||||||
SlashActionKind::OrderedList => editor.toggle_ordered_list(),
|
|
||||||
SlashActionKind::Todo | SlashActionKind::AdvancedTodo => editor.toggle_task_list(),
|
|
||||||
SlashActionKind::Quote => editor.toggle_blockquote(),
|
|
||||||
SlashActionKind::CodeBlock => editor.toggle_code_block(Some(TiptapCodeBlockAttributes {
|
|
||||||
language: Some("rust".into()),
|
|
||||||
})),
|
|
||||||
SlashActionKind::Divider => editor.set_horizontal_rule(),
|
|
||||||
SlashActionKind::SimpleTable => {
|
|
||||||
let _ = editor.focus();
|
|
||||||
editor.insert_table(4, 3, false)
|
|
||||||
}
|
|
||||||
SlashActionKind::Mindmap => editor.insert_content(
|
|
||||||
TiptapContent::json(mindmap_paragraph_node(next_mindmap_id)),
|
|
||||||
None,
|
|
||||||
),
|
|
||||||
SlashActionKind::Image => {
|
|
||||||
dispatch_editor_upload_request("image", "image/*")?;
|
|
||||||
return Ok("已打开图片上传");
|
|
||||||
}
|
|
||||||
SlashActionKind::UploadAttachment => {
|
|
||||||
dispatch_editor_upload_request("attachment", "")?;
|
|
||||||
return Ok("已打开附件上传");
|
|
||||||
}
|
|
||||||
SlashActionKind::Toc => editor.insert_toc_node(TiptapTocNodeAttrs {
|
|
||||||
top_offset: Some(0),
|
|
||||||
max_show_count: Some(20),
|
|
||||||
show_title: Some(true),
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
|
|
||||||
result
|
|
||||||
.map(|_| match action {
|
|
||||||
SlashActionKind::AiAssistant
|
|
||||||
| SlashActionKind::AiWrite
|
|
||||||
| SlashActionKind::ContinueWriting
|
|
||||||
| SlashActionKind::Summarize
|
|
||||||
| SlashActionKind::MoreAi => "AI 命令入口已打开",
|
|
||||||
SlashActionKind::Paragraph => "已切到文本",
|
|
||||||
SlashActionKind::Heading1 => "已切到一级标题",
|
|
||||||
SlashActionKind::Heading2 => "已切到二级标题",
|
|
||||||
SlashActionKind::Heading3 => "已切到三级标题",
|
|
||||||
SlashActionKind::Heading4 => "已切到四级标题",
|
|
||||||
SlashActionKind::BulletList => "已切到无序列表",
|
|
||||||
SlashActionKind::OrderedList => "已切到有序列表",
|
|
||||||
SlashActionKind::Todo | SlashActionKind::AdvancedTodo => "已切到待办列表",
|
|
||||||
SlashActionKind::Quote => "已切到引述文字",
|
|
||||||
SlashActionKind::CodeBlock => "已切到代码块",
|
|
||||||
SlashActionKind::Divider => "已插入分割线",
|
|
||||||
SlashActionKind::SimpleTable => "已插入简单表格",
|
|
||||||
SlashActionKind::Mindmap => "已插入思维导图",
|
|
||||||
SlashActionKind::Image => "已打开图片上传",
|
|
||||||
SlashActionKind::UploadAttachment => "已打开附件上传",
|
|
||||||
SlashActionKind::Toc => "已插入页面目录",
|
|
||||||
})
|
|
||||||
.map_err(|err| format!("命令执行失败:{err}"))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn run_block_turn_into_action(
|
fn run_block_turn_into_action(
|
||||||
editor: TiptapEditorHandle,
|
editor: TiptapEditorHandle,
|
||||||
block_index: usize,
|
block_index: usize,
|
||||||
@@ -1709,117 +1575,6 @@ fn block_plain_text_from_index(
|
|||||||
Ok(normalized_page_block_title(&collect_plain_text(block)))
|
Ok(normalized_page_block_title(&collect_plain_text(block)))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn inline_has_page_block_link(node: &Value) -> bool {
|
|
||||||
if node
|
|
||||||
.get("marks")
|
|
||||||
.and_then(Value::as_array)
|
|
||||||
.map(|marks| {
|
|
||||||
marks.iter().any(|mark| {
|
|
||||||
mark.get("type").and_then(Value::as_str) == Some("link")
|
|
||||||
&& mark
|
|
||||||
.get("attrs")
|
|
||||||
.and_then(|attrs| attrs.get("class"))
|
|
||||||
.and_then(Value::as_str)
|
|
||||||
.map(|class_name| {
|
|
||||||
class_name
|
|
||||||
.split_whitespace()
|
|
||||||
.any(|part| part == "mnote-page-block-link")
|
|
||||||
})
|
|
||||||
.unwrap_or(false)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.unwrap_or(false)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
node.get("content")
|
|
||||||
.and_then(Value::as_array)
|
|
||||||
.map(|children| children.iter().any(inline_has_page_block_link))
|
|
||||||
.unwrap_or(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn top_level_block_is_page(editor: TiptapEditorHandle, block_index: usize) -> bool {
|
|
||||||
editor
|
|
||||||
.get_json()
|
|
||||||
.ok()
|
|
||||||
.and_then(|document| {
|
|
||||||
document
|
|
||||||
.get("content")
|
|
||||||
.and_then(Value::as_array)
|
|
||||||
.and_then(|content| content.get(block_index).cloned())
|
|
||||||
})
|
|
||||||
.map(|block| inline_has_page_block_link(&block))
|
|
||||||
.unwrap_or(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn top_level_block_matches_action(
|
|
||||||
editor: TiptapEditorHandle,
|
|
||||||
block_index: usize,
|
|
||||||
action: SlashActionKind,
|
|
||||||
) -> bool {
|
|
||||||
let Some(block) = editor.get_json().ok().and_then(|document| {
|
|
||||||
document
|
|
||||||
.get("content")
|
|
||||||
.and_then(Value::as_array)
|
|
||||||
.and_then(|content| content.get(block_index).cloned())
|
|
||||||
}) else {
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
|
|
||||||
let node_type = block
|
|
||||||
.get("type")
|
|
||||||
.and_then(Value::as_str)
|
|
||||||
.unwrap_or_default();
|
|
||||||
match action {
|
|
||||||
SlashActionKind::Paragraph => {
|
|
||||||
node_type == "paragraph" && !inline_has_page_block_link(&block)
|
|
||||||
}
|
|
||||||
SlashActionKind::Heading1
|
|
||||||
| SlashActionKind::Heading2
|
|
||||||
| SlashActionKind::Heading3
|
|
||||||
| SlashActionKind::Heading4 => {
|
|
||||||
let expected_level = match action {
|
|
||||||
SlashActionKind::Heading1 => 1,
|
|
||||||
SlashActionKind::Heading2 => 2,
|
|
||||||
SlashActionKind::Heading3 => 3,
|
|
||||||
SlashActionKind::Heading4 => 4,
|
|
||||||
_ => 0,
|
|
||||||
};
|
|
||||||
node_type == "heading"
|
|
||||||
&& block
|
|
||||||
.get("attrs")
|
|
||||||
.and_then(|attrs| attrs.get("level"))
|
|
||||||
.and_then(Value::as_i64)
|
|
||||||
.map(|level| level == expected_level)
|
|
||||||
.unwrap_or(false)
|
|
||||||
}
|
|
||||||
SlashActionKind::BulletList => node_type == "bulletList",
|
|
||||||
SlashActionKind::OrderedList => node_type == "orderedList",
|
|
||||||
SlashActionKind::Todo | SlashActionKind::AdvancedTodo => node_type == "taskList",
|
|
||||||
SlashActionKind::Quote => node_type == "blockquote",
|
|
||||||
SlashActionKind::CodeBlock => node_type == "codeBlock",
|
|
||||||
SlashActionKind::Divider => node_type == "horizontalRule",
|
|
||||||
SlashActionKind::SimpleTable => node_type == "table",
|
|
||||||
SlashActionKind::Mindmap => {
|
|
||||||
node_type == "paragraph"
|
|
||||||
&& block
|
|
||||||
.get("attrs")
|
|
||||||
.and_then(|attrs| attrs.get("mnoteBlockType"))
|
|
||||||
.and_then(Value::as_str)
|
|
||||||
== Some("mindmap")
|
|
||||||
}
|
|
||||||
SlashActionKind::Image => node_type == "image",
|
|
||||||
SlashActionKind::UploadAttachment => false,
|
|
||||||
SlashActionKind::Toc => node_type == "tocNode",
|
|
||||||
SlashActionKind::AiAssistant
|
|
||||||
| SlashActionKind::AiWrite
|
|
||||||
| SlashActionKind::ContinueWriting
|
|
||||||
| SlashActionKind::Summarize
|
|
||||||
| SlashActionKind::MoreAi => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn page_reference_paragraph_node(page_id: &str, workspace_id: Option<&str>, title: &str) -> Value {
|
fn page_reference_paragraph_node(page_id: &str, workspace_id: Option<&str>, title: &str) -> Value {
|
||||||
let href = if page_id.trim().is_empty() {
|
let href = if page_id.trim().is_empty() {
|
||||||
"#".to_string()
|
"#".to_string()
|
||||||
|
|||||||
Reference in New Issue
Block a user