♻️(export) change pdf block from embed to iframe

When trying to print with a embed PDF the
browser's print dialog stays blocked and the user
can't print the document. Changing the PDF block
to use an iframe instead of an embed resolves
this issue.
This commit is contained in:
Anthony LC
2026-02-05 16:45:00 +01:00
parent 18edcf8537
commit b8c1504e7a
2 changed files with 36 additions and 42 deletions

View File

@@ -976,7 +976,10 @@ test.describe('Doc Editor', () => {
await expect(pdfBlock).toBeVisible(); await expect(pdfBlock).toBeVisible();
// Try with invalid PDF first // Try with invalid PDF first
await page.getByText(/Add (PDF|file)/).click(); await page
.getByText(/Add (PDF|file)/)
.first()
.click();
await page.locator('[data-test="embed-tab"]').click(); await page.locator('[data-test="embed-tab"]').click();
@@ -994,7 +997,6 @@ test.describe('Doc Editor', () => {
// Now with a valid PDF // Now with a valid PDF
await page.getByText(/Add (PDF|file)/).click(); await page.getByText(/Add (PDF|file)/).click();
const fileChooserPromise = page.waitForEvent('filechooser'); const fileChooserPromise = page.waitForEvent('filechooser');
const downloadPromise = page.waitForEvent('download');
await page.getByText(/Upload (PDF|file)/).click(); await page.getByText(/Upload (PDF|file)/).click();
const fileChooser = await fileChooserPromise; const fileChooser = await fileChooserPromise;
@@ -1003,24 +1005,16 @@ test.describe('Doc Editor', () => {
// Wait for the media-check to be processed // Wait for the media-check to be processed
await page.waitForTimeout(1000); await page.waitForTimeout(1000);
const pdfEmbed = page const pdfIframe = page
.locator('.--docs--editor-container embed.bn-visual-media') .locator('.--docs--editor-container iframe.bn-visual-media')
.first(); .first();
// Check src of pdf // Check src of pdf
expect(await pdfEmbed.getAttribute('src')).toMatch( expect(await pdfIframe.getAttribute('src')).toMatch(
/http:\/\/localhost:8083\/media\/.*\/attachments\/.*.pdf/, /http:\/\/localhost:8083\/media\/.*\/attachments\/.*.pdf/,
); );
await expect(pdfEmbed).toHaveAttribute('type', 'application/pdf'); await expect(pdfIframe).toHaveAttribute('role', 'presentation');
await expect(pdfEmbed).toHaveAttribute('role', 'presentation');
// Check download with original filename
await pdfBlock.click();
await page.locator('[data-test="downloadfile"]').click();
const download = await downloadPromise;
expect(download.suggestedFilename()).toBe('test-pdf.pdf');
}); });
test('it preserves text when switching between mobile and desktop views', async ({ test('it preserves text when switching between mobile and desktop views', async ({

View File

@@ -15,14 +15,18 @@ import {
import { TFunction } from 'i18next'; import { TFunction } from 'i18next';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { createGlobalStyle, css } from 'styled-components'; import { createGlobalStyle } from 'styled-components';
import { Box, Icon, Loading } from '@/components'; import { Box, Icon, Loading } from '@/components';
import Warning from '../../assets/warning.svg';
import { ANALYZE_URL } from '../../conf'; import { ANALYZE_URL } from '../../conf';
import { DocsBlockNoteEditor } from '../../types'; import { DocsBlockNoteEditor } from '../../types';
const PDFBlockStyle = createGlobalStyle` const PDFBlockStyle = createGlobalStyle`
.bn-block-content[data-content-type="pdf"] .bn-file-block-content-wrapper {
width: fit-content;
}
.bn-block-content[data-content-type="pdf"] .bn-file-block-content-wrapper[style*="fit-content"] { .bn-block-content[data-content-type="pdf"] .bn-file-block-content-wrapper[style*="fit-content"] {
width: 100% !important; width: 100% !important;
} }
@@ -59,11 +63,7 @@ interface PdfBlockComponentProps {
>; >;
} }
const PdfBlockComponent = ({ const PdfBlockComponent = ({ editor, block }: PdfBlockComponentProps) => {
editor,
block,
contentRef,
}: PdfBlockComponentProps) => {
const pdfUrl = block.props.url; const pdfUrl = block.props.url;
const { i18n, t } = useTranslation(); const { i18n, t } = useTranslation();
const lang = i18n.resolvedLanguage; const lang = i18n.resolvedLanguage;
@@ -114,27 +114,29 @@ const PdfBlockComponent = ({
void validatePDFContent(); void validatePDFContent();
}, [pdfUrl]); }, [pdfUrl]);
const isInvalidPDF =
!isPDFContentLoading && isPDFContent !== null && !isPDFContent;
if (isInvalidPDF) {
return (
<Box
$direction="row"
$gap="0.5rem"
$width="inherit"
$css="pointer-events: none;"
contentEditable={false}
draggable={false}
>
<Warning />
{t('Invalid or missing PDF file.')}
</Box>
);
}
return ( return (
<Box ref={contentRef} className="bn-file-block-content-wrapper"> <>
<PDFBlockStyle /> <PDFBlockStyle />
{isPDFContentLoading && <Loading />} {isPDFContentLoading && <Loading />}
{!isPDFContentLoading && isPDFContent !== null && !isPDFContent && (
<Box
$align="center"
$justify="center"
$color="#666"
$background="#f5f5f5"
$border="1px solid #ddd"
$height="300px"
$css={css`
text-align: center;
`}
contentEditable={false}
onClick={() => editor.setTextCursorPosition(block)}
>
{t('Invalid or missing PDF file.')}
</Box>
)}
<ResizableFileBlockWrapper <ResizableFileBlockWrapper
buttonIcon={ buttonIcon={
<Icon iconName="upload" $size="24px" $css="line-height: normal;" /> <Icon iconName="upload" $size="24px" $css="line-height: normal;" />
@@ -144,21 +146,19 @@ const PdfBlockComponent = ({
> >
{!isPDFContentLoading && isPDFContent && ( {!isPDFContentLoading && isPDFContent && (
<Box <Box
as="embed" as="iframe"
className="bn-visual-media" className="bn-visual-media"
role="presentation" role="presentation"
$width="100%" $width="100%"
$height="450px" $height="450px"
type="application/pdf"
src={pdfUrl} src={pdfUrl}
aria-label={block.props.name || t('PDF document')} aria-label={block.props.name || t('PDF document')}
contentEditable={false} contentEditable={false}
draggable={false} draggable={false}
onClick={() => editor.setTextCursorPosition(block)}
/> />
)} )}
</ResizableFileBlockWrapper> </ResizableFileBlockWrapper>
</Box> </>
); );
}; };