feat(editor): insert embed doc with quick search by default (#12165)

Close [BS-3404](https://linear.app/affine-design/issue/BS-3404/通过embed-doc时,插入的doc还是card-view,应该要默认embed-view)

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **New Features**
  - Added the ability to insert and display embedded synced documents, supporting different link types based on editor mode and user preferences.
  - Introduced new UI interactions and view options for embedded synced documents in edgeless mode.

- **Bug Fixes**
  - Updated UI selectors and preference keys to ensure consistent behavior and correct application of user settings.

- **Tests**
  - Added and updated end-to-end tests for embedding synced documents, including header interactions and viewport fitting.
  - Improved test coverage for quick search insertion and edgeless embed synced doc features.

- **Chores**
  - Renamed settings and updated exports to align with new embedding functionality.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
L-Sun
2025-05-08 10:21:32 +00:00
parent d379143db3
commit 11dfc1d1df
14 changed files with 113 additions and 12 deletions
@@ -2,9 +2,14 @@ import { insertEmbedIframeWithUrlCommand } from '@blocksuite/affine-block-embed'
import {
type InsertedLinkType,
insertEmbedLinkedDocCommand,
insertEmbedSyncedDocCommand,
type LinkableFlavour,
} from '@blocksuite/affine-block-embed-doc';
import { QuickSearchProvider } from '@blocksuite/affine-shared/services';
import {
DocModeProvider,
EditorSettingProvider,
QuickSearchProvider,
} from '@blocksuite/affine-shared/services';
import type { Command } from '@blocksuite/std';
import { insertBookmarkCommand } from './insert-bookmark';
@@ -26,12 +31,26 @@ export const insertLinkByQuickSearchCommand: Command<
// add linked doc
if ('docId' in result) {
std.command.exec(insertEmbedLinkedDocCommand, {
const editorMode = std.get(DocModeProvider).getEditorMode();
const editorSettings = std.get(EditorSettingProvider);
let flavour: LinkableFlavour = 'affine:embed-linked-doc';
if (editorMode === 'edgeless') {
flavour =
editorSettings.setting$.value.docCanvasPreferView ?? flavour;
}
const insertCommand =
flavour === 'affine:embed-linked-doc'
? insertEmbedLinkedDocCommand
: insertEmbedSyncedDocCommand;
std.command.exec(insertCommand, {
docId: result.docId,
params: result.params,
});
return {
flavour: 'affine:embed-linked-doc',
flavour,
};
}
@@ -5,6 +5,7 @@ import type { Command } from '@blocksuite/std';
export type LinkableFlavour =
| 'affine:bookmark'
| 'affine:embed-linked-doc'
| 'affine:embed-synced-doc'
| 'affine:embed-iframe'
| 'affine:embed-figma'
| 'affine:embed-github'
@@ -224,7 +224,7 @@ const conversionsActionGroup = {
) {
const editorSetting = ctx.std.getOptional(EditorSettingProvider);
editorSetting?.set?.(
'docDropCanvasPreferView',
'docCanvasPreferView',
'affine:embed-synced-doc'
);
}
@@ -0,0 +1 @@
export { insertEmbedSyncedDocCommand } from './insert-embed-synced-doc';
@@ -0,0 +1,20 @@
import { insertEmbedCard } from '@blocksuite/affine-block-embed';
import type { EmbedCardStyle, ReferenceParams } from '@blocksuite/affine-model';
import type { Command } from '@blocksuite/std';
export const insertEmbedSyncedDocCommand: Command<
{
docId: string;
params?: ReferenceParams;
},
{ blockId: string }
> = (ctx, next) => {
const { docId, params, std } = ctx;
const flavour = 'affine:embed-synced-doc';
const targetStyle: EmbedCardStyle = 'syncedDoc';
const props: Record<string, unknown> = { pageId: docId };
if (params) props.params = params;
const blockId = insertEmbedCard(std, { flavour, targetStyle, props });
if (!blockId) return;
next({ blockId });
};
@@ -171,7 +171,7 @@ const conversionsActionGroup = {
) {
const editorSetting = ctx.std.getOptional(EditorSettingProvider);
editorSetting?.set?.(
'docDropCanvasPreferView',
'docCanvasPreferView',
'affine:embed-linked-doc'
);
}
@@ -1,4 +1,5 @@
export * from './adapters';
export * from './commands';
export * from './configs';
export * from './edgeless-clipboard-config';
export * from './embed-synced-doc-block';
@@ -10,7 +10,7 @@ export const GeneralSettingSchema = z
.object({
edgelessScrollZoom: z.boolean().default(false),
edgelessDisableScheduleUpdate: z.boolean().default(false),
docDropCanvasPreferView: z
docCanvasPreferView: z
.enum(['affine:embed-linked-doc', 'affine:embed-synced-doc'])
.default('affine:embed-synced-doc'),
})
@@ -70,6 +70,8 @@ const ToggleButton = ({ model }: { model: EmbedSyncedDocModel }) => {
return (
<IconButton
data-testid="edgeless-embed-synced-doc-fold-button"
data-folded={isFolded}
className={styles.button}
size={styles.iconSize}
onClick={toggle}
@@ -153,8 +153,7 @@ export class DndService extends Service {
const flavour =
dropTarget === 'canvas'
? this.editorSettingService.editorSetting.docDropCanvasPreferView
.value
? this.editorSettingService.editorSetting.docCanvasPreferView.value
: 'affine:embed-linked-doc';
const { entity, bsEntity } = args.source.data;
@@ -29,6 +29,8 @@ test('should close embed editing modal when editor switching to page mode by sho
await page.keyboard.press('@');
await page.getByTestId('cmdk-label').getByText('Getting Started').click();
const toolbar = locateToolbar(page);
await toolbar.getByLabel('Switch view').click();
await toolbar.getByLabel('Card view').click();
await toolbar.getByLabel('Edit').click();
const editingModal = page.locator('embed-card-edit-modal');
@@ -1,9 +1,12 @@
import { test } from '@affine-test/kit/playwright';
import {
clickEdgelessModeButton,
clickView,
createEdgelessNoteBlock,
fitViewportToContent,
locateEditorContainer,
} from '@affine-test/kit/utils/editor';
import { pressEnter } from '@affine-test/kit/utils/keyboard';
import { openHomePage } from '@affine-test/kit/utils/load-page';
import {
clickNewPageButton,
@@ -18,6 +21,8 @@ test.beforeEach(async ({ page }) => {
await openHomePage(page);
await waitForEditorLoad(page);
await clickNewPageButton(page, title);
await pressEnter(page);
await page.keyboard.type('test content');
await clickEdgelessModeButton(page);
const container = locateEditorContainer(page);
await container.click();
@@ -56,3 +61,43 @@ test('should not show hidden note in embed view page mode', async ({
await expect(embedLink.getByText(/visible content/)).toBeVisible();
await expect(embedLink.getByText(/hidden content/)).toBeHidden();
});
test.describe('edgeless', () => {
test.beforeEach(async ({ page }) => {
await clickNewPageButton(page);
await clickEdgelessModeButton(page);
await clickView(page, [0, 0]);
await page.keyboard.type('@' + title);
await page
.getByTestId('cmdk-quick-search')
.getByText(/^Synced Block Test$/)
.click();
await fitViewportToContent(page);
});
test.describe('header of edgeless embed synced doc', () => {
test('should fold button works', async ({ page }) => {
const embedBlock = page.locator('affine-embed-edgeless-synced-doc-block');
const foldButton = embedBlock.getByTestId(
'edgeless-embed-synced-doc-fold-button'
);
const content = embedBlock.locator('editor-host');
await expect(foldButton).toHaveAttribute('data-folded', 'false');
await expect(content).toBeVisible();
await foldButton.click();
await expect(content).toBeHidden();
await expect(foldButton).toHaveAttribute('data-folded', 'true');
});
test('should show title in header', async ({ page }) => {
const embedBlock = page.locator('affine-embed-edgeless-synced-doc-block');
const headerTitle = embedBlock.getByTestId(
'edgeless-embed-synced-doc-title'
);
await expect(headerTitle).toHaveText(title);
});
});
});
+4 -4
View File
@@ -484,21 +484,21 @@ test('can use @ to open quick search to search for doc and insert into canvas',
// press enter to insert the page to canvas
await page.keyboard.press('Enter');
await expect(
page.locator('affine-embed-edgeless-linked-doc-block')
page.locator('affine-embed-edgeless-synced-doc-block')
).toBeVisible();
await expect(
page.locator('.affine-embed-linked-doc-content-title')
page.getByTestId('edgeless-embed-synced-doc-title')
).toContainText(docTitle);
// focus on the note block
await page.waitForTimeout(500);
await page
.locator('affine-embed-edgeless-linked-doc-block')
.locator('affine-embed-edgeless-synced-doc-block')
.click({ force: true });
await page.waitForTimeout(500);
// double clock to show peek view
await page
.locator('affine-embed-edgeless-linked-doc-block')
.locator('affine-embed-edgeless-synced-doc-block')
.dblclick({ force: true });
await expect(page.getByTestId('peek-view-modal')).toBeVisible();
});
+11
View File
@@ -163,6 +163,17 @@ export async function setViewportZoom(page: Page, zoom = 1, editorIndex = 0) {
}, zoom);
}
export async function fitViewportToContent(page: Page, editorIndex = 0) {
const container = locateEditorContainer(page, editorIndex);
return container.evaluate(container => {
const root = container.querySelector('affine-edgeless-root');
if (!root) {
throw new Error('Edgeless root not found');
}
root.gfx.fitToScreen();
});
}
/**
* Convert a canvas point to view coordinate
* @param point the coordinate on the canvas