Skip to content

fix: template editor cursor position and style#165

Merged
gene9831 merged 8 commits intoopentiny:developfrom
SonyLeo:fix/template-cursor
Jul 21, 2025
Merged

fix: template editor cursor position and style#165
gene9831 merged 8 commits intoopentiny:developfrom
SonyLeo:fix/template-cursor

Conversation

@SonyLeo
Copy link
Copy Markdown
Collaborator

@SonyLeo SonyLeo commented Jul 21, 2025

  1. 模板编辑器只有一个编辑块时,删除后编辑,光标在编辑块内,输入文本显示在编辑块外侧
  2. 模板编辑器清空内容后,输入框的光标未正常激活
  3. 模板编辑器修复字体大小:
    在 紧凑模式(tr-sender-compact)下,文字大小为 14px; 全屏模式下,文字大小为 16px

Summary by CodeRabbit

  • New Features

    • Improved handling of empty template data in the template editor, automatically exiting template mode when appropriate.
    • Enhanced deletion behavior for special zero-width space nodes, ensuring smoother editing experience.
  • Style

    • Updated clear icon button sizing to use theme variables for better flexibility.
    • Added compact styling for sender input when not in fullscreen mode.
  • Bug Fixes

    • Improved browser-specific handling of zero-width space characters in template editing to ensure consistent behavior across browsers.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jul 21, 2025

Walkthrough

The changes implement dynamic class binding for compact styling in the chat input footer, update icon sizing to use CSS variables, introduce centralized state management and browser-specific handling for zero-width space characters in the template editor, add special deletion handling for sentinel nodes, and add logic to exit template mode when the template is empty. These updates enhance consistency and flexibility in UI behavior and state handling.

Changes

Files Change Summary
docs/demos/examples/Assistant.vue Added dynamic class binding to <tr-sender> for compact styling based on fullscreen state.
packages/components/src/sender/components/ActionButtons.vue Changed clear icon button sizing from fixed pixels to CSS variables for flexible theming.
packages/components/src/sender/components/TemplateEditor.vue Centralized state update logic with setOriginalData, added browser-specific zero-width space handling, special deletion logic for sentinel nodes, and new compact style class.
packages/components/src/sender/index.vue Added logic to exit template mode if template data is empty or only contains a zero-width space character.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant TemplateEditor
    participant Sender
    participant UI

    User->>TemplateEditor: Edit template content
    TemplateEditor->>TemplateEditor: setOriginalData(items)
    TemplateEditor->>TemplateEditor: handleSentinelNodeForwardDeletion(selectedItems, range, inputType)
    TemplateEditor->>Sender: Emit updated template data
    Sender->>Sender: handleTemplateUpdate(data)
    alt Data is empty or only zero-width space
        Sender->>Sender: exitTemplateMode()
        Sender->>UI: Exit template editing mode
    else Data is valid
        Sender->>UI: Update template content
    end
Loading

Estimated code review effort

3 (30–60 minutes)

Poem

In the warren of code, a rabbit did hop,
Tweaking icons and classes with a CSS swap.
Zero-width spaces now handled with care,
Compact modes and templates—responsive flair!
With paws on the keys, the changes are neat,
This bunny’s refactor is truly a treat.
🐇✨

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

packages/components/src/sender/components/TemplateEditor.vue

