From d8f90c04bddc3f838be5c7a7a96d8b281e43d574 Mon Sep 17 00:00:00 2001 From: dakshesh14 <65905942+dakshesh14@users.noreply.github.com> Date: Sun, 17 Aug 2025 12:37:31 +0530 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8(frontend)=20add=20pdf=20blocks=20to?= =?UTF-8?q?=20the=20editor?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Added pdf block in the editor. Signed-off-by: dakshesh14 <65905942+dakshesh14@users.noreply.github.com> --- .github/workflows/impress.yml | 1 + CHANGELOG.md | 4 + .../__tests__/app-impress/assets/test-pdf.pdf | Bin 0 -> 4263 bytes .../__tests__/app-impress/doc-editor.spec.ts | 34 +++++++ .../doc-editor/components/BlockNoteEditor.tsx | 2 + .../components/BlockNoteSuggestionMenu.tsx | 9 +- .../components/custom-blocks/PdfBlock.tsx | 86 ++++++++++++++++++ .../components/custom-blocks/index.ts | 1 + .../features/docs/doc-export/mappingDocx.tsx | 4 + .../features/docs/doc-export/mappingPDF.tsx | 4 + 10 files changed, 143 insertions(+), 2 deletions(-) create mode 100644 src/frontend/apps/e2e/__tests__/app-impress/assets/test-pdf.pdf create mode 100644 src/frontend/apps/impress/src/features/docs/doc-editor/components/custom-blocks/PdfBlock.tsx diff --git a/.github/workflows/impress.yml b/.github/workflows/impress.yml index 8cc2ff82..2e01cd68 100644 --- a/.github/workflows/impress.yml +++ b/.github/workflows/impress.yml @@ -79,6 +79,7 @@ jobs: --check-filenames \ --ignore-words-list "Dokument,afterAll,excpt,statics" \ --skip "./git/" \ + --skip "**/*.pdf" \ --skip "**/*.po" \ --skip "**/*.pot" \ --skip "**/*.json" \ diff --git a/CHANGELOG.md b/CHANGELOG.md index 6badccff..a35e49d9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/src/frontend/apps/e2e/__tests__/app-impress/assets/test-pdf.pdf b/src/frontend/apps/e2e/__tests__/app-impress/assets/test-pdf.pdf new file mode 100644 index 0000000000000000000000000000000000000000..f0ea5df4e39c541ec3964078a0837d8c2d2e01db GIT binary patch literal 4263 zcma)A2{=^i8w_oMI5vol3#461$? zc&JVf@QGX5TxW849Bq%tcuU$^d_hf9hj>n1xb8Oh|AMB$5du!vSeOF(l>v zh!8~zaXHj&6$pe4Fp7|YHEBuTS}c?DKx`p|Gyw>`$gLrSJgnQ@-cTd`DelP&k{%we z5X!|7VAmeHTm|7EV{Dv!eH?s@9YI9p2q%It^~8fCB?1y75di|tBx0eJG#J{t%f-@Q$k0&)9265VK**1bh>+l9{DIYd`T8>;P>-csnR)t=(-74{YP&$ekJ0J$m)Cc9xU=wiLikSq`d zM79YK8D{|*Qd|mx=`F^iaJjo255ht3B->t31$Jw@kao~P19XcdBw{J9=+Dw1lDf|$ z8)_uT41`K|5{rOmy^j6N<-S%$S`4xVcn`io5WUAjKk8EQke~3r?$ZqyDq$`^-e^iuKQm|jMpf_ijNkL} z@LPJj4th_|5Qla}Wjj2~S$$;w(eL898G@rBEc4w;t=f!lZ{J>c`9x9FneQ^@wuSo| zm5CRZzV6&*)AHIf)%X`Zn%m4>4_l0Pr?kzKy+k(}n!Cli#7)I(=<|_g4ddqP#p|zUl}x5QxPRG6 zl6l6no-)gNy7&68vsG_ZJ2t!;RyS&t|L|L))#r33U&Ysg}y+A4@8~ zec@?XAE0Q=P|jCfFpl!M^f!Nvue|oe^_A7L6PkH$*UI@CO4nnSUqg~uJhS1e&UK7p zO#XUpxK;aahjavXmR9Zco0=i-qZ`w%C11JK?CTl1DJ^~ZD7K+PQn@GQY;@GqW5Z~^ z%kjlwXu7Fbeu2iX;O{Q}bE{iZLr!$-cAwTKuRG7p)qrkR$eR2b&t6q3*Jf^`oC{c^ z-ITUu>;kb4l;)81!B(ei)qeBScu_{oh4>oHvOBC*aQ^Vl+JtFuA+@2$H=L=@RTw$Z zO7+GVom?Wgame}l=mnw4u8vxpgBTUYI8{GB`JCuzZ29?)dTz=o^Tbm`k>9>kql0uV zFPZZ`vf%Qf)Ww8oU7PRInP0YSuU4A*x!M*OyZhAyRxC-)YFOrT@dbi&eTrU(`4?-B zX&tK=VzaH0ADkEtdt{tl?+Q;m>EFr>$hf%ekN3@n_8VrD1|KW;uzNJdb=={cZI1#z zoTe|>_-<+Rle^z*AJ?U9DfHiya`S0fn-P8PrJPSo;!ji^Qm4Jm-}cl#K%}f3`lj%3 z#Ur)bdHyM}7RGgTtfH0iVM(jPs_jP4RYvJ)6^9D>f&~vt&Ej?j< z@|2pSk8!02Wm#qwFB5V+^wmzD4;prE_?IYRtHB}JyOc;b-H>mMytF@e?wUC7eSOxc zYL804GcS`f+mn3*PE)U&d`f_bcy)rGKO?Gk39e({INPp@|U!SfBuNH%(q;ITR%N^0X(?a*XZ7!%q zTFg8=@4ZMbTo-eYem$Xxd3wr~sUHqJ&ox^4VET!BM~u<~J|9^ZxTfXn+f%!m>{Ci! zG_;>mc7^(0G^OQZE4ITv^a;%+?OD;E?@s8bHSRbz<;8_bU6Xh*q4|b!XP>suX(sab zrfE;oEPiIkoHya~?mdZ(0ziRHWn!9<7OPA`}kGY2A@;PAbhg>&a_8-_ip!?zFI|(g z(+!3=%wG}x%dhK-cT^6wP!_U{D;Ho_pS`KM?{ZnYGl?-r?QmAORsE~`m*CD@LCWOg zf4F|iCu;8&exJB+x7CdexxVA8RVNp77H1ncr|<9&XZ-!p;7G~k2yb3B{BCOQ+fQTn2KgafDkZAhHx4G592y7w`k^QoME{H9 z+wNAO#jwW3H=1A88rJO8_j6xCaT$6^|J9_}5U<${zgZ*iziH*jwc~U(s`NJ9Ef2MK z9+4Mdvb4Bh)b-Pugvb26d`jx^GJo?_2b+V3&UQ`9{cCm0yU0)GhXv;sd1&u6+K8+? z))*u*ONt1W-AU8(?l}3T&V%=oJ!8^F`@Gc%wEUCpmwJ;&<&=Q6BO)T(-uTB-3qH`? z((=Nq8q^Cu)t#_nMOhxX?pCjowfA^w_>(FACI04VV>h;iP9G=MHmjeMIQ^i+sVYZD zrz-F7n0-}u>~a^!4byG9*I1xBDl9f+FjdiD-l4(}3RB@955&SS1_lK|y)uN!2D20S zK++#F3zKN!XaC-1OiF`gV9p+hW1?UZ{iiO%A_v%i%3y3TFEAL=2jUno)=x&r1>g!K z@BTLcUmS~*F`|3QWCYZEdF;NeAiYzqBMt;v&AGq2X4*&oF literal 0 HcmV?d00001 diff --git a/src/frontend/apps/e2e/__tests__/app-impress/doc-editor.spec.ts b/src/frontend/apps/e2e/__tests__/app-impress/doc-editor.spec.ts index 36520cb7..fcb4374d 100644 --- a/src/frontend/apps/e2e/__tests__/app-impress/doc-editor.spec.ts +++ b/src/frontend/apps/e2e/__tests__/app-impress/doc-editor.spec.ts @@ -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'); + }); }); diff --git a/src/frontend/apps/impress/src/features/docs/doc-editor/components/BlockNoteEditor.tsx b/src/frontend/apps/impress/src/features/docs/doc-editor/components/BlockNoteEditor.tsx index b12e5981..3eb1a71e 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-editor/components/BlockNoteEditor.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-editor/components/BlockNoteEditor.tsx @@ -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, diff --git a/src/frontend/apps/impress/src/features/docs/doc-editor/components/BlockNoteSuggestionMenu.tsx b/src/frontend/apps/impress/src/features/docs/doc-editor/components/BlockNoteSuggestionMenu.tsx index 4e8c6e30..62999dc8 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-editor/components/BlockNoteSuggestionMenu.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-editor/components/BlockNoteSuggestionMenu.tsx @@ -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 ( [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 ( + + + } + block={block} + editor={editor as unknown as FileBlockEditor} + buttonText={t('Add PDF')} + > + editor.setTextCursorPosition(block)} + /> + + + ); + }, + }, +); + +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: , + subtext: t('Embed a PDF file'), + }, +]; diff --git a/src/frontend/apps/impress/src/features/docs/doc-editor/components/custom-blocks/index.ts b/src/frontend/apps/impress/src/features/docs/doc-editor/components/custom-blocks/index.ts index 99c1ee27..1a2ea21e 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-editor/components/custom-blocks/index.ts +++ b/src/frontend/apps/impress/src/features/docs/doc-editor/components/custom-blocks/index.ts @@ -1,3 +1,4 @@ export * from './AccessibleImageBlock'; export * from './CalloutBlock'; export * from './DividerBlock'; +export * from './PdfBlock'; diff --git a/src/frontend/apps/impress/src/features/docs/doc-export/mappingDocx.tsx b/src/frontend/apps/impress/src/features/docs/doc-export/mappingDocx.tsx index 5c44f060..73e66d11 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-export/mappingDocx.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-export/mappingDocx.tsx @@ -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, }, diff --git a/src/frontend/apps/impress/src/features/docs/doc-export/mappingPDF.tsx b/src/frontend/apps/impress/src/features/docs/doc-export/mappingPDF.tsx index 9e96b32a..1b72ab1c 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-export/mappingPDF.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-export/mappingPDF.tsx @@ -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,