Skip to content

单击块选中和多文本选中后点击手柄进行多块选中#12

Closed
14743768433 wants to merge 25 commits intoAriestar:mainfrom
14743768433:codex/wip-selection-20260213-094042
Closed

单击块选中和多文本选中后点击手柄进行多块选中#12
14743768433 wants to merge 25 commits intoAriestar:mainfrom
14743768433:codex/wip-selection-20260213-094042

Conversation

@14743768433
Copy link
Copy Markdown

标题
feat: 智能文本选区块拖拽 + 选中态稳定恢复 + 交互与日志清理

描述

## 变更概述

这个 PR 完整实现了“文本选区 -> 块选中 -> 直接拖拽”的工作流,并修复了选中态在点击、拖拽、放下、刷新过程中的稳定性问题;同时补齐了多段选区恢复逻辑,最后清理了运行时调试日志。

## 主要改动

1. 智能选区转块选区
- 新增 `EditorSelectionBridge``SmartBlockSelector`- 当用户已选中文字并点击对应手柄时,将文本选区对齐为块级范围并进入块拖拽链路。

2. 拖拽与选中流程稳定化
- 鼠标快速点击可提交块选中(不必依赖二次长按)。
- 保留 smart selection 后直接原生 HTML5 拖拽能力。
- 优化鼠标/触摸在 long-press、move threshold、状态切换上的一致性。

3. drop 后恢复选中逻辑加固
- 修复恢复时 source block 元信息漂移(不再硬编码成 paragraph)。
- 保留并恢复 range span,避免多段选区被错误压扁。
- 恢复后的 `selectedBlock` 与后续拖拽语义保持一致。

4. 生命周期与边界问题修复
- 修复 `_pendingSelectionSnapshot` 旧快照复用导致的污染问题。
- 收紧 pointerdown 清理 guard,降低“误清/漏清”概率。
- 修复视图刷新后 committed selection 被意外清空的问题。
- 支持点击选中块阴影区域即可取消选中。

5. 视觉与交互优化
- 选中时隐藏非锚点块手柄。
- 统一 handle hover/selected 行为,避免选中态期间视觉跳变。
- 调整拖拽手柄可见性与命中体验。
- 样式侧补充选中背景高亮与相关细节修正。

6. 调试日志清理
- 移除运行时 `[Dragger Debug]` 与相关 `console.log/debug` 输出。
- 将性能日志函数在常规运行路径下改为 no-op,避免噪音。

## 测试与验证

1. 类型检查通过:`npm run -s typecheck`
2. 全量测试通过:`npm test`(19 files / 130 tests)
3. 本次覆盖了 smart selection、单块/多块、drop restore、pointer guard、native drag 等关键路径。

## 说明

这是同一功能链路上的连续迭代提交(功能实现 + 稳定性修复 + 日志清理),建议按最终行为整体评审。

14743768433 and others added 25 commits February 12, 2026 23:22
Increase default handle size from 16px to 24px and core-to-handle ratio
from 50% to 65% to match Heptabase-style grip dots. Raise max size slider
limit from 28px to 36px. Make CSS left offset dynamic via calc().

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
单块拖拽和多块选取时,被选中的行显示可配置颜色的背景光晕。
新增 SelectionHighlightManager 管理高亮状态,通过 CM6 update
周期自动重新应用 class 以应对 DOM 重渲染。点击非手柄区域自动
清除光晕。设置面板新增「选中高亮颜色」配置项。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The .dnd-drop-highlight class was using --dnd-drop-highlight-color
which was never set in main.ts. Changed to use --dnd-drop-indicator-color
so the indicator color setting actually applies to drop highlights.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Changed default handle color from --interactive-accent to --text-normal
so the handle matches the normal text color (black/white based on
light/dark theme).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When user selects text in editor (possibly across multiple blocks)
and clicks a handle within that selection, all blocks covered by
the text selection are automatically selected as complete blocks.

New modules:
- EditorSelectionBridge: reads CodeMirror text selection and
  converts to block-aligned line ranges
- SmartBlockSelector: evaluates whether smart block selection
  should be triggered based on clicked block and editor selection

Modified:
- DragEventHandler: integrated SmartBlockSelector and added
  startRangeSelectWithPrecomputedRanges method

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When text selection is converted to block selection via handle click,
the editor's text selection is now cleared to avoid visual confusion
from having both selection states visible simultaneously.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When blocks are selected via text selection + handle click, the
vertical link line between handles is now hidden. Only the block
highlight is shown, keeping the visual cleaner.

