From b2aa3084ecd58771e7e0e5c612e9cd5cabef96f4 Mon Sep 17 00:00:00 2001 From: donteatfriedrice Date: Mon, 31 Mar 2025 06:23:11 +0000 Subject: [PATCH] feat(editor): support to drag embed iframe from note to surface (#11267) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Close [BS-2807](https://linear.app/affine-design/issue/BS-2807/note-中与-surface-中-embed-iframe-block-互相拖动时的优化) --- .../components/embed-iframe-error-card.ts | 64 ++++++++++---- .../components/embed-iframe-idle-card.ts | 83 +++++++++++++++++-- .../embed-iframe-link-input-base.ts | 1 + .../components/embed-iframe-loading-card.ts | 11 ++- .../src/embed-iframe-block/consts.ts | 4 + .../embed-iframe-block/embed-iframe-block.ts | 37 +++++++-- .../widgets/widget-drag-handle/package.json | 1 + .../src/watchers/drag-event-watcher.ts | 39 ++++++--- .../widgets/widget-drag-handle/tsconfig.json | 1 + tools/utils/src/workspace.gen.ts | 1 + yarn.lock | 1 + 11 files changed, 199 insertions(+), 44 deletions(-) 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 8f93938e19..786aa43987 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 @@ -11,10 +11,10 @@ import { property, query } from 'lit/decorators.js'; import { classMap } from 'lit/directives/class-map.js'; import { styleMap } from 'lit/directives/style-map.js'; +import { ERROR_CARD_DEFAULT_HEIGHT } from '../consts'; import type { EmbedIframeStatusCardOptions } from '../types'; const LINK_EDIT_POPUP_OFFSET = 12; -const ERROR_CARD_DEFAULT_HEIGHT = 114; export class EmbedIframeErrorCard extends WithDisposable(LitElement) { static override styles = css` @@ -24,7 +24,7 @@ export class EmbedIframeErrorCard extends WithDisposable(LitElement) { } .affine-embed-iframe-error-card { - container: affine-embed-iframe-error-card / inline-size; + container: affine-embed-iframe-error-card / size; display: flex; box-sizing: border-box; user-select: none; @@ -41,7 +41,6 @@ export class EmbedIframeErrorCard extends WithDisposable(LitElement) { display: flex; flex-direction: column; gap: 4px; - flex: 1 0 0; .error-title { display: flex; @@ -64,6 +63,9 @@ export class EmbedIframeErrorCard extends WithDisposable(LitElement) { font-style: normal; font-weight: 600; line-height: 22px; /* 157.143% */ + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; } } @@ -119,12 +121,6 @@ export class EmbedIframeErrorCard extends WithDisposable(LitElement) { } } } - - @container affine-embed-iframe-error-card (width < 480px) { - .error-banner { - display: none; - } - } } .affine-embed-iframe-error-card.horizontal { @@ -133,12 +129,19 @@ export class EmbedIframeErrorCard extends WithDisposable(LitElement) { .error-content { align-items: flex-start; + flex: 1 0 0; .error-message { height: 40px; align-items: flex-start; } } + + @container affine-embed-iframe-error-card (width < 480px) { + .error-banner { + display: none; + } + } } .affine-embed-iframe-error-card.vertical { @@ -155,6 +158,18 @@ export class EmbedIframeErrorCard extends WithDisposable(LitElement) { align-items: center; } } + + .icon-box { + svg { + transform: scale(1.6) translateY(-14px); + } + } + + @container affine-embed-iframe-error-card (height < 300px) or (width < 300px) { + .error-banner { + display: none; + } + } } `; @@ -216,10 +231,10 @@ export class EmbedIframeErrorCard extends WithDisposable(LitElement) {
-
+ ${InformationIcon({ width: '16px', height: '16px' })} -
-
This link couldn’t be loaded.
+ + This link couldn’t be loaded.
${this.error?.message || 'Failed to load embedded content'} @@ -244,8 +259,7 @@ export class EmbedIframeErrorCard extends WithDisposable(LitElement) {
- -
+
${EmbedIframeErrorIcon}
`; @@ -280,3 +294,25 @@ export class EmbedIframeErrorCard extends WithDisposable(LitElement) { height: ERROR_CARD_DEFAULT_HEIGHT, }; } + +export const EmbedIframeErrorIcon = html` + + + + + + + + +`; diff --git a/blocksuite/affine/blocks/block-embed/src/embed-iframe-block/components/embed-iframe-idle-card.ts b/blocksuite/affine/blocks/block-embed/src/embed-iframe-block/components/embed-iframe-idle-card.ts index e80b1f534a..c3ac974c58 100644 --- a/blocksuite/affine/blocks/block-embed/src/embed-iframe-block/components/embed-iframe-idle-card.ts +++ b/blocksuite/affine/blocks/block-embed/src/embed-iframe-block/components/embed-iframe-idle-card.ts @@ -3,16 +3,22 @@ import { WithDisposable } from '@blocksuite/global/lit'; import { EmbedIcon } from '@blocksuite/icons/lit'; import { baseTheme } from '@toeverything/theme'; import { css, html, LitElement, unsafeCSS } from 'lit'; +import { property } from 'lit/decorators.js'; +import { classMap } from 'lit/directives/class-map.js'; +import { styleMap } from 'lit/directives/style-map.js'; + +import { IDLE_CARD_DEFAULT_HEIGHT } from '../consts'; +import type { EmbedIframeStatusCardOptions } from '../types'; export class EmbedIframeIdleCard extends WithDisposable(LitElement) { static override styles = css` :host { width: 100%; + height: 100%; } .affine-embed-iframe-idle-card { - width: 100%; - height: 48px; + container: affine-embed-iframe-idle-card / size; box-sizing: border-box; display: flex; align-items: center; @@ -23,8 +29,6 @@ export class EmbedIframeIdleCard extends WithDisposable(LitElement) { .icon { display: flex; - width: 24px; - height: 24px; justify-content: center; align-items: center; color: ${unsafeCSSVarV2('icon/secondary')}; @@ -48,18 +52,81 @@ export class EmbedIframeIdleCard extends WithDisposable(LitElement) { .affine-embed-iframe-idle-card:hover { cursor: pointer; } + + .affine-embed-iframe-idle-card.horizontal { + flex-direction: row; + + .icon { + width: 24px; + height: 24px; + + svg { + width: 24px; + height: 24px; + } + } + } + + .affine-embed-iframe-idle-card.vertical { + flex-direction: column; + justify-content: center; + overflow: hidden; + gap: 12px; + + .icon { + width: 176px; + height: 112px; + overflow-y: hidden; + + svg { + width: 112px; + height: 112px; + transform: rotate(12deg) translateY(18%); + } + } + + .text { + text-align: center; + white-space: normal; + word-break: break-word; + } + + @container affine-embed-iframe-idle-card (height < 180px) { + .icon { + display: none; + } + } + } `; override render() { + const { layout, width, height } = this.options; + const cardClasses = classMap({ + 'affine-embed-iframe-idle-card': true, + horizontal: layout === 'horizontal', + vertical: layout === 'vertical', + }); + + const cardWidth = width ? `${width}px` : '100%'; + const cardHeight = height ? `${height}px` : '100%'; + const cardStyle = styleMap({ + width: cardWidth, + height: cardHeight, + }); + return html` -
- - ${EmbedIcon({ width: '24px', height: '24px' })} - +
+ ${EmbedIcon()} Embed anything (Google Drive, Google Docs, Spotify, Miro…)
`; } + + @property({ attribute: false }) + accessor options: EmbedIframeStatusCardOptions = { + layout: 'horizontal', + height: IDLE_CARD_DEFAULT_HEIGHT, + }; } diff --git a/blocksuite/affine/blocks/block-embed/src/embed-iframe-block/components/embed-iframe-link-input-base.ts b/blocksuite/affine/blocks/block-embed/src/embed-iframe-block/components/embed-iframe-link-input-base.ts index 9526292317..c3ed81ae58 100644 --- a/blocksuite/affine/blocks/block-embed/src/embed-iframe-block/components/embed-iframe-link-input-base.ts +++ b/blocksuite/affine/blocks/block-embed/src/embed-iframe-block/components/embed-iframe-link-input-base.ts @@ -104,6 +104,7 @@ export class EmbedIframeLinkInputBase extends WithDisposable(LitElement) { this.disposables.addFromEvent(this, 'cut', stopPropagation); this.disposables.addFromEvent(this, 'copy', stopPropagation); this.disposables.addFromEvent(this, 'paste', stopPropagation); + this.disposables.addFromEvent(this, 'pointerdown', stopPropagation); } get store() { 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 c0bb0432a4..cff2e3136e 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 @@ -8,10 +8,9 @@ import { classMap } from 'lit/directives/class-map.js'; import { styleMap } from 'lit/directives/style-map.js'; import { getEmbedCardIcons } from '../../common/utils'; +import { LOADING_CARD_DEFAULT_HEIGHT } from '../consts'; import type { EmbedIframeStatusCardOptions } from '../types'; -const LOADING_CARD_DEFAULT_HEIGHT = 114; - export class EmbedIframeLoadingCard extends LitElement { static override styles = css` :host { @@ -20,7 +19,7 @@ export class EmbedIframeLoadingCard extends LitElement { } .affine-embed-iframe-loading-card { - container: affine-embed-iframe-loading-card / inline-size; + container: affine-embed-iframe-loading-card / size; display: flex; box-sizing: border-box; border-radius: 8px; @@ -147,6 +146,12 @@ export class EmbedIframeLoadingCard extends LitElement { } } } + + @container affine-embed-iframe-loading-card (height < 240px) { + .loading-banner { + display: none; + } + } } `; 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 index 97966c62b5..e0d0a03c28 100644 --- a/blocksuite/affine/blocks/block-embed/src/embed-iframe-block/consts.ts +++ b/blocksuite/affine/blocks/block-embed/src/embed-iframe-block/consts.ts @@ -6,3 +6,7 @@ export const DEFAULT_IFRAME_HEIGHT = 152; export const DEFAULT_IFRAME_WIDTH = '100%'; export const LINK_CREATE_POPUP_OFFSET = 4; + +export const IDLE_CARD_DEFAULT_HEIGHT = 48; +export const LOADING_CARD_DEFAULT_HEIGHT = 114; +export const ERROR_CARD_DEFAULT_HEIGHT = 114; 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 7432746f9d..82b7c37371 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 @@ -34,7 +34,10 @@ import { DEFAULT_IFRAME_HEIGHT, DEFAULT_IFRAME_WIDTH, EMBED_IFRAME_DEFAULT_CONTAINER_BORDER_RADIUS, + ERROR_CARD_DEFAULT_HEIGHT, + IDLE_CARD_DEFAULT_HEIGHT, LINK_CREATE_POPUP_OFFSET, + LOADING_CARD_DEFAULT_HEIGHT, } from './consts.js'; import { embedIframeBlockStyles } from './style.js'; import type { EmbedIframeStatusCardOptions } from './types.js'; @@ -109,10 +112,23 @@ export class EmbedIframeBlockComponent extends CaptionedBlockComponent { @@ -257,19 +273,21 @@ export class EmbedIframeBlockComponent extends CaptionedBlockComponent { - // We don't need to select the block when the block is in the surface - if (this.inSurface) { - return; - } - // when the block is in idle status and the url is not set, clear the selection // and show the link input popup if (this.isIdle$.value && !this.model.props.url) { - this.selectionManager.clear(['block']); + // when the block is in the surface, clear the surface selection + // otherwise, clear the block selection + this.selectionManager.clear([this.inSurface ? 'surface' : 'block']); this.toggleLinkInputPopup(); return; } + // We don't need to select the block when the block is in the surface + if (this.inSurface) { + return; + } + // otherwise, select the block this._selectBlock(); }; @@ -311,7 +329,9 @@ export class EmbedIframeBlockComponent extends CaptionedBlockComponent { if (this.isIdle$.value) { - return html``; + return html``; } if (this.isLoading$.value) { @@ -356,6 +376,7 @@ export class EmbedIframeBlockComponent extends CaptionedBlockComponent { this.refreshData().catch(console.error); diff --git a/blocksuite/affine/widgets/widget-drag-handle/package.json b/blocksuite/affine/widgets/widget-drag-handle/package.json index 0bb8adfd5e..7d9c148eeb 100644 --- a/blocksuite/affine/widgets/widget-drag-handle/package.json +++ b/blocksuite/affine/widgets/widget-drag-handle/package.json @@ -11,6 +11,7 @@ "license": "MIT", "dependencies": { "@blocksuite/affine-block-callout": "workspace:*", + "@blocksuite/affine-block-embed": "workspace:*", "@blocksuite/affine-block-list": "workspace:*", "@blocksuite/affine-block-note": "workspace:*", "@blocksuite/affine-block-paragraph": "workspace:*", diff --git a/blocksuite/affine/widgets/widget-drag-handle/src/watchers/drag-event-watcher.ts b/blocksuite/affine/widgets/widget-drag-handle/src/watchers/drag-event-watcher.ts index 5fbaf5f3c3..5c2a47666f 100644 --- a/blocksuite/affine/widgets/widget-drag-handle/src/watchers/drag-event-watcher.ts +++ b/blocksuite/affine/widgets/widget-drag-handle/src/watchers/drag-event-watcher.ts @@ -1,3 +1,7 @@ +import { + EMBED_IFRAME_DEFAULT_HEIGHT_IN_SURFACE, + EMBED_IFRAME_DEFAULT_WIDTH_IN_SURFACE, +} from '@blocksuite/affine-block-embed'; import { ParagraphBlockComponent } from '@blocksuite/affine-block-paragraph'; import { DropIndicator } from '@blocksuite/affine-components/drop-indicator'; import { @@ -511,6 +515,7 @@ export class DragEventWatcher { this._mergeSnapshotToCurDoc(snapshot, point).catch(console.error); } else { this._dropAsGfxBlock(snapshot, point); + this.widget.selectionHelper.selection.clear(['block']); } } else { this._onPageDrop(dropBlock, dragPayload, dropPayload, point); @@ -1052,7 +1057,10 @@ export class DragEventWatcher { Bound.deserialize(block.props.xywh as SerializedXYWH) ?? new Bound(0, 0, 0, 0); - if ( + if (block.flavour === 'affine:embed-iframe') { + blockBound.w = EMBED_IFRAME_DEFAULT_WIDTH_IN_SURFACE; + blockBound.h = EMBED_IFRAME_DEFAULT_HEIGHT_IN_SURFACE; + } else if ( block.flavour === 'affine:attachment' || block.flavour === 'affine:bookmark' || block.flavour.startsWith('affine:embed-') @@ -1132,17 +1140,22 @@ export class DragEventWatcher { this._dropToModel(surfaceSnapshot, this.gfx.surface!.id) .then(slices => { slices?.content.forEach((block, idx) => { - if ( - block.id === content[idx].id && - (block.flavour === 'affine:image' || + if (block.id === content[idx].id) { + if (block.flavour === 'affine:embed-iframe') { + store.updateBlock(block.id, { + xywh: content[idx].props.xywh, + }); + } else if ( + block.flavour === 'affine:image' || block.flavour === 'affine:attachment' || block.flavour === 'affine:bookmark' || - block.flavour.startsWith('affine:embed-')) - ) { - store.updateBlock(block.id, { - xywh: content[idx].props.xywh, - style: content[idx].props.style, - }); + block.flavour.startsWith('affine:embed-') + ) { + store.updateBlock(block.id, { + xywh: content[idx].props.xywh, + style: content[idx].props.style, + }); + } } }); }) @@ -1160,7 +1173,11 @@ export class DragEventWatcher { this._dropToModel(pageSnapshot, this.widget.doc.root!.id) .then(slices => { slices?.content.forEach((block, idx) => { - if ( + if (block.flavour === 'affine:embed-iframe') { + store.updateBlock(block.id, { + xywh: content[idx].props.xywh, + }); + } else if ( block.flavour === 'affine:attachment' || block.flavour.startsWith('affine:embed-') ) { diff --git a/blocksuite/affine/widgets/widget-drag-handle/tsconfig.json b/blocksuite/affine/widgets/widget-drag-handle/tsconfig.json index e83cee1b32..0a906357d0 100644 --- a/blocksuite/affine/widgets/widget-drag-handle/tsconfig.json +++ b/blocksuite/affine/widgets/widget-drag-handle/tsconfig.json @@ -8,6 +8,7 @@ "include": ["./src"], "references": [ { "path": "../../blocks/block-callout" }, + { "path": "../../blocks/block-embed" }, { "path": "../../blocks/block-list" }, { "path": "../../blocks/block-note" }, { "path": "../../blocks/block-paragraph" }, diff --git a/tools/utils/src/workspace.gen.ts b/tools/utils/src/workspace.gen.ts index ba26ef2d66..0fde1809cc 100644 --- a/tools/utils/src/workspace.gen.ts +++ b/tools/utils/src/workspace.gen.ts @@ -695,6 +695,7 @@ export const PackageList = [ name: '@blocksuite/affine-widget-drag-handle', workspaceDependencies: [ 'blocksuite/affine/blocks/block-callout', + 'blocksuite/affine/blocks/block-embed', 'blocksuite/affine/blocks/block-list', 'blocksuite/affine/blocks/block-note', 'blocksuite/affine/blocks/block-paragraph', diff --git a/yarn.lock b/yarn.lock index b867235bdd..f924530398 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3470,6 +3470,7 @@ __metadata: resolution: "@blocksuite/affine-widget-drag-handle@workspace:blocksuite/affine/widgets/widget-drag-handle" dependencies: "@blocksuite/affine-block-callout": "workspace:*" + "@blocksuite/affine-block-embed": "workspace:*" "@blocksuite/affine-block-list": "workspace:*" "@blocksuite/affine-block-note": "workspace:*" "@blocksuite/affine-block-paragraph": "workspace:*"