mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-07-18 18:46:19 +08:00
chore: merge blocksuite source code (#9213)
This commit is contained in:
@@ -0,0 +1,179 @@
|
||||
import { BlockSuiteError, ErrorCode } from '@blocksuite/global/exceptions';
|
||||
import { assertExists } from '@blocksuite/global/utils';
|
||||
import { html, render } from 'lit';
|
||||
import { repeat } from 'lit/directives/repeat.js';
|
||||
import * as Y from 'yjs';
|
||||
|
||||
import type { VLine } from '../components/v-line.js';
|
||||
import type { InlineEditor } from '../inline-editor.js';
|
||||
import type { InlineRange } from '../types.js';
|
||||
import type { BaseTextAttributes } from '../utils/base-attributes.js';
|
||||
import { deltaInsertsToChunks } from '../utils/delta-convert.js';
|
||||
|
||||
export class RenderService<TextAttributes extends BaseTextAttributes> {
|
||||
private _onYTextChange = (_: Y.YTextEvent, transaction: Y.Transaction) => {
|
||||
this.editor.slots.textChange.emit();
|
||||
|
||||
const yText = this.editor.yText;
|
||||
|
||||
if (yText.toString().includes('\r')) {
|
||||
throw new BlockSuiteError(
|
||||
ErrorCode.InlineEditorError,
|
||||
'yText must not contain "\\r" because it will break the range synchronization'
|
||||
);
|
||||
}
|
||||
|
||||
this.render();
|
||||
|
||||
const inlineRange = this.editor.inlineRange$.peek();
|
||||
if (!inlineRange || transaction.local) return;
|
||||
|
||||
const lastStartRelativePosition = this.editor.lastStartRelativePosition;
|
||||
const lastEndRelativePosition = this.editor.lastEndRelativePosition;
|
||||
if (!lastStartRelativePosition || !lastEndRelativePosition) return;
|
||||
|
||||
const doc = this.editor.yText.doc;
|
||||
assertExists(doc);
|
||||
const absoluteStart = Y.createAbsolutePositionFromRelativePosition(
|
||||
lastStartRelativePosition,
|
||||
doc
|
||||
);
|
||||
const absoluteEnd = Y.createAbsolutePositionFromRelativePosition(
|
||||
lastEndRelativePosition,
|
||||
doc
|
||||
);
|
||||
|
||||
const startIndex = absoluteStart?.index;
|
||||
const endIndex = absoluteEnd?.index;
|
||||
if (!startIndex || !endIndex) return;
|
||||
|
||||
const newInlineRange: InlineRange = {
|
||||
index: startIndex,
|
||||
length: endIndex - startIndex,
|
||||
};
|
||||
if (!this.editor.isValidInlineRange(newInlineRange)) return;
|
||||
|
||||
this.editor.setInlineRange(newInlineRange);
|
||||
this.editor.syncInlineRange();
|
||||
};
|
||||
|
||||
mount = () => {
|
||||
const editor = this.editor;
|
||||
const yText = editor.yText;
|
||||
|
||||
yText.observe(this._onYTextChange);
|
||||
editor.disposables.add({
|
||||
dispose: () => {
|
||||
yText.unobserve(this._onYTextChange);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
private _rendering = false;
|
||||
get rendering() {
|
||||
return this._rendering;
|
||||
}
|
||||
// render current deltas to VLines
|
||||
render = () => {
|
||||
if (!this.editor.mounted) return;
|
||||
|
||||
this._rendering = true;
|
||||
|
||||
const rootElement = this.editor.rootElement;
|
||||
const embedDeltas = this.editor.deltaService.embedDeltas;
|
||||
const chunks = deltaInsertsToChunks(embedDeltas);
|
||||
|
||||
let deltaIndex = 0;
|
||||
// every chunk is a line
|
||||
const lines = chunks.map((chunk, lineIndex) => {
|
||||
if (lineIndex > 0) {
|
||||
deltaIndex += 1; // for '\n'
|
||||
}
|
||||
|
||||
const lineStartOffset = deltaIndex;
|
||||
if (chunk.length > 0) {
|
||||
const elements: VLine['elements'] = chunk.map(delta => {
|
||||
const startOffset = deltaIndex;
|
||||
deltaIndex += delta.insert.length;
|
||||
const endOffset = deltaIndex;
|
||||
|
||||
return [
|
||||
html`<v-element
|
||||
.inlineEditor=${this.editor}
|
||||
.delta=${{
|
||||
insert: delta.insert,
|
||||
attributes: this.editor.attributeService.normalizeAttributes(
|
||||
delta.attributes
|
||||
),
|
||||
}}
|
||||
.startOffset=${startOffset}
|
||||
.endOffset=${endOffset}
|
||||
.lineIndex=${lineIndex}
|
||||
></v-element>`,
|
||||
delta,
|
||||
];
|
||||
});
|
||||
|
||||
return html`<v-line
|
||||
.elements=${elements}
|
||||
.index=${lineIndex}
|
||||
.startOffset=${lineStartOffset}
|
||||
.endOffset=${deltaIndex}
|
||||
></v-line>`;
|
||||
} else {
|
||||
return html`<v-line
|
||||
.elements=${[]}
|
||||
.index=${lineIndex}
|
||||
.startOffset=${lineStartOffset}
|
||||
.endOffset=${deltaIndex}
|
||||
></v-line>`;
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
render(
|
||||
repeat(
|
||||
lines.map((line, i) => ({ line, index: i })),
|
||||
entry => entry.index,
|
||||
entry => entry.line
|
||||
),
|
||||
rootElement
|
||||
);
|
||||
} catch {
|
||||
// Lit may be crashed by IME input and we need to rerender whole editor for it
|
||||
this.editor.rerenderWholeEditor();
|
||||
}
|
||||
|
||||
this.editor
|
||||
.waitForUpdate()
|
||||
.then(() => {
|
||||
this._rendering = false;
|
||||
this.editor.slots.renderComplete.emit();
|
||||
this.editor.syncInlineRange();
|
||||
})
|
||||
.catch(console.error);
|
||||
};
|
||||
|
||||
rerenderWholeEditor = () => {
|
||||
const rootElement = this.editor.rootElement;
|
||||
|
||||
if (!rootElement.isConnected) return;
|
||||
|
||||
rootElement.replaceChildren();
|
||||
// Because we bypassed Lit and disrupted the DOM structure, this will cause an inconsistency in the original state of `ChildPart`.
|
||||
// Therefore, we need to remove the original `ChildPart`.
|
||||
// https://github.com/lit/lit/blob/a2cd76cfdea4ed717362bb1db32710d70550469d/packages/lit-html/src/lit-html.ts#L2248
|
||||
|
||||
delete (rootElement as any)['_$litPart$'];
|
||||
this.render();
|
||||
};
|
||||
|
||||
waitForUpdate = async () => {
|
||||
const vLines = Array.from(
|
||||
this.editor.rootElement.querySelectorAll('v-line')
|
||||
);
|
||||
await Promise.all(vLines.map(line => line.updateComplete));
|
||||
};
|
||||
|
||||
constructor(readonly editor: InlineEditor<TextAttributes>) {}
|
||||
}
|
||||
Reference in New Issue
Block a user