fix(editor): disable OS-level autocorrect causing aggressive "im" replacement (#14477)#14486
Conversation
📝 WalkthroughWalkthroughThe changes disable macOS text replacement across multiple editor components by setting Changes
Estimated code review effort🎯 2 (Simple) | ⏱️ ~8 minutes Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
Tip Issue Planner is now in beta. Read the docs and try it out! Share your feedback on Discord. 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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
blocksuite/affine/rich-text/src/rich-text.ts (1)
402-407:autocorrect/autocapitalizeare static in the template regardless ofreadonly.The other components in this PR (
page-root-block.ts,edgeless-text-block.ts) remove these attributes when entering readonly mode. Here they're always present even whencontenteditable="false". Functionally harmless, but the pattern is inconsistent across the PR.♻️ Optional — align with the rest of the PR using Lit's `nothing`
-import { css, html, type TemplateResult } from 'lit'; +import { css, html, nothing, type TemplateResult } from 'lit';return html`<div contenteditable=${this.readonly ? 'false' : 'true'} - autocorrect="off" - autocapitalize="off" + autocorrect=${this.readonly ? nothing : 'off'} + autocapitalize=${this.readonly ? nothing : 'off'} class=${classes} ></div>`;🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@blocksuite/affine/rich-text/src/rich-text.ts` around lines 402 - 407, The template in rich-text.ts currently renders autocorrect and autocapitalize attributes unconditionally even when this.readonly is true; change the html template that returns the contenteditable div to conditionally omit these attributes when readonly by using Lit's nothing for autocorrect and autocapitalize (e.g., bind autocorrect=${this.readonly ? nothing : 'off'} and autocapitalize=${this.readonly ? nothing : 'off'}) so the div matches the readonly behavior in page-root-block.ts and edgeless-text-block.ts; keep the contenteditable and class=${classes} bindings as-is.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@blocksuite/playground/examples/inline/test-page.ts`:
- Around line 128-136: The autocorrect/autocapitalize attributes are being set
on the outer TestRichText element instead of on the actual editable container,
so move the two setAttribute calls from firstUpdated to apply to the _container
element before calling this.inlineEditor.mount(this._container, this);
specifically, in the override firstUpdated() method set
_container.setAttribute('autocorrect','off') and
_container.setAttribute('autocapitalize','off') (or otherwise ensure the same
attributes are present on the element passed into InlineEditor.mount and not the
host element), then call this.inlineEditor.mount(this._container, this).
---
Nitpick comments:
In `@blocksuite/affine/rich-text/src/rich-text.ts`:
- Around line 402-407: The template in rich-text.ts currently renders
autocorrect and autocapitalize attributes unconditionally even when
this.readonly is true; change the html template that returns the contenteditable
div to conditionally omit these attributes when readonly by using Lit's nothing
for autocorrect and autocapitalize (e.g., bind autocorrect=${this.readonly ?
nothing : 'off'} and autocapitalize=${this.readonly ? nothing : 'off'}) so the
div matches the readonly behavior in page-root-block.ts and
edgeless-text-block.ts; keep the contenteditable and class=${classes} bindings
as-is.
| override firstUpdated() { | ||
| this.contentEditable = 'true'; | ||
| this.style.outline = 'none'; | ||
|
|
||
| // Disable macOS text replacement to prevent aggressive autocorrect | ||
| this.setAttribute('autocorrect', 'off'); | ||
| this.setAttribute('autocapitalize', 'off'); | ||
|
|
||
| this.inlineEditor.mount(this._container, this); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, let's find the InlineEditor implementation files
find . -type f -name "*.ts" -path "*inline*" | head -20Repository: toeverything/AFFiNE
Length of output: 1100
🏁 Script executed:
# Search for the mount method definition in inline editor code
rg -n "mount\s*\(" blocksuite --type ts -A 5 | grep -A 5 "mount.*contentEditable\|contentEditable.*mount" | head -30Repository: toeverything/AFFiNE
Length of output: 45
🏁 Script executed:
# Look for InlineEditor class and its mount method
ast-grep --pattern 'class InlineEditor {
$$$
mount($$$) {
$$$
}
$$$
}'Repository: toeverything/AFFiNE
Length of output: 45
🏁 Script executed:
# Search for contentEditable assignments in the inline editor implementation
rg -n "contentEditable\s*=" blocksuite --type ts -B 2 -A 2Repository: toeverything/AFFiNE
Length of output: 8899
🏁 Script executed:
# Check the test-page.ts file structure
cat -n blocksuite/playground/examples/inline/test-page.ts | head -150Repository: toeverything/AFFiNE
Length of output: 5105
🏁 Script executed:
# Find and examine the InlineEditor.mount() method
rg -n "mount\s*\(" blocksuite/framework/std/src/inline/inline-editor.ts -B 2 -A 15Repository: toeverything/AFFiNE
Length of output: 1359
🏁 Script executed:
# Look at the full InlineEditor implementation for mount and how it handles rootElement
cat -n blocksuite/framework/std/src/inline/inline-editor.ts | sed -n '1,100p'Repository: toeverything/AFFiNE
Length of output: 4688
🏁 Script executed:
# Search for where rootElement is assigned in mount
rg -n "rootElement\s*=" blocksuite/framework/std/src/inline/inline-editor.ts -B 3 -A 3Repository: toeverything/AFFiNE
Length of output: 595
🏁 Script executed:
# Look at rich-text.ts to see the production pattern being referenced
find . -name "rich-text.ts" -type f | head -5Repository: toeverything/AFFiNE
Length of output: 195
🏁 Script executed:
# Find and examine the rich-text.ts file mentioned in the review
rg -n "firstUpdated\|mount\|autocorrect" blocksuite --type ts | grep -i "rich-text" | head -10Repository: toeverything/AFFiNE
Length of output: 45
🏁 Script executed:
# Search for rich-text implementation that has the correct pattern
fd "rich-text" --type f --extension ts | head -10Repository: toeverything/AFFiNE
Length of output: 345
🏁 Script executed:
# Examine the production rich-text.ts implementation
cat -n blocksuite/affine/rich-text/src/rich-text.ts | head -200Repository: toeverything/AFFiNE
Length of output: 6993
🏁 Script executed:
# Look for mount, firstUpdated, autocorrect in rich-text.ts
rg -n "mount\|firstUpdated\|autocorrect\|autocapitalize" blocksuite/affine/rich-text/src/rich-text.ts -B 2 -A 5Repository: toeverything/AFFiNE
Length of output: 45
🏁 Script executed:
# Look at test-page.ts more carefully around the mount call and _container definition
cat -n blocksuite/playground/examples/inline/test-page.ts | sed -n '120,160p'Repository: toeverything/AFFiNE
Length of output: 1544
🏁 Script executed:
# Look for where rich-text.ts mounts the editor and handles attributes
rg -n "mount\|firstUpdated\|connectedCallback" blocksuite/affine/rich-text/src/rich-text.ts -B 3 -A 8Repository: toeverything/AFFiNE
Length of output: 45
🏁 Script executed:
# Check the render method in rich-text.ts to see the template structure
cat -n blocksuite/affine/rich-text/src/rich-text.ts | sed -n '230,350p'Repository: toeverything/AFFiNE
Length of output: 4532
🏁 Script executed:
# Look for the _inlineEditorContainer query and template in rich-text.ts
rg -n "_inlineEditorContainer\|inline-editor\|@query" blocksuite/affine/rich-text/src/rich-text.ts -B 2 -A 3Repository: toeverything/AFFiNE
Length of output: 45
🏁 Script executed:
# Look for the render method in rich-text.ts
cat -n blocksuite/affine/rich-text/src/rich-text.ts | sed -n '370,450p'Repository: toeverything/AFFiNE
Length of output: 3012
🏁 Script executed:
# Search for where inlineEditorContainer is defined (property or query)
rg -n "inlineEditorContainer\|@query\|query(" blocksuite/affine/rich-text/src/rich-text.ts -B 1 -A 2Repository: toeverything/AFFiNE
Length of output: 187
🏁 Script executed:
# Look at the full render template in rich-text.ts
cat -n blocksuite/affine/rich-text/src/rich-text.ts | sed -n '375,425p'Repository: toeverything/AFFiNE
Length of output: 1758
🏁 Script executed:
# Search for where attributes like autocorrect are set in rich-text.ts
rg -n "autocorrect\|autocapitalize\|setAttribute" blocksuite/affine/rich-text/src/rich-text.ts -B 2 -A 2Repository: toeverything/AFFiNE
Length of output: 45
🏁 Script executed:
# Look at test-page render to understand _container
cat -n blocksuite/playground/examples/inline/test-page.ts | sed -n '155,200p'Repository: toeverything/AFFiNE
Length of output: 1620
🏁 Script executed:
# Look for the _container definition and how it's queried in test-page.ts
rg -n "_container\|@query" blocksuite/playground/examples/inline/test-page.ts -B 1 -A 2Repository: toeverything/AFFiNE
Length of output: 45
🏁 Script executed:
# Verify that mount() calls setReadonly which sets contentEditable
cat -n blocksuite/framework/std/src/inline/inline-editor.ts | sed -n '230,280p'Repository: toeverything/AFFiNE
Length of output: 1704
🏁 Script executed:
# Search for `@query` decorator defining _container in test-page.ts
cat -n blocksuite/playground/examples/inline/test-page.ts | grep -n "query\|_container"Repository: toeverything/AFFiNE
Length of output: 1002
🏁 Script executed:
# Check the entire TestRichText class definition
cat -n blocksuite/playground/examples/inline/test-page.ts | sed -n '126,250p'Repository: toeverything/AFFiNE
Length of output: 4567
Set autocorrect and autocapitalize attributes on the _container element passed to mount(), not on the outer element.
The attributes are currently set on the outer TestRichText element, but InlineEditor.mount() calls setReadonly(), which explicitly sets contentEditable on _container (line 276 of inline-editor.ts). This makes _container an independent contenteditable element that defaults to autocorrect=on and autocapitalize=on per-element, bypassing the settings on the ancestor element.
The production component (rich-text.ts) correctly places these attributes directly on the <div> that is passed to inlineEditor.mount(). Align this playground code with that approach:
✅ Set attributes on _container
override firstUpdated() {
this.contentEditable = 'true';
this.style.outline = 'none';
-
- // Disable macOS text replacement to prevent aggressive autocorrect
- this.setAttribute('autocorrect', 'off');
- this.setAttribute('autocapitalize', 'off');
this.inlineEditor.mount(this._container, this);
+ // Disable macOS text replacement to prevent aggressive autocorrect
+ this._container.setAttribute('autocorrect', 'off');
+ this._container.setAttribute('autocapitalize', 'off');📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| override firstUpdated() { | |
| this.contentEditable = 'true'; | |
| this.style.outline = 'none'; | |
| // Disable macOS text replacement to prevent aggressive autocorrect | |
| this.setAttribute('autocorrect', 'off'); | |
| this.setAttribute('autocapitalize', 'off'); | |
| this.inlineEditor.mount(this._container, this); | |
| override firstUpdated() { | |
| this.contentEditable = 'true'; | |
| this.style.outline = 'none'; | |
| this.inlineEditor.mount(this._container, this); | |
| // Disable macOS text replacement to prevent aggressive autocorrect | |
| this._container.setAttribute('autocorrect', 'off'); | |
| this._container.setAttribute('autocapitalize', 'off'); |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@blocksuite/playground/examples/inline/test-page.ts` around lines 128 - 136,
The autocorrect/autocapitalize attributes are being set on the outer
TestRichText element instead of on the actual editable container, so move the
two setAttribute calls from firstUpdated to apply to the _container element
before calling this.inlineEditor.mount(this._container, this); specifically, in
the override firstUpdated() method set
_container.setAttribute('autocorrect','off') and
_container.setAttribute('autocapitalize','off') (or otherwise ensure the same
attributes are present on the element passed into InlineEditor.mount and not the
host element), then call this.inlineEditor.mount(this._container, this).
Closes #14477
Summary
Fixes an issue where typing words starting with "im" (e.g., "implicit", "image", "important") would automatically be replaced with "I'm".
Root Cause
On macOS,
contenteditableelements without:allow OS-level text replacement.
macOS includes a built-in rule:
im → I'm
This incorrectly triggers even when "im" is part of a longer word.
Solution
Added:
to relevant contenteditable elements to prevent OS-level interference.
Result
Summary by CodeRabbit