fix(editor): block can be null in widget (#10959)

Closes: [BS-2826](https://linear.app/affine-design/issue/BS-2826/typeerror-thisblock-is-null)
This commit is contained in:
Saul-Mirone
2025-03-18 10:58:19 +00:00
parent b7ab49a263
commit 321e3449ec
17 changed files with 118 additions and 55 deletions

View File

@@ -35,6 +35,10 @@ export class AffineCodeToolbarWidget extends WidgetComponent<
this, this,
({ abortController }) => { ({ abortController }) => {
const codeBlock = this.block; const codeBlock = this.block;
if (!codeBlock) {
return null;
}
const selection = this.host.selection; const selection = this.host.selection;
const textSelection = selection.find(TextSelection); const textSelection = selection.find(TextSelection);
@@ -105,6 +109,9 @@ export class AffineCodeToolbarWidget extends WidgetComponent<
); );
const codeBlock = this.block; const codeBlock = this.block;
if (!codeBlock) {
return;
}
this._hoverController.setReference(codeBlock); this._hoverController.setReference(codeBlock);
this._hoverController.onAbort = () => { this._hoverController.onAbort = () => {
// If the more menu is opened, don't close it. // If the more menu is opened, don't close it.

View File

@@ -143,7 +143,7 @@ export class NoteSlicer extends WidgetComponent<
} }
get selectedRectEle() { get selectedRectEle() {
return this.block.selectedRectWidget; return this.block?.selectedRectWidget;
} }
private _sliceNote() { private _sliceNote() {
@@ -266,6 +266,10 @@ export class NoteSlicer extends WidgetComponent<
this._updateDivingLineAndBlockIds(); this._updateDivingLineAndBlockIds();
if (!block) {
return;
}
disposables.add( disposables.add(
block.slots.elementResizeStart.subscribe(() => { block.slots.elementResizeStart.subscribe(() => {
this._isResizing = true; this._isResizing = true;
@@ -343,7 +347,7 @@ export class NoteSlicer extends WidgetComponent<
} }
override firstUpdated() { override firstUpdated() {
if (!this.block.service) return; if (!this.block?.service) return;
this.disposables.add( this.disposables.add(
this.block.service.uiEventDispatcher.add('wheel', () => { this.block.service.uiEventDispatcher.add('wheel', () => {
this._hidden = true; this._hidden = true;

View File

@@ -33,6 +33,9 @@ export class EdgelessDraggingAreaRectWidget extends WidgetComponent<
`; `;
override render() { override render() {
if (!this.block) {
return nothing;
}
const rect = this.block.gfx.tool.draggingViewArea$.value; const rect = this.block.gfx.tool.draggingViewArea$.value;
const tool = this.block.gfx.tool.currentTool$.value; const tool = this.block.gfx.tool.currentTool$.value;

View File

@@ -477,7 +477,7 @@ export class EdgelessSelectedRectWidget extends WidgetComponent<
this._scaleDirection = undefined; this._scaleDirection = undefined;
this._updateMode(); this._updateMode();
this.block.slots.elementResizeEnd.next(); this.block?.slots.elementResizeEnd.next();
this.frameOverlay.clear(); this.frameOverlay.clear();
}; };
@@ -606,7 +606,7 @@ export class EdgelessSelectedRectWidget extends WidgetComponent<
const rotation = this._resizeManager.rotation; const rotation = this._resizeManager.rotation;
this._dragEndCallback = []; this._dragEndCallback = [];
this.block.slots.elementResizeStart.next(); this.block?.slots.elementResizeStart.next();
this.selection.selectedElements.forEach(el => { this.selection.selectedElements.forEach(el => {
el.stash('xywh'); el.stash('xywh');
@@ -846,7 +846,7 @@ export class EdgelessSelectedRectWidget extends WidgetComponent<
} }
get edgelessSlots() { get edgelessSlots() {
return this.block.slots; return this.block?.slots;
} }
get frameOverlay() { get frameOverlay() {
@@ -1327,41 +1327,46 @@ export class EdgelessSelectedRectWidget extends WidgetComponent<
selection.slots.updated.subscribe(this._updateOnSelectionChange) selection.slots.updated.subscribe(this._updateOnSelectionChange)
); );
_disposables.add( if (block) {
block.slots.readonlyUpdated.subscribe(() => this.requestUpdate()) _disposables.add(
); block.slots.readonlyUpdated.subscribe(() => this.requestUpdate())
);
_disposables.add(
block.slots.elementResizeStart.subscribe(
() => (this._isResizing = true)
)
);
_disposables.add(
block.slots.elementResizeEnd.subscribe(() => (this._isResizing = false))
);
block.handleEvent(
'keyDown',
ctx => {
const event = ctx.get('defaultState').event;
if (event instanceof KeyboardEvent) {
this._shift(event);
}
},
{ global: true }
);
block.handleEvent(
'keyUp',
ctx => {
const event = ctx.get('defaultState').event;
if (event instanceof KeyboardEvent) {
this._shift(event);
}
},
{ global: true }
);
}
_disposables.add(
block.slots.elementResizeStart.subscribe(() => (this._isResizing = true))
);
_disposables.add(
block.slots.elementResizeEnd.subscribe(() => (this._isResizing = false))
);
_disposables.add(() => { _disposables.add(() => {
this._propDisposables.forEach(disposable => disposable.unsubscribe()); this._propDisposables.forEach(disposable => disposable.unsubscribe());
}); });
this.block.handleEvent(
'keyDown',
ctx => {
const event = ctx.get('defaultState').event;
if (event instanceof KeyboardEvent) {
this._shift(event);
}
},
{ global: true }
);
this.block.handleEvent(
'keyUp',
ctx => {
const event = ctx.get('defaultState').event;
if (event instanceof KeyboardEvent) {
this._shift(event);
}
},
{ global: true }
);
} }
private _shift(event: KeyboardEvent) { private _shift(event: KeyboardEvent) {

View File

@@ -335,6 +335,9 @@ export class EdgelessToolbarWidget extends WidgetComponent<
} }
private get _quickTools() { private get _quickTools() {
if (!this.block) {
return [];
}
return getQuickTools({ edgeless: this.block }); return getQuickTools({ edgeless: this.block });
} }
@@ -376,6 +379,9 @@ export class EdgelessToolbarWidget extends WidgetComponent<
} }
private get _seniorTools() { private get _seniorTools() {
if (!this.block) {
return [];
}
return getSeniorTools({ return getSeniorTools({
edgeless: this.block, edgeless: this.block,
toolbarContainer: this.toolbarContainer, toolbarContainer: this.toolbarContainer,
@@ -573,6 +579,9 @@ export class EdgelessToolbarWidget extends WidgetComponent<
.get(ThemeProvider) .get(ThemeProvider)
.theme$.subscribe(mode => this._themeProvider.setValue(mode)) .theme$.subscribe(mode => this._themeProvider.setValue(mode))
); );
if (!this.block) {
return;
}
this._disposables.add( this._disposables.add(
this.block.bindHotKey( this.block.bindHotKey(
{ {
@@ -603,6 +612,9 @@ export class EdgelessToolbarWidget extends WidgetComponent<
override firstUpdated() { override firstUpdated() {
const { _disposables, block, gfx } = this; const { _disposables, block, gfx } = this;
if (!block) {
return;
}
_disposables.add( _disposables.add(
gfx.viewport.viewportUpdated.subscribe(() => this.requestUpdate()) gfx.viewport.viewportUpdated.subscribe(() => this.requestUpdate())

View File

@@ -48,7 +48,7 @@ export class AffineEdgelessZoomToolbarWidget extends WidgetComponent<
this.disposables.add( this.disposables.add(
effect(() => { effect(() => {
const currentTool = this.edgeless.gfx.tool.currentToolName$.value; const currentTool = this.edgeless?.gfx.tool.currentToolName$.value;
if (currentTool !== 'frameNavigator') { if (currentTool !== 'frameNavigator') {
this._hide = false; this._hide = false;

View File

@@ -37,6 +37,9 @@ export class AffineImageToolbarWidget extends WidgetComponent<
this, this,
({ abortController }) => { ({ abortController }) => {
const imageBlock = this.block; const imageBlock = this.block;
if (!imageBlock) {
return null;
}
const selection = this.host.selection; const selection = this.host.selection;
const textSelection = selection.find(TextSelection); const textSelection = selection.find(TextSelection);
@@ -103,6 +106,9 @@ export class AffineImageToolbarWidget extends WidgetComponent<
); );
const imageBlock = this.block; const imageBlock = this.block;
if (!imageBlock) {
return;
}
this._hoverController.setReference(imageBlock); this._hoverController.setReference(imageBlock);
this._hoverController.onAbort = () => { this._hoverController.onAbort = () => {
// If the more menu is opened, don't close it. // If the more menu is opened, don't close it.

View File

@@ -22,7 +22,7 @@ export class AffineKeyboardToolbarWidget extends WidgetComponent<
if (blur) { if (blur) {
if (document.activeElement === this._docTitle?.inlineEditorContainer) { if (document.activeElement === this._docTitle?.inlineEditorContainer) {
this._docTitle?.inlineEditor?.setInlineRange(null); this._docTitle?.inlineEditor?.setInlineRange(null);
} else if (document.activeElement === this.block.rootComponent) { } else if (document.activeElement === this.block?.rootComponent) {
this.std.selection.clear(); this.std.selection.clear();
} }
} }
@@ -46,7 +46,7 @@ export class AffineKeyboardToolbarWidget extends WidgetComponent<
override connectedCallback(): void { override connectedCallback(): void {
super.connectedCallback(); super.connectedCallback();
const { rootComponent } = this.block; const rootComponent = this.block?.rootComponent;
if (rootComponent) { if (rootComponent) {
this.disposables.addFromEvent(rootComponent, 'focus', () => { this.disposables.addFromEvent(rootComponent, 'focus', () => {
this._show$.value = true; this._show$.value = true;
@@ -79,7 +79,7 @@ export class AffineKeyboardToolbarWidget extends WidgetComponent<
if (!this._show$.value) return nothing; if (!this._show$.value) return nothing;
if (!this.block.rootComponent) return nothing; if (!this.block?.rootComponent) return nothing;
return html`<blocksuite-portal return html`<blocksuite-portal
.shadowDom=${false} .shadowDom=${false}

View File

@@ -72,7 +72,7 @@ export class AffineLinkedDocWidget extends WidgetComponent<
} }
private readonly _renderLinkedDocMenu = () => { private readonly _renderLinkedDocMenu = () => {
if (!this.block.rootComponent) return nothing; if (!this.block?.rootComponent) return nothing;
return html`<affine-mobile-linked-doc-menu return html`<affine-mobile-linked-doc-menu
.context=${this._context} .context=${this._context}

View File

@@ -114,7 +114,7 @@ export class AffinePageDraggingAreaWidget extends WidgetComponent<
}); });
this._lastPointerState = state; this._lastPointerState = state;
if (shouldAutoScroll) { if (shouldAutoScroll && this.scrollContainer) {
const rect = this.scrollContainer.getBoundingClientRect(); const rect = this.scrollContainer.getBoundingClientRect();
const result = autoScroll(this.scrollContainer, state.raw.y - rect.top); const result = autoScroll(this.scrollContainer, state.raw.y - rect.top);
if (!result) { if (!result) {
@@ -173,6 +173,9 @@ export class AffinePageDraggingAreaWidget extends WidgetComponent<
} }
private get scrollContainer() { private get scrollContainer() {
if (!this.block) {
return null;
}
return getScrollContainer(this.block); return getScrollContainer(this.block);
} }

View File

@@ -49,6 +49,9 @@ export class AffineSurfaceRefToolbar extends WidgetComponent<
this, this,
({ abortController }) => { ({ abortController }) => {
const surfaceRefBlock = this.block; const surfaceRefBlock = this.block;
if (!surfaceRefBlock) {
return null;
}
const selection = this.host.selection; const selection = this.host.selection;
const textSelection = selection.find(TextSelection); const textSelection = selection.find(TextSelection);
@@ -100,6 +103,9 @@ export class AffineSurfaceRefToolbar extends WidgetComponent<
super.connectedCallback(); super.connectedCallback();
this.moreGroups = getMoreMenuConfig(this.std).configure(this.moreGroups); this.moreGroups = getMoreMenuConfig(this.std).configure(this.moreGroups);
if (!this.block) {
return;
}
this._hoverController.setReference(this.block); this._hoverController.setReference(this.block);
} }
} }

View File

@@ -161,6 +161,8 @@ export class PointerEventWatcher {
if (this.widget.isGfxDragHandleVisible) return; if (this.widget.isGfxDragHandleVisible) return;
const point = new Point(state.raw.x, state.raw.y); const point = new Point(state.raw.x, state.raw.y);
if (!this.widget.rootComponent) return;
const closestBlock = getClosestBlockByPoint( const closestBlock = getClosestBlockByPoint(
this.widget.host, this.widget.host,
this.widget.rootComponent, this.widget.rootComponent,
@@ -246,6 +248,8 @@ export class PointerEventWatcher {
// When pointer on drag handle, should do nothing // When pointer on drag handle, should do nothing
if (element.closest('.affine-drag-handle-container')) return; if (element.closest('.affine-drag-handle-container')) return;
if (!this.widget.rootComponent) return;
// When pointer out of note block hover area or inside database, should hide drag handle // When pointer out of note block hover area or inside database, should hide drag handle
const point = new Point(state.raw.x, state.raw.y); const point = new Point(state.raw.x, state.raw.y);

View File

@@ -284,6 +284,9 @@ export class EdgelessAutoConnectWidget extends WidgetComponent<RootBlockModel> {
} }
private _initLabels() { private _initLabels() {
if (!this.block) {
return;
}
const { service } = this.block; const { service } = this.block;
const surfaceRefs = service.doc const surfaceRefs = service.doc
.getBlocksByFlavour('affine:surface-ref') .getBlocksByFlavour('affine:surface-ref')

View File

@@ -144,6 +144,10 @@ export class AffineDocRemoteSelectionWidget extends WidgetComponent {
} }
private _getCursorRect(selections: BaseSelection[]): SelectionRect | null { private _getCursorRect(selections: BaseSelection[]): SelectionRect | null {
if (!this.block) {
return null;
}
if (this.block.model.flavour !== 'affine:page') { if (this.block.model.flavour !== 'affine:page') {
console.error('remote selection widget must be used in page component'); console.error('remote selection widget must be used in page component');
return null; return null;
@@ -208,6 +212,10 @@ export class AffineDocRemoteSelectionWidget extends WidgetComponent {
private readonly _getSelectionRect = ( private readonly _getSelectionRect = (
selections: BaseSelection[] selections: BaseSelection[]
): SelectionRect[] => { ): SelectionRect[] => {
if (!this.block) {
return [];
}
if (this.block.model.flavour !== 'affine:page') { if (this.block.model.flavour !== 'affine:page') {
console.error('remote selection widget must be used in page component'); console.error('remote selection widget must be used in page component');
return []; return [];

View File

@@ -105,7 +105,7 @@ export class AffineSlashMenuWidget extends WidgetComponent {
} }
}; };
if (this.block.model.flavour !== 'affine:page') { if (this.block?.model.flavour !== 'affine:page') {
console.error('SlashMenuWidget should be used in RootBlock'); console.error('SlashMenuWidget should be used in RootBlock');
return; return;
} }

View File

@@ -28,7 +28,7 @@ export class WidgetComponent<
}; };
get block() { get block() {
return this.std.view.getBlock(this.model.id) as B; return this.std.view.getBlock(this.model.id) as B | null;
} }
get doc() { get doc() {

View File

@@ -424,18 +424,20 @@ export class AffineAIPanelWidget extends WidgetComponent {
'pointerdown', 'pointerdown',
this._onDocumentClick this._onDocumentClick
); );
this.disposables.add( if (this.block) {
this.block.host.event.add('pointerDown', evtState => this.disposables.add(
this._onDocumentClick( this.block.host.event.add('pointerDown', evtState =>
evtState.get('pointerState').event as PointerEvent this._onDocumentClick(
evtState.get('pointerState').event as PointerEvent
)
) )
) );
); this.disposables.add(
this.disposables.add( this.block.host.event.add('click', () => {
this.block.host.event.add('click', () => { return this.state !== 'hidden' ? true : false;
return this.state !== 'hidden' ? true : false; })
}) );
); }
this.disposables.addFromEvent(this, 'wheel', stopPropagation); this.disposables.addFromEvent(this, 'wheel', stopPropagation);
this.disposables.addFromEvent(this, 'pointerdown', stopPropagation); this.disposables.addFromEvent(this, 'pointerdown', stopPropagation);
this.disposables.addFromEvent(this, 'pointerup', stopPropagation); this.disposables.addFromEvent(this, 'pointerup', stopPropagation);