refactor: extract block state helper
This commit is contained in:
@@ -275,4 +275,15 @@ UI / 浏览器可见项必须有真实浏览器截图或结构化 smoke 证据
|
||||
- 2026-05-25:Batch AH 未派发 Reasonix worker;主控本地完成 table selection overlay style 小切片。
|
||||
- 修改:`editor_runtime/overlays.rs` 新增 `table_selection_overlay_style(anchor, selection)`;`lib.rs` 删除同名实现并通过 import 继续调用。
|
||||
- 边界:未迁 table selection signal、row/column click handler、overlay DOM view、`current_table_overlay_anchor` 之外的 table 控件编排或 listener;本批只外置 `TableOverlayAnchor + TableSelectionOverlayState -> CSS style` 的纯样式计算。
|
||||
- 已验证:`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`、临时当前源码服务 `MNOTE_WEB_BIND=127.0.0.1:3016 MNOTE_WEB_PUBLIC_BIND=127.0.0.1:3016 cargo run --manifest-path rust/Cargo.toml -p mnote-web`、`MNOTE_WEB_SMOKE_BASE_URL=http://127.0.0.1:3016 NODE_PATH=/mnt/Data1T/mnote/node_modules node scripts/task489-block-menu-delete-undo-smoke.js`、`MNOTE_WEB_SMOKE_BASE_URL=http://127.0.0.1:3016 NODE_PATH=/mnt/Data1T/mnote/node_modules node scripts/task488-local-attachment-link-delete-undo-smoke.js`、`MNOTE_WEB_SMOKE_BASE_URL=http://127.0.0.1:3016 NODE_PATH=/mnt/Data1T/mnote/node_modules node scripts/task479-local-folder-markdown-resource-lifecycle-smoke.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`、临时当前源码服务 `MNOTE_WEB_BIND=127.0.0.1:3015 MNOTE_WEB_PUBLIC_BIND=127.0.0.1:3015 cargo run --manifest-path rust/Cargo.toml -p mnote-web`、`MNOTE_WEB_SMOKE_BASE_URL=http://127.0.0.1:3015 NODE_PATH=/mnt/Data1T/mnote/node_modules node scripts/task489-block-menu-delete-undo-smoke.js`、`MNOTE_WEB_SMOKE_BASE_URL=http://127.0.0.1:3015 NODE_PATH=/mnt/Data1T/mnote/node_modules node scripts/task488-local-attachment-link-delete-undo-smoke.js`、`MNOTE_WEB_SMOKE_BASE_URL=http://127.0.0.1:3015 NODE_PATH=/mnt/Data1T/mnote/node_modules node scripts/task479-local-folder-markdown-resource-lifecycle-smoke.js`。
|
||||
- 2026-05-25:Batch AI 派发 1 个只读 Reasonix worker 审计 `hovered_block_from_target` / `drop_indicator_from_target` 的下一刀边界,使用独立 worktree `/mnt/Data1T/mnote-worktrees/0525-ai-worker-a-5-29-dnd-boundary-readonly`,run id `reasonix-2026-05-25T00-03-35-386Z-3c494bf2`,显式 `--no-memory-recall`。
|
||||
- Codex 已读取 Hindsight recall、worktree `process-handoff.md`,并核对 worktree `git diff --stat` / `git status --short` 仅有 handoff 文件;runner 未生成完整 `result.json` / `final.md` / `process-handoff.json`,因此不按完整 Reasonix handoff 采纳,只把只读结论作为审计输入。
|
||||
- 审计结论经主控复核后采纳:`hovered_block_from_target` / `drop_indicator_from_target` 的 listener 壳层与 signal 编排仍留在 `lib.rs`;下一刀优先迁 `block_state_from_index(index)`,因为它是 9 个调用方共享的纯 DOM/rect -> `HoveredBlockState` 投影逻辑。
|
||||
- Codex 主控完成最小机械切片:`editor_runtime/block_dnd.rs` 新增 `block_state_from_index(index)`,承接 root/stage/block rect 与 `block_label_for_element` 计算;`lib.rs` 的同名函数退化为薄 wrapper,继续供现有 hover/keyboard/mindmap 调用链使用。
|
||||
- 边界:未迁 `hovered_block_from_target`、`drop_indicator_from_target`、`drop_indicator_from_point`、drag/drop listener、keyboard signal 分支或 block reorder 行为;本批只外置共享 block state projection。
|
||||
- 已验证:`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`、临时当前源码服务 `MNOTE_WEB_BIND=127.0.0.1:3017 MNOTE_WEB_PUBLIC_BIND=127.0.0.1:3017 cargo run --manifest-path rust/Cargo.toml -p mnote-web`、`MNOTE_WEB_SMOKE_BASE_URL=http://127.0.0.1:3017 NODE_PATH=/mnt/Data1T/mnote/node_modules node scripts/task489-block-menu-delete-undo-smoke.js`、`MNOTE_WEB_SMOKE_BASE_URL=http://127.0.0.1:3017 NODE_PATH=/mnt/Data1T/mnote/node_modules node scripts/task488-local-attachment-link-delete-undo-smoke.js`、`MNOTE_WEB_SMOKE_BASE_URL=http://127.0.0.1:3017 NODE_PATH=/mnt/Data1T/mnote/node_modules node scripts/task479-local-folder-markdown-resource-lifecycle-smoke.js`。
|
||||
- 2026-05-25:Batch AI 只派发 1 个只读 Reasonix worker 审计 `handle_lane_left` / block DnD 几何边界,使用独立 worktree `/mnt/Data1T/mnote-worktrees/0525-ai-worker-a-5-29-handle-lane-readonly`,run id `reasonix-2026-05-25T00-04-47-885Z-b85d144f`。
|
||||
- Codex 已读取 Hindsight recall、`process-handoff.md/json`,并核对 worktree `git status --short` / `git diff --stat` 为空;过程中发现另一组未纳入本批的旧 `0525-ai-worker-a-5-29-dnd-boundary-readonly` runner / ACP 仍在运行,主控已终止,不采纳其任何输出。
|
||||
- 审计结论经 Codex 复核后采纳:`handle_lane_left(stage_rect_width)` 属于 editor layout helper,不应迁入 `editor_runtime/block_dnd.rs`;若继续推进 DnD 边界,下一刀应迁 `pointer_in_handle_corridor` 这一层 DOM 查询包装,而不是复制 `CONTENT_COLUMN_*` / `HANDLE_*` 常量。
|
||||
- Codex 主控完成后续小切片:`editor_runtime/block_dnd.rs` 新增 `pointer_in_handle_corridor(client_x, client_y, block, handle_selector, handle_left_fallback, handle_width)`,承接 block/stage/handle DOM 查询与 `HandleCorridorGeometry` 组装;`lib.rs` 删除同名 wrapper,调用点改为显式委托 `block_dnd` helper,并继续由壳层传入 `handle_lane_left(stage_rect.width())` fallback,不复制布局常量。
|
||||
- 已验证:`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`、临时当前源码服务 `MNOTE_WEB_BIND=127.0.0.1:3018 MNOTE_WEB_PUBLIC_BIND=127.0.0.1:3018 cargo run --manifest-path rust/Cargo.toml -p mnote-web`、`MNOTE_WEB_SMOKE_BASE_URL=http://127.0.0.1:3018 NODE_PATH=/mnt/Data1T/mnote/node_modules node scripts/task489-block-menu-delete-undo-smoke.js`、`MNOTE_WEB_SMOKE_BASE_URL=http://127.0.0.1:3018 NODE_PATH=/mnt/Data1T/mnote/node_modules node scripts/task488-local-attachment-link-delete-undo-smoke.js`、`MNOTE_WEB_SMOKE_BASE_URL=http://127.0.0.1:3018 NODE_PATH=/mnt/Data1T/mnote/node_modules node scripts/task479-local-folder-markdown-resource-lifecycle-smoke.js`。
|
||||
|
||||
+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: 566, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
|
||||
// Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [NamedExternref("KeyboardEvent")], shim_idx: 883, 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.
@@ -3,7 +3,12 @@
|
||||
//! 本模块只承接拖拽命中所需的窄 helper,不处理 Leptos signal,
|
||||
//! 也不分发 editor command;调用方负责保留编辑器状态编排。
|
||||
|
||||
use crate::editor_runtime::block_hover_state::{DropIndicatorState, DropPlacement};
|
||||
use crate::{
|
||||
editor_runtime::block_hover_state::{
|
||||
block_label_for_element, DropIndicatorState, DropPlacement, HoveredBlockState,
|
||||
},
|
||||
editor_root_element, editor_stage_element,
|
||||
};
|
||||
use web_sys::{window, Element, EventTarget};
|
||||
|
||||
pub(crate) struct HandleCorridorGeometry {
|
||||
@@ -124,6 +129,78 @@ pub(crate) fn drop_indicator_from_block_rects(
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn block_state_from_index(index: usize) -> Option<HoveredBlockState> {
|
||||
let root = editor_root_element()?;
|
||||
let stage = editor_stage_element()?;
|
||||
let block = root.children().item(index as u32)?;
|
||||
let block_rect = block.get_bounding_client_rect();
|
||||
let stage_rect = stage.get_bounding_client_rect();
|
||||
let block_height = if block_rect.height() > 0.0 {
|
||||
block_rect.height()
|
||||
} else {
|
||||
28.0
|
||||
};
|
||||
|
||||
Some(HoveredBlockState {
|
||||
index,
|
||||
label: block_label_for_element(&block),
|
||||
top: block_rect.top() - stage_rect.top(),
|
||||
height: block_height,
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn pointer_in_handle_corridor(
|
||||
client_x: i32,
|
||||
client_y: i32,
|
||||
block: &HoveredBlockState,
|
||||
handle_selector: &str,
|
||||
handle_left_fallback: f64,
|
||||
handle_width: f64,
|
||||
) -> bool {
|
||||
let Some(stage) = editor_stage_element() else {
|
||||
return false;
|
||||
};
|
||||
let Some(root) = editor_root_element() else {
|
||||
return false;
|
||||
};
|
||||
let Some(block_element) = root.children().item(block.index as u32) else {
|
||||
return false;
|
||||
};
|
||||
|
||||
let stage_rect = stage.get_bounding_client_rect();
|
||||
let block_rect = block_element.get_bounding_client_rect();
|
||||
let handle_rect = window()
|
||||
.and_then(|win| win.document())
|
||||
.and_then(|document| document.query_selector(handle_selector).ok().flatten())
|
||||
.map(|handle| handle.get_bounding_client_rect());
|
||||
|
||||
let block_left = block_rect.left() - stage_rect.left();
|
||||
let block_top = block_rect.top() - stage_rect.top();
|
||||
let block_bottom = block_rect.bottom() - stage_rect.top();
|
||||
let handle_left = handle_rect
|
||||
.as_ref()
|
||||
.map(|rect| rect.left() - stage_rect.left())
|
||||
.unwrap_or(handle_left_fallback);
|
||||
let handle_right = handle_rect
|
||||
.as_ref()
|
||||
.map(|rect| rect.right() - stage_rect.left())
|
||||
.unwrap_or_else(|| handle_left + handle_width);
|
||||
|
||||
pointer_in_handle_corridor_geometry(
|
||||
client_x,
|
||||
client_y,
|
||||
&HandleCorridorGeometry {
|
||||
stage_left: stage_rect.left(),
|
||||
stage_top: stage_rect.top(),
|
||||
block_left,
|
||||
block_top,
|
||||
block_bottom,
|
||||
handle_left,
|
||||
handle_right,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn top_level_block_index(root: &Element, block: &Element) -> Option<usize> {
|
||||
let children = root.children();
|
||||
(0..children.length()).find_map(|index| {
|
||||
|
||||
@@ -22,9 +22,9 @@ use web_sys::{
|
||||
|
||||
use editor_runtime::attachment_upload::dispatch_editor_upload_request;
|
||||
use editor_runtime::block_dnd::{
|
||||
direct_block_from_element, direct_block_index_from_element,
|
||||
drop_indicator_from_block_rects, element_within_handle_shell, event_target_from_point,
|
||||
pointer_in_handle_corridor_geometry, top_level_block_index, HandleCorridorGeometry,
|
||||
block_state_from_index as block_state_from_index_runtime, direct_block_from_element,
|
||||
direct_block_index_from_element, drop_indicator_from_block_rects, element_within_handle_shell,
|
||||
event_target_from_point, top_level_block_index,
|
||||
};
|
||||
use editor_runtime::block_menu_overlay;
|
||||
use editor_runtime::block_menu_document::{
|
||||
@@ -34,8 +34,7 @@ use editor_runtime::block_menu_legacy_html::{
|
||||
duplicate_top_level_block_html, reorder_top_level_block_html,
|
||||
};
|
||||
use editor_runtime::block_hover_state::{
|
||||
block_label_for_element, BlockMenuLayout, DropIndicatorState, HoveredBlockState,
|
||||
PendingDragState,
|
||||
BlockMenuLayout, DropIndicatorState, HoveredBlockState, PendingDragState,
|
||||
};
|
||||
use editor_runtime::command_sync::{
|
||||
read_editor_snapshot, sync_editor_outputs, sync_persisted_editor_command,
|
||||
@@ -4867,23 +4866,7 @@ fn hovered_block_from_selection() -> Option<HoveredBlockState> {
|
||||
}
|
||||
|
||||
fn block_state_from_index(index: usize) -> Option<HoveredBlockState> {
|
||||
let root = editor_root_element()?;
|
||||
let stage = editor_stage_element()?;
|
||||
let block = root.children().item(index as u32)?;
|
||||
let block_rect = block.get_bounding_client_rect();
|
||||
let stage_rect = stage.get_bounding_client_rect();
|
||||
let block_height = if block_rect.height() > 0.0 {
|
||||
block_rect.height()
|
||||
} else {
|
||||
28.0
|
||||
};
|
||||
|
||||
Some(HoveredBlockState {
|
||||
index,
|
||||
label: block_label_for_element(&block),
|
||||
top: block_rect.top() - stage_rect.top(),
|
||||
height: block_height,
|
||||
})
|
||||
block_state_from_index_runtime(index)
|
||||
}
|
||||
|
||||
fn prosemirror_node_size(node: &Value) -> Option<u32> {
|
||||
@@ -5046,56 +5029,6 @@ fn image_element_from_target(target: web_sys::EventTarget) -> Option<Element> {
|
||||
.flatten()
|
||||
}
|
||||
|
||||
fn pointer_in_handle_corridor(client_x: i32, client_y: i32, block: &HoveredBlockState) -> bool {
|
||||
let Some(stage) = editor_stage_element() else {
|
||||
return false;
|
||||
};
|
||||
let Some(root) = editor_root_element() else {
|
||||
return false;
|
||||
};
|
||||
let Some(block_element) = root.children().item(block.index as u32) else {
|
||||
return false;
|
||||
};
|
||||
|
||||
let stage_rect = stage.get_bounding_client_rect();
|
||||
let block_rect = block_element.get_bounding_client_rect();
|
||||
let handle_rect = window()
|
||||
.and_then(|win| win.document())
|
||||
.and_then(|document| {
|
||||
document
|
||||
.query_selector(HANDLE_SHELL_SELECTOR)
|
||||
.ok()
|
||||
.flatten()
|
||||
})
|
||||
.map(|handle| handle.get_bounding_client_rect());
|
||||
|
||||
let block_left = block_rect.left() - stage_rect.left();
|
||||
let block_top = block_rect.top() - stage_rect.top();
|
||||
let block_bottom = block_rect.bottom() - stage_rect.top();
|
||||
let handle_left = handle_rect
|
||||
.as_ref()
|
||||
.map(|rect| rect.left() - stage_rect.left())
|
||||
.unwrap_or_else(|| handle_lane_left(stage_rect.width()));
|
||||
let handle_right = handle_rect
|
||||
.as_ref()
|
||||
.map(|rect| rect.right() - stage_rect.left())
|
||||
.unwrap_or_else(|| handle_left + HANDLE_TRIGGER_WIDTH);
|
||||
|
||||
pointer_in_handle_corridor_geometry(
|
||||
client_x,
|
||||
client_y,
|
||||
&HandleCorridorGeometry {
|
||||
stage_left: stage_rect.left(),
|
||||
stage_top: stage_rect.top(),
|
||||
block_left,
|
||||
block_top,
|
||||
block_bottom,
|
||||
handle_left,
|
||||
handle_right,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn drop_indicator_from_target(
|
||||
target: web_sys::EventTarget,
|
||||
client_y: i32,
|
||||
@@ -7794,7 +7727,18 @@ fn App(mount_options: MountOptions) -> impl IntoView {
|
||||
}
|
||||
|
||||
if let Some(current_block) = hovered_block.get_untracked() {
|
||||
if pointer_in_handle_corridor(event.client_x(), event.client_y(), ¤t_block) {
|
||||
if editor_runtime::block_dnd::pointer_in_handle_corridor(
|
||||
event.client_x(),
|
||||
event.client_y(),
|
||||
¤t_block,
|
||||
HANDLE_SHELL_SELECTOR,
|
||||
handle_lane_left(
|
||||
editor_stage_element()
|
||||
.map(|stage| stage.get_bounding_client_rect().width())
|
||||
.unwrap_or(0.0),
|
||||
),
|
||||
HANDLE_TRIGGER_WIDTH,
|
||||
) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -7818,7 +7762,18 @@ fn App(mount_options: MountOptions) -> impl IntoView {
|
||||
return;
|
||||
}
|
||||
if let Some(current_block) = hovered_block.get_untracked() {
|
||||
if pointer_in_handle_corridor(event.client_x(), event.client_y(), ¤t_block) {
|
||||
if editor_runtime::block_dnd::pointer_in_handle_corridor(
|
||||
event.client_x(),
|
||||
event.client_y(),
|
||||
¤t_block,
|
||||
HANDLE_SHELL_SELECTOR,
|
||||
handle_lane_left(
|
||||
editor_stage_element()
|
||||
.map(|stage| stage.get_bounding_client_rect().width())
|
||||
.unwrap_or(0.0),
|
||||
),
|
||||
HANDLE_TRIGGER_WIDTH,
|
||||
) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user