add editor transform to page parity
This commit is contained in:
+7
-7
@@ -1058,37 +1058,37 @@ function __wbg_get_imports() {
|
||||
return ret;
|
||||
},
|
||||
__wbindgen_cast_0000000000000001: function(arg0, arg1) {
|
||||
// Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [Externref], shim_idx: 1102, ret: Unit, inner_ret: Some(Unit) }, mutable: false }) -> Externref`.
|
||||
// Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [Externref], shim_idx: 1111, ret: Unit, inner_ret: Some(Unit) }, mutable: false }) -> Externref`.
|
||||
const ret = makeClosure(arg0, arg1, wasm_bindgen__convert__closures_____invoke__h1ea2b89742286ce6);
|
||||
return ret;
|
||||
},
|
||||
__wbindgen_cast_0000000000000002: function(arg0, arg1) {
|
||||
// Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [Externref], shim_idx: 1357, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
|
||||
// Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [Externref], shim_idx: 1366, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
|
||||
const ret = makeMutClosure(arg0, arg1, wasm_bindgen__convert__closures_____invoke__h2593b18d2d6f8a75);
|
||||
return ret;
|
||||
},
|
||||
__wbindgen_cast_0000000000000003: function(arg0, arg1) {
|
||||
// Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [Externref], shim_idx: 1448, ret: Result(Unit), inner_ret: Some(Result(Unit)) }, mutable: true }) -> Externref`.
|
||||
// Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [Externref], shim_idx: 1457, ret: Result(Unit), inner_ret: Some(Result(Unit)) }, mutable: true }) -> Externref`.
|
||||
const ret = makeMutClosure(arg0, arg1, wasm_bindgen__convert__closures_____invoke__h36e788bd1cd1f0f7);
|
||||
return ret;
|
||||
},
|
||||
__wbindgen_cast_0000000000000004: function(arg0, arg1) {
|
||||
// Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [NamedExternref("Event")], shim_idx: 1283, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
|
||||
// Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [NamedExternref("Event")], shim_idx: 1292, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
|
||||
const ret = makeMutClosure(arg0, arg1, wasm_bindgen__convert__closures_____invoke__h03deb55668e1377f);
|
||||
return ret;
|
||||
},
|
||||
__wbindgen_cast_0000000000000005: function(arg0, arg1) {
|
||||
// Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [NamedExternref("Event")], shim_idx: 1359, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
|
||||
// Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [NamedExternref("Event")], shim_idx: 1368, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
|
||||
const ret = makeMutClosure(arg0, arg1, wasm_bindgen__convert__closures_____invoke__h837fba73fce77300);
|
||||
return ret;
|
||||
},
|
||||
__wbindgen_cast_0000000000000006: function(arg0, arg1) {
|
||||
// Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [], shim_idx: 1296, ret: Unit, inner_ret: Some(Unit) }, mutable: false }) -> Externref`.
|
||||
// Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [], shim_idx: 1305, ret: Unit, inner_ret: Some(Unit) }, mutable: false }) -> Externref`.
|
||||
const ret = makeClosure(arg0, arg1, wasm_bindgen__convert__closures_____invoke__hd17f30facfdd737f);
|
||||
return ret;
|
||||
},
|
||||
__wbindgen_cast_0000000000000007: function(arg0, arg1) {
|
||||
// Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [], shim_idx: 1358, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
|
||||
// Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [], shim_idx: 1367, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
|
||||
const ret = makeMutClosure(arg0, arg1, wasm_bindgen__convert__closures_____invoke__hf726e22cfd1ee0a9);
|
||||
return ret;
|
||||
},
|
||||
|
||||
BIN
Binary file not shown.
@@ -1196,6 +1196,45 @@ const SPIKE_STYLE: &str = r#"
|
||||
min-height: 1.6em;
|
||||
}
|
||||
|
||||
.editor-surface .ProseMirror p:has(> a.mnote-page-block-link) {
|
||||
width: 100%;
|
||||
margin: 0 0 8px;
|
||||
padding: 2px 6px;
|
||||
border-radius: 4px;
|
||||
background: rgba(255, 71, 71, 0.12);
|
||||
}
|
||||
|
||||
.editor-surface .ProseMirror a.mnote-page-block-link {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
max-width: 100%;
|
||||
padding: 1px 4px;
|
||||
border-radius: 4px;
|
||||
color: #37352f;
|
||||
font-weight: 600;
|
||||
line-height: 1.45;
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.editor-surface .ProseMirror a.mnote-page-block-link::before {
|
||||
content: "";
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
flex: 0 0 auto;
|
||||
border: 1.4px solid #c8c5c0;
|
||||
border-radius: 3px;
|
||||
background: linear-gradient(180deg, #ffffff 0%, #f6f4f1 100%);
|
||||
box-shadow: inset 0 -2px 0 rgba(55, 53, 47, 0.08);
|
||||
}
|
||||
|
||||
.editor-surface .ProseMirror a.mnote-page-block-link:hover {
|
||||
color: #37352f;
|
||||
background: rgba(55, 53, 47, 0.08);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.footer-strip {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
@@ -3986,6 +4025,168 @@ fn run_block_turn_into_action(
|
||||
}
|
||||
}
|
||||
|
||||
fn normalized_page_block_title(text: &str) -> String {
|
||||
let trimmed = text.trim();
|
||||
if trimmed.is_empty() {
|
||||
"未命名页面".to_string()
|
||||
} else {
|
||||
trimmed.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
fn block_plain_text_from_index(
|
||||
editor: TiptapEditorHandle,
|
||||
block_index: usize,
|
||||
) -> Result<String, String> {
|
||||
let document = editor
|
||||
.get_json()
|
||||
.map_err(|err| format!("读取当前 JSON 失败:{err}"))?;
|
||||
let block = document
|
||||
.get("content")
|
||||
.and_then(Value::as_array)
|
||||
.and_then(|content| content.get(block_index))
|
||||
.ok_or_else(|| format!("找不到第 {} 个块", block_index + 1))?;
|
||||
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 page_reference_paragraph_node(page_id: &str, title: &str) -> Value {
|
||||
let href = if page_id.trim().is_empty() {
|
||||
"#".to_string()
|
||||
} else {
|
||||
format!("/documents/{page_id}")
|
||||
};
|
||||
paragraph_node(vec![json!({
|
||||
"type": "text",
|
||||
"text": normalized_page_block_title(title),
|
||||
"marks": [{
|
||||
"type": "link",
|
||||
"attrs": {
|
||||
"href": href,
|
||||
"target": null,
|
||||
"rel": "noopener noreferrer nofollow",
|
||||
"class": "mnote-page-block-link",
|
||||
}
|
||||
}]
|
||||
})])
|
||||
}
|
||||
|
||||
fn replace_block_with_page_reference(
|
||||
editor: TiptapEditorHandle,
|
||||
block_index: usize,
|
||||
page_id: &str,
|
||||
title: &str,
|
||||
) -> Result<(), String> {
|
||||
let mut document = editor
|
||||
.get_json()
|
||||
.map_err(|err| format!("读取当前 JSON 失败:{err}"))?;
|
||||
let content = document_content_mut(&mut document)?;
|
||||
if block_index >= content.len() {
|
||||
return Err(format!("找不到第 {} 个块", block_index + 1));
|
||||
}
|
||||
content[block_index] = page_reference_paragraph_node(page_id, title);
|
||||
let next_payload = document
|
||||
.get("content")
|
||||
.and_then(Value::as_array)
|
||||
.cloned()
|
||||
.map(Value::Array)
|
||||
.unwrap_or(document);
|
||||
editor
|
||||
.set_content(TiptapContent::json(next_payload))
|
||||
.map_err(|err| format!("转换为页面失败:{err}"))?;
|
||||
focus_top_level_block_start(editor, block_index)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn run_block_turn_into_page_action(
|
||||
editor: TiptapEditorHandle,
|
||||
block_index: usize,
|
||||
document_id: ReadSignal<Option<String>>,
|
||||
workspace_id: ReadSignal<Option<String>>,
|
||||
title: ReadSignal<String>,
|
||||
set_dirty_count: WriteSignal<u32>,
|
||||
set_html_output: WriteSignal<String>,
|
||||
set_document_json: WriteSignal<Value>,
|
||||
set_json_output: WriteSignal<String>,
|
||||
set_command_feedback: WriteSignal<String>,
|
||||
set_block_menu_open: WriteSignal<bool>,
|
||||
set_block_menu_anchor: WriteSignal<Option<HoveredBlockState>>,
|
||||
set_block_turn_into_open: WriteSignal<bool>,
|
||||
) {
|
||||
let page_title = match block_plain_text_from_index(editor, block_index) {
|
||||
Ok(value) => value,
|
||||
Err(err) => {
|
||||
set_command_feedback.set(err);
|
||||
return;
|
||||
}
|
||||
};
|
||||
let page_id = document_id
|
||||
.get_untracked()
|
||||
.unwrap_or_else(|| format!("page-block-{}", block_index + 1));
|
||||
match replace_block_with_page_reference(editor, block_index, &page_id, &page_title) {
|
||||
Ok(()) => {
|
||||
set_block_menu_open.set(false);
|
||||
set_block_menu_anchor.set(None);
|
||||
set_block_turn_into_open.set(false);
|
||||
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,
|
||||
format!("已转换为页面:{page_title}"),
|
||||
);
|
||||
}
|
||||
Err(err) => set_command_feedback.set(err),
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_feedback_result<E>(setter: WriteSignal<String>, result: Result<&'static str, E>)
|
||||
where
|
||||
E: Display,
|
||||
@@ -5616,6 +5817,8 @@ fn App(mount_options: MountOptions) -> impl IntoView {
|
||||
});
|
||||
let menu_top = format!("{}px", menu_layout.top);
|
||||
let menu_bottom = "auto";
|
||||
let current_block_is_page = top_level_block_is_page(editor, block_index);
|
||||
let page_current_marker = if current_block_is_page { "✓" } else { "Ctrl+Shift+9" };
|
||||
view! {
|
||||
<div
|
||||
class="block-drag-menu"
|
||||
@@ -5940,7 +6143,33 @@ fn App(mount_options: MountOptions) -> impl IntoView {
|
||||
}
|
||||
})
|
||||
.collect_view()}
|
||||
<button class="block-drag-menu-item" data-testid="block-transform-item-page" disabled=true><span class="block-drag-menu-icon">"▣"</span><span>"页面"</span><span class="block-drag-menu-shortcut">"Ctrl+Shift+9"</span></button>
|
||||
<button
|
||||
class="block-drag-menu-item"
|
||||
data-testid="block-transform-item-page"
|
||||
data-current=current_block_is_page.to_string()
|
||||
on:click=move |event: MouseEvent| {
|
||||
event.stop_propagation();
|
||||
run_block_turn_into_page_action(
|
||||
editor,
|
||||
block_index,
|
||||
document_id,
|
||||
workspace_id,
|
||||
title,
|
||||
set_dirty_count,
|
||||
set_html_output,
|
||||
set_document_json,
|
||||
set_json_output,
|
||||
set_command_feedback,
|
||||
set_block_menu_open,
|
||||
set_block_menu_anchor,
|
||||
set_block_turn_into_open,
|
||||
);
|
||||
}
|
||||
>
|
||||
<span class="block-drag-menu-icon">"▣"</span>
|
||||
<span>"页面"</span>
|
||||
<span class="block-drag-menu-shortcut">{page_current_marker}</span>
|
||||
</button>
|
||||
<button class="block-drag-menu-item" data-testid="block-transform-item-folded-list" disabled=true><span class="block-drag-menu-icon">"≡"</span><span>"折叠列表"</span><span class="block-drag-menu-shortcut">"Ctrl+Shift+8"</span></button>
|
||||
<button class="block-drag-menu-item" data-testid="block-transform-item-folded-title" disabled=true><span class="block-drag-menu-icon">"H1"</span><span>"折叠标题"</span><span class="block-drag-menu-arrow">"›"</span></button>
|
||||
<button class="block-drag-menu-item" data-testid="block-transform-item-folded-todo" disabled=true><span class="block-drag-menu-icon">"☑"</span><span>"折叠待办"</span><span class="block-drag-menu-arrow">"›"</span></button>
|
||||
|
||||
Reference in New Issue
Block a user