diff --git a/blocksuite/affine/blocks/bookmark/src/configs/toolbar.ts b/blocksuite/affine/blocks/bookmark/src/configs/toolbar.ts index ab6ae6fa8d..7d1ebc85b8 100644 --- a/blocksuite/affine/blocks/bookmark/src/configs/toolbar.ts +++ b/blocksuite/affine/blocks/bookmark/src/configs/toolbar.ts @@ -407,7 +407,7 @@ const builtinSurfaceToolbarConfig = { if (options?.viewType !== 'embed') return; const { flavour, styles } = options; - let { style } = model.props; + let style: EmbedCardStyle = model.props.style; if (!styles.includes(style)) { style = styles[0]; @@ -482,24 +482,26 @@ const builtinSurfaceToolbarConfig = { } satisfies ToolbarActionGroup, { id: 'b.style', - actions: [ - { - id: 'horizontal', - label: 'Large horizontal style', - }, - { - id: 'list', - label: 'Small horizontal style', - }, - { - id: 'vertical', - label: 'Large vertical style', - }, - { - id: 'cube', - label: 'Small vertical style', - }, - ].filter(action => BookmarkStyles.includes(action.id as EmbedCardStyle)), + actions: ( + [ + { + id: 'horizontal', + label: 'Large horizontal style', + }, + { + id: 'list', + label: 'Small horizontal style', + }, + { + id: 'vertical', + label: 'Large vertical style', + }, + { + id: 'cube', + label: 'Small vertical style', + }, + ] as const + ).filter(action => BookmarkStyles.includes(action.id)), content(ctx) { const model = ctx.getCurrentModelByType(BookmarkBlockModel); if (!model) return null; diff --git a/blocksuite/affine/blocks/embed-doc/src/embed-linked-doc-block/configs/toolbar.ts b/blocksuite/affine/blocks/embed-doc/src/embed-linked-doc-block/configs/toolbar.ts index 8da910b21c..7d68545575 100644 --- a/blocksuite/affine/blocks/embed-doc/src/embed-linked-doc-block/configs/toolbar.ts +++ b/blocksuite/affine/blocks/embed-doc/src/embed-linked-doc-block/configs/toolbar.ts @@ -259,18 +259,18 @@ const builtinToolbarConfig = { conversionsActionGroup, { id: 'c.style', - actions: [ - { - id: 'horizontal', - label: 'Large horizontal style', - }, - { - id: 'list', - label: 'Small horizontal style', - }, - ].filter(action => - EmbedLinkedDocStyles.includes(action.id as EmbedCardStyle) - ), + actions: ( + [ + { + id: 'horizontal', + label: 'Large horizontal style', + }, + { + id: 'list', + label: 'Small horizontal style', + }, + ] as const + ).filter(action => EmbedLinkedDocStyles.includes(action.id)), content(ctx) { const model = ctx.getCurrentModelByType(EmbedLinkedDocModel); if (!model) return null; @@ -368,26 +368,26 @@ const builtinSurfaceToolbarConfig = { conversionsActionGroup, { id: 'c.style', - actions: [ - { - id: 'horizontal', - label: 'Large horizontal style', - }, - { - id: 'list', - label: 'Small horizontal style', - }, - { - id: 'vertical', - label: 'Large vertical style', - }, - { - id: 'cube', - label: 'Small vertical style', - }, - ].filter(action => - EmbedLinkedDocStyles.includes(action.id as EmbedCardStyle) - ), + actions: ( + [ + { + id: 'horizontal', + label: 'Large horizontal style', + }, + { + id: 'list', + label: 'Small horizontal style', + }, + { + id: 'vertical', + label: 'Large vertical style', + }, + { + id: 'cube', + label: 'Small vertical style', + }, + ] as const + ).filter(action => EmbedLinkedDocStyles.includes(action.id)), content(ctx) { const model = ctx.getCurrentModelByType(EmbedLinkedDocModel); if (!model) return null; diff --git a/blocksuite/affine/blocks/embed/src/configs/toolbar.ts b/blocksuite/affine/blocks/embed/src/configs/toolbar.ts index bfd82413d6..81ce40f61c 100644 --- a/blocksuite/affine/blocks/embed/src/configs/toolbar.ts +++ b/blocksuite/affine/blocks/embed/src/configs/toolbar.ts @@ -153,7 +153,7 @@ function createBuiltinToolbarConfigForExternal( .get(EmbedOptionProvider) .getEmbedBlockOptions(url); - let { style } = model.props; + let style: EmbedCardStyle = model.props.style; let flavour = 'affine:bookmark'; if (options?.viewType === 'card') { @@ -227,7 +227,7 @@ function createBuiltinToolbarConfigForExternal( if (options?.viewType !== 'embed') return; const { flavour, styles } = options; - let { style } = model.props; + let style: EmbedCardStyle = model.props.style; if (!styles.includes(style)) { style = @@ -441,7 +441,11 @@ const createBuiltinSurfaceToolbarConfigForExternal = ( let { style } = model.props; let flavour = 'affine:bookmark'; - if (!BookmarkStyles.includes(style)) { + if ( + !BookmarkStyles.includes( + style as (typeof BookmarkStyles)[number] + ) + ) { style = BookmarkStyles[0]; } @@ -517,26 +521,26 @@ const createBuiltinSurfaceToolbarConfigForExternal = ( } satisfies ToolbarActionGroup, { id: 'c.style', - actions: [ - { - id: 'horizontal', - label: 'Large horizontal style', - }, - { - id: 'list', - label: 'Small horizontal style', - }, - { - id: 'vertical', - label: 'Large vertical style', - }, - { - id: 'cube', - label: 'Small vertical style', - }, - ].filter(action => - EmbedGithubStyles.includes(action.id as EmbedCardStyle) - ), + actions: ( + [ + { + id: 'horizontal', + label: 'Large horizontal style', + }, + { + id: 'list', + label: 'Small horizontal style', + }, + { + id: 'vertical', + label: 'Large vertical style', + }, + { + id: 'cube', + label: 'Small vertical style', + }, + ] as const + ).filter(action => EmbedGithubStyles.includes(action.id)), when(ctx) { return Boolean(ctx.getCurrentModelByType(EmbedGithubModel)); }, diff --git a/blocksuite/affine/blocks/root/src/edgeless/clipboard/clipboard.ts b/blocksuite/affine/blocks/root/src/edgeless/clipboard/clipboard.ts index b5d74b2385..2e2d9d5ebc 100644 --- a/blocksuite/affine/blocks/root/src/edgeless/clipboard/clipboard.ts +++ b/blocksuite/affine/blocks/root/src/edgeless/clipboard/clipboard.ts @@ -9,7 +9,10 @@ import { getSurfaceComponent, } from '@blocksuite/affine-block-surface'; import { splitIntoLines } from '@blocksuite/affine-gfx-text'; -import type { ShapeElementModel } from '@blocksuite/affine-model'; +import type { + EmbedCardStyle, + ShapeElementModel, +} from '@blocksuite/affine-model'; import { BookmarkStyles, DEFAULT_NOTE_HEIGHT, @@ -236,7 +239,7 @@ export class EdgelessClipboardController extends PageClipboard { const options: Record = {}; let flavour = 'affine:bookmark'; - let style = BookmarkStyles[0]; + let style: EmbedCardStyle = BookmarkStyles[0]; let isInternalLink = false; let isLinkedBlock = false; diff --git a/blocksuite/affine/model/src/blocks/attachment/attachment-model.ts b/blocksuite/affine/model/src/blocks/attachment/attachment-model.ts index 4bf1d98762..f82d2033cb 100644 --- a/blocksuite/affine/model/src/blocks/attachment/attachment-model.ts +++ b/blocksuite/affine/model/src/blocks/attachment/attachment-model.ts @@ -30,11 +30,12 @@ import { AttachmentBlockTransformer } from './attachment-transformer.js'; */ type BackwardCompatibleUndefined = undefined; -export const AttachmentBlockStyles: EmbedCardStyle[] = [ +export const AttachmentBlockStyles = [ 'cubeThick', 'horizontalThin', 'pdf', -] as const; + 'citation', +] as const satisfies EmbedCardStyle[]; export type AttachmentBlockProps = { name: string; diff --git a/blocksuite/affine/model/src/blocks/bookmark/bookmark-model.ts b/blocksuite/affine/model/src/blocks/bookmark/bookmark-model.ts index 84bd990188..d3979a202e 100644 --- a/blocksuite/affine/model/src/blocks/bookmark/bookmark-model.ts +++ b/blocksuite/affine/model/src/blocks/bookmark/bookmark-model.ts @@ -15,13 +15,13 @@ import type { LinkPreviewData, } from '../../utils/index.js'; -export const BookmarkStyles: EmbedCardStyle[] = [ +export const BookmarkStyles = [ 'vertical', 'horizontal', 'list', 'cube', 'citation', -] as const; +] as const satisfies EmbedCardStyle[]; export type BookmarkBlockProps = { style: (typeof BookmarkStyles)[number]; diff --git a/blocksuite/affine/model/src/blocks/embed/figma/figma-model.ts b/blocksuite/affine/model/src/blocks/embed/figma/figma-model.ts index 34c3c2260e..a13227100e 100644 --- a/blocksuite/affine/model/src/blocks/embed/figma/figma-model.ts +++ b/blocksuite/affine/model/src/blocks/embed/figma/figma-model.ts @@ -8,7 +8,7 @@ export type EmbedFigmaBlockUrlData = { description: string | null; }; -export const EmbedFigmaStyles: EmbedCardStyle[] = ['figma'] as const; +export const EmbedFigmaStyles = ['figma'] as const satisfies EmbedCardStyle[]; export type EmbedFigmaBlockProps = { style: (typeof EmbedFigmaStyles)[number]; diff --git a/blocksuite/affine/model/src/blocks/embed/github/github-model.ts b/blocksuite/affine/model/src/blocks/embed/github/github-model.ts index 3d37ddfded..323cb3953f 100644 --- a/blocksuite/affine/model/src/blocks/embed/github/github-model.ts +++ b/blocksuite/affine/model/src/blocks/embed/github/github-model.ts @@ -13,12 +13,12 @@ export type EmbedGithubBlockUrlData = { assignees: string[] | null; }; -export const EmbedGithubStyles: EmbedCardStyle[] = [ +export const EmbedGithubStyles = [ 'vertical', 'horizontal', 'list', 'cube', -] as const; +] as const satisfies EmbedCardStyle[]; export type EmbedGithubBlockProps = { style: (typeof EmbedGithubStyles)[number]; diff --git a/blocksuite/affine/model/src/blocks/embed/html/html-model.ts b/blocksuite/affine/model/src/blocks/embed/html/html-model.ts index f96ebccf78..4a65c060bf 100644 --- a/blocksuite/affine/model/src/blocks/embed/html/html-model.ts +++ b/blocksuite/affine/model/src/blocks/embed/html/html-model.ts @@ -3,7 +3,7 @@ import { BlockModel } from '@blocksuite/store'; import type { EmbedCardStyle } from '../../../utils/index.js'; import { defineEmbedModel } from '../../../utils/index.js'; -export const EmbedHtmlStyles: EmbedCardStyle[] = ['html'] as const; +export const EmbedHtmlStyles = ['html'] as const satisfies EmbedCardStyle[]; export type EmbedHtmlBlockProps = { style: (typeof EmbedHtmlStyles)[number]; diff --git a/blocksuite/affine/model/src/blocks/embed/iframe/iframe-model.ts b/blocksuite/affine/model/src/blocks/embed/iframe/iframe-model.ts index a4b93685ae..cd21c0fa5b 100644 --- a/blocksuite/affine/model/src/blocks/embed/iframe/iframe-model.ts +++ b/blocksuite/affine/model/src/blocks/embed/iframe/iframe-model.ts @@ -7,7 +7,7 @@ import { BlockModel } from '@blocksuite/store'; import { type EmbedCardStyle } from '../../../utils/index.js'; -export const EmbedIframeStyles: EmbedCardStyle[] = ['figma'] as const; +export const EmbedIframeStyles = ['figma'] as const satisfies EmbedCardStyle[]; export type EmbedIframeBlockProps = { url: string; // the original url that user input diff --git a/blocksuite/affine/model/src/blocks/embed/linked-doc/linked-doc-model.ts b/blocksuite/affine/model/src/blocks/embed/linked-doc/linked-doc-model.ts index e119040d73..f375706b8d 100644 --- a/blocksuite/affine/model/src/blocks/embed/linked-doc/linked-doc-model.ts +++ b/blocksuite/affine/model/src/blocks/embed/linked-doc/linked-doc-model.ts @@ -4,17 +4,17 @@ import type { ReferenceInfo } from '../../../consts/doc.js'; import type { EmbedCardStyle } from '../../../utils/index.js'; import { defineEmbedModel } from '../../../utils/index.js'; -export const EmbedLinkedDocStyles: EmbedCardStyle[] = [ +export const EmbedLinkedDocStyles = [ 'vertical', 'horizontal', 'list', 'cube', 'horizontalThin', 'citation', -]; +] as const satisfies EmbedCardStyle[]; export type EmbedLinkedDocBlockProps = { - style: EmbedCardStyle; + style: (typeof EmbedLinkedDocStyles)[number]; caption: string | null; footnoteIdentifier: string | null; } & ReferenceInfo; diff --git a/blocksuite/affine/model/src/blocks/embed/loom/loom-model.ts b/blocksuite/affine/model/src/blocks/embed/loom/loom-model.ts index bc5e05ab8d..23f7049a75 100644 --- a/blocksuite/affine/model/src/blocks/embed/loom/loom-model.ts +++ b/blocksuite/affine/model/src/blocks/embed/loom/loom-model.ts @@ -10,7 +10,7 @@ export type EmbedLoomBlockUrlData = { description: string | null; }; -export const EmbedLoomStyles: EmbedCardStyle[] = ['video'] as const; +export const EmbedLoomStyles = ['video'] as const satisfies EmbedCardStyle[]; export type EmbedLoomBlockProps = { style: (typeof EmbedLoomStyles)[number]; diff --git a/blocksuite/affine/model/src/blocks/embed/synced-doc/synced-doc-model.ts b/blocksuite/affine/model/src/blocks/embed/synced-doc/synced-doc-model.ts index 0f248bbf55..ca9a0533a3 100644 --- a/blocksuite/affine/model/src/blocks/embed/synced-doc/synced-doc-model.ts +++ b/blocksuite/affine/model/src/blocks/embed/synced-doc/synced-doc-model.ts @@ -5,7 +5,9 @@ import type { ReferenceInfo } from '../../../consts/doc.js'; import type { EmbedCardStyle } from '../../../utils/index.js'; import { defineEmbedModel } from '../../../utils/index.js'; -export const EmbedSyncedDocStyles: EmbedCardStyle[] = ['syncedDoc']; +export const EmbedSyncedDocStyles = [ + 'syncedDoc', +] as const satisfies EmbedCardStyle[]; export type EmbedSyncedDocBlockProps = { style: EmbedCardStyle; diff --git a/blocksuite/affine/model/src/blocks/embed/types.ts b/blocksuite/affine/model/src/blocks/embed/types.ts index d6f34a68be..211494441e 100644 --- a/blocksuite/affine/model/src/blocks/embed/types.ts +++ b/blocksuite/affine/model/src/blocks/embed/types.ts @@ -1,6 +1,7 @@ import type { GfxModel } from '@blocksuite/std/gfx'; import type { BlockModel } from '@blocksuite/store'; +import type { BookmarkBlockModel } from '../bookmark'; import { EmbedFigmaModel } from './figma'; import { EmbedGithubModel } from './github'; import type { EmbedHtmlModel } from './html'; @@ -30,7 +31,10 @@ export type EmbedCardModel = InstanceType< ExternalEmbedModel | InternalEmbedModel >; -export type LinkableEmbedModel = EmbedCardModel | EmbedIframeBlockModel; +export type LinkableEmbedModel = + | EmbedCardModel + | EmbedIframeBlockModel + | BookmarkBlockModel; export type BuiltInEmbedModel = EmbedCardModel | EmbedHtmlModel; diff --git a/blocksuite/affine/model/src/blocks/embed/youtube/youtube-model.ts b/blocksuite/affine/model/src/blocks/embed/youtube/youtube-model.ts index 4e2233ccc4..8c9a2415c2 100644 --- a/blocksuite/affine/model/src/blocks/embed/youtube/youtube-model.ts +++ b/blocksuite/affine/model/src/blocks/embed/youtube/youtube-model.ts @@ -13,7 +13,7 @@ export type EmbedYoutubeBlockUrlData = { creatorImage: string | null; }; -export const EmbedYoutubeStyles: EmbedCardStyle[] = ['video'] as const; +export const EmbedYoutubeStyles = ['video'] as const satisfies EmbedCardStyle[]; export type EmbedYoutubeBlockProps = { style: (typeof EmbedYoutubeStyles)[number]; diff --git a/blocksuite/affine/widgets/drag-handle/src/middleware/card-style-updater.ts b/blocksuite/affine/widgets/drag-handle/src/middleware/card-style-updater.ts new file mode 100644 index 0000000000..1b2de1c0e5 --- /dev/null +++ b/blocksuite/affine/widgets/drag-handle/src/middleware/card-style-updater.ts @@ -0,0 +1,56 @@ +import { + AttachmentBlockModel, + BookmarkBlockModel, + EmbedGithubModel, + EmbedLinkedDocModel, + NoteBlockModel, +} from '@blocksuite/affine-model'; +import { matchModels } from '@blocksuite/affine-shared/utils'; +import type { BlockStdScope } from '@blocksuite/std'; +import type { TransformerMiddleware } from '@blocksuite/store'; + +export const cardStyleUpdater = + (std: BlockStdScope): TransformerMiddleware => + ({ slots }) => { + slots.beforeImport.subscribe(payload => { + if (payload.type !== 'block' || !payload.parent) return; + const parentModel = std.store.getModelById(payload.parent); + if (!matchModels(parentModel, [NoteBlockModel])) return; + + // TODO(@L-Sun): Refactor this after refactor `store.moveBlocks` + // Currently, drag a block will use store.moveBlocks to update the tree of blocks + // but the instance of it is not changed. + // So change the style of snapshot.props in the middleware is not working. + // Instead, we can change the style of the model instance in the middleware, + const model = std.store.getModelById(payload.snapshot.id); + if (!model) return; + + if (model instanceof AttachmentBlockModel) { + std.store.updateBlock(model, { + style: 'horizontalThin', + }); + return; + } + + if (model instanceof BookmarkBlockModel) { + std.store.updateBlock(model, { + style: 'horizontal', + }); + return; + } + + if (model instanceof EmbedGithubModel) { + std.store.updateBlock(model, { + style: 'horizontal', + }); + return; + } + + if (model instanceof EmbedLinkedDocModel) { + std.store.updateBlock(model, { + style: 'horizontal', + }); + return; + } + }); + }; diff --git a/blocksuite/affine/widgets/drag-handle/src/watchers/drag-event-watcher.ts b/blocksuite/affine/widgets/drag-handle/src/watchers/drag-event-watcher.ts index 54db204e8b..57a8bd25fb 100644 --- a/blocksuite/affine/widgets/drag-handle/src/watchers/drag-event-watcher.ts +++ b/blocksuite/affine/widgets/drag-handle/src/watchers/drag-event-watcher.ts @@ -76,6 +76,7 @@ import last from 'lodash-es/last'; import type { AffineDragHandleWidget } from '../drag-handle.js'; import { PreviewHelper } from '../helpers/preview-helper.js'; import { gfxBlocksFilter } from '../middleware/blocks-filter.js'; +import { cardStyleUpdater } from '../middleware/card-style-updater.js'; import { newIdCrossDoc } from '../middleware/new-id-cross-doc.js'; import { reorderList } from '../middleware/reorder-list'; import { @@ -1433,6 +1434,7 @@ export class DragEventWatcher { newIdCrossDoc(std), reorderList(std), surfaceRefToEmbed(std), + cardStyleUpdater(std), ]; if (selectedIds) { diff --git a/blocksuite/docs/api/@blocksuite/store/classes/Store.md b/blocksuite/docs/api/@blocksuite/store/classes/Store.md index e37ee44540..4f42a177f4 100644 --- a/blocksuite/docs/api/@blocksuite/store/classes/Store.md +++ b/blocksuite/docs/api/@blocksuite/store/classes/Store.md @@ -566,23 +566,29 @@ Optional flag to insert before sibling ### updateBlock() -> **updateBlock**(`modelOrId`, `callBackOrProps`): `void` +> **updateBlock**\<`T`\>(`modelOrId`, `callBackOrProps`): `void` Updates a block's properties or executes a callback in a transaction +#### Type Parameters + +##### T + +`T` *extends* `BlockModel`\<`object`\> = `BlockModel`\<`object`\> + #### Parameters ##### modelOrId The block model or block ID to update -`string` | `BlockModel`\<`object`\> +`string` | `T` ##### callBackOrProps Either a callback function to execute or properties to update -`Partial`\<`BlockProps`\> | () => `void` +() => `void` | `Partial`\<`BlockProps` \| `PropsOfModel`\<`T`\> & `BlockSysProps`\> #### Returns diff --git a/blocksuite/framework/store/src/model/block/types.ts b/blocksuite/framework/store/src/model/block/types.ts index 79566dd981..0c2e25df40 100644 --- a/blocksuite/framework/store/src/model/block/types.ts +++ b/blocksuite/framework/store/src/model/block/types.ts @@ -19,3 +19,5 @@ export type BlockSysProps = { children?: BlockModel[]; }; export type BlockProps = BlockSysProps & Record; + +export type PropsOfModel = T extends BlockModel ? P : never; diff --git a/blocksuite/framework/store/src/model/store/store.ts b/blocksuite/framework/store/src/model/store/store.ts index c81180f4a1..bcfccd0390 100644 --- a/blocksuite/framework/store/src/model/store/store.ts +++ b/blocksuite/framework/store/src/model/store/store.ts @@ -22,6 +22,8 @@ import { type BlockModel, type BlockOptions, type BlockProps, + type BlockSysProps, + type PropsOfModel, type YBlock, } from '../block/index.js'; import { DocCRUD } from './crud.js'; @@ -852,9 +854,12 @@ export class Store { * * @category Block CRUD */ - updateBlock( - modelOrId: BlockModel | string, - callBackOrProps: (() => void) | Partial + + updateBlock( + modelOrId: T | string, + callBackOrProps: + | (() => void) + | Partial<(PropsOfModel & BlockSysProps) | BlockProps> ) { if (this.readonly) { console.error('cannot modify data in readonly mode'); diff --git a/tests/blocksuite/e2e/bookmark.spec.ts b/tests/blocksuite/e2e/bookmark.spec.ts index a9e45d8d66..f59533719f 100644 --- a/tests/blocksuite/e2e/bookmark.spec.ts +++ b/tests/blocksuite/e2e/bookmark.spec.ts @@ -1,5 +1,6 @@ import './utils/declare-test-window.js'; +import type { BookmarkBlockComponent } from '@blocksuite/affine/blocks/bookmark'; import type { BlockSnapshot } from '@blocksuite/store'; import type { Page } from '@playwright/test'; import { expect } from '@playwright/test'; @@ -8,7 +9,9 @@ import { activeNoteInEdgeless, clickView, copyByKeyboard, + createNote, dragBlockToPoint, + edgelessCommonSetup, enterPlaygroundRoom, expectConsoleMessage, focusRichText, @@ -500,3 +503,28 @@ test.describe('embed github card', () => { await expect(banner).toBeVisible(); }); }); + +test('drag a card from canvas to note should not change the style of the card', async ({ + page, +}) => { + const url = 'https://github.com/toeverything/AFFiNE/pull/12660'; + + await edgelessCommonSetup(page); + await createNote(page, [-100, -300]); + await page.locator('edgeless-link-tool-button').click(); + await page.locator('.embed-card-modal-input').fill(url); + await pressEnter(page); + + const edgelessBookmark = page.locator('affine-edgeless-bookmark'); + await edgelessBookmark.click(); + await waitNextFrame(page); + const dragHandle = page.locator('.affine-drag-handle-container'); + await dragHandle.dragTo(page.locator('affine-edgeless-note')); + + const noteBookmark = page.locator('affine-bookmark'); + await expect(noteBookmark).toBeVisible(); + const style = await noteBookmark.evaluate( + (el: BookmarkBlockComponent) => el.model.props.style + ); + expect(style).toBe('horizontal'); +});