(frontend) add pdf blocks to the editor

Added pdf block in the editor.

Signed-off-by: dakshesh14 <65905942+dakshesh14@users.noreply.github.com>
This commit is contained in:
dakshesh14
2025-08-17 12:37:31 +05:30
committed by Anthony LC
parent 1fdf70bdcf
commit d8f90c04bd
10 changed files with 143 additions and 2 deletions

View File

@@ -79,6 +79,7 @@ jobs:
--check-filenames \
--ignore-words-list "Dokument,afterAll,excpt,statics" \
--skip "./git/" \
--skip "**/*.pdf" \
--skip "**/*.po" \
--skip "**/*.pot" \
--skip "**/*.json" \

View File

@@ -7,6 +7,10 @@ and this project adheres to
## [Unreleased]
### Added
- ✨(frontend) add pdf block to the editor #1293
### Changed
- ♻️(frontend) replace Arial font-family with token font #1411

View File

@@ -840,4 +840,38 @@ test.describe('Doc Editor', () => {
).toBeInViewport();
await expect(editor.getByText('Hello Child 14')).not.toBeInViewport();
});
test('it embeds PDF', async ({ page, browserName }) => {
await createDoc(page, 'doc-toolbar', browserName, 1);
await openSuggestionMenu({ page });
await page.getByText('Embed a PDF file').click();
const pdfBlock = page.locator('div[data-content-type="pdf"]').first();
await expect(pdfBlock).toBeVisible();
await page.getByText('Add PDF').click();
const fileChooserPromise = page.waitForEvent('filechooser');
await page.getByText('Upload file').click();
const fileChooser = await fileChooserPromise;
console.log(path.join(__dirname, 'assets/test-pdf.pdf'));
await fileChooser.setFiles(path.join(__dirname, 'assets/test-pdf.pdf'));
// Wait for the media-check to be processed
await page.waitForTimeout(1000);
const pdfEmbed = page
.locator('.--docs--editor-container embed.bn-visual-media')
.first();
// Check src of pdf
expect(await pdfEmbed.getAttribute('src')).toMatch(
/http:\/\/localhost:8083\/media\/.*\/attachments\/.*.pdf/,
);
await expect(pdfEmbed).toHaveAttribute('type', 'application/pdf');
await expect(pdfEmbed).toHaveAttribute('role', 'presentation');
});
});

View File

