refactor: split editor mount and sidebar page ai

This commit is contained in:
lix-2026
2026-05-26 04:01:52 +08:00
parent c65135f7ed
commit a8bc87173f
10 changed files with 3124 additions and 2973 deletions
+14 -158
View File
@@ -1,5 +1,5 @@
use leptos::ev;
use leptos::mount::{mount_to, mount_to_body};
use leptos::mount::mount_to_body;
use leptos::prelude::*;
use leptos_dom::helpers::window_event_listener;
use leptos_tiptap::{
@@ -9,7 +9,7 @@ use leptos_tiptap::{
use send_wrapper::SendWrapper;
use serde::Deserialize;
use serde_json::{json, Value};
use std::{cell::Cell, fmt::Display};
use std::fmt::Display;
use wasm_bindgen::{closure::Closure, prelude::*, JsCast, JsValue};
use wasm_bindgen_futures::spawn_local;
use web_sys::{
@@ -37,8 +37,8 @@ use editor_runtime::block_transform::{
use editor_runtime::bridge_dispatch::HostCommandKind;
use editor_runtime::bridge_events::{
BridgeSelectorsPayload, ChangeMetaPayload, ChangePayload, HeightPayload, HostCommandEnvelope,
HostCommandPayload, HostStatusPayload, ReadyPayload, RuntimePageOptions, StatePayload,
BLOCK_DELTA_EVENT, COMMAND_EVENT, HEIGHT_EVENT, PROTOCOL, RUNTIME_NAME,
HostCommandPayload, HostStatusPayload, ReadyPayload, StatePayload, BLOCK_DELTA_EVENT,
COMMAND_EVENT, HEIGHT_EVENT, PROTOCOL, RUNTIME_NAME,
};
use editor_runtime::command_sync::{
read_editor_snapshot, sync_editor_outputs, sync_persisted_editor_command,
@@ -53,6 +53,13 @@ use editor_runtime::dom_selection::{
};
use editor_runtime::editor_focus::{active_editor_stage, body_has_focus, schedule_editor_focus};
use editor_runtime::mindmap_node_view::{mount_mindmap_shell_impl, unmount_mindmap_shell_impl};
use editor_runtime::mount::{
initial_content, mount_app_into, runtime_delivery_mode, runtime_editor_instance_id,
runtime_mount_context, set_runtime_mount_context, set_runtime_mount_options, MountOptions,
RuntimeDeliveryMode, RuntimeStandaloneObject,
};
#[cfg(test)]
use editor_runtime::mount::resolve_runtime_delivery_mode;
use editor_runtime::overlays::{
clamp_overlay_anchor, close_editor_floating_overlays_if_escape, image_element_from_target,
image_toolbar_anchor_from_image, open_block_menu_overlay, open_image_toolbar_overlay,
@@ -68,10 +75,9 @@ use editor_runtime::persistence::{
persisted_document_identity, PersistedDocumentIdentity,
};
use editor_runtime::runtime_bridge::{
build_command_listener, dispatch_change_event_to_target, dispatch_ready_event_to_target,
dispatch_change_event_to_target, dispatch_ready_event_to_target,
dispatch_runtime_event, dispatch_selection_event_to_target, dispatch_state_event_to_target,
dispatch_status_event_to_target, register_runtime_listener, register_unmount_handle,
take_unmount_handle,
dispatch_status_event_to_target, register_runtime_listener, take_unmount_handle,
};
use editor_runtime::slash_actions::{SlashActionKind, SLASH_ACTIONS};
use editor_runtime::style::SPIKE_STYLE;
@@ -515,98 +521,6 @@ mod tests {
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum RuntimeDeliveryMode {
Standalone,
Embedded,
}
#[derive(Clone, Debug, Default, Deserialize)]
#[serde(rename_all = "camelCase")]
struct MountOptions {
document_id: Option<String>,
workspace_id: Option<String>,
title: Option<String>,
content: Option<Value>,
html: Option<String>,
editable: Option<bool>,
read_only: Option<bool>,
revision: Option<i64>,
conflict_detection_key: Option<String>,
page_options: Option<RuntimePageOptions>,
#[serde(default)]
standalone_object: Option<RuntimeStandaloneObject>,
}
#[derive(Clone, Debug, Default, Deserialize)]
#[serde(rename_all = "camelCase")]
struct RuntimeStandaloneObject {
kind: Option<String>,
document_id: Option<String>,
mindmap_id: Option<String>,
}
struct RuntimeMountContext {
id: u32,
target: EventTarget,
mode: RuntimeDeliveryMode,
}
#[derive(Clone, Debug)]
#[allow(dead_code)]
struct RuntimeMountOptions {
options: MountOptions,
mode: RuntimeDeliveryMode,
}
thread_local! {
static RUNTIME_MOUNT_CONTEXT: std::cell::RefCell<Option<RuntimeMountContext>> = const { std::cell::RefCell::new(None) };
static RUNTIME_MOUNT_OPTIONS: std::cell::RefCell<Option<RuntimeMountOptions>> = const { std::cell::RefCell::new(None) };
static NEXT_MOUNT_ID: Cell<u32> = const { Cell::new(1) };
}
fn next_mount_id() -> u32 {
NEXT_MOUNT_ID.with(|cell| {
let next = cell.get();
cell.set(next.saturating_add(1).max(1));
next
})
}
fn set_runtime_mount_context(context: Option<RuntimeMountContext>) {
RUNTIME_MOUNT_CONTEXT.with(|cell| {
*cell.borrow_mut() = context;
});
}
fn runtime_mount_context() -> Option<(u32, EventTarget, RuntimeDeliveryMode)> {
RUNTIME_MOUNT_CONTEXT.with(|cell| {
cell.borrow()
.as_ref()
.map(|context| (context.id, context.target.clone(), context.mode))
})
}
fn runtime_delivery_mode() -> RuntimeDeliveryMode {
resolve_runtime_delivery_mode(
runtime_mount_context().map(|(_, _, mode)| mode),
url_requests_embedded_mode(),
)
}
fn set_runtime_mount_options(options: Option<RuntimeMountOptions>) {
RUNTIME_MOUNT_OPTIONS.with(|cell| {
*cell.borrow_mut() = options;
});
}
fn runtime_editor_instance_id(mount_id: Option<u32>) -> String {
mount_id
.filter(|value| *value > 0)
.map(|value| format!("mnote-leptos-tiptap-spike-{value}"))
.unwrap_or_else(|| "mnote-leptos-tiptap-spike-standalone".to_string())
}
fn editor_block_by_id(block_id: &str) -> Option<Element> {
find_mnote_block_anchor(block_id)
}
@@ -728,7 +642,7 @@ pub fn unmount(mount_id: u32) -> Result<(), JsValue> {
if let Some(handle) = take_unmount_handle(mount_id) {
if runtime_mount_context().map(|(id, _, _)| id) == Some(mount_id) {
set_runtime_mount_context(None);
set_runtime_mount_options(None);
set_runtime_mount_options(None, RuntimeDeliveryMode::Embedded);
}
drop(handle);
Ok(())
@@ -737,35 +651,6 @@ pub fn unmount(mount_id: u32) -> Result<(), JsValue> {
}
}
fn mount_app_into(target: HtmlElement, options: MountOptions, mode: RuntimeDeliveryMode) -> u32 {
let mount_id = next_mount_id();
let context_target: EventTarget = target.clone().into();
let mount_options = RuntimeMountOptions {
options: options.clone(),
mode,
};
set_runtime_mount_options(Some(mount_options.clone()));
set_runtime_mount_context(Some(RuntimeMountContext {
id: mount_id,
target: context_target.clone(),
mode,
}));
let handle = mount_to(target, move || {
view! {
<App mount_options=options />
}
});
let command_listener = build_command_listener(mount_id);
let _ = context_target
.add_event_listener_with_callback(COMMAND_EVENT, command_listener.as_ref().unchecked_ref());
register_unmount_handle(mount_id, context_target, command_listener, handle);
mount_id
}
fn default_title() -> String {
"Leptos Tiptap P0".to_string()
}
@@ -818,28 +703,6 @@ fn runtime_persisted_identity(
persisted_document_identity(document_id.get_untracked(), workspace_id.get_untracked())
}
fn url_requests_embedded_mode() -> bool {
window()
.and_then(|win| win.location().search().ok())
.map(|search| search.contains("embedded=1"))
.unwrap_or(false)
}
fn resolve_runtime_delivery_mode(
explicit_mode: Option<RuntimeDeliveryMode>,
url_embedded: bool,
) -> RuntimeDeliveryMode {
if let Some(mode) = explicit_mode {
return mode;
}
if url_embedded {
RuntimeDeliveryMode::Embedded
} else {
RuntimeDeliveryMode::Standalone
}
}
fn is_embedded_mode() -> bool {
runtime_delivery_mode() == RuntimeDeliveryMode::Embedded
}
@@ -4386,13 +4249,6 @@ fn App(mount_options: MountOptions) -> impl IntoView {
}
}
fn initial_content() -> TiptapContent {
TiptapContent::json(serde_json::json!({
"type": "doc",
"content": []
}))
}
pub fn standalone_main() {
console_error_panic_hook::set_once();
mount_to_body(|| {