- Added showLinks parameter to RangeSelectionVisualManager.render()
- Added hideLinks() helper method
- Updated startRangeSelectWithPrecomputedRanges to pass showLinks=false

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The previous fix only hid the link line during initial render, but
commitRangeSelection was still showing it. Added showLinks field to
MouseRangeSelectState to track whether links should be displayed,
and use it consistently throughout the selection lifecycle.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When using text-selection-based block selection, the handles now
stay in their normal appearance instead of becoming highlighted.
Only the clicked block's handle remains visible in its regular style,
while other selected blocks only show background highlight.

- Added highlightHandles option to RangeSelectionVisualManager.render()
- Added highlightHandles field to MouseRangeSelectState
- Smart block selection sets highlightHandles=false

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When using text-selection-based block selection, only the clicked
block's handle remains visible. Other selected blocks' handles are
hidden to avoid cluttering the UI while their background highlight
remains visible.

- Added setHiddenRangesForSelection/clearHiddenRangesForSelection
  to HandleVisibilityController
- Modified resolveVisibleHandleFromTarget and
  resolveVisibleHandleFromPointerWhenLineNumbersHidden to skip
  handles in hidden ranges
- Connected DragEventHandler to HandleVisibilityController via
  new deps callbacks

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Disable CSS :hover rule when block selection is active by adding
dnd-block-selection-active body class. This prevents handles from
appearing on hover when the user has selected blocks via smart
selection or single-handle click.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Apply the same handle hiding logic to regular single-click range
selection that was already working for smart text-selection based
block selection.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- For mouse clicks, commit block selection immediately without requiring
  long press (touch still requires long press)
- Keep handle appearance normal (not highlighted) when selection is
  committed, matching smart selection behavior

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…blocks

- Capture editor selection snapshot at pointerdown start before browser clears it
- Pass snapshot to SmartBlockSelector.evaluate() for reliable multi-block detection
- Fix syncSelectionElements to check DOM class existence instead of Set identity
- Prevent default/stop propagation in smart selection to avoid event conflicts
- Remove link bar (vertical line) from selection visuals per user preference
- Improve CSS color fallback chain for selection highlight

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Don't preventDefault/stopPropagation for mouse events in smart selection
- Keep draggable attribute enabled for mouse to allow native drag
- Touch events still use pointer capture approach

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@gemini-code-assist
Copy link
Copy Markdown

Summary of Changes

Hello @14743768433, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

此拉取请求全面实现了“文本选区 -> 块选中 -> 直接拖拽”的工作流,显著提升了点击、拖拽、放下和刷新等交互过程中选中状态的稳定性。它还改进了多段选区在拖放后的恢复逻辑,并清理了调试日志以提供更流畅的用户体验。同时,视觉和交互元素也得到了优化,以提供更清晰的反馈和更好的可用性。

Highlights

  • 智能选区转块选区: 新增 EditorSelectionBridge 和 SmartBlockSelector,当用户选中文字并点击手柄时,将文本选区对齐为块级范围并进入块拖拽链路。
  • 拖拽与选中流程稳定化: 优化鼠标快速点击提交块选中、保留原生 HTML5 拖拽能力,并提升鼠标/触摸在长按、移动阈值和状态切换上的一致性。
  • Drop 后恢复选中逻辑加固: 修复恢复时源块元信息漂移问题,保留并恢复多段选区,确保恢复后的 selectedBlock 与后续拖拽语义保持一致。
  • 生命周期与边界问题修复: 修复 _pendingSelectionSnapshot 污染、收紧 pointerdown 清理 guard、修复视图刷新后 committed selection 被意外清空的问题,并支持点击选中块阴影区域取消选中。
  • 视觉与交互优化: 选中时隐藏非锚点块手柄,统一手柄悬停/选中行为,调整拖拽手柄可见性与命中体验,并补充选中背景高亮样式。
  • 调试日志清理: 移除运行时 [Dragger Debug] 和 console.log/debug 输出,将性能日志函数在常规运行路径下改为 no-op。
