Skip to content

fix: fix cursor jumping and IME focus loss#171

Closed
SonyLeo wants to merge 7 commits intoopentiny:developfrom
SonyLeo:fix/template-cursor-issues
Closed

fix: fix cursor jumping and IME focus loss#171
SonyLeo wants to merge 7 commits intoopentiny:developfrom
SonyLeo:fix/template-cursor-issues

Conversation

@SonyLeo
Copy link
Copy Markdown
Collaborator

@SonyLeo SonyLeo commented Jul 24, 2025

修复输入块光标问题

场景:编辑块右侧无内容时
【已处理】1. 右侧输入英文时,光标位置不正确
【已处理】2. 右侧输入中文时,按下空格按键确认选择后,编辑器失焦
【已处理】3. 右侧输入中文,
中文输入法下,输入第二个字符时,光标移动至前一个编辑块,空格确认时,文字出现在前面的编辑块中;

另外修复了undo redo操作后数据未同步的问题

Summary by CodeRabbit

  • New Features

    • Improved handling of placeholder nodes around templates, ensuring better editing experience and cursor placement.
    • Enhanced history and selection management for more consistent user interactions.
  • Bug Fixes

    • Fixed issues where templates could appear at the start or end without proper placeholders.
    • Addressed edge cases in detecting empty templates, now recognizing any template with only empty text nodes as empty.
  • Improvements

    • Optional IDs are now supported in user text and template items for better data consistency.
    • Editor selection range now includes direct references to HTML elements, allowing more precise cursor control.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jul 24, 2025

Walkthrough

The changes focus on refining the TemplateEditor component's internal data handling, placeholder logic, and input processing. Type definitions are updated to allow optional IDs for user items, and the EditorRange interface is extended with element references. The logic for determining empty templates is broadened, and several event handling and history management improvements are introduced.

Changes

Cohort / File(s) Change Summary
TemplateEditor Component Logic
packages/components/src/sender/components/TemplateEditor.vue
Refactored internal data handling: unified zero-width character constant, improved placeholder logic, changed ID assignment and preservation, revised input and history management, removed sentinel node deletion, enhanced placeholder restoration, reordered event handler logic, added global selectionchange listener, and improved logging.
User Item Type Definitions
packages/components/src/sender/index.type.ts
Updated UserTextItem and UserTemplateItem types to allow an optional id property, changing from fully omitting IDs to making them optional.
Template Empty State Logic
packages/components/src/sender/index.vue
Broadened the empty template condition to consider the template empty if all nodes are empty text nodes, not just a single specific case.
Editor Range Interface
packages/components/src/sender/types/editor.type.ts
Extended EditorRange interface by adding optional startEl and endEl properties for direct element references, in addition to existing ID/type fields.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant TemplateEditor
    participant HistoryManager
    participant DOM

    User->>TemplateEditor: Input or edit content
    TemplateEditor->>TemplateEditor: Process input (update placeholders, IDs)
    TemplateEditor->>HistoryManager: Commit history after data update
    TemplateEditor->>DOM: Adjust caret (selectionchange event)
    TemplateEditor->>User: Emit updated template data
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~40 minutes

Poem

A rabbit hops through fields of code,
Tidying templates where placeholders showed.
IDs now wander, sometimes they stay,
While caret and history dance in ballet.
Types are refined, the logic is neat—
In this patch, old bugs admit defeat! 🐇✨

Note

⚡️ Unit Test Generation is now available in beta!

Learn more here, or try it out under "Finishing Touches" below.


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 597f4ed and 2b97f4a.

📒 Files selected for processing (1)
  • packages/components/src/sender/components/TemplateEditor.vue (18 hunks)
🧰 Additional context used
🧠 Learnings (1)
packages/components/src/sender/components/TemplateEditor.vue (2)

Learnt from: gene9831
PR: #59
File: packages/components/src/suggestion-popover/index.vue:131-133
Timestamp: 2025-05-27T03:35:11.008Z
Learning: In the SuggestionPopover component (packages/components/src/suggestion-popover/index.vue), the click handler can be bound unconditionally because the show computed property has a custom setter that prevents state mutations when props.trigger === 'manual'. This design centralizes trigger mode logic in the computed property rather than requiring conditional checks in event handlers.

Learnt from: gene9831
PR: #59
File: packages/components/src/suggestion-popover/index.vue:0-0
Timestamp: 2025-05-27T03:45:56.392Z
Learning: In Vue components using VueUse's onClickOutside, calling stopPropagation() in the onClickOutside handler can prevent subsequent click event handlers on the same element from being triggered. This means that clicking a trigger element while a popover is open can successfully close the popover without immediately reopening it, even when the onClickOutside only listens to the popover element and not the trigger element.

🔇 Additional comments (9)
packages/components/src/sender/components/TemplateEditor.vue (9)

2-2: Good addition of lifecycle hooks for proper cleanup.

The import of onMounted and onUnmounted enables proper cleanup of the global event listener, addressing memory leak concerns.


38-41: Excellent consolidation of placeholder constants.

Using a unified ZERO_WIDTH_CHAR constant for all placeholder-related values improves maintainability and ensures consistency across the editor.


57-72: Improved ID handling aligns with type definition updates.

The changes properly handle optional IDs by using existing ones or generating new ones, and ensure the id field is included in user data transformation. This supports better data consistency and the optional ID feature mentioned in related type changes.


76-107: Well-documented rewrite addresses template positioning issues.

The comprehensive rewrite with detailed comments clearly explains the 3-step process and implements sophisticated placeholder handling to prevent templates from being at start/end positions. The logic for conditionally adding placeholder nodes and cleaning existing placeholders is sound and should resolve cursor positioning issues.


