diff --git a/blocksuite/affine/blocks/attachment/src/attachment-block.ts b/blocksuite/affine/blocks/attachment/src/attachment-block.ts index 0062f1afdd..2640557ba9 100644 --- a/blocksuite/affine/blocks/attachment/src/attachment-block.ts +++ b/blocksuite/affine/blocks/attachment/src/attachment-block.ts @@ -28,7 +28,7 @@ import { checkAttachmentBlob, downloadAttachmentBlob } from './utils'; @Peekable({ enableOn: ({ model }: AttachmentBlockComponent) => { - return model.props.type.endsWith('pdf'); + return !model.doc.readonly && model.props.type.endsWith('pdf'); }, }) export class AttachmentBlockComponent extends CaptionedBlockComponent { diff --git a/blocksuite/affine/blocks/attachment/src/embed.ts b/blocksuite/affine/blocks/attachment/src/embed.ts index 19130080fa..310e4584f1 100644 --- a/blocksuite/affine/blocks/attachment/src/embed.ts +++ b/blocksuite/affine/blocks/attachment/src/embed.ts @@ -146,17 +146,20 @@ const embedConfig: AttachmentEmbedConfig[] = [ // More options: https://tinytip.co/tips/html-pdf-params/ // https://chromium.googlesource.com/chromium/src/+/refs/tags/121.0.6153.1/chrome/browser/resources/pdf/open_pdf_params_parser.ts const parameters = '#toolbar=0'; - return html``; + return html` + +
+ `; }, }, { diff --git a/blocksuite/affine/blocks/attachment/src/styles.ts b/blocksuite/affine/blocks/attachment/src/styles.ts index 09869f97cf..c782b9b2e8 100644 --- a/blocksuite/affine/blocks/attachment/src/styles.ts +++ b/blocksuite/affine/blocks/attachment/src/styles.ts @@ -136,4 +136,9 @@ export const styles = css` width: 100%; height: 100%; } + + .affine-attachment-embed-event-mask { + position: absolute; + inset: 0; + } `; diff --git a/packages/frontend/core/src/blocksuite/block-suite-editor/lit-adaper.tsx b/packages/frontend/core/src/blocksuite/block-suite-editor/lit-adaper.tsx index c558a82568..7a89997a6c 100644 --- a/packages/frontend/core/src/blocksuite/block-suite-editor/lit-adaper.tsx +++ b/packages/frontend/core/src/blocksuite/block-suite-editor/lit-adaper.tsx @@ -155,7 +155,9 @@ const usePatchSpecs = (mode: DocMode) => { ); const enablePDFEmbedPreview = useLiveData( - featureFlagService.flags.enable_pdf_embed_preview.$ + featureFlagService.flags.enable_pdf_embed_preview.$.map( + flag => !workspaceService.workspace.openOptions.isSharedMode && flag + ) ); const patchedSpecs = useMemo(() => { diff --git a/packages/frontend/core/src/modules/peek-view/view/peek-view-controls.tsx b/packages/frontend/core/src/modules/peek-view/view/peek-view-controls.tsx index ec119908ac..ba5a4f1ae0 100644 --- a/packages/frontend/core/src/modules/peek-view/view/peek-view-controls.tsx +++ b/packages/frontend/core/src/modules/peek-view/view/peek-view-controls.tsx @@ -40,8 +40,11 @@ type ControlButtonProps = { icon: ReactElement>; name: string; onClick: () => void; + enabled: boolean; }; +const filterByEnabled = (props: ControlButtonProps) => props.enabled; + export const ControlButton = ({ icon, nameKey, @@ -85,12 +88,13 @@ export const DefaultPeekViewControls = ({ const controls = useMemo(() => { return [ { - icon: , nameKey: 'close', name: t['com.affine.peek-view-controls.close'](), + icon: , onClick: () => peekView.close(), + enabled: true, }, - ].filter((opt): opt is ControlButtonProps => Boolean(opt)); + ].filter(filterByEnabled); }, [peekView, t]); return (
@@ -115,42 +119,46 @@ export const DocPeekViewControls = ({ const controls = useMemo(() => { return [ { - icon: , nameKey: 'close', name: t['com.affine.peek-view-controls.close'](), + icon: , onClick: () => peekView.close(), + enabled: true, }, { - icon: , - name: t['com.affine.peek-view-controls.open-doc'](), nameKey: 'open', + name: t['com.affine.peek-view-controls.open-doc'](), + icon: , onClick: () => { workbench.openDoc(docRef); peekView.close(false); }, + enabled: true, }, { - icon: , nameKey: 'new-tab', name: t['com.affine.peek-view-controls.open-doc-in-new-tab'](), + icon: , onClick: () => { workbench.openDoc(docRef, { at: 'new-tab' }); peekView.close(false); }, + enabled: true, }, - BUILD_CONFIG.isElectron && { - icon: , + { nameKey: 'split-view', name: t['com.affine.peek-view-controls.open-doc-in-split-view'](), + icon: , onClick: () => { workbench.openDoc(docRef, { at: 'beside' }); peekView.close(false); }, + enabled: BUILD_CONFIG.isElectron, }, { - icon: , nameKey: 'copy-link', name: t['com.affine.peek-view-controls.copy-link'](), + icon: , onClick: async () => { const preferredMode = docsService.list.getPrimaryMode(docRef.docId); const search = toDocSearchParams({ @@ -167,16 +175,18 @@ export const DocPeekViewControls = ({ await copyTextToClipboard(url.toString()); notify.success({ title: t['Copied link to clipboard']() }); }, + enabled: true, }, { - icon: , nameKey: 'info', name: t['com.affine.peek-view-controls.open-info'](), + icon: , onClick: () => { workspaceDialogService.open('doc-info', { docId: docRef.docId }); }, + enabled: true, }, - ].filter((opt): opt is ControlButtonProps => Boolean(opt)); + ].filter(filterByEnabled); }, [ t, peekView, @@ -213,10 +223,11 @@ export const AttachmentPeekViewControls = ({ const controls = useMemo(() => { const controls = [ { - icon: , nameKey: 'close', name: t['com.affine.peek-view-controls.close'](), + icon: , onClick: () => peekView.close(), + enabled: true, }, ]; if (!type) return controls; @@ -224,42 +235,45 @@ export const AttachmentPeekViewControls = ({ return [ ...controls, // TODO(@fundon): needs to be implemented on mobile - BUILD_CONFIG.isDesktopEdition && { - icon: , - name: t['com.affine.peek-view-controls.open-attachment'](), + { nameKey: 'open', + name: t['com.affine.peek-view-controls.open-attachment'](), + icon: , onClick: () => { workbench.openAttachment(docId, blockId); peekView.close(false); track.$.attachment.$.openAttachmentInFullscreen({ type }); }, + enabled: BUILD_CONFIG.isDesktopEdition, }, { - icon: , nameKey: 'new-tab', name: t['com.affine.peek-view-controls.open-attachment-in-new-tab'](), + icon: , onClick: () => { workbench.openAttachment(docId, blockId, { at: 'new-tab' }); peekView.close(false); track.$.attachment.$.openAttachmentInNewTab({ type }); }, + enabled: true, }, - BUILD_CONFIG.isElectron && { - icon: , + { nameKey: 'split-view', name: t[ 'com.affine.peek-view-controls.open-attachment-in-split-view' ](), + icon: , onClick: () => { workbench.openAttachment(docId, blockId, { at: 'beside' }); peekView.close(false); track.$.attachment.$.openAttachmentInSplitView({ type }); }, + enabled: BUILD_CONFIG.isElectron, }, - ].filter((opt): opt is ControlButtonProps => Boolean(opt)); + ].filter(filterByEnabled); }, [t, peekView, workbench, docId, blockId, type]); useEffect(() => { diff --git a/tests/affine-cloud/e2e/share-page.spec.ts b/tests/affine-cloud/e2e/share-page.spec.ts index fb9edab078..c43db08589 100644 --- a/tests/affine-cloud/e2e/share-page.spec.ts +++ b/tests/affine-cloud/e2e/share-page.spec.ts @@ -1,4 +1,5 @@ import { skipOnboarding, test } from '@affine-test/kit/playwright'; +import { importAttachment } from '@affine-test/kit/utils/attachment'; import { createRandomUser, enableCloudWorkspaceFromShareButton, @@ -8,6 +9,7 @@ import { import { clickEdgelessModeButton, getParagraphIds, + locateToolbar, } from '@affine-test/kit/utils/editor'; import { importImage } from '@affine-test/kit/utils/image'; import { copyByKeyboard } from '@affine-test/kit/utils/keyboard'; @@ -455,3 +457,60 @@ test('share page should support copying content', async ({ page, browser }) => { expect(copiedText).toContain('Hello World'); } }); + +test('should disable opening peek view with pdf viewer in readonly and sharing modes', async ({ + page, + browser, +}) => { + await page.reload(); + await waitForEditorLoad(page); + await createLocalWorkspace( + { + name: 'test', + }, + page + ); + await enableCloudWorkspaceFromShareButton(page); + const title = getBlockSuiteEditorTitle(page); + await title.click(); + await page.keyboard.press('Enter'); + await importAttachment(page, 'lorem-ipsum.pdf'); + + const toolbar = locateToolbar(page); + const switchViewButton = toolbar.getByLabel('Switch view'); + const embedViewButton = toolbar.getByLabel('Embed view'); + + const attachment = page.locator('affine-attachment'); + await attachment.click(); + + await switchViewButton.click(); + await embedViewButton.click(); + + await expect(attachment.locator('iframe')).toBeVisible(); + + // enable share page and copy page link + await enableShare(page); + await page.getByTestId('share-menu-copy-link-button').click(); + await page.getByTestId('share-link-menu-copy-page').click(); + + // check share page is accessible + { + const context = await browser.newContext(); + await skipOnboarding(context); + const url: string = await page.evaluate(() => + navigator.clipboard.readText() + ); + const page2 = await context.newPage(); + await page2.goto(url); + await waitForEditorLoad(page2); + + const attachment = page2.locator('affine-attachment'); + + await expect(attachment.locator('iframe')).toBeVisible(); + + await attachment.dblclick(); + + const pdfViewer = page2.getByTestId('pdf-viewer'); + await expect(pdfViewer).not.toBeVisible(); + } +}); diff --git a/tests/affine-local/e2e/attachment-preview.spec.ts b/tests/affine-local/e2e/attachment-preview.spec.ts index 8c719177fa..2da6375114 100644 --- a/tests/affine-local/e2e/attachment-preview.spec.ts +++ b/tests/affine-local/e2e/attachment-preview.spec.ts @@ -1,4 +1,6 @@ -import { Path, test } from '@affine-test/kit/playwright'; +import { test } from '@affine-test/kit/playwright'; +import { importAttachment } from '@affine-test/kit/utils/attachment'; +import { locateToolbar } from '@affine-test/kit/utils/editor'; import { openHomePage } from '@affine-test/kit/utils/load-page'; import { clickNewPageButton, @@ -14,8 +16,6 @@ import { import type { Page } from '@playwright/test'; import { expect } from '@playwright/test'; -const fixturesDir = Path.dir(import.meta.url).join('../../fixtures'); - async function clickPeekViewControl(page: Page, n = 0) { await page.getByTestId('peek-view-control').nth(n).click(); await page.waitForTimeout(500); @@ -41,22 +41,6 @@ async function enablePDFEmbedView(page: Page) { await page.keyboard.press('Escape'); } -async function insertAttachment(page: Page, filepath: string) { - await page.evaluate(() => { - // Force fallback to input[type=file] in tests - // See https://github.com/microsoft/playwright/issues/8850 - window.showOpenFilePicker = undefined; - }); - - const fileChooser = page.waitForEvent('filechooser'); - - // open slash menu - await page.keyboard.type('/attachment', { delay: 50 }); - await page.keyboard.press('Enter'); - - await (await fileChooser).setFiles(filepath); -} - test('attachment preview should be shown', async ({ page }) => { await openHomePage(page); await waitForEditorLoad(page); @@ -65,7 +49,7 @@ test('attachment preview should be shown', async ({ page }) => { await title.click(); await page.keyboard.press('Enter'); - await insertAttachment(page, fixturesDir.join('lorem-ipsum.pdf').value); + await importAttachment(page, 'lorem-ipsum.pdf'); await page.locator('affine-attachment').first().dblclick(); @@ -103,7 +87,7 @@ test('attachment preview can be expanded', async ({ page }) => { await title.click(); await page.keyboard.press('Enter'); - await insertAttachment(page, fixturesDir.join('lorem-ipsum.pdf').value); + await importAttachment(page, 'lorem-ipsum.pdf'); await page.locator('affine-attachment').first().dblclick(); @@ -154,17 +138,17 @@ test('should preview PDF in embed view', async ({ page }) => { await page.keyboard.press('Enter'); - await insertAttachment(page, fixturesDir.join('lorem-ipsum.pdf').value); + await importAttachment(page, 'lorem-ipsum.pdf'); const attachment = page.locator('affine-attachment'); await attachment.click(); - const toolbar = page.locator('affine-toolbar-widget editor-toolbar'); + const toolbar = locateToolbar(page); await expect(toolbar).toBeVisible(); // Switches to embed view - await toolbar.getByRole('button', { name: 'Switch view' }).click(); - await toolbar.getByRole('button', { name: 'Embed view' }).click(); + await toolbar.getByLabel('Switch view').click(); + await toolbar.getByLabel('Embed view').click(); await page.waitForTimeout(500); @@ -260,12 +244,12 @@ test('should sync name in pdf embed view', async ({ page }) => { await title.click(); await page.keyboard.press('Enter'); - await insertAttachment(page, fixturesDir.join('lorem-ipsum.pdf').value); + await importAttachment(page, 'lorem-ipsum.pdf'); const attachment = page.locator('affine-attachment'); await attachment.click(); - const toolbar = page.locator('affine-toolbar-widget editor-toolbar'); + const toolbar = locateToolbar(page); await expect(toolbar).toBeVisible(); const attachmentTitle = attachment.locator( @@ -285,8 +269,8 @@ test('should sync name in pdf embed view', async ({ page }) => { await attachment.click(); // Switches to embed view - await toolbar.getByRole('button', { name: 'Switch view' }).click(); - await toolbar.getByRole('button', { name: 'Embed view' }).click(); + await toolbar.getByLabel('Switch view').click(); + await toolbar.getByLabel('Embed view').click(); await page.waitForTimeout(500); @@ -305,3 +289,57 @@ test('should sync name in pdf embed view', async ({ page }) => { await page.keyboard.press('Enter'); await expect(portalName).toHaveText('lorem-ipsum.pdf'); }); + +test('should enable pointer event in pdf viewer', async ({ page }) => { + await openHomePage(page); + await clickNewPageButton(page); + await waitForEmptyEditor(page); + + const title = getBlockSuiteEditorTitle(page); + await title.click(); + await page.keyboard.type('PDF preview'); + + await page.keyboard.press('Enter'); + + await importAttachment(page, 'lorem-ipsum.pdf'); + + const attachment = page.locator('affine-attachment'); + await attachment.click(); + + const attachmentSelection = attachment.locator('affine-block-selection'); + + const toolbar = locateToolbar(page); + + // Switches to embed view + await toolbar.getByLabel('Switch view').click(); + await toolbar.getByLabel('Embed view').click(); + + await attachment.locator('iframe').waitFor({ state: 'visible' }); + + const rect = await attachment.boundingBox(); + expect(rect).not.toBeNull(); + + const { x, y, width, height } = rect!; + const startPoint = [x - 10, y - 10]; + const endPoint = [x + width / 2, y + height / 2]; + + await page.mouse.move(startPoint[0], startPoint[1]); + await page.mouse.down(); + await page.mouse.move(endPoint[0], endPoint[1]); + await page.mouse.move( + endPoint[0] + width / 2 + 10, + endPoint[1] + height / 2 + 10 + ); + + await expect(attachmentSelection).toBeVisible(); + + await page.mouse.move(startPoint[0], startPoint[1]); + + await expect(attachmentSelection).toBeHidden(); + + await page.mouse.move(startPoint[0] - 50, startPoint[1] - 50); + + await page.mouse.up(); + + await expect(attachmentSelection).toBeHidden(); +}); diff --git a/tests/kit/src/utils/attachment.ts b/tests/kit/src/utils/attachment.ts new file mode 100644 index 0000000000..427fbb1b7a --- /dev/null +++ b/tests/kit/src/utils/attachment.ts @@ -0,0 +1,21 @@ +import type { Page } from '@playwright/test'; + +import { Path } from '../playwright'; + +const fixturesDir = Path.dir(import.meta.url).join('../../../fixtures'); + +export async function importAttachment(page: Page, file: string) { + await page.evaluate(() => { + // Force fallback to input[type=file] in tests + // See https://github.com/microsoft/playwright/issues/8850 + window.showOpenFilePicker = undefined; + }); + + const fileChooser = page.waitForEvent('filechooser'); + + // open slash menu + await page.keyboard.type('/attachment', { delay: 50 }); + await page.keyboard.press('Enter'); + + await (await fileChooser).setFiles(fixturesDir.join(file).value); +}