Changelog
  • esbuild.config.mjs
    • 更新了 pluginDir 路径。
  • package-lock.json
    • 更新了包版本从 1.2.1 到 1.2.2。
  • src/editor/core/constants.ts
    • 增加了默认和最大 handleSizePx 值。
    • 新增了 BLOCK_SELECTION_ACTIVE_CLASS 常量。
  • src/editor/core/line-map.spec.ts
    • 移除了一个 console.debug 语句。
  • src/editor/core/perf-session.ts
    • 移除了 serializeSnapshot 函数。
    • 修改了 logDragPerfSession 在正常运行时为无操作。
  • src/editor/core/selectors.ts
    • 新增了 SELECTION_HIGHLIGHT_LINE_CLASS。
  • src/editor/core/services/EditorSelectionBridge.ts
    • 新增了 EditorSelectionBridge 文件,用于连接 CodeMirror 编辑器文本选区与基于块的选区,支持单选和多选文本选区,并将其转换为块对齐的行范围。
  • src/editor/drag-handle.ts
    • 导入了 DRAG_HANDLE_CLASS。
    • 添加了 onDocumentPointerDown 事件监听器。
    • 在 dragEventHandler 中集成了 setHiddenRangesForSelection 和 clearHiddenRangesForSelection 新方法。
    • 在更新时调用 reapplySelectionHighlight 和 reapplySelectionHandleVisibility。
    • 实现了 handleDocumentPointerDown 以清除选区高亮。
  • src/editor/interaction/DragEventHandler.spec.ts
    • 导入了 EditorSelection。
    • 新增了 dispatchDrop、applyTextSelection、applyMultiTextSelections 和 getCommittedSelectionTarget 辅助函数。
    • 更新了多项测试,以反映选区处理、已提交选区目标和拖拽行为的变化,特别是验证了手柄点击时的块选区、智能拖拽源映射、多块选区支持以及拖放后的选区恢复。
  • src/editor/interaction/DragEventHandler.ts
    • 导入了 EditorSelection 和 SmartBlockSelector。
    • 新增了智能选区计时常量。
    • 引入了用于已提交选区跟踪和待定选区恢复的状态变量。
    • 实现了 SmartBlockSelector 用于智能块选区。
    • 优化了 onEditorPointerDown 以捕获选区快照并处理已提交选区。
    • 修改了 performDropAtPoint 以返回目标行号并排队等待选区恢复。
    • 修改了 clearCommittedRangeSelection 以接受用于保留选区和防止过早清除的选项。
    • 新增了 startRangeSelectWithPrecomputedRanges 的逻辑用于智能选区。
    • 实现了拖放后恢复选区、计算行数和解析原生拖拽源的方法。
  • src/editor/interaction/RangeSelectionLogic.ts
    • 为 MouseRangeSelectState 添加了 showLinks、highlightHandles 和 shouldClearEditorSelectionOnCommit 属性。
  • src/editor/interaction/SmartBlockSelector.ts
    • 新增了 SmartBlockSelector 文件,用于管理编辑器文本选区到块对齐选区的转换,包括捕获选区快照和根据点击的块评估智能选区。
  • src/editor/orchestration/HandleInteractionOrchestrator.ts
    • 修改了 startDragFromHandle 以使用 resolveNativeDragSourceForHandleDrag 进行智能拖拽源解析。
    • 更新了 performDropAtPoint 以返回行号。
    • 调整了 shouldPrimePointerVisual 的逻辑。
  • src/editor/visual/HandleVisibilityController.spec.ts
    • 新增了 HandleVisibilityController 的测试文件,以验证块选区期间的选区高亮清除和手柄可见性行为。
  • src/editor/visual/HandleVisibilityController.ts
    • 导入了 SelectionHighlightManager 和新常量。
    • 新增了用于隐藏范围和选区锚点手柄的状态。
    • 实现了 setHiddenRangesForSelection 和 clearHiddenRangesForSelection 以管理块选区期间的手柄可见性。
    • 更新了 setActiveVisibleHandle 以尊重锚点手柄的可见性。
    • 新增了 clearSelectionHighlight、reapplySelectionHighlight 和 reapplySelectionHandleVisibility 方法。
  • src/editor/visual/HoverHighlightManager.ts
    • 新增了 HoverHighlightManager 文件,用于管理单块选区或抓取期间的行视觉高亮,确保高亮在 DOM 更新后仍然存在。
  • src/editor/visual/RangeSelectionVisualManager.ts
    • 新增了行解析重试的常量。
    • 引入了行元素解析的重试逻辑。
    • 修改了 render 以接受 showLinks 和 highlightHandles 的选项。
    • 更新了 clear 以清除孤立的 DOM 元素并隐藏链接。
    • 新增了 resolveConnectedLineFromPos、scheduleLineResolutionRetry 和 clearLineResolutionRetry 辅助方法。
  • src/i18n.ts
    • 在中英文翻译中新增了 selectionHighlightColor 和 selectionHighlightColorDesc 的翻译键。
  • src/main.ts
    • 将默认手柄颜色更改为 var(--text-normal)。
    • 添加了根据设置应用 selectionHighlightColor 的逻辑。
    • 增加了默认 handleSize 并调整了 handle-core-size 的计算。
  • src/settings.ts
    • 新增了 selectionHighlightColorMode 和 selectionHighlightColor 设置。
    • 更新了默认 handleSize。
    • 调整了 handleSize 的滑块限制。
  • styles.css
    • 调整了 dnd-drag-handle 的 left、width、height 属性。
    • 为 .dnd-drag-handle:hover 添加了 body:not(.dnd-block-selection-active) 以在块选区期间禁用悬停效果。
    • 更新了 dnd-handle-core 的 width、height、font-size。
    • 新增了 .cm-line.dnd-selection-highlight-line 的样式。
    • 引入了 dnd-selection-handle-hidden 和 dnd-selection-anchor-handle 新类用于手柄可见性。
    • 优化了 .dnd-range-selection-link 的宽度和颜色回退。
    • 修正了 .dnd-drop-target-indicator 的 background 和 box-shadow。
