Skip to content

Commit

Permalink
refactor(blocks): using portal render linked doc widget
Browse files Browse the repository at this point in the history
  • Loading branch information
L-Sun committed Nov 4, 2024
1 parent c52d2b5 commit dbe4d96
Show file tree
Hide file tree
Showing 8 changed files with 184 additions and 130 deletions.
4 changes: 2 additions & 2 deletions packages/affine/components/src/portal/portal.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { html, LitElement } from 'lit';
import { html, LitElement, type TemplateResult } from 'lit';
import { property } from 'lit/decorators.js';

/**
Expand Down Expand Up @@ -50,7 +50,7 @@ export class Portal extends LitElement {
accessor shadowDom: boolean | ShadowRootInit = true;

@property({ attribute: false })
accessor template = html``;
accessor template: TemplateResult | undefined = html``;
}

declare global {
Expand Down
1 change: 0 additions & 1 deletion packages/blocks/src/effects.ts
Original file line number Diff line number Diff line change
Expand Up @@ -613,7 +613,6 @@ export function effects() {
'edgeless-change-attachment-button',
EdgelessChangeAttachmentButton
);
customElements.define('import-doc', ImportDoc);
customElements.define('edgeless-more-button', EdgelessMoreButton);
customElements.define('edgeless-shape-style-panel', EdgelessShapeStylePanel);
customElements.define(
Expand Down
19 changes: 9 additions & 10 deletions packages/blocks/src/root-block/widgets/keyboard-toolbar/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -315,8 +315,14 @@ const pageToolGroup: KeyboardToolPanelGroup = {
action: ({ rootComponent, closeToolbar }) => {
const { std } = rootComponent;

const triggerKey =
std.getConfig('affine:page')?.linkedWidget?.triggerKeys?.[0] ?? '@';
const linkedDocWidget = std.view.getWidget(
'affine-linked-doc-widget',
rootComponent.model.id
);
if (!linkedDocWidget) return;
assertType<AffineLinkedDocWidget>(linkedDocWidget);

const triggerKey = linkedDocWidget.config.triggerKeys[0];

std.command
.chain()
Expand All @@ -328,17 +334,10 @@ const pageToolGroup: KeyboardToolPanelGroup = {
const currentModel = selectedModels[0];
insertContent(std.host, currentModel, triggerKey);

const linkedDocWidget = std.view.getWidget(
'affine-linked-doc-widget',
rootComponent.model.id
);
if (!linkedDocWidget) return;
assertType<AffineLinkedDocWidget>(linkedDocWidget);

const inlineEditor = getInlineEditorByModel(std.host, currentModel);
// Wait for range to be updated
inlineEditor?.slots.inlineRangeSync.once(() => {
linkedDocWidget.showLinkedDocPopover(inlineEditor, triggerKey);
linkedDocWidget.showLinkedDocPopover();
closeToolbar();
});
})
Expand Down
10 changes: 9 additions & 1 deletion packages/blocks/src/root-block/widgets/linked-doc/config.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { EditorHost } from '@blocksuite/block-std';
import type { BlockStdScope, EditorHost } from '@blocksuite/block-std';
import type { TemplateResult } from 'lit';

import {
Expand Down Expand Up @@ -42,6 +42,14 @@ export type LinkedMenuGroup = {
overflowText?: string;
};

export type LinkedDocContext = {
std: BlockStdScope;
inlineEditor: AffineInlineEditor;
triggerKey: string;
getMenus: typeof getMenus;
close: () => void;
};

const DEFAULT_DOC_NAME = 'Untitled';
const DISPLAY_NAME_LENGTH = 8;

Expand Down
2 changes: 2 additions & 0 deletions packages/blocks/src/root-block/widgets/linked-doc/effects.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { ImportDoc } from './import-doc/import-doc.js';
import { AFFINE_LINKED_DOC_WIDGET, AffineLinkedDocWidget } from './index.js';
import { LinkedDocPopover } from './linked-doc-popover.js';

export function effects() {
customElements.define('affine-linked-doc-popover', LinkedDocPopover);
customElements.define(AFFINE_LINKED_DOC_WIDGET, AffineLinkedDocWidget);
customElements.define('import-doc', ImportDoc);
}
161 changes: 85 additions & 76 deletions packages/blocks/src/root-block/widgets/linked-doc/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,19 @@ import type { AffineInlineEditor } from '@blocksuite/affine-components/rich-text
import type { EditorHost, UIEventStateContext } from '@blocksuite/block-std';

import { getInlineEditorByModel } from '@blocksuite/affine-components/rich-text';
import {
getCurrentNativeRange,
getViewportElement,
matchFlavours,
} from '@blocksuite/affine-shared/utils';
import { matchFlavours } from '@blocksuite/affine-shared/utils';
import { WidgetComponent } from '@blocksuite/block-std';
import { DisposableGroup, throttle } from '@blocksuite/global/utils';
import { InlineEditor } from '@blocksuite/inline';
import { signal } from '@preact/signals-core';
import { html, nothing } from 'lit';
import { state } from 'lit/decorators.js';
import { choose } from 'lit/directives/choose.js';

import { getPopperPosition } from '../../../root-block/utils/position.js';
import { getMenus, type LinkedMenuGroup } from './config.js';
import { LinkedDocPopover } from './linked-doc-popover.js';
import {
getMenus,
type LinkedDocContext,
type LinkedMenuGroup,
} from './config.js';

export const AFFINE_LINKED_DOC_WIDGET = 'affine-linked-doc-widget';

Expand All @@ -38,10 +39,10 @@ export interface LinkedWidgetConfig {
}

export class AffineLinkedDocWidget extends WidgetComponent {
private _abortController: AbortController | null = null;

private _getInlineEditor = (evt: KeyboardEvent | CompositionEvent) => {
if (evt.target instanceof HTMLElement) {
private readonly _getInlineEditor = (
evt?: KeyboardEvent | CompositionEvent
) => {
if (evt && evt.target instanceof HTMLElement) {
const editor = (
evt.target.closest('.can-link-doc > .inline-editor') as {
inlineEditor?: AffineInlineEditor;
Expand All @@ -55,19 +56,19 @@ export class AffineLinkedDocWidget extends WidgetComponent {
const text = this.host.selection.value.find(selection =>
selection.is('text')
);
if (!text) return;
if (!text) return null;

const model = this.host.doc.getBlockById(text.blockId);
if (!model) return;
if (!model) return null;

if (matchFlavours(model, this.config.ignoreBlockTypes)) {
return;
return null;
}

return getInlineEditorByModel(this.host, model);
};

private _onCompositionEnd = (ctx: UIEventStateContext) => {
private readonly _onCompositionEnd = (ctx: UIEventStateContext) => {
const event = ctx.get('defaultState').event as CompositionEvent;

const key = event.data;
Expand All @@ -78,13 +79,13 @@ export class AffineLinkedDocWidget extends WidgetComponent {
)
return;

const inlineEditor = this._getInlineEditor(event);
if (!inlineEditor) return;
this._inlineEditor = this._getInlineEditor(event);
if (!this._inlineEditor) return;

this._handleInput(inlineEditor, true);
this._handleInput(true);
};

private _onKeyDown = (ctx: UIEventStateContext) => {
private readonly _onKeyDown = (ctx: UIEventStateContext) => {
const eventState = ctx.get('keyboardState');
const event = eventState.raw;

Expand All @@ -96,9 +97,10 @@ export class AffineLinkedDocWidget extends WidgetComponent {
)
return;

const inlineEditor = this._getInlineEditor(event);
if (!inlineEditor) return;
const inlineRange = inlineEditor.getInlineRange();
this._inlineEditor = this._getInlineEditor(event);
if (!this._inlineEditor) return;

const inlineRange = this._inlineEditor.getInlineRange();
if (!inlineRange) return;

if (inlineRange.length > 0) {
Expand All @@ -108,62 +110,44 @@ export class AffineLinkedDocWidget extends WidgetComponent {
return;
}

this._handleInput(inlineEditor, false);
this._handleInput(false);
};

showLinkedDocPopover = (
inlineEditor: AffineInlineEditor,
triggerKey: string
) => {
const curRange = getCurrentNativeRange();
if (!curRange) return;

this._abortController?.abort();
this._abortController = new AbortController();
const disposables = new DisposableGroup();
this._abortController.signal.addEventListener('abort', () =>
disposables.dispose()
);

const linkedDoc = new LinkedDocPopover(
triggerKey,
this.config.getMenus,
this.host,
inlineEditor,
this._abortController
);

// Mount
document.body.append(linkedDoc);
disposables.add(() => linkedDoc.remove());

// Handle position
const updatePosition = throttle(() => {
const linkedDocElement = linkedDoc.linkedDocElement;
if (!linkedDocElement) return;
const position = getPopperPosition(linkedDocElement, curRange);
linkedDoc.updatePosition(position);
}, 10);
disposables.addFromEvent(window, 'resize', updatePosition);
const scrollContainer = getViewportElement(this.host);
if (scrollContainer) {
// Note: in edgeless mode, the scroll container is not exist!
disposables.addFromEvent(scrollContainer, 'scroll', updatePosition, {
passive: true,
});
}
private readonly _renderDesktopLinkedDocPopover = () => {
return html`<affine-linked-doc-popover
.context=${this._context}
></affine-linked-doc-popover>`;
};

// Wait for node to be mounted
setTimeout(updatePosition);
private readonly _show = signal<'desktop' | 'none'>('none');

disposables.addFromEvent(window, 'mousedown', (e: Event) => {
if (e.target === linkedDoc) return;
this._abortController?.abort();
});
closeLinkedDocPopover = () => {
this._inlineEditor = null;
this._triggerKey = '';
this._show.value = 'none';
};

return linkedDoc;
showLinkedDocPopover = () => {
if (this._inlineEditor === null) {
this._inlineEditor = this._getInlineEditor();
}
if (this._triggerKey === '') {
this._triggerKey = this.config.triggerKeys[0];
}
this._show.value = 'desktop';
return;
};

private get _context(): LinkedDocContext {
return {
std: this.std,
inlineEditor: this._inlineEditor!,
triggerKey: this._triggerKey,
getMenus: this.config.getMenus,
close: this.closeLinkedDocPopover,
};
}

get config(): LinkedWidgetConfig {
return {
triggerKeys: ['@', '[[', '【【'],
Expand All @@ -174,9 +158,12 @@ export class AffineLinkedDocWidget extends WidgetComponent {
};
}

private _handleInput(inlineEditor: InlineEditor, isCompositionEnd: boolean) {
private _handleInput(isCompositionEnd: boolean) {
const primaryTriggerKey = this.config.triggerKeys[0];

const inlineEditor = this._inlineEditor;
if (!inlineEditor) return;

const inlineRangeApplyCallback = (callback: () => void) => {
// the inline ranged updated in compositionEnd event before this event callback
if (isCompositionEnd) callback();
Expand Down Expand Up @@ -205,6 +192,7 @@ export class AffineLinkedDocWidget extends WidgetComponent {

// Convert to the primary trigger key
// e.g. [[ -> @
this._triggerKey = primaryTriggerKey;
const startIdxBeforeMatchKey = inlineRange.index - matchedKey.length;
inlineEditor.deleteText({
index: startIdxBeforeMatchKey,
Expand All @@ -219,11 +207,13 @@ export class AffineLinkedDocWidget extends WidgetComponent {
length: 0,
});
inlineEditor.slots.inlineRangeSync.once(() => {
this.showLinkedDocPopover(inlineEditor, primaryTriggerKey);
this.showLinkedDocPopover();
});
return;
} else {
this._triggerKey = matchedKey;
this.showLinkedDocPopover();
}
this.showLinkedDocPopover(inlineEditor, matchedKey);
});
}

Expand All @@ -232,6 +222,25 @@ export class AffineLinkedDocWidget extends WidgetComponent {
this.handleEvent('keyDown', this._onKeyDown);
this.handleEvent('compositionEnd', this._onCompositionEnd);
}

override render() {
if (this._show.value === 'none') return nothing;

return html`<blocksuite-portal
.shadowDom=${false}
.template=${choose(
this._show.value,
[['desktop', this._renderDesktopLinkedDocPopover]],
() => html`${nothing}`
)}
></blocksuite-portal>`;
}

@state()
private accessor _inlineEditor: AffineInlineEditor | null = null;

@state()
private accessor _triggerKey = '';
}

declare global {
Expand Down
Loading

0 comments on commit dbe4d96

Please sign in to comment.