fix(editor): ime input error at empty line (#11636)

Close [BS-3106](https://linear.app/affine-design/issue/BS-3106/mac-chrom在空行使用ime输入,文档卡住)
This commit is contained in:
L-Sun
2025-04-11 10:39:16 +00:00
parent e1e5e8fc14
commit aabb09b31f
22 changed files with 148 additions and 110 deletions

View File

@@ -1,7 +1,7 @@
import { affineTextStyles } from '@blocksuite/affine-shared/styles';
import type { AffineTextAttributes } from '@blocksuite/affine-shared/types';
import { ShadowlessElement } from '@blocksuite/std';
import { ZERO_WIDTH_SPACE } from '@blocksuite/std/inline';
import { ZERO_WIDTH_FOR_EMPTY_LINE } from '@blocksuite/std/inline';
import type { DeltaInsert } from '@blocksuite/store';
import { html } from 'lit';
import { property } from 'lit/decorators.js';
@@ -111,7 +111,7 @@ export class AffineCodeUnit extends ShadowlessElement {
@property({ type: Object })
accessor delta: DeltaInsert<AffineTextAttributes> = {
insert: ZERO_WIDTH_SPACE,
insert: ZERO_WIDTH_FOR_EMPTY_LINE,
};
}

View File

@@ -12,8 +12,8 @@ import {
import {
INLINE_ROOT_ATTR,
type InlineRootElement,
ZERO_WIDTH_NON_JOINER,
ZERO_WIDTH_SPACE,
ZERO_WIDTH_FOR_EMBED_NODE,
ZERO_WIDTH_FOR_EMPTY_LINE,
} from '@blocksuite/std/inline';
import type { DeltaInsert } from '@blocksuite/store';
import { shift } from '@floating-ui/dom';
@@ -186,7 +186,7 @@ export class AffineFootnoteNode extends WithDisposable(ShadowlessElement) {
return html`<span
${this.hidePopup ? '' : ref(this._whenHover.setReference)}
class=${nodeClasses}
>${node}<v-text .str=${ZERO_WIDTH_NON_JOINER}></v-text
>${node}<v-text .str=${ZERO_WIDTH_FOR_EMBED_NODE}></v-text
></span>`;
}
@@ -195,7 +195,7 @@ export class AffineFootnoteNode extends WithDisposable(ShadowlessElement) {
@property({ type: Object })
accessor delta: DeltaInsert<AffineTextAttributes> = {
insert: ZERO_WIDTH_SPACE,
insert: ZERO_WIDTH_FOR_EMPTY_LINE,
attributes: {},
};

View File

@@ -1,6 +1,6 @@
import type { AffineTextAttributes } from '@blocksuite/affine-shared/types';
import { ShadowlessElement } from '@blocksuite/std';
import { ZERO_WIDTH_SPACE } from '@blocksuite/std/inline';
import { ZERO_WIDTH_FOR_EMPTY_LINE } from '@blocksuite/std/inline';
import type { DeltaInsert } from '@blocksuite/store';
import { html } from 'lit';
import { property } from 'lit/decorators.js';
@@ -50,6 +50,6 @@ export class LatexEditorUnit extends ShadowlessElement {
@property({ attribute: false })
accessor delta: DeltaInsert<AffineTextAttributes> = {
insert: ZERO_WIDTH_SPACE,
insert: ZERO_WIDTH_FOR_EMPTY_LINE,
};
}

View File

@@ -9,8 +9,8 @@ import {
} from '@blocksuite/std';
import {
type InlineEditor,
ZERO_WIDTH_NON_JOINER,
ZERO_WIDTH_SPACE,
ZERO_WIDTH_FOR_EMBED_NODE,
ZERO_WIDTH_FOR_EMPTY_LINE,
} from '@blocksuite/std/inline';
import type { DeltaInsert } from '@blocksuite/store';
import { signal } from '@preact/signals-core';
@@ -178,7 +178,7 @@ export class AffineLatexNode extends SignalWatcher(
override render() {
return html`<span class="affine-latex" data-selected=${this.selected}
><div class="latex-container"></div>
<v-text .str=${ZERO_WIDTH_NON_JOINER}></v-text
<v-text .str=${ZERO_WIDTH_FOR_EMBED_NODE}></v-text
></span>`;
}
@@ -244,7 +244,7 @@ export class AffineLatexNode extends SignalWatcher(
@property({ attribute: false })
accessor delta: DeltaInsert<AffineTextAttributes> = {
insert: ZERO_WIDTH_SPACE,
insert: ZERO_WIDTH_FOR_EMPTY_LINE,
};
@property({ attribute: false })

View File

@@ -13,7 +13,7 @@ import { BLOCK_ID_ATTR, ShadowlessElement } from '@blocksuite/std';
import {
INLINE_ROOT_ATTR,
type InlineRootElement,
ZERO_WIDTH_SPACE,
ZERO_WIDTH_FOR_EMPTY_LINE,
} from '@blocksuite/std/inline';
import type { DeltaInsert } from '@blocksuite/store';
import { css, html } from 'lit';
@@ -177,7 +177,7 @@ export class AffineLink extends WithDisposable(ShadowlessElement) {
@property({ type: Object })
accessor delta: DeltaInsert<AffineTextAttributes> = {
insert: ZERO_WIDTH_SPACE,
insert: ZERO_WIDTH_FOR_EMPTY_LINE,
};
@property({ attribute: false })

View File

@@ -5,8 +5,8 @@ import { SignalWatcher, WithDisposable } from '@blocksuite/global/lit';
import type { BlockStdScope } from '@blocksuite/std';
import { ShadowlessElement } from '@blocksuite/std';
import {
ZERO_WIDTH_NON_JOINER,
ZERO_WIDTH_SPACE,
ZERO_WIDTH_FOR_EMBED_NODE,
ZERO_WIDTH_FOR_EMPTY_LINE,
} from '@blocksuite/std/inline';
import type { DeltaInsert } from '@blocksuite/store';
import { css, html } from 'lit';
@@ -88,7 +88,7 @@ export class AffineMention extends SignalWatcher(
data-selected=${this.selected}
data-type="error"
class="affine-mention"
>@Unknown Member<v-text .str=${ZERO_WIDTH_NON_JOINER}></v-text
>@Unknown Member<v-text .str=${ZERO_WIDTH_FOR_EMBED_NODE}></v-text
></span>`;
const userService = this.std.getOptional(UserProvider);
@@ -107,7 +107,7 @@ export class AffineMention extends SignalWatcher(
data-selected=${this.selected}
data-type="removed"
class="affine-mention"
>@Inactive Member<v-text .str=${ZERO_WIDTH_NON_JOINER}></v-text
>@Inactive Member<v-text .str=${ZERO_WIDTH_FOR_EMBED_NODE}></v-text
></span>`;
} else {
return html`<span
@@ -115,7 +115,7 @@ export class AffineMention extends SignalWatcher(
data-type="default"
class="affine-mention"
>@${userInfo$.value.name ?? 'Unknown'}<v-text
.str=${ZERO_WIDTH_NON_JOINER}
.str=${ZERO_WIDTH_FOR_EMBED_NODE}
></v-text
></span>`;
}
@@ -129,7 +129,7 @@ export class AffineMention extends SignalWatcher(
>@loading<span class="dots"
><span class="dot">.</span><span class="dot">.</span
><span class="dot">.</span></span
><v-text .str=${ZERO_WIDTH_NON_JOINER}></v-text
><v-text .str=${ZERO_WIDTH_FOR_EMBED_NODE}></v-text
></span>`;
}
@@ -138,7 +138,7 @@ export class AffineMention extends SignalWatcher(
@property({ type: Object })
accessor delta: DeltaInsert<AffineTextAttributes> = {
insert: ZERO_WIDTH_SPACE,
insert: ZERO_WIDTH_FOR_EMPTY_LINE,
attributes: {},
};

View File

@@ -1,7 +1,7 @@
import { affineTextStyles } from '@blocksuite/affine-shared/styles';
import type { AffineTextAttributes } from '@blocksuite/affine-shared/types';
import { ShadowlessElement } from '@blocksuite/std';
import { ZERO_WIDTH_SPACE } from '@blocksuite/std/inline';
import { ZERO_WIDTH_FOR_EMPTY_LINE } from '@blocksuite/std/inline';
import type { DeltaInsert } from '@blocksuite/store';
import { html } from 'lit';
import { property } from 'lit/decorators.js';
@@ -30,6 +30,6 @@ export class AffineText extends ShadowlessElement {
@property({ type: Object })
accessor delta: DeltaInsert<AffineTextAttributes> = {
insert: ZERO_WIDTH_SPACE,
insert: ZERO_WIDTH_FOR_EMPTY_LINE,
};
}

View File

@@ -22,8 +22,8 @@ import { BLOCK_ID_ATTR, ShadowlessElement } from '@blocksuite/std';
import {
INLINE_ROOT_ATTR,
type InlineRootElement,
ZERO_WIDTH_NON_JOINER,
ZERO_WIDTH_SPACE,
ZERO_WIDTH_FOR_EMBED_NODE,
ZERO_WIDTH_FOR_EMPTY_LINE,
} from '@blocksuite/std/inline';
import type { DeltaInsert, DocMeta, Store } from '@blocksuite/store';
import { css, html, nothing } from 'lit';
@@ -274,14 +274,14 @@ export class AffineReference extends WithDisposable(ShadowlessElement) {
>${title}</span
>`;
// we need to add `<v-text .str=${ZERO_WIDTH_NON_JOINER}></v-text>` in an
// we need to add `<v-text .str=${ZERO_WIDTH_FOR_EMBED_NODE}></v-text>` in an
// embed element to make sure inline range calculation is correct
return html`<span
data-selected=${this.selected}
class="affine-reference"
style=${styleMap(style)}
@click=${(event: MouseEvent) => this.open({ event })}
>${content}<v-text .str=${ZERO_WIDTH_NON_JOINER}></v-text
>${content}<v-text .str=${ZERO_WIDTH_FOR_EMBED_NODE}></v-text
></span>`;
}
@@ -299,7 +299,7 @@ export class AffineReference extends WithDisposable(ShadowlessElement) {
@property({ type: Object })
accessor delta: DeltaInsert<AffineTextAttributes> = {
insert: ZERO_WIDTH_SPACE,
insert: ZERO_WIDTH_FOR_EMPTY_LINE,
attributes: {},
};

View File

@@ -7,7 +7,7 @@ import { html, LitElement } from 'lit';
import { property } from 'lit/decorators.js';
import { styleMap } from 'lit/directives/style-map.js';
import { ZERO_WIDTH_SPACE } from '../consts.js';
import { ZERO_WIDTH_FOR_EMPTY_LINE } from '../consts.js';
import type { InlineEditor } from '../inline-editor.js';
import { isInlineRangeIntersect } from '../utils/inline-range.js';
@@ -90,7 +90,7 @@ export class VElement<
@property({ type: Object })
accessor delta: DeltaInsert<T> = {
insert: ZERO_WIDTH_SPACE,
insert: ZERO_WIDTH_FOR_EMPTY_LINE,
};
@property({ attribute: false })

View File

@@ -4,7 +4,7 @@ import { html, LitElement, type TemplateResult } from 'lit';
import { property } from 'lit/decorators.js';
import { styleMap } from 'lit/directives/style-map.js';
import { INLINE_ROOT_ATTR, ZERO_WIDTH_SPACE } from '../consts.js';
import { INLINE_ROOT_ATTR, ZERO_WIDTH_FOR_EMPTY_LINE } from '../consts.js';
import type { InlineRootElement } from '../inline-editor.js';
import { EmbedGap } from './embed-gap.js';
@@ -89,7 +89,9 @@ export class VLine extends LitElement {
renderVElements() {
if (this.elements.length === 0) {
// don't use v-element because it not correspond to the actual delta
return html`<div><v-text .str=${ZERO_WIDTH_SPACE}></v-text></div>`;
return html`
<div><v-text .str=${ZERO_WIDTH_FOR_EMPTY_LINE}></v-text></div>
`;
}
const inlineEditor = this.inlineEditor;

View File

@@ -2,7 +2,7 @@ import { html, LitElement } from 'lit';
import { property } from 'lit/decorators.js';
import { styleMap } from 'lit/directives/style-map.js';
import { ZERO_WIDTH_SPACE } from '../consts.js';
import { ZERO_WIDTH_FOR_EMPTY_LINE } from '../consts.js';
export class VText extends LitElement {
override createRenderRoot() {
@@ -24,7 +24,7 @@ export class VText extends LitElement {
}
@property({ attribute: false })
accessor str: string = ZERO_WIDTH_SPACE;
accessor str: string = ZERO_WIDTH_FOR_EMPTY_LINE;
}
declare global {

View File

@@ -1,5 +1,6 @@
export const ZERO_WIDTH_SPACE = '\u200C';
// see https://en.wikipedia.org/wiki/Zero-width_non-joiner
export const ZERO_WIDTH_NON_JOINER = '\u200B';
import { IS_SAFARI } from '@blocksuite/global/env';
export const ZERO_WIDTH_FOR_EMPTY_LINE = IS_SAFARI ? '\u200C' : '\u200B';
export const ZERO_WIDTH_FOR_EMBED_NODE = IS_SAFARI ? '\u200B' : '\u200C';
export const INLINE_ROOT_ATTR = 'data-v-root';

View File

@@ -1,7 +1,7 @@
import { BlockSuiteError, ErrorCode } from '@blocksuite/global/exceptions';
import type { VElement, VLine } from '../components/index.js';
import { INLINE_ROOT_ATTR, ZERO_WIDTH_SPACE } from '../consts.js';
import { INLINE_ROOT_ATTR, ZERO_WIDTH_FOR_EMPTY_LINE } from '../consts.js';
import type { DomPoint, TextPoint } from '../types.js';
import {
isInlineRoot,
@@ -76,7 +76,7 @@ export function textPointToDomPoint(
index += calculateTextLength(text);
}
if (text.wholeText !== ZERO_WIDTH_SPACE) {
if (text.wholeText !== ZERO_WIDTH_FOR_EMPTY_LINE) {
index += offset;
}

View File

@@ -1,7 +1,7 @@
import { ZERO_WIDTH_SPACE } from '../consts.js';
import { ZERO_WIDTH_FOR_EMPTY_LINE } from '../consts.js';
export function calculateTextLength(text: Text): number {
if (text.wholeText === ZERO_WIDTH_SPACE) {
if (text.wholeText === ZERO_WIDTH_FOR_EMPTY_LINE) {
return 0;
} else {
return text.wholeText.length;

View File

@@ -5,7 +5,7 @@ import { effects } from '@blocksuite/affine/std/effects';
import {
type AttributeRenderer,
InlineEditor,
ZERO_WIDTH_NON_JOINER,
ZERO_WIDTH_FOR_EMBED_NODE,
} from '@blocksuite/affine/std/inline';
import {
type BaseTextAttributes,
@@ -62,7 +62,7 @@ const attributeRenderer: AttributeRenderer = ({ delta, selected }) => {
border: selected ? '1px solid #eb763a' : '',
background: 'rgba(135,131,120,0.15)',
})}
>@flrande<v-text .str=${ZERO_WIDTH_NON_JOINER}></v-text
>@flrande<v-text .str=${ZERO_WIDTH_FOR_EMBED_NODE}></v-text
></span>`;
}