refactor: extract editor focus helpers
This commit is contained in:
@@ -98,3 +98,7 @@ UI / 浏览器可见项必须有真实浏览器截图或结构化 smoke 证据
|
||||
- Codex 已读取 Hindsight recall、`process-handoff.md/json`、`result.json`,并核对 worktree `git status --short` / `git diff --stat` 为空;runner 已产出 completed handoff 但进程未自行退出,主控已终止残留 runner。
|
||||
- 审计结论:当前不宜直接把 `sync_editor_overlay_state` / `try_sync_editor_overlay_state` 迁入 `overlays.rs`,因为它们仍强依赖 `HoveredBlockState`、slash/block menu signals、`active_editor_stage` 和 8/18 overlay signal 的局部关闭策略;`on_selection_change` 仍有一套内联 signal 同步逻辑,直接迁出会造成两套 overlay reconciler。
|
||||
- 下一刀建议:先做类型/边界准备或更窄 helper,而不是迁整个 sync 函数;候选是抽出 block/overlay shared types(例如 `HoveredBlockState` 及相关 anchor 类型)或把 `active_editor_stage` 的 DOM 判断收口到 runtime helper,之后再考虑 `close_editor_floating_overlays` 整簇迁移。
|
||||
- 2026-05-25:Codex 主控完成 editor focus helper 第一刀:新增 `editor_runtime/editor_focus.rs`,迁出 `schedule_editor_focus` 与 `active_editor_stage`;`lib.rs` 仍保留 overlay / block menu signal 编排和调用点。
|
||||
- 修改:`rust/spikes/leptos-tiptap-spike/src/editor_runtime/editor_focus.rs`、`rust/spikes/leptos-tiptap-spike/src/editor_runtime/mod.rs`、`rust/spikes/leptos-tiptap-spike/src/lib.rs`、generated island wasm/js。
|
||||
- 已验证:`cargo fmt --manifest-path rust/Cargo.toml --all --check`、`cargo check --manifest-path rust/spikes/leptos-tiptap-spike/Cargo.toml`、wasm release build、`wasm-bindgen`、`node scripts/task489-block-menu-delete-undo-smoke.js`、`node scripts/task488-local-attachment-link-delete-undo-smoke.js`。
|
||||
- 未完成:`sync_editor_overlay_state` / `try_sync_editor_overlay_state`、`HoveredBlockState` shared type、block menu signal 仍未拆出。
|
||||
|
||||
+1
-1
@@ -1284,7 +1284,7 @@ function __wbg_get_imports() {
|
||||
return ret;
|
||||
},
|
||||
__wbindgen_cast_0000000000000006: function(arg0, arg1) {
|
||||
// Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [NamedExternref("KeyboardEvent")], shim_idx: 882, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
|
||||
// Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [NamedExternref("KeyboardEvent")], shim_idx: 724, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
|
||||
const ret = makeMutClosure(arg0, arg1, wasm_bindgen__convert__closures_____invoke__hd90af689bc3e71bf);
|
||||
return ret;
|
||||
},
|
||||
|
||||
BIN
Binary file not shown.
@@ -0,0 +1,51 @@
|
||||
use leptos_tiptap::TiptapEditorHandle;
|
||||
use wasm_bindgen::{closure::Closure, JsCast};
|
||||
use web_sys::window;
|
||||
|
||||
use crate::{
|
||||
editor_root_element, editor_runtime::dom_selection::node_is_within_root, EDITOR_STAGE_SELECTOR,
|
||||
};
|
||||
|
||||
pub(crate) fn schedule_editor_focus(editor: TiptapEditorHandle) {
|
||||
let Some(win) = window() else {
|
||||
return;
|
||||
};
|
||||
let callback = Closure::<dyn FnMut()>::new(move || {
|
||||
let _ = editor.focus();
|
||||
});
|
||||
let _ = win.set_timeout_with_callback_and_timeout_and_arguments_0(
|
||||
callback.as_ref().unchecked_ref(),
|
||||
0,
|
||||
);
|
||||
callback.forget();
|
||||
}
|
||||
|
||||
pub(crate) fn active_editor_stage() -> bool {
|
||||
let active_element_inside_stage = window()
|
||||
.and_then(|win| win.document())
|
||||
.and_then(|document| document.active_element())
|
||||
.and_then(|element| element.closest(EDITOR_STAGE_SELECTOR).ok().flatten())
|
||||
.is_some();
|
||||
|
||||
if active_element_inside_stage {
|
||||
return true;
|
||||
}
|
||||
|
||||
let Some(root) = editor_root_element() else {
|
||||
return false;
|
||||
};
|
||||
let Some(selection) = window().and_then(|win| win.get_selection().ok().flatten()) else {
|
||||
return false;
|
||||
};
|
||||
|
||||
let anchor_inside = selection
|
||||
.anchor_node()
|
||||
.map(|node| node_is_within_root(node, &root))
|
||||
.unwrap_or(false);
|
||||
let focus_inside = selection
|
||||
.focus_node()
|
||||
.map(|node| node_is_within_root(node, &root))
|
||||
.unwrap_or(false);
|
||||
|
||||
anchor_inside || focus_inside
|
||||
}
|
||||
@@ -4,6 +4,7 @@ pub(crate) mod block_menu_document;
|
||||
pub(crate) mod block_menu_legacy_html;
|
||||
pub(crate) mod command_sync;
|
||||
pub(crate) mod dom_selection;
|
||||
pub(crate) mod editor_focus;
|
||||
pub(crate) mod history_safe_commands;
|
||||
pub(crate) mod overlays;
|
||||
pub(crate) mod persistence;
|
||||
|
||||
@@ -28,8 +28,9 @@ use editor_runtime::command_sync::{
|
||||
read_editor_snapshot, sync_editor_outputs, sync_persisted_editor_command,
|
||||
};
|
||||
use editor_runtime::dom_selection::{
|
||||
has_active_text_selection, node_is_within_root, selection_summary,
|
||||
has_active_text_selection, selection_summary,
|
||||
};
|
||||
use editor_runtime::editor_focus::{active_editor_stage, schedule_editor_focus};
|
||||
use editor_runtime::overlays::{
|
||||
floating_toolbar_anchor_from_selection, sync_text_selection_overlay, FloatingToolbarAnchor,
|
||||
};
|
||||
@@ -3655,20 +3656,6 @@ fn schedule_scroll_mnote_block_anchor_from_hash_retry(remaining: u8) {
|
||||
callback.forget();
|
||||
}
|
||||
|
||||
fn schedule_editor_focus(editor: TiptapEditorHandle) {
|
||||
let Some(win) = window() else {
|
||||
return;
|
||||
};
|
||||
let callback = Closure::<dyn FnMut()>::new(move || {
|
||||
let _ = editor.focus();
|
||||
});
|
||||
let _ = win.set_timeout_with_callback_and_timeout_and_arguments_0(
|
||||
callback.as_ref().unchecked_ref(),
|
||||
0,
|
||||
);
|
||||
callback.forget();
|
||||
}
|
||||
|
||||
fn current_viewport_scroll() -> Option<(f64, f64)> {
|
||||
let win = window()?;
|
||||
let x = win.scroll_x().ok()?;
|
||||
@@ -6310,36 +6297,6 @@ fn p0_extensions() -> Vec<TiptapExtension> {
|
||||
]
|
||||
}
|
||||
|
||||
fn active_editor_stage() -> bool {
|
||||
let active_element_inside_stage = window()
|
||||
.and_then(|win| win.document())
|
||||
.and_then(|document| document.active_element())
|
||||
.and_then(|element| element.closest(EDITOR_STAGE_SELECTOR).ok().flatten())
|
||||
.is_some();
|
||||
|
||||
if active_element_inside_stage {
|
||||
return true;
|
||||
}
|
||||
|
||||
let Some(root) = editor_root_element() else {
|
||||
return false;
|
||||
};
|
||||
let Some(selection) = window().and_then(|win| win.get_selection().ok().flatten()) else {
|
||||
return false;
|
||||
};
|
||||
|
||||
let anchor_inside = selection
|
||||
.anchor_node()
|
||||
.map(|node| node_is_within_root(node, &root))
|
||||
.unwrap_or(false);
|
||||
let focus_inside = selection
|
||||
.focus_node()
|
||||
.map(|node| node_is_within_root(node, &root))
|
||||
.unwrap_or(false);
|
||||
|
||||
anchor_inside || focus_inside
|
||||
}
|
||||
|
||||
fn current_block_info_from_index(index: Option<usize>) -> CurrentBlockInfo {
|
||||
index
|
||||
.map(|value| CurrentBlockInfo {
|
||||
|
||||
Reference in New Issue
Block a user