diff --git a/blocksuite/affine/blocks/block-bookmark/src/commands/insert-bookmark.ts b/blocksuite/affine/blocks/block-bookmark/src/commands/insert-bookmark.ts index 79afdc99e..b54ba66e9 100644 --- a/blocksuite/affine/blocks/block-bookmark/src/commands/insert-bookmark.ts +++ b/blocksuite/affine/blocks/block-bookmark/src/commands/insert-bookmark.ts @@ -5,7 +5,10 @@ import type { EmbedCardStyle } from '@blocksuite/affine-model'; import { EmbedOptionProvider } from '@blocksuite/affine-shared/services'; import type { Command } from '@blocksuite/block-std'; -export const insertBookmarkCommand: Command<{ url: string }> = (ctx, next) => { +export const insertBookmarkCommand: Command< + { url: string }, + { blockId: string; flavour: string } +> = (ctx, next) => { const { url, std } = ctx; const embedOptions = std.get(EmbedOptionProvider).getEmbedBlockOptions(url); @@ -16,6 +19,7 @@ export const insertBookmarkCommand: Command<{ url: string }> = (ctx, next) => { flavour = embedOptions.flavour; targetStyle = embedOptions.styles[0]; } - insertEmbedCard(std, { flavour, targetStyle, props }); - next(); + const blockId = insertEmbedCard(std, { flavour, targetStyle, props }); + if (!blockId) return; + next({ blockId, flavour }); }; diff --git a/blocksuite/affine/blocks/block-bookmark/src/commands/insert-link-by-quick-search.ts b/blocksuite/affine/blocks/block-bookmark/src/commands/insert-link-by-quick-search.ts index 8d82d74be..49764f31f 100644 --- a/blocksuite/affine/blocks/block-bookmark/src/commands/insert-link-by-quick-search.ts +++ b/blocksuite/affine/blocks/block-bookmark/src/commands/insert-link-by-quick-search.ts @@ -1,8 +1,13 @@ import { type InsertedLinkType, + insertEmbedIframeCommand, insertEmbedLinkedDocCommand, + type LinkableFlavour, } from '@blocksuite/affine-block-embed'; -import { QuickSearchProvider } from '@blocksuite/affine-shared/services'; +import { + FeatureFlagService, + QuickSearchProvider, +} from '@blocksuite/affine-shared/services'; import type { Command } from '@blocksuite/block-std'; import { insertBookmarkCommand } from './insert-bookmark'; @@ -36,10 +41,33 @@ export const insertLinkByQuickSearchCommand: Command< // add normal link; if ('externalUrl' in result) { - std.command.exec(insertBookmarkCommand, { url: result.externalUrl }); - return { - flavour: 'affine:bookmark', - }; + const featureFlagService = std.get(FeatureFlagService); + const enableEmbedIframeBlock = featureFlagService.getFlag( + 'enable_embed_iframe_block' + ); + if (enableEmbedIframeBlock) { + // try to insert embed iframe block first + const [success, { flavour }] = std.command + .chain() + .try(chain => [ + chain.pipe(insertEmbedIframeCommand, { url: result.externalUrl }), + chain.pipe(insertBookmarkCommand, { url: result.externalUrl }), + ]) + .run(); + if (!success || !flavour) return null; + return { + flavour: flavour as LinkableFlavour, + }; + } else { + const [success, { flavour }] = std.command.exec( + insertBookmarkCommand, + { url: result.externalUrl } + ); + if (!success || !flavour) return null; + return { + flavour: flavour as LinkableFlavour, + }; + } } return null; diff --git a/blocksuite/affine/blocks/block-embed/src/common/insert-embed-card.ts b/blocksuite/affine/blocks/block-embed/src/common/insert-embed-card.ts index ead7ccb42..080fad80b 100644 --- a/blocksuite/affine/blocks/block-embed/src/common/insert-embed-card.ts +++ b/blocksuite/affine/blocks/block-embed/src/common/insert-embed-card.ts @@ -48,7 +48,13 @@ export function insertEmbedCard( const parent = host.doc.getParent(block.model); if (!parent) return; const index = parent.children.indexOf(block.model); - host.doc.addBlock(flavour as never, props, parent, index + 1); + const cardId = host.doc.addBlock( + flavour as never, + props, + parent, + index + 1 + ); + return cardId; } else { const rootId = std.store.root?.id; if (!rootId) return; @@ -85,5 +91,7 @@ export function insertEmbedCard( // @ts-expect-error FIXME: resolve after gfx tool refactor 'default' ); + + return cardId; } } diff --git a/blocksuite/affine/blocks/block-embed/src/effects.ts b/blocksuite/affine/blocks/block-embed/src/effects.ts index 143ae00c4..51d4bd762 100644 --- a/blocksuite/affine/blocks/block-embed/src/effects.ts +++ b/blocksuite/affine/blocks/block-embed/src/effects.ts @@ -9,6 +9,7 @@ import { EmbedIframeCreateModal } from './embed-iframe-block/components/embed-if import { EmbedIframeErrorCard } from './embed-iframe-block/components/embed-iframe-error-card'; import { EmbedIframeLinkEditPopup } from './embed-iframe-block/components/embed-iframe-link-edit-popup'; import { EmbedIframeLoadingCard } from './embed-iframe-block/components/embed-iframe-loading-card'; +import { EmbedEdgelessIframeBlockComponent } from './embed-iframe-block/embed-edgeless-iframe-block'; import { EmbedIframeBlockComponent } from './embed-iframe-block/embed-iframe-block'; import { EmbedLinkedDocBlockComponent } from './embed-linked-doc-block'; import { EmbedEdgelessLinkedDocBlockComponent } from './embed-linked-doc-block/embed-edgeless-linked-doc-block'; @@ -78,6 +79,10 @@ export function effects() { EmbedSyncedDocBlockComponent ); + customElements.define( + 'affine-embed-edgeless-iframe-block', + EmbedEdgelessIframeBlockComponent + ); customElements.define('affine-embed-iframe-block', EmbedIframeBlockComponent); customElements.define( 'affine-embed-iframe-create-modal', diff --git a/blocksuite/affine/blocks/block-embed/src/embed-iframe-block/commands/index.ts b/blocksuite/affine/blocks/block-embed/src/embed-iframe-block/commands/index.ts new file mode 100644 index 000000000..2c69456c9 --- /dev/null +++ b/blocksuite/affine/blocks/block-embed/src/embed-iframe-block/commands/index.ts @@ -0,0 +1 @@ +export * from './insert-embed-iframe'; diff --git a/blocksuite/affine/blocks/block-embed/src/embed-iframe-block/commands/insert-embed-iframe.ts b/blocksuite/affine/blocks/block-embed/src/embed-iframe-block/commands/insert-embed-iframe.ts new file mode 100644 index 000000000..7c15ade5c --- /dev/null +++ b/blocksuite/affine/blocks/block-embed/src/embed-iframe-block/commands/insert-embed-iframe.ts @@ -0,0 +1,109 @@ +import { + EdgelessCRUDIdentifier, + SurfaceBlockComponent, +} from '@blocksuite/affine-block-surface'; +import { + BlockSelection, + type Command, + SurfaceSelection, + TextSelection, +} from '@blocksuite/block-std'; +import { GfxControllerIdentifier } from '@blocksuite/block-std/gfx'; +import { Bound, Vec } from '@blocksuite/global/gfx'; + +import { + EMBED_IFRAME_DEFAULT_HEIGHT_IN_SURFACE, + EMBED_IFRAME_DEFAULT_WIDTH_IN_SURFACE, +} from '../consts'; +import { EmbedIframeService } from '../extension/embed-iframe-service'; + +export const insertEmbedIframeCommand: Command< + { url: string }, + { blockId: string; flavour: string } +> = (ctx, next) => { + const { url, std } = ctx; + const embedIframeService = std.get(EmbedIframeService); + if (!embedIframeService || !embedIframeService.canEmbed(url)) { + return; + } + + const config = embedIframeService.getConfig(url); + if (!config) { + return; + } + + const { host } = std; + const selectionManager = host.selection; + + let selectedBlockId: string | undefined; + const textSelection = selectionManager.find(TextSelection); + const blockSelection = selectionManager.find(BlockSelection); + const surfaceSelection = selectionManager.find(SurfaceSelection); + if (textSelection) { + selectedBlockId = textSelection.blockId; + } else if (blockSelection) { + selectedBlockId = blockSelection.blockId; + } else if (surfaceSelection && surfaceSelection.editing) { + selectedBlockId = surfaceSelection.blockId; + } + + const flavour = 'affine:embed-iframe'; + const props: Record = { url }; + // When there is a selected block, it means that the selection is in note or edgeless text + // we should insert the embed iframe block after the selected block and only need the url prop + let newBlockId: string | undefined; + if (selectedBlockId) { + const block = host.view.getBlock(selectedBlockId); + if (!block) return; + const parent = host.doc.getParent(block.model); + if (!parent) return; + const index = parent.children.indexOf(block.model); + newBlockId = host.doc.addBlock(flavour, props, parent, index + 1); + } else { + // When there is no selected block and in edgeless mode + // We should insert the embed iframe block to surface + // It means that not only the url prop but also the xywh prop is needed + const rootId = std.store.root?.id; + if (!rootId) return; + const edgelessRoot = std.view.getBlock(rootId); + if (!edgelessRoot) return; + + const gfx = std.get(GfxControllerIdentifier); + const crud = std.get(EdgelessCRUDIdentifier); + + gfx.viewport.smoothZoom(1); + const surfaceBlock = gfx.surfaceComponent; + if (!(surfaceBlock instanceof SurfaceBlockComponent)) return; + + const options = config.options; + const { widthInSurface, heightInSurface } = options ?? {}; + const width = widthInSurface ?? EMBED_IFRAME_DEFAULT_WIDTH_IN_SURFACE; + const height = heightInSurface ?? EMBED_IFRAME_DEFAULT_HEIGHT_IN_SURFACE; + const center = Vec.toVec(surfaceBlock.renderer.viewport.center); + const xywh = Bound.fromCenter(center, width, height).serialize(); + newBlockId = crud.addBlock( + flavour, + { + ...props, + xywh, + }, + surfaceBlock.model + ); + + gfx.selection.set({ + elements: [newBlockId], + editing: false, + }); + + gfx.tool.setTool( + // @ts-expect-error FIXME: resolve after gfx tool refactor + 'default' + ); + } + + if (!newBlockId) { + return; + } + + next({ blockId: newBlockId, flavour }); +}; diff --git a/blocksuite/affine/blocks/block-embed/src/embed-iframe-block/components/embed-iframe-error-card.ts b/blocksuite/affine/blocks/block-embed/src/embed-iframe-block/components/embed-iframe-error-card.ts index 994287194..83f69e772 100644 --- a/blocksuite/affine/blocks/block-embed/src/embed-iframe-block/components/embed-iframe-error-card.ts +++ b/blocksuite/affine/blocks/block-embed/src/embed-iframe-block/components/embed-iframe-error-card.ts @@ -19,6 +19,7 @@ export class EmbedIframeErrorCard extends WithDisposable(LitElement) { } .affine-embed-iframe-error-card { + container: affine-embed-iframe-error-card / inline-size; display: flex; box-sizing: border-box; width: 100%; @@ -124,6 +125,12 @@ export class EmbedIframeErrorCard extends WithDisposable(LitElement) { width: 204px; height: 102px; } + + @container affine-embed-iframe-error-card (width < 480px) { + .error-banner { + display: none; + } + } } `; diff --git a/blocksuite/affine/blocks/block-embed/src/embed-iframe-block/components/embed-iframe-loading-card.ts b/blocksuite/affine/blocks/block-embed/src/embed-iframe-block/components/embed-iframe-loading-card.ts index 17ae68581..743175255 100644 --- a/blocksuite/affine/blocks/block-embed/src/embed-iframe-block/components/embed-iframe-loading-card.ts +++ b/blocksuite/affine/blocks/block-embed/src/embed-iframe-block/components/embed-iframe-loading-card.ts @@ -14,6 +14,7 @@ export class EmbedIframeLoadingCard extends LitElement { } .affine-embed-iframe-loading-card { + container: affine-embed-iframe-loading-card / inline-size; display: flex; box-sizing: border-box; width: 100%; @@ -86,6 +87,12 @@ export class EmbedIframeLoadingCard extends LitElement { } } } + + @container affine-embed-iframe-loading-card (width < 480px) { + .loading-banner { + display: none; + } + } } `; diff --git a/blocksuite/affine/blocks/block-embed/src/embed-iframe-block/configs/providers/google-drive.ts b/blocksuite/affine/blocks/block-embed/src/embed-iframe-block/configs/providers/google-drive.ts index 96d0f89ad..872e26211 100644 --- a/blocksuite/affine/blocks/block-embed/src/embed-iframe-block/configs/providers/google-drive.ts +++ b/blocksuite/affine/blocks/block-embed/src/embed-iframe-block/configs/providers/google-drive.ts @@ -4,8 +4,10 @@ import { validateEmbedIframeUrl, } from '../../utils'; -const GOOGLE_DRIVE_DEFAULT_WIDTH = '100%'; -const GOOGLE_DRIVE_DEFAULT_HEIGHT = '480px'; +const GOOGLE_DRIVE_DEFAULT_WIDTH_IN_SURFACE = 640; +const GOOGLE_DRIVE_DEFAULT_HEIGHT_IN_SURFACE = 480; +const GOOGLE_DRIVE_DEFAULT_WIDTH_PERCENT = 100; +const GOOGLE_DRIVE_DEFAULT_HEIGHT_IN_NOTE = 480; const GOOGLE_DRIVE_EMBED_FOLDER_URL = 'https://drive.google.com/embeddedfolderview'; const GOOGLE_DRIVE_EMBED_FILE_URL = 'https://drive.google.com/file/d/'; @@ -181,8 +183,10 @@ const googleDriveConfig = { }, useOEmbedUrlDirectly: true, options: { - defaultWidth: GOOGLE_DRIVE_DEFAULT_WIDTH, - defaultHeight: GOOGLE_DRIVE_DEFAULT_HEIGHT, + widthInSurface: GOOGLE_DRIVE_DEFAULT_WIDTH_IN_SURFACE, + heightInSurface: GOOGLE_DRIVE_DEFAULT_HEIGHT_IN_SURFACE, + widthPercent: GOOGLE_DRIVE_DEFAULT_WIDTH_PERCENT, + heightInNote: GOOGLE_DRIVE_DEFAULT_HEIGHT_IN_NOTE, allowFullscreen: true, style: 'border: none; border-radius: 8px;', }, diff --git a/blocksuite/affine/blocks/block-embed/src/embed-iframe-block/configs/providers/spotify.ts b/blocksuite/affine/blocks/block-embed/src/embed-iframe-block/configs/providers/spotify.ts index 72556d78a..4c0e31758 100644 --- a/blocksuite/affine/blocks/block-embed/src/embed-iframe-block/configs/providers/spotify.ts +++ b/blocksuite/affine/blocks/block-embed/src/embed-iframe-block/configs/providers/spotify.ts @@ -4,8 +4,10 @@ import { validateEmbedIframeUrl, } from '../../utils'; -const SPOTIFY_DEFAULT_WIDTH = '100%'; -const SPOTIFY_DEFAULT_HEIGHT = '152px'; +const SPOTIFY_DEFAULT_WIDTH_IN_SURFACE = 640; +const SPOTIFY_DEFAULT_HEIGHT_IN_SURFACE = 152; +const SPOTIFY_DEFAULT_HEIGHT_IN_NOTE = 152; +const SPOTIFY_DEFAULT_WIDTH_PERCENT = 100; // https://developer.spotify.com/documentation/embeds/reference/oembed const spotifyEndpoint = 'https://open.spotify.com/oembed'; @@ -30,8 +32,10 @@ const spotifyConfig = { }, useOEmbedUrlDirectly: false, options: { - defaultWidth: SPOTIFY_DEFAULT_WIDTH, - defaultHeight: SPOTIFY_DEFAULT_HEIGHT, + widthInSurface: SPOTIFY_DEFAULT_WIDTH_IN_SURFACE, + heightInSurface: SPOTIFY_DEFAULT_HEIGHT_IN_SURFACE, + heightInNote: SPOTIFY_DEFAULT_HEIGHT_IN_NOTE, + widthPercent: SPOTIFY_DEFAULT_WIDTH_PERCENT, allow: 'autoplay; clipboard-write; encrypted-media; fullscreen; picture-in-picture', style: 'border-radius: 8px;', diff --git a/blocksuite/affine/blocks/block-embed/src/embed-iframe-block/consts.ts b/blocksuite/affine/blocks/block-embed/src/embed-iframe-block/consts.ts new file mode 100644 index 000000000..8e3074eff --- /dev/null +++ b/blocksuite/affine/blocks/block-embed/src/embed-iframe-block/consts.ts @@ -0,0 +1,2 @@ +export const EMBED_IFRAME_DEFAULT_WIDTH_IN_SURFACE = 752; +export const EMBED_IFRAME_DEFAULT_HEIGHT_IN_SURFACE = 116; diff --git a/blocksuite/affine/blocks/block-embed/src/embed-iframe-block/embed-edgeless-iframe-block.ts b/blocksuite/affine/blocks/block-embed/src/embed-iframe-block/embed-edgeless-iframe-block.ts new file mode 100644 index 000000000..09626fcb6 --- /dev/null +++ b/blocksuite/affine/blocks/block-embed/src/embed-iframe-block/embed-edgeless-iframe-block.ts @@ -0,0 +1,57 @@ +import { EdgelessLegacySlotIdentifier } from '@blocksuite/affine-block-surface'; +import { toGfxBlockComponent } from '@blocksuite/block-std'; +import { Bound } from '@blocksuite/global/gfx'; +import { nothing } from 'lit'; +import { styleMap } from 'lit/directives/style-map.js'; +import { html } from 'lit/static-html.js'; + +import { EmbedIframeBlockComponent } from './embed-iframe-block'; + +export class EmbedEdgelessIframeBlockComponent extends toGfxBlockComponent( + EmbedIframeBlockComponent +) { + override selectedStyle$ = null; + + override blockDraggable = false; + + override accessor blockContainerStyles = { margin: '0' }; + + get edgelessSlots() { + return this.std.get(EdgelessLegacySlotIdentifier); + } + + override connectedCallback() { + super.connectedCallback(); + + this.edgelessSlots.elementResizeStart.subscribe(() => { + this.isResizing$.value = true; + }); + + this.edgelessSlots.elementResizeEnd.subscribe(() => { + this.isResizing$.value = false; + }); + } + + override renderGfxBlock() { + if (!this.isEmbedIframeBlockEnabled) { + return nothing; + } + + const bound = Bound.deserialize(this.model.xywh$.value); + const scale = this.model.scale$.value; + const width = bound.w / scale; + const height = bound.h / scale; + const style = { + width: `${width}px`, + height: `${height}px`, + transformOrigin: '0 0', + transform: `scale(${scale})`, + }; + + return html` +
+ ${this.renderPageContent()} +
+ `; + } +} diff --git a/blocksuite/affine/blocks/block-embed/src/embed-iframe-block/embed-iframe-block.ts b/blocksuite/affine/blocks/block-embed/src/embed-iframe-block/embed-iframe-block.ts index 771f32974..8492f0d0f 100644 --- a/blocksuite/affine/blocks/block-embed/src/embed-iframe-block/embed-iframe-block.ts +++ b/blocksuite/affine/blocks/block-embed/src/embed-iframe-block/embed-iframe-block.ts @@ -1,3 +1,4 @@ +import { SurfaceBlockModel } from '@blocksuite/affine-block-surface'; import { CaptionedBlockComponent, SelectedStyle, @@ -7,13 +8,13 @@ import { FeatureFlagService, LinkPreviewerService, } from '@blocksuite/affine-shared/services'; +import { matchModels } from '@blocksuite/affine-shared/utils'; import { BlockSelection } from '@blocksuite/block-std'; import { BlockSuiteError, ErrorCode } from '@blocksuite/global/exceptions'; import { computed, type ReadonlySignal, signal } from '@preact/signals-core'; import { html, nothing } from 'lit'; import { type ClassInfo, classMap } from 'lit/directives/class-map.js'; import { ifDefined } from 'lit/directives/if-defined.js'; -import { styleMap } from 'lit/directives/style-map.js'; import type { IframeOptions } from './extension/embed-iframe-config.js'; import { EmbedIframeService } from './extension/embed-iframe-service.js'; @@ -21,6 +22,7 @@ import { embedIframeBlockStyles } from './style.js'; export type EmbedIframeStatus = 'idle' | 'loading' | 'success' | 'error'; const DEFAULT_IFRAME_HEIGHT = 152; +const DEFAULT_IFRAME_WIDTH = '100%'; export class EmbedIframeBlockComponent extends CaptionedBlockComponent { selectedStyle$: ReadonlySignal | null = computed( @@ -40,6 +42,18 @@ export class EmbedIframeBlockComponent extends CaptionedBlockComponent this.status$.value === 'error'); readonly isSuccess$ = computed(() => this.status$.value === 'success'); + readonly isDraggingOnHost$ = signal(false); + readonly isResizing$ = signal(false); + // show overlay to prevent the iframe from capturing pointer events + // when the block is dragging, resizing, or not selected + readonly showOverlay$ = computed( + () => + this.isSuccess$.value && + (this.isDraggingOnHost$.value || + this.isResizing$.value || + !this.selected$.value) + ); + private _iframeOptions: IframeOptions | undefined = undefined; get embedIframeService() { @@ -50,6 +64,16 @@ export class EmbedIframeBlockComponent extends CaptionedBlockComponent { const link = this.model.url; window.open(link, '_blank'); @@ -134,8 +158,12 @@ export class EmbedIframeBlockComponent extends CaptionedBlockComponent { + protected _handleClick = (event: MouseEvent) => { event.stopPropagation(); + // We don't need to select the block when the block is in the surface + if (this.inSurface) { + return; + } this._selectBlock(); }; @@ -143,27 +171,25 @@ export class EmbedIframeBlockComponent extends CaptionedBlockComponent { - const featureFlagService = this.doc.get(FeatureFlagService); - const flag = featureFlagService.getFlag('enable_embed_iframe_block'); - return flag ?? false; - }); - private readonly _renderIframe = () => { const { iframeUrl } = this.model; const { - defaultWidth, - defaultHeight, + widthPercent, + heightInNote, style, allow, referrerpolicy, scrolling, allowFullscreen, } = this._iframeOptions ?? {}; + const width = `${widthPercent}%`; + // if the block is in the surface, use 100% as the height + // otherwise, use the heightInNote + const height = this.inSurface ? '100%' : heightInNote; return html` */ export type IframeOptions = { - defaultWidth?: string; - defaultHeight?: string; + widthInSurface: number; // the default width of embed iframe in surface, in pixels + heightInSurface: number; // the default height of embed iframe in surface, in pixels + heightInNote: number; // the default height of embed iframe in note, in pixels + widthPercent: number; // the width percentage of embed iframe relative to parent container width, normalized to 0-100 style?: string; referrerpolicy?: string; scrolling?: boolean; diff --git a/blocksuite/affine/blocks/block-embed/src/embed-iframe-block/index.ts b/blocksuite/affine/blocks/block-embed/src/embed-iframe-block/index.ts index 319964e78..52ec403af 100644 --- a/blocksuite/affine/blocks/block-embed/src/embed-iframe-block/index.ts +++ b/blocksuite/affine/blocks/block-embed/src/embed-iframe-block/index.ts @@ -1,4 +1,5 @@ export * from './adapters'; +export * from './commands'; export * from './components/embed-iframe-create-modal'; export * from './configs'; export * from './embed-iframe-block'; diff --git a/blocksuite/affine/blocks/block-embed/src/embed-iframe-block/style.ts b/blocksuite/affine/blocks/block-embed/src/embed-iframe-block/style.ts index e9a4c0b67..0d2236f39 100644 --- a/blocksuite/affine/blocks/block-embed/src/embed-iframe-block/style.ts +++ b/blocksuite/affine/blocks/block-embed/src/embed-iframe-block/style.ts @@ -1,12 +1,29 @@ import { css } from 'lit'; export const embedIframeBlockStyles = css` - .affine-embed-iframe-block { + .affine-embed-iframe-block-container { display: flex; width: 100%; border-radius: 8px; user-select: none; align-items: center; justify-content: center; + position: relative; + } + + .affine-embed-iframe-block-container.in-surface { + height: 100%; + } + + .affine-embed-iframe-block-overlay { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + display: none; + } + .affine-embed-iframe-block-overlay.show { + display: block; } `; diff --git a/blocksuite/affine/blocks/block-embed/src/embed-linked-doc-block/commands/insert-embed-linked-doc.ts b/blocksuite/affine/blocks/block-embed/src/embed-linked-doc-block/commands/insert-embed-linked-doc.ts index 8e5cf1a64..bc5dcf3d1 100644 --- a/blocksuite/affine/blocks/block-embed/src/embed-linked-doc-block/commands/insert-embed-linked-doc.ts +++ b/blocksuite/affine/blocks/block-embed/src/embed-linked-doc-block/commands/insert-embed-linked-doc.ts @@ -3,19 +3,32 @@ import type { Command } from '@blocksuite/block-std'; import { insertEmbedCard } from '../../common/insert-embed-card.js'; +export type LinkableFlavour = + | 'affine:bookmark' + | 'affine:embed-linked-doc' + | 'affine:embed-iframe' + | 'affine:embed-figma' + | 'affine:embed-github' + | 'affine:embed-loom' + | 'affine:embed-youtube'; + export type InsertedLinkType = { - flavour?: 'affine:bookmark' | 'affine:embed-linked-doc'; + flavour: LinkableFlavour; } | null; -export const insertEmbedLinkedDocCommand: Command<{ - docId: string; - params?: ReferenceParams; -}> = (ctx, next) => { +export const insertEmbedLinkedDocCommand: Command< + { + docId: string; + params?: ReferenceParams; + }, + { blockId: string } +> = (ctx, next) => { const { docId, params, std } = ctx; const flavour = 'affine:embed-linked-doc'; const targetStyle: EmbedCardStyle = 'vertical'; const props: Record = { pageId: docId }; if (params) props.params = params; - insertEmbedCard(std, { flavour, targetStyle, props }); - next(); + const blockId = insertEmbedCard(std, { flavour, targetStyle, props }); + if (!blockId) return; + next({ blockId }); }; diff --git a/blocksuite/affine/blocks/block-root/src/edgeless/clipboard/clipboard.ts b/blocksuite/affine/blocks/block-root/src/edgeless/clipboard/clipboard.ts index e879f3e0e..cdf2a0f62 100644 --- a/blocksuite/affine/blocks/block-root/src/edgeless/clipboard/clipboard.ts +++ b/blocksuite/affine/blocks/block-root/src/edgeless/clipboard/clipboard.ts @@ -441,6 +441,7 @@ export class EdgelessClipboardController extends PageClipboard { this.registerBlock('affine:embed-html', this._createHtmlEmbedBlock); this.registerBlock('affine:embed-loom', this._createLoomEmbedBlock); this.registerBlock('affine:embed-youtube', this._createYoutubeEmbedBlock); + this.registerBlock('affine:embed-iframe', this._createIframeEmbedBlock); // internal links this.registerBlock( @@ -881,6 +882,36 @@ export class EdgelessClipboardController extends PageClipboard { return embedYoutubeId; } + private _createIframeEmbedBlock(embedIframe: BlockSnapshot) { + const { + xywh, + caption, + url, + title, + description, + iframeUrl, + scale, + width, + height, + } = embedIframe.props; + + return this.crud.addBlock( + 'affine:embed-iframe', + { + url, + iframeUrl, + xywh, + caption, + title, + description, + scale, + width, + height, + }, + this.surface.model.id + ); + } + private async _edgelessToCanvas( edgeless: EdgelessRootBlockComponent, bound: IBound,