feat: improve editor performance (#14429)

#### PR Dependency Tree


* **PR #14429** 👈

This tree was auto-generated by
[Charcoal](https://github.com/danerwilliams/charcoal)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* HTML import now splits lines on <br> into separate paragraphs while
preserving inline formatting.

* **Bug Fixes**
* Paste falls back to inserting after the first paragraph when no
explicit target is found.

* **Style**
  * Improved page-mode viewport styling for consistent content layout.

* **Tests**
* Added snapshot tests for <br>-based paragraph splitting; re-enabled an
e2e drag-page test.

* **Chores**
* Deferred/deduplicated font loading, inline text caching,
drag-handle/pointer optimizations, and safer inline render
synchronization.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
DarkSky
2026-02-14 00:43:36 +08:00
committed by GitHub
parent 98e5747fdc
commit 72df9cb457
14 changed files with 873 additions and 111 deletions
@@ -10,25 +10,15 @@ import type { InlineRange } from '../types.js';
import { deltaInsertsToChunks } from '../utils/delta-convert.js';
export class RenderService<TextAttributes extends BaseTextAttributes> {
private readonly _onYTextChange = (
_: Y.YTextEvent,
transaction: Y.Transaction
) => {
this.editor.slots.textChange.next();
private _pendingRemoteInlineRangeSync = false;
const yText = this.editor.yText;
private _carriageReturnValidationCounter = 0;
if (yText.toString().includes('\r')) {
throw new BlockSuiteError(
ErrorCode.InlineEditorError,
'yText must not contain "\\r" because it will break the range synchronization'
);
}
this.render();
private _renderVersion = 0;
private readonly _syncRemoteInlineRange = () => {
const inlineRange = this.editor.inlineRange$.peek();
if (!inlineRange || transaction.local) return;
if (!inlineRange) return;
const lastStartRelativePosition = this.editor.lastStartRelativePosition;
const lastEndRelativePosition = this.editor.lastEndRelativePosition;
@@ -50,7 +40,7 @@ export class RenderService<TextAttributes extends BaseTextAttributes> {
const startIndex = absoluteStart?.index;
const endIndex = absoluteEnd?.index;
if (!startIndex || !endIndex) return;
if (startIndex == null || endIndex == null) return;
const newInlineRange: InlineRange = {
index: startIndex,
@@ -59,7 +49,31 @@ export class RenderService<TextAttributes extends BaseTextAttributes> {
if (!this.editor.isValidInlineRange(newInlineRange)) return;
this.editor.setInlineRange(newInlineRange);
this.editor.syncInlineRange();
};
private readonly _onYTextChange = (
_: Y.YTextEvent,
transaction: Y.Transaction
) => {
this.editor.slots.textChange.next();
const yText = this.editor.yText;
if (
(this._carriageReturnValidationCounter++ & 0x3f) === 0 &&
yText.toString().includes('\r')
) {
throw new BlockSuiteError(
ErrorCode.InlineEditorError,
'yText must not contain "\\r" because it will break the range synchronization'
);
}
if (!transaction.local) {
this._pendingRemoteInlineRangeSync = true;
}
this.render();
};
mount = () => {
@@ -70,6 +84,7 @@ export class RenderService<TextAttributes extends BaseTextAttributes> {
editor.disposables.add({
dispose: () => {
yText.unobserve(this._onYTextChange);
this._pendingRemoteInlineRangeSync = false;
},
});
};
@@ -82,6 +97,7 @@ export class RenderService<TextAttributes extends BaseTextAttributes> {
render = () => {
if (!this.editor.rootElement) return;
const renderVersion = ++this._renderVersion;
this._rendering = true;
const rootElement = this.editor.rootElement;
@@ -152,11 +168,21 @@ export class RenderService<TextAttributes extends BaseTextAttributes> {
this.editor
.waitForUpdate()
.then(() => {
if (renderVersion !== this._renderVersion) return;
if (this._pendingRemoteInlineRangeSync) {
this._pendingRemoteInlineRangeSync = false;
this._syncRemoteInlineRange();
}
this._rendering = false;
this.editor.slots.renderComplete.next();
this.editor.syncInlineRange();
})
.catch(console.error);
.catch(error => {
if (renderVersion === this._renderVersion) {
this._rendering = false;
}
console.error(error);
});
};
rerenderWholeEditor = () => {