✨(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:
1
.github/workflows/impress.yml
vendored
1
.github/workflows/impress.yml
vendored
@@ -79,6 +79,7 @@ jobs:
|
|||||||
--check-filenames \
|
--check-filenames \
|
||||||
--ignore-words-list "Dokument,afterAll,excpt,statics" \
|
--ignore-words-list "Dokument,afterAll,excpt,statics" \
|
||||||
--skip "./git/" \
|
--skip "./git/" \
|
||||||
|
--skip "**/*.pdf" \
|
||||||
--skip "**/*.po" \
|
--skip "**/*.po" \
|
||||||
--skip "**/*.pot" \
|
--skip "**/*.pot" \
|
||||||
--skip "**/*.json" \
|
--skip "**/*.json" \
|
||||||
|
|||||||
@@ -7,6 +7,10 @@ and this project adheres to
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- ✨(frontend) add pdf block to the editor #1293
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- ♻️(frontend) replace Arial font-family with token font #1411
|
- ♻️(frontend) replace Arial font-family with token font #1411
|
||||||
|
|||||||
BIN
src/frontend/apps/e2e/__tests__/app-impress/assets/test-pdf.pdf
Normal file
BIN
src/frontend/apps/e2e/__tests__/app-impress/assets/test-pdf.pdf
Normal file
Binary file not shown.
@@ -840,4 +840,38 @@ test.describe('Doc Editor', () => {
|
|||||||
).toBeInViewport();
|
).toBeInViewport();
|
||||||
await expect(editor.getByText('Hello Child 14')).not.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');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ import {
|
|||||||
AccessibleImageBlock,
|
AccessibleImageBlock,
|
||||||
CalloutBlock,
|
CalloutBlock,
|
||||||
DividerBlock,
|
DividerBlock,
|
||||||
|
PdfBlock,
|
||||||
} from './custom-blocks';
|
} from './custom-blocks';
|
||||||
import {
|
import {
|
||||||
InterlinkingLinkInlineContent,
|
InterlinkingLinkInlineContent,
|
||||||
@@ -54,6 +55,7 @@ const baseBlockNoteSchema = withPageBreak(
|
|||||||
callout: CalloutBlock,
|
callout: CalloutBlock,
|
||||||
divider: DividerBlock,
|
divider: DividerBlock,
|
||||||
image: AccessibleImageBlock,
|
image: AccessibleImageBlock,
|
||||||
|
pdf: PdfBlock,
|
||||||
},
|
},
|
||||||
inlineContentSpecs: {
|
inlineContentSpecs: {
|
||||||
...defaultInlineContentSpecs,
|
...defaultInlineContentSpecs,
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import {
|
|||||||
import {
|
import {
|
||||||
getCalloutReactSlashMenuItems,
|
getCalloutReactSlashMenuItems,
|
||||||
getDividerReactSlashMenuItems,
|
getDividerReactSlashMenuItems,
|
||||||
|
getPdfReactSlashMenuItems,
|
||||||
} from './custom-blocks';
|
} from './custom-blocks';
|
||||||
import { useGetInterlinkingMenuItems } from './custom-inline-content';
|
import { useGetInterlinkingMenuItems } from './custom-inline-content';
|
||||||
import XLMultiColumn from './xl-multi-column';
|
import XLMultiColumn from './xl-multi-column';
|
||||||
@@ -32,7 +33,10 @@ export const BlockNoteSuggestionMenu = () => {
|
|||||||
DocsStyleSchema
|
DocsStyleSchema
|
||||||
>();
|
>();
|
||||||
const { t } = useTranslation();
|
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 getInterlinkingMenuItems = useGetInterlinkingMenuItems();
|
||||||
|
|
||||||
const getSlashMenuItems = useMemo(() => {
|
const getSlashMenuItems = useMemo(() => {
|
||||||
@@ -56,11 +60,12 @@ export const BlockNoteSuggestionMenu = () => {
|
|||||||
getMultiColumnSlashMenuItems?.(editor) || [],
|
getMultiColumnSlashMenuItems?.(editor) || [],
|
||||||
getPageBreakReactSlashMenuItems(editor),
|
getPageBreakReactSlashMenuItems(editor),
|
||||||
getDividerReactSlashMenuItems(editor, t, basicBlocksName),
|
getDividerReactSlashMenuItems(editor, t, basicBlocksName),
|
||||||
|
getPdfReactSlashMenuItems(editor, t, fileBlocksName),
|
||||||
),
|
),
|
||||||
query,
|
query,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}, [basicBlocksName, editor, getInterlinkingMenuItems, t]);
|
}, [basicBlocksName, editor, getInterlinkingMenuItems, t, fileBlocksName]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SuggestionMenuController
|
<SuggestionMenuController
|
||||||
|
|||||||
@@ -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'),
|
||||||
|
},
|
||||||
|
];
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
export * from './AccessibleImageBlock';
|
export * from './AccessibleImageBlock';
|
||||||
export * from './CalloutBlock';
|
export * from './CalloutBlock';
|
||||||
export * from './DividerBlock';
|
export * from './DividerBlock';
|
||||||
|
export * from './PdfBlock';
|
||||||
|
|||||||
@@ -16,6 +16,10 @@ export const docxDocsSchemaMappings: DocsExporterDocx['mappings'] = {
|
|||||||
...docxDefaultSchemaMappings.blockMapping,
|
...docxDefaultSchemaMappings.blockMapping,
|
||||||
callout: blockMappingCalloutDocx,
|
callout: blockMappingCalloutDocx,
|
||||||
divider: blockMappingDividerDocx,
|
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,
|
quote: blockMappingQuoteDocx,
|
||||||
image: blockMappingImageDocx,
|
image: blockMappingImageDocx,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -23,6 +23,10 @@ export const pdfDocsSchemaMappings: DocsExporterPDF['mappings'] = {
|
|||||||
divider: blockMappingDividerPDF,
|
divider: blockMappingDividerPDF,
|
||||||
quote: blockMappingQuotePDF,
|
quote: blockMappingQuotePDF,
|
||||||
table: blockMappingTablePDF,
|
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: {
|
inlineContentMapping: {
|
||||||
...pdfDefaultSchemaMappings.inlineContentMapping,
|
...pdfDefaultSchemaMappings.inlineContentMapping,
|
||||||
|
|||||||
Reference in New Issue
Block a user