Oops! Something went wrong! :(

ESLint: 9.31.0

Error: The 'jiti' library is required for loading TypeScript configuration files. Make sure to install it.
at /node_modules/.pnpm/eslint@9.31.0/node_modules/eslint/lib/config/config-loader.js:145:10
at async loadTypeScriptConfigFileWithJiti (/node_modules/.pnpm/eslint@9.31.0/node_modules/eslint/lib/config/config-loader.js:144:3)
at async loadConfigFile (/node_modules/.pnpm/eslint@9.31.0/node_modules/eslint/lib/config/config-loader.js:266:11)
at async ConfigLoader.calculateConfigArray (/node_modules/.pnpm/eslint@9.31.0/node_modules/eslint/lib/config/config-loader.js:589:23)
at async #calculateConfigArray (/node_modules/.pnpm/eslint@9.31.0/node_modules/eslint/lib/config/config-loader.js:743:23)
at async /node_modules/.pnpm/eslint@9.31.0/node_modules/eslint/lib/eslint/eslint.js:767:6
at async Promise.all (index 0)
at async ESLint.lintFiles (/node_modules/.pnpm/eslint@9.31.0/node_modules/eslint/lib/eslint/eslint.js:764:19)
at async Object.execute (/node_modules/.pnpm/eslint@9.31.0/node_modules/eslint/lib/cli.js:632:14)
at async main (/node_modules/.pnpm/eslint@9.31.0/node_modules/eslint/bin/eslint.js:175:19)


📜 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 a05f6d3 and dab0cdf.

📒 Files selected for processing (1)
  • packages/components/src/sender/components/TemplateEditor.vue (7 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/components/src/sender/components/TemplateEditor.vue

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 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.

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)

74-101: Document the browser-specific zero-width space handling logic.

The browser-specific cleanup logic for zero-width spaces is non-obvious and should be documented to explain why Safari requires cleanup at the end while Chrome requires it at the beginning. This will help future maintainers understand the rationale.

Add comments explaining the browser differences:

 const setOriginalData = (items: (TextItem | TemplateItem)[]) => {
   if (items.length > 0) {
     if (items[0].type === 'template') {
+      // Prepend a zero-width space text node as a sentinel to ensure templates are never first
       originalData.value = [{ type: 'text', content: '\u200B', id: randomId() } as TextItem | TemplateItem].concat(
         items,
       )
     } else {
       originalData.value = items
       const firstItem = items[0]
       const lastItem = items[items.length - 1]
 
       if (isSafariBrowser) {
-        // 在 safari 环境下,如果最后一个元素是 text,清空 content
+        // 在 safari 环境下,如果最后一个元素是 text,清空 content
+        // Safari handles cursor positioning differently at the end of content
         if (lastItem.content !== '\u200B') {
           lastItem.content = lastItem.content.replace(/\u200B/g, '')
         }
       } else {
-        // 在 chrome 环境下,如果第一个元素是 text,清空 content
+        // 在 chrome 环境下,如果第一个元素是 text,清空 content  
+        // Chrome requires cleanup at the beginning for proper cursor behavior
         if (firstItem.content !== '\u200B') {
           firstItem.content = firstItem.content.replace(/\u200B/g, '')
         }
       }
     }
   } else {
     originalData.value = items
   }
 }
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 183cacc and 183b4b7.

📒 Files selected for processing (4)
  • docs/demos/examples/Assistant.vue (1 hunks)
  • packages/components/src/sender/components/ActionButtons.vue (1 hunks)
  • packages/components/src/sender/components/TemplateEditor.vue (8 hunks)
  • packages/components/src/sender/index.vue (1 hunks)
🧰 Additional context used
🧠 Learnings (4)
📓 Common learnings
Learnt from: gene9831
PR: opentiny/tiny-robot#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.
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.

packages/components/src/sender/components/ActionButtons.vue (2)

Learnt from: SonyLeo
PR: #119
File: packages/components/src/attachments/index.less:213-213
Timestamp: 2025-06-18T09:29:47.974Z
Learning: 在 packages/components/src/attachments/index.less 中,.tr-file-card__close 的背景色使用了硬编码的 rgb(194, 194, 194),但这个UI元素(关闭按钮)将会被直接替换为图标,所以不需要抽取为CSS变量。

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.

docs/demos/examples/Assistant.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.

🔇 Additional comments (5)
docs/demos/examples/Assistant.vue (1)

58-58: LGTM!

The conditional class binding correctly applies compact styling when not in fullscreen mode, enhancing the UI responsiveness.

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

257-258: LGTM!

Good refactoring to use CSS variables for icon sizing, which improves maintainability and enables responsive styling through the compact mode overrides.

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

279-287: Good UX improvement for automatic template mode exit.

The logic correctly identifies empty templates, including the edge case of a single zero-width space character which serves as a sentinel node. This enhances the user experience by automatically transitioning back to normal input mode when the template is cleared.

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

420-458: Well-implemented sentinel node protection.

The function correctly prevents deletion of zero-width space sentinel nodes by redirecting the cursor to the next element. This maintains the structural integrity of the template editor while providing a seamless user experience.


191-191: Excellent refactoring to centralize state updates.

All direct assignments to originalData.value have been consistently replaced with setOriginalData() calls, ensuring uniform application of browser-specific handling and sentinel node management throughout the component.

Also applies to: 290-301, 511-521, 834-834

Comment thread packages/components/src/sender/components/TemplateEditor.vue Outdated
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

♻️ Duplicate comments (1)
packages/components/src/sender/components/TemplateEditor.vue (1)

927-931: Address global styling conflicts with .tr-sender-compact class.

This change creates the same global styling issue previously identified. The .tr-sender-compact class is defined in multiple components, and removing scoped will cause these styles to become global and potentially override each other.

Consider these solutions:

  1. Use a more specific selector: .template-editor .tr-sender-compact { ... }
  2. Use a unique class name: .tr-template-editor-compact { ... }
  3. Re-add scoped attribute and handle compact styling through props/computed classes

The current approach may work for the demo but creates maintenance issues and unpredictable styling behavior in larger applications.

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

74-100: Good centralization of state management logic.

The setOriginalData function effectively centralizes the complex logic for handling zero-width space characters and browser-specific behavior. However, consider these improvements:

+/**
+ * Centralized function to update originalData with browser-specific zero-width space handling
+ * @param items - Array of text or template items to set as the new data
+ */
 const setOriginalData = (items: (TextItem | TemplateItem)[]) => {

Note: The function mutates the input items array by modifying the content property of items. Consider cloning items if immutability is important elsewhere in the codebase.


511-520: Consider combining consecutive setOriginalData calls for efficiency.

The two consecutive calls to setOriginalData could be combined into a single operation to avoid unnecessary processing:

- // 删除空数据
- setOriginalData(originalData.value.filter((item) => !toDeleted.includes(item.id)))
- setOriginalData(
-   originalData.value.filter((item) => {
-     if (item.type === 'text') {
-       return item.content.length > 0
-     }
-     const templateItem = item as TemplateItem
-     return [templateItem.prefix, templateItem.suffix, templateItem.content].join('').length > 0
-   }),
- )
+ // 删除空数据
+ setOriginalData(
+   originalData.value.filter((item) => {
+     // First filter: exclude items marked for deletion
+     if (toDeleted.includes(item.id)) {
+       return false
+     }
+     // Second filter: exclude empty items
+     if (item.type === 'text') {
+       return item.content.length > 0
+     }
+     const templateItem = item as TemplateItem
+     return [templateItem.prefix, templateItem.suffix, templateItem.content].join('').length > 0
+   }),
+ )
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 183b4b7 and dc8f73b.

📒 Files selected for processing (1)
  • packages/components/src/sender/components/TemplateEditor.vue (7 hunks)
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: gene9831
PR: opentiny/tiny-robot#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.
packages/components/src/sender/components/TemplateEditor.vue (1)

Learnt from: SonyLeo
PR: #119
File: packages/components/src/attachments/index.less:213-213
Timestamp: 2025-06-18T09:29:47.974Z
Learning: 在 packages/components/src/attachments/index.less 中,.tr-file-card__close 的背景色使用了硬编码的 rgb(194, 194, 194),但这个UI元素(关闭按钮)将会被直接替换为图标,所以不需要抽取为CSS变量。

Comment thread packages/components/src/sender/components/TemplateEditor.vue Outdated
gene9831
gene9831 previously approved these changes Jul 21, 2025
Comment thread packages/components/src/sender/components/TemplateEditor.vue
@gene9831 gene9831 self-requested a review July 21, 2025 08:08
Comment thread packages/components/src/sender/components/TemplateEditor.vue Outdated
@gene9831 gene9831 merged commit ce7d7fc into opentiny:develop Jul 21, 2025
@SonyLeo SonyLeo deleted the fix/template-cursor branch August 4, 2025 07:46
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