@@ -37,6 +37,7 @@ import {
AccessibleImageBlock,
CalloutBlock,
DividerBlock,
PdfBlock,
} from './custom-blocks';
import {
InterlinkingLinkInlineContent,
@@ -54,6 +55,7 @@ const baseBlockNoteSchema = withPageBreak(
callout: CalloutBlock,
divider: DividerBlock,
image: AccessibleImageBlock,
pdf: PdfBlock,
},
inlineContentSpecs: {
...defaultInlineContentSpecs,

View File

@@ -18,6 +18,7 @@ import {
import {
getCalloutReactSlashMenuItems,
getDividerReactSlashMenuItems,
getPdfReactSlashMenuItems,
} from './custom-blocks';
import { useGetInterlinkingMenuItems } from './custom-inline-content';
import XLMultiColumn from './xl-multi-column';
@@ -32,7 +33,10 @@ export const BlockNoteSuggestionMenu = () => {
DocsStyleSchema
>();
const { t } = useTranslation();
const basicBlocksName = useDictionary().slash_menu.page_break.group;
const dictionaryDate = useDictionary();
const basicBlocksName = dictionaryDate.slash_menu.page_break.group;
const fileBlocksName = dictionaryDate.slash_menu.file.group;
const getInterlinkingMenuItems = useGetInterlinkingMenuItems();
const getSlashMenuItems = useMemo(() => {
@@ -56,11 +60,12 @@ export const BlockNoteSuggestionMenu = () => {
getMultiColumnSlashMenuItems?.(editor) || [],
getPageBreakReactSlashMenuItems(editor),
getDividerReactSlashMenuItems(editor, t, basicBlocksName),
getPdfReactSlashMenuItems(editor, t, fileBlocksName),
),
query,
),
);
}, [basicBlocksName, editor, getInterlinkingMenuItems, t]);
}, [basicBlocksName, editor, getInterlinkingMenuItems, t, fileBlocksName]);
return (
<SuggestionMenuController

View File

@@ -0,0 +1,86 @@
/* eslint-disable react-hooks/rules-of-hooks */
import { insertOrUpdateBlock } from '@blocknote/core';
import {
AddFileButton,
ResizableFileBlockWrapper,
createReactBlockSpec,
} from '@blocknote/react';
import { TFunction } from 'i18next';
import { useTranslation } from 'react-i18next';
import { createGlobalStyle } from 'styled-components';
import { Box, Icon } from '@/components';
import { DocsBlockNoteEditor } from '../../types';
const PDFBlockStyle = createGlobalStyle`
.bn-block-content[data-content-type="pdf"] {
width: fit-content;
}
`;
type FileBlockEditor = Parameters<typeof AddFileButton>[0]['editor'];
export const PdfBlock = createReactBlockSpec(
{
type: 'pdf',
content: 'none',
propSchema: {
name: { default: '' as const },
url: { default: '' as const },
caption: { default: '' as const },
showPreview: { default: true },
previewWidth: { default: undefined, type: 'number' },
},
isFileBlock: true,
fileBlockAccept: ['application/pdf'],
},
{
render: ({ editor, block, contentRef }) => {
const { t } = useTranslation();
const pdfUrl = block.props.url;
return (
<Box ref={contentRef} className="bn-file-block-content-wrapper">
<PDFBlockStyle />
<ResizableFileBlockWrapper
buttonIcon={<Icon iconName="upload" />}
block={block}
editor={editor as unknown as FileBlockEditor}
buttonText={t('Add PDF')}
>
<Box
className="bn-visual-media"
role="presentation"
as="embed"
$width="100%"
$height="450px"
type="application/pdf"
src={pdfUrl}
contentEditable={false}
draggable={false}
onClick={() => editor.setTextCursorPosition(block)}
/>
</ResizableFileBlockWrapper>
</Box>
);
},
},
);
export const getPdfReactSlashMenuItems = (
editor: DocsBlockNoteEditor,
t: TFunction<'translation', undefined>,
group: string,
) => [
{
title: t('PDF'),
onItemClick: () => {
insertOrUpdateBlock(editor, { type: 'pdf' });
},
aliases: [t('pdf'), t('document'), t('embed'), t('file')],
group,
icon: <Icon iconName="picture_as_pdf" $size="18px" />,
subtext: t('Embed a PDF file'),
},
];

View File

@@ -1,3 +1,4 @@
export * from './AccessibleImageBlock';
export * from './CalloutBlock';
export * from './DividerBlock';
export * from './PdfBlock';

View File

@@ -16,6 +16,10 @@ export const docxDocsSchemaMappings: DocsExporterDocx['mappings'] = {
...docxDefaultSchemaMappings.blockMapping,
callout: blockMappingCalloutDocx,
divider: blockMappingDividerDocx,
// We're using the file block mapping for PDF blocks
// The types don't match exactly but the implementation is compatible
// eslint-disable-next-line @typescript-eslint/no-explicit-any
pdf: docxDefaultSchemaMappings.blockMapping.file as any,
quote: blockMappingQuoteDocx,
image: blockMappingImageDocx,
},

View File

@@ -23,6 +23,10 @@ export const pdfDocsSchemaMappings: DocsExporterPDF['mappings'] = {
divider: blockMappingDividerPDF,
quote: blockMappingQuotePDF,
table: blockMappingTablePDF,
// We're using the file block mapping for PDF blocks
// The types don't match exactly but the implementation is compatible
// eslint-disable-next-line @typescript-eslint/no-explicit-any
pdf: pdfDefaultSchemaMappings.blockMapping.file as any,
},
inlineContentMapping: {
...pdfDefaultSchemaMappings.inlineContentMapping,