import { assertExists } from '@blockeditor/global/utils'; import type { EditorHost } from '@blockeditor/lit'; import { ShadowlessElement, WithDisposable } from '@blockeditor/lit'; import type { BlockModel } from '@blockeditor/store'; import { html } from 'lit'; import { customElement, property, query, state } from 'lit/decorators.js'; import { classMap } from 'lit/directives/class-map.js'; import type { EdgelessPageBlockComponent } from '../../../../page-block/edgeless/edgeless-page-block.js'; import type { PageService } from '../../../../page-block/page-service.js'; import type { EdgelessElementType } from '../../../../surface-block/edgeless-types.js'; import { Bound } from '../../../../surface-block/utils/bound.js'; import { Vec } from '../../../../surface-block/utils/vec.js'; import { EMBED_CARD_HEIGHT, EMBED_CARD_WIDTH } from '../../../consts.js'; import type { EmbedCardStyle } from '../../../types.js'; import { getPageByEditorHost } from '../../../utils/query.js'; import { isValidUrl } from '../../../utils/url.js'; import { toast } from '../../toast.js'; import { embedCardModalStyles } from './styles.js'; @customElement('embed-card-create-modal') export class EmbedCardCreateModal extends WithDisposable(ShadowlessElement) { static override styles = embedCardModalStyles; @property({ attribute: false }) host!: EditorHost; @property({ attribute: false }) titleText!: string; @property({ attribute: false }) descriptionText!: string; @property({ attribute: false }) createOptions!: | { mode: 'page'; parentModel: BlockModel | string; index?: number; } | { mode: 'edgeless'; }; @property({ attribute: false }) onConfirm!: () => void; @query('input') input!: HTMLInputElement; @state() private _linkInputValue = ''; override connectedCallback() { super.connectedCallback(); this.updateComplete .then(() => { requestAnimationFrame(() => { this.input.focus(); }); }) .catch(console.error); this.disposables.addFromEvent(this, 'keydown', this._onDocumentKeydown); } private _handleInput(e: InputEvent) { const target = e.target as HTMLInputElement; this._linkInputValue = target.value; } private _onDocumentKeydown = (e: KeyboardEvent) => { e.stopPropagation(); if (e.key === 'Enter' && !e.isComposing) { this._onConfirm(); } if (e.key === 'Escape') { this.remove(); } }; private _onConfirm = () => { const url = this.input.value; if (!isValidUrl(url)) { toast(this.host, 'Invalid link'); return; } const pageService = this.host.spec.getService( 'affine:page' ) as PageService | null; assertExists(pageService); const embedOptions = pageService.getEmbedBlockOptions(url); const { mode } = this.createOptions; if (mode === 'page') { const { parentModel, index } = this.createOptions; let flavour = 'affine:bookmark'; if (embedOptions) { flavour = embedOptions.flavour; } this.host.page.addBlock( flavour, { url, }, parentModel, index ); } else if (mode === 'edgeless') { let flavour = 'affine:bookmark', targetStyle: EmbedCardStyle = 'vertical'; if (embedOptions) { flavour = embedOptions.flavour; targetStyle = embedOptions.styles[0]; } const edgelessPageElement = getPageByEditorHost( this.host ) as EdgelessPageBlockComponent | null; assertExists(edgelessPageElement); const surface = edgelessPageElement.surface; const center = Vec.toVec(surface.renderer.center); edgelessPageElement.service.addBlock( flavour as EdgelessElementType, { url, xywh: Bound.fromCenter( center, EMBED_CARD_WIDTH[targetStyle], EMBED_CARD_HEIGHT[targetStyle] ).serialize(), style: targetStyle, }, surface.model ); edgelessPageElement.tools.setEdgelessTool({ type: 'default', }); } this.onConfirm(); this.remove(); }; private _onCancel = () => { this.remove(); }; override render() { return html`
`; } } export async function toggleEmbedCardCreateModal( host: EditorHost, titleText: string, descriptionText: string, createOptions: | { mode: 'page'; parentModel: BlockModel | string; index?: number; } | { mode: 'edgeless'; } ): Promise