Activity
  • 作者 14743768433 提交了大量修改,引入了智能块选区的新组件,优化了拖放交互,并改进了视觉反馈。
  • 拉取请求包含了广泛的测试更新,表明对新功能和稳定性改进进行了彻底验证。
  • 拉取请求描述详细,概述了变更内容、测试情况,并建议进行整体评审。
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

这个 PR 引入了一个重要的智能文本到块选区拖拽的新功能,并包含了大量的稳定性改进和错误修复。代码改动范围很广,但实现质量很高,特别是处理复杂选区状态和增强 UI 对 CodeMirror DOM 更新的鲁棒性方面的逻辑。此外,全面的测试覆盖也值得称赞。

我发现了一些可以改进的地方:

  • 一个构建配置中的硬编码本地路径,这是一个关键问题。
  • 几个中等严重性的建议,旨在通过移除未使用代码和重命名文件来提高代码的清晰度和可维护性。

总的来说,这是一个高质量的贡献。在解决了关键问题后,这个 PR 就可以合并了。


const prod = process.argv[2] === "production";
const pluginDir = "C:/Users/19411_4bs7lzt/OneDrive/obsidian/.obsidian/plugins/dragger";
const pluginDir = "/Users/jinyujia/Downloads/test/.obsidian/plugins/dragger";
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

这个文件里硬编码了一个个人本地开发路径。这会导致其他开发者的构建失败,并且可能会泄露个人信息。建议使用环境变量来配置此路径,或者在提交前移除此更改。

const pluginDir = process.env.OBSIDIAN_PLUGIN_DIR || "dist";

readonly pointer: PointerSessionController;
private readonly smartSelector: SmartBlockSelector;
// Selection snapshot captured at pointerdown start, before browser might clear it
private selectionSnapshotAtPointerDown: SmartSelectionResult | null = null;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

selectionSnapshotAtPointerDown 这个类属性被声明和初始化了,但在代码中似乎没有被使用。在 onEditorPointerDown 中它被设为 null,但之后再也没有被赋值或读取。实际的快照逻辑似乎是由 pendingSelectionSnapshot 处理的。

如果这个属性确实没有被使用,建议将其移除以简化代码并避免混淆。

* Tracks the highlighted line range so the class can be re-applied
* after CM6 DOM updates (which may reset element classes).
*/
export class SelectionHighlightManager {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

文件名 HoverHighlightManager.ts 与类名 SelectionHighlightManager 不匹配。根据类的功能(管理选中块的高亮),SelectionHighlightManager 这个名字更准确。
为了保持代码库的一致性和可维护性,建议将文件名重命名为 SelectionHighlightManager.ts

@Ariestar
Copy link
Copy Markdown
Owner

其实背景高亮我也实现了一份,这个更改可以不需要,其他可以合并

@Ariestar
Copy link
Copy Markdown
Owner

另外不要一次提交这么大的pr,如果有多种功能修改可以分多次pr

@14743768433
Copy link
Copy Markdown
Author

14743768433 commented Feb 16, 2026 via email

@Ariestar Ariestar closed this Mar 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants