mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-16 05:47:09 +08:00
fix(editor): use nullable inline editor root element (#9320)
Fixes `sentry-7906c03b79a54ede819c56cc15ad9889`
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
/* eslint-disable no-useless-escape */
|
/* oxlint-disable no-useless-escape */
|
||||||
import type { BlockComponent, ExtensionType } from '@blocksuite/block-std';
|
import type { BlockComponent, ExtensionType } from '@blocksuite/block-std';
|
||||||
import {
|
import {
|
||||||
KEYBOARD_ALLOW_DEFAULT,
|
KEYBOARD_ALLOW_DEFAULT,
|
||||||
@@ -450,6 +450,7 @@ export const LatexExtension = InlineMarkdownExtension({
|
|||||||
|
|
||||||
undoManager.stopCapturing();
|
undoManager.stopCapturing();
|
||||||
|
|
||||||
|
if (!inlineEditor.rootElement) return KEYBOARD_ALLOW_DEFAULT;
|
||||||
const blockComponent =
|
const blockComponent =
|
||||||
inlineEditor.rootElement.closest<BlockComponent>('[data-block-id]');
|
inlineEditor.rootElement.closest<BlockComponent>('[data-block-id]');
|
||||||
if (!blockComponent) return KEYBOARD_ALLOW_DEFAULT;
|
if (!blockComponent) return KEYBOARD_ALLOW_DEFAULT;
|
||||||
|
|||||||
@@ -103,7 +103,8 @@ export class AffineLink extends ShadowlessElement {
|
|||||||
// Please also note that when readonly mode active,
|
// Please also note that when readonly mode active,
|
||||||
// this workaround is not necessary and links work normally.
|
// this workaround is not necessary and links work normally.
|
||||||
get block() {
|
get block() {
|
||||||
const block = this.inlineEditor?.rootElement.closest<BlockComponent>(
|
if (!this.inlineEditor?.rootElement) return null;
|
||||||
|
const block = this.inlineEditor.rootElement.closest<BlockComponent>(
|
||||||
`[${BLOCK_ID_ATTR}]`
|
`[${BLOCK_ID_ATTR}]`
|
||||||
);
|
);
|
||||||
return block;
|
return block;
|
||||||
|
|||||||
@@ -148,7 +148,8 @@ export class AffineReference extends WithDisposable(ShadowlessElement) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get block() {
|
get block() {
|
||||||
const block = this.inlineEditor?.rootElement.closest<BlockComponent>(
|
if (!this.inlineEditor?.rootElement) return null;
|
||||||
|
const block = this.inlineEditor.rootElement.closest<BlockComponent>(
|
||||||
`[${BLOCK_ID_ATTR}]`
|
`[${BLOCK_ID_ATTR}]`
|
||||||
);
|
);
|
||||||
return block;
|
return block;
|
||||||
|
|||||||
@@ -138,7 +138,7 @@ export class ReferencePopup extends WithDisposable(LitElement) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get block() {
|
get block() {
|
||||||
const block = this.inlineEditor.rootElement.closest<BlockComponent>(
|
const block = this.inlineEditor.rootElement?.closest<BlockComponent>(
|
||||||
`[${BLOCK_ID_ATTR}]`
|
`[${BLOCK_ID_ATTR}]`
|
||||||
);
|
);
|
||||||
assertExists(block);
|
assertExists(block);
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
|
||||||
import { TextUtils } from '@blocksuite/affine-block-surface';
|
import { TextUtils } from '@blocksuite/affine-block-surface';
|
||||||
import type { RichText } from '@blocksuite/affine-components/rich-text';
|
import type { RichText } from '@blocksuite/affine-components/rich-text';
|
||||||
import type { ConnectorElementModel } from '@blocksuite/affine-model';
|
import type { ConnectorElementModel } from '@blocksuite/affine-model';
|
||||||
@@ -71,6 +70,8 @@ export class EdgelessConnectorLabelEditor extends WithDisposable(
|
|||||||
const { connector, edgeless } = this;
|
const { connector, edgeless } = this;
|
||||||
if (!connector || !edgeless) return;
|
if (!connector || !edgeless) return;
|
||||||
|
|
||||||
|
if (!this.inlineEditorContainer) return;
|
||||||
|
|
||||||
const newWidth = this.inlineEditorContainer.scrollWidth;
|
const newWidth = this.inlineEditorContainer.scrollWidth;
|
||||||
const newHeight = this.inlineEditorContainer.scrollHeight;
|
const newHeight = this.inlineEditorContainer.scrollHeight;
|
||||||
const center = connector.getPointByOffsetDistance(
|
const center = connector.getPointByOffsetDistance(
|
||||||
@@ -137,7 +138,7 @@ export class EdgelessConnectorLabelEditor extends WithDisposable(
|
|||||||
const isModEnter = onlyCmd && key === 'Enter';
|
const isModEnter = onlyCmd && key === 'Enter';
|
||||||
const isEscape = key === 'Escape';
|
const isEscape = key === 'Escape';
|
||||||
if (!isComposing && (isModEnter || isEscape)) {
|
if (!isComposing && (isModEnter || isEscape)) {
|
||||||
this.inlineEditorContainer.blur();
|
this.inlineEditorContainer?.blur();
|
||||||
|
|
||||||
edgeless.service.selection.set({
|
edgeless.service.selection.set({
|
||||||
elements: [connector.id],
|
elements: [connector.id],
|
||||||
@@ -192,6 +193,8 @@ export class EdgelessConnectorLabelEditor extends WithDisposable(
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (!this.inlineEditorContainer) return;
|
||||||
|
|
||||||
this.disposables.addFromEvent(
|
this.disposables.addFromEvent(
|
||||||
this.inlineEditorContainer,
|
this.inlineEditorContainer,
|
||||||
'blur',
|
'blur',
|
||||||
|
|||||||
@@ -94,6 +94,8 @@ export class EdgelessFrameTitleEditor extends WithDisposable(
|
|||||||
|
|
||||||
this.disposables.add(dispatcher.add('click', () => true));
|
this.disposables.add(dispatcher.add('click', () => true));
|
||||||
this.disposables.add(dispatcher.add('doubleClick', () => true));
|
this.disposables.add(dispatcher.add('doubleClick', () => true));
|
||||||
|
|
||||||
|
if (!this.inlineEditor.rootElement) return;
|
||||||
this.disposables.addFromEvent(
|
this.disposables.addFromEvent(
|
||||||
this.inlineEditor.rootElement,
|
this.inlineEditor.rootElement,
|
||||||
'blur',
|
'blur',
|
||||||
|
|||||||
@@ -79,6 +79,8 @@ export class EdgelessGroupTitleEditor extends WithDisposable(
|
|||||||
|
|
||||||
this.disposables.add(dispatcher.add('click', () => true));
|
this.disposables.add(dispatcher.add('click', () => true));
|
||||||
this.disposables.add(dispatcher.add('doubleClick', () => true));
|
this.disposables.add(dispatcher.add('doubleClick', () => true));
|
||||||
|
|
||||||
|
if (!this.inlineEditorContainer) return;
|
||||||
this.disposables.addFromEvent(
|
this.disposables.addFromEvent(
|
||||||
this.inlineEditorContainer,
|
this.inlineEditorContainer,
|
||||||
'blur',
|
'blur',
|
||||||
|
|||||||
@@ -212,6 +212,8 @@ export class EdgelessShapeTextEditor extends WithDisposable(ShadowlessElement) {
|
|||||||
this._updateElementWH();
|
this._updateElementWH();
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (!this.inlineEditorContainer) return;
|
||||||
this.disposables.addFromEvent(
|
this.disposables.addFromEvent(
|
||||||
this.inlineEditorContainer,
|
this.inlineEditorContainer,
|
||||||
'blur',
|
'blur',
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ export class EdgelessTextEditor extends WithDisposable(ShadowlessElement) {
|
|||||||
const edgeless = this.edgeless;
|
const edgeless = this.edgeless;
|
||||||
const element = this.element;
|
const element = this.element;
|
||||||
|
|
||||||
if (!edgeless || !element) return;
|
if (!edgeless || !element || !this.inlineEditorContainer) return;
|
||||||
|
|
||||||
const newWidth = this.inlineEditorContainer.scrollWidth;
|
const newWidth = this.inlineEditorContainer.scrollWidth;
|
||||||
const newHeight = this.inlineEditorContainer.scrollHeight;
|
const newHeight = this.inlineEditorContainer.scrollHeight;
|
||||||
@@ -206,6 +206,7 @@ export class EdgelessTextEditor extends WithDisposable(ShadowlessElement) {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (!this.inlineEditorContainer) return;
|
||||||
this.disposables.addFromEvent(
|
this.disposables.addFromEvent(
|
||||||
this.inlineEditorContainer,
|
this.inlineEditorContainer,
|
||||||
'blur',
|
'blur',
|
||||||
|
|||||||
@@ -117,7 +117,8 @@ export class AffineMobileLinkedDocMenu extends SignalWatcher(
|
|||||||
offset = scrollTopOffset ?? 0;
|
offset = scrollTopOffset ?? 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
container?.scrollTo({
|
if (!inlineEditor.rootElement || !container) return;
|
||||||
|
container.scrollTo({
|
||||||
top:
|
top:
|
||||||
inlineEditor.rootElement.getBoundingClientRect().top +
|
inlineEditor.rootElement.getBoundingClientRect().top +
|
||||||
containerScrollTop -
|
containerScrollTop -
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { BlockSuiteError, ErrorCode } from '@blocksuite/global/exceptions';
|
import { BlockSuiteError, ErrorCode } from '@blocksuite/global/exceptions';
|
||||||
import { assertExists, DisposableGroup, Slot } from '@blocksuite/global/utils';
|
import { DisposableGroup, Slot } from '@blocksuite/global/utils';
|
||||||
import { type Signal, signal } from '@preact/signals-core';
|
import { type Signal, signal } from '@preact/signals-core';
|
||||||
import { nothing, render, type TemplateResult } from 'lit';
|
import { nothing, render, type TemplateResult } from 'lit';
|
||||||
import type * as Y from 'yjs';
|
import type * as Y from 'yjs';
|
||||||
@@ -136,7 +136,6 @@ export class InlineEditor<
|
|||||||
|
|
||||||
private _rootElement: InlineRootElement<TextAttributes> | null = null;
|
private _rootElement: InlineRootElement<TextAttributes> | null = null;
|
||||||
get rootElement() {
|
get rootElement() {
|
||||||
assertExists(this._rootElement);
|
|
||||||
return this._rootElement;
|
return this._rootElement;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -244,7 +243,7 @@ export class InlineEditor<
|
|||||||
this._rootElement.dataset.vRoot = 'true';
|
this._rootElement.dataset.vRoot = 'true';
|
||||||
this.setReadonly(isReadonly);
|
this.setReadonly(isReadonly);
|
||||||
|
|
||||||
this.rootElement.replaceChildren();
|
this._rootElement.replaceChildren();
|
||||||
|
|
||||||
delete (this.rootElement as any)['_$litPart$'];
|
delete (this.rootElement as any)['_$litPart$'];
|
||||||
|
|
||||||
@@ -259,10 +258,12 @@ export class InlineEditor<
|
|||||||
}
|
}
|
||||||
|
|
||||||
unmount() {
|
unmount() {
|
||||||
if (this.rootElement.isConnected) {
|
if (this.rootElement) {
|
||||||
render(nothing, this.rootElement);
|
if (this.rootElement.isConnected) {
|
||||||
|
render(nothing, this.rootElement);
|
||||||
|
}
|
||||||
|
this.rootElement.removeAttribute(INLINE_ROOT_ATTR);
|
||||||
}
|
}
|
||||||
this.rootElement.removeAttribute(INLINE_ROOT_ATTR);
|
|
||||||
this._rootElement = null;
|
this._rootElement = null;
|
||||||
this._mounted = false;
|
this._mounted = false;
|
||||||
this.disposables.dispose();
|
this.disposables.dispose();
|
||||||
@@ -272,7 +273,7 @@ export class InlineEditor<
|
|||||||
setReadonly(isReadonly: boolean): void {
|
setReadonly(isReadonly: boolean): void {
|
||||||
const value = isReadonly ? 'false' : 'true';
|
const value = isReadonly ? 'false' : 'true';
|
||||||
|
|
||||||
if (this.rootElement.contentEditable !== value) {
|
if (this.rootElement && this.rootElement.contentEditable !== value) {
|
||||||
this.rootElement.contentEditable = value;
|
this.rootElement.contentEditable = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,6 +19,8 @@ export class EventService<TextAttributes extends BaseTextAttributes> {
|
|||||||
if (range.commonAncestorContainer.ownerDocument !== document) return false;
|
if (range.commonAncestorContainer.ownerDocument !== document) return false;
|
||||||
|
|
||||||
const rootElement = this.editor.rootElement;
|
const rootElement = this.editor.rootElement;
|
||||||
|
if (!rootElement) return false;
|
||||||
|
|
||||||
const rootRange = document.createRange();
|
const rootRange = document.createRange();
|
||||||
rootRange.selectNode(rootElement);
|
rootRange.selectNode(rootElement);
|
||||||
|
|
||||||
@@ -140,7 +142,9 @@ export class EventService<TextAttributes extends BaseTextAttributes> {
|
|||||||
|
|
||||||
private readonly _onCompositionEnd = async (event: CompositionEvent) => {
|
private readonly _onCompositionEnd = async (event: CompositionEvent) => {
|
||||||
this._isComposing = false;
|
this._isComposing = false;
|
||||||
if (!this.editor.rootElement.isConnected) return;
|
if (!this.editor.rootElement || !this.editor.rootElement.isConnected) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const range = this.editor.rangeService.getNativeRange();
|
const range = this.editor.rangeService.getNativeRange();
|
||||||
if (
|
if (
|
||||||
@@ -181,6 +185,7 @@ export class EventService<TextAttributes extends BaseTextAttributes> {
|
|||||||
|
|
||||||
private readonly _onCompositionStart = () => {
|
private readonly _onCompositionStart = () => {
|
||||||
this._isComposing = true;
|
this._isComposing = true;
|
||||||
|
if (!this.editor.rootElement) return;
|
||||||
// embeds is not editable and it will break IME
|
// embeds is not editable and it will break IME
|
||||||
const embeds = this.editor.rootElement.querySelectorAll(
|
const embeds = this.editor.rootElement.querySelectorAll(
|
||||||
'[data-v-embed="true"]'
|
'[data-v-embed="true"]'
|
||||||
@@ -198,7 +203,9 @@ export class EventService<TextAttributes extends BaseTextAttributes> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
private readonly _onCompositionUpdate = () => {
|
private readonly _onCompositionUpdate = () => {
|
||||||
if (!this.editor.rootElement.isConnected) return;
|
if (!this.editor.rootElement || !this.editor.rootElement.isConnected) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const range = this.editor.rangeService.getNativeRange();
|
const range = this.editor.rangeService.getNativeRange();
|
||||||
if (
|
if (
|
||||||
@@ -272,6 +279,8 @@ export class EventService<TextAttributes extends BaseTextAttributes> {
|
|||||||
|
|
||||||
private readonly _onSelectionChange = () => {
|
private readonly _onSelectionChange = () => {
|
||||||
const rootElement = this.editor.rootElement;
|
const rootElement = this.editor.rootElement;
|
||||||
|
if (!rootElement) return;
|
||||||
|
|
||||||
const previousInlineRange = this.editor.getInlineRange();
|
const previousInlineRange = this.editor.getInlineRange();
|
||||||
if (this._isComposing) {
|
if (this._isComposing) {
|
||||||
return;
|
return;
|
||||||
@@ -361,7 +370,9 @@ export class EventService<TextAttributes extends BaseTextAttributes> {
|
|||||||
'keydown',
|
'keydown',
|
||||||
this._onKeyDown
|
this._onKeyDown
|
||||||
);
|
);
|
||||||
this.editor.disposables.addFromEvent(rootElement, 'click', this._onClick);
|
if (rootElement) {
|
||||||
|
this.editor.disposables.addFromEvent(rootElement, 'click', this._onClick);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
get isComposing() {
|
get isComposing() {
|
||||||
|
|||||||
@@ -63,6 +63,8 @@ export class RangeService<TextAttributes extends BaseTextAttributes> {
|
|||||||
rangeIndexRelatedToLine: number;
|
rangeIndexRelatedToLine: number;
|
||||||
} | null => {
|
} | null => {
|
||||||
const rootElement = this.editor.rootElement;
|
const rootElement = this.editor.rootElement;
|
||||||
|
if (!rootElement) return null;
|
||||||
|
|
||||||
const lineElements = Array.from(rootElement.querySelectorAll('v-line'));
|
const lineElements = Array.from(rootElement.querySelectorAll('v-line'));
|
||||||
|
|
||||||
let beforeIndex = 0;
|
let beforeIndex = 0;
|
||||||
@@ -100,6 +102,8 @@ export class RangeService<TextAttributes extends BaseTextAttributes> {
|
|||||||
|
|
||||||
getTextPoint = (rangeIndex: InlineRange['index']): TextPoint | null => {
|
getTextPoint = (rangeIndex: InlineRange['index']): TextPoint | null => {
|
||||||
const rootElement = this.editor.rootElement;
|
const rootElement = this.editor.rootElement;
|
||||||
|
if (!rootElement) return null;
|
||||||
|
|
||||||
const vLines = Array.from(rootElement.querySelectorAll('v-line'));
|
const vLines = Array.from(rootElement.querySelectorAll('v-line'));
|
||||||
|
|
||||||
let index = 0;
|
let index = 0;
|
||||||
@@ -280,6 +284,7 @@ export class RangeService<TextAttributes extends BaseTextAttributes> {
|
|||||||
const handler = () => {
|
const handler = () => {
|
||||||
const selection = document.getSelection();
|
const selection = document.getSelection();
|
||||||
if (!selection) return;
|
if (!selection) return;
|
||||||
|
if (!this.editor.rootElement) return;
|
||||||
|
|
||||||
if (inlineRange === null) {
|
if (inlineRange === null) {
|
||||||
if (selection.rangeCount > 0) {
|
if (selection.rangeCount > 0) {
|
||||||
@@ -321,6 +326,7 @@ export class RangeService<TextAttributes extends BaseTextAttributes> {
|
|||||||
*/
|
*/
|
||||||
toDomRange = (inlineRange: InlineRange): Range | null => {
|
toDomRange = (inlineRange: InlineRange): Range | null => {
|
||||||
const rootElement = this.editor.rootElement;
|
const rootElement = this.editor.rootElement;
|
||||||
|
if (!rootElement) return null;
|
||||||
return inlineRangeToDomRange(rootElement, inlineRange);
|
return inlineRangeToDomRange(rootElement, inlineRange);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -358,7 +364,7 @@ export class RangeService<TextAttributes extends BaseTextAttributes> {
|
|||||||
*/
|
*/
|
||||||
toInlineRange = (range: Range): InlineRange | null => {
|
toInlineRange = (range: Range): InlineRange | null => {
|
||||||
const { rootElement, yText } = this.editor;
|
const { rootElement, yText } = this.editor;
|
||||||
|
if (!rootElement || !yText) return null;
|
||||||
return domRangeToInlineRange(range, rootElement, yText);
|
return domRangeToInlineRange(range, rootElement, yText);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ export class RenderService<TextAttributes extends BaseTextAttributes> {
|
|||||||
}
|
}
|
||||||
// render current deltas to VLines
|
// render current deltas to VLines
|
||||||
render = () => {
|
render = () => {
|
||||||
if (!this.editor.mounted) return;
|
if (!this.editor.rootElement) return;
|
||||||
|
|
||||||
this._rendering = true;
|
this._rendering = true;
|
||||||
|
|
||||||
@@ -160,7 +160,7 @@ export class RenderService<TextAttributes extends BaseTextAttributes> {
|
|||||||
rerenderWholeEditor = () => {
|
rerenderWholeEditor = () => {
|
||||||
const rootElement = this.editor.rootElement;
|
const rootElement = this.editor.rootElement;
|
||||||
|
|
||||||
if (!rootElement.isConnected) return;
|
if (!rootElement || !rootElement.isConnected) return;
|
||||||
|
|
||||||
rootElement.replaceChildren();
|
rootElement.replaceChildren();
|
||||||
// Because we bypassed Lit and disrupted the DOM structure, this will cause an inconsistency in the original state of `ChildPart`.
|
// Because we bypassed Lit and disrupted the DOM structure, this will cause an inconsistency in the original state of `ChildPart`.
|
||||||
@@ -172,6 +172,7 @@ export class RenderService<TextAttributes extends BaseTextAttributes> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
waitForUpdate = async () => {
|
waitForUpdate = async () => {
|
||||||
|
if (!this.editor.rootElement) return;
|
||||||
const vLines = Array.from(
|
const vLines = Array.from(
|
||||||
this.editor.rootElement.querySelectorAll('v-line')
|
this.editor.rootElement.querySelectorAll('v-line')
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ export class FrameCardTitleEditor extends WithDisposable(ShadowlessElement) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const inlineEditorContainer = this.inlineEditor.rootElement;
|
const inlineEditorContainer = this.inlineEditor.rootElement;
|
||||||
|
if (!inlineEditorContainer) return;
|
||||||
|
|
||||||
this.disposables.addFromEvent(inlineEditorContainer, 'blur', () => {
|
this.disposables.addFromEvent(inlineEditorContainer, 'blur', () => {
|
||||||
this._unmount();
|
this._unmount();
|
||||||
|
|||||||
@@ -321,6 +321,9 @@ export class AtMenuConfigService extends Service {
|
|||||||
close();
|
close();
|
||||||
|
|
||||||
const getRect = () => {
|
const getRect = () => {
|
||||||
|
if (!inlineEditor.rootElement) {
|
||||||
|
return { x: 0, y: 0, width: 0, height: 0 };
|
||||||
|
}
|
||||||
let rect = inlineEditor.getNativeRange()?.getBoundingClientRect();
|
let rect = inlineEditor.getNativeRange()?.getBoundingClientRect();
|
||||||
|
|
||||||
if (!rect || rect.width === 0 || rect.height === 0) {
|
if (!rect || rect.width === 0 || rect.height === 0) {
|
||||||
|
|||||||
Reference in New Issue
Block a user