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 00000000..f0ea5df4 Binary files /dev/null and b/src/frontend/apps/e2e/__tests__/app-impress/assets/test-pdf.pdf differ 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,