194-220: Smart optimization prevents unnecessary internal update processing.

The logic to differentiate between internal and external updates using JSON comparison prevents infinite loops and unnecessary processing. While JSON.stringify could be expensive for large datasets, it's a pragmatic solution for the current use case.


433-433: Enhanced range tracking with element references.

Adding endEl and startEl properties provides direct DOM element access, improving efficiency for cursor positioning and selection operations. This aligns well with the EditorRange interface updates.

Also applies to: 438-438


455-535: Comprehensive refactor improves template positioning and cleanup.

The simplified logic removes unnecessary sentinel node handling while adding sophisticated template positioning logic. The multi-step cleanup process (empty template removal, adjacent text handling, empty text filtering, separator restoration) is well-structured and should resolve cursor positioning issues mentioned in the PR objectives.


403-403: Improved selection range tracking order prevents cursor issues.

Moving rangeMap.set() calls before processInput() ensures selection ranges are properly tracked before input processing begins. This reordering directly addresses the cursor jumping issues mentioned in the PR objectives.

Also applies to: 415-415, 767-767, 772-772


848-848: Ensures model synchronization after history operations.

Adding model.value update after restoring data addresses the synchronization problem mentioned in PR objectives, ensuring the model stays consistent with internal data after undo/redo operations.

✨ Finishing Touches
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai generate unit tests to generate unit tests for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@gene9831 gene9831 changed the title fix: template cursor issue fix: fix cursor jumping and IME focus loss Jul 26, 2025
@hexqi hexqi marked this pull request as ready for review July 26, 2025 10:43
hexqi
hexqi previously approved these changes Jul 26, 2025
@hexqi hexqi marked this pull request as draft July 26, 2025 10:45
@SonyLeo SonyLeo marked this pull request as ready for review July 29, 2025 02:01
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
packages/components/src/sender/components/TemplateEditor.vue (1)

194-221: Good safeguard against infinite update loops.

The internal update detection logic prevents infinite loops when the model is updated from within the component. Recording the selection range before updates ensures proper cursor restoration.

Consider addressing the TODO comment about finding better ways to detect internal updates - perhaps using a flag or versioning system could be more efficient than JSON comparison.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between aa0ae58 and 597f4ed.

📒 Files selected for processing (4)
  • packages/components/src/sender/components/TemplateEditor.vue (17 hunks)
  • packages/components/src/sender/index.type.ts (1 hunks)
  • packages/components/src/sender/index.vue (1 hunks)
  • packages/components/src/sender/types/editor.type.ts (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
packages/components/src/sender/index.vue (1)

Learnt from: gene9831
PR: #123
File: packages/components/src/bubble/message/Message.vue:22-40
Timestamp: 2025-06-25T07:04:18.791Z
Learning: In the Message component (packages/components/src/bubble/message/Message.vue), the renderer resolution is intentionally not reactive to type changes - the component is designed to work with a fixed type that doesn't change after initialization.

🧬 Code Graph Analysis (1)
packages/components/src/sender/index.type.ts (1)
packages/components/src/sender/types/editor.type.ts (2)
  • TextItem (7-9)
  • TemplateItem (11-15)
🔇 Additional comments (10)
packages/components/src/sender/types/editor.type.ts (1)

29-36: LGTM! Good addition for precise DOM element tracking.

Adding optional startEl and endEl properties to the EditorRange interface provides direct element references that support the improved cursor positioning logic in the TemplateEditor component.

packages/components/src/sender/index.vue (1)

278-281: Good improvement to empty template detection logic.

The refactored logic using isEmptyTextNode helper and every() method properly handles cases where multiple placeholder text nodes exist, not just the single node case. This aligns well with the broader placeholder management strategy.

packages/components/src/sender/index.type.ts (1)

127-131: Good type refinement for flexible ID handling.

Making the id property optional rather than completely omitted allows for better ID preservation and assignment logic in the TemplateEditor component. This supports consistent data transformations between user and internal representations.

packages/components/src/sender/components/TemplateEditor.vue (7)

38-42: Good unification of placeholder constants.

Using a single ZERO_WIDTH_CHAR constant with semantic aliases (PLACEHOLDER, PREFIX, SUFFIX) improves code maintainability and readability.


56-72: Improved ID management in transformation functions.

The changes properly handle optional IDs by preserving existing ones or generating new ones, which is more robust than the previous index-based approach.


76-107: Well-structured placeholder management logic.

The rewritten setOriginalData function systematically handles placeholder nodes to prevent templates from being at the start or end of the data, while cleaning up placeholder characters from text nodes with actual content.


320-327: Proper history tracking added.

Good addition of history.commit calls after data updates to ensure undo/redo functionality works correctly.


465-522: Improved template deletion and placeholder management.

The logic properly handles:

  1. Renaming toDeleted to toDeletedTemplate for clarity
  2. Filtering out empty templates and text nodes
  3. Ensuring templates don't end up at the start or end by converting adjacent empty text nodes to placeholders

This systematic approach prevents the cursor jumping issues mentioned in the PR objectives.


763-784: Proper IME composition handling.

The changes correctly handle IME composition by:

  1. Recording selection range for proper cursor restoration
  2. Processing composition input through the standard processInput flow
  3. Force re-rendering when composition events cause untracked DOM changes

This addresses the IME focus loss issues mentioned in the PR objectives.


847-849: Critical fix for undo/redo synchronization.

Adding model.value = transformInternalToUser(originalData.value) ensures the parent component is properly notified of data changes during undo/redo operations, fixing the synchronization issue mentioned in the PR objectives.

Comment thread packages/components/src/sender/components/TemplateEditor.vue Outdated
@gene9831 gene9831 closed this Jul 29, 2025
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.

3 participants