Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/impress.yml
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ jobs:
--check-filenames \
--ignore-words-list "Dokument,afterAll,excpt,statics" \
--skip "./git/" \
--skip "**/*.pdf" \
--skip "**/*.po" \
--skip "**/*.pot" \
--skip "**/*.json" \
Expand Down
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Binary file not shown.
34 changes: 34 additions & 0 deletions src/frontend/apps/e2e/__tests__/app-impress/doc-editor.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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');
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ import {
AccessibleImageBlock,
CalloutBlock,
DividerBlock,
PdfBlock,
UploadLoaderBlock,
} from './custom-blocks';
import {
InterlinkingLinkInlineContent,
Expand All @@ -54,6 +56,8 @@ const baseBlockNoteSchema = withPageBreak(
callout: CalloutBlock,
divider: DividerBlock,
image: AccessibleImageBlock,
pdf: PdfBlock,
uploadLoader: UploadLoaderBlock,
},
inlineContentSpecs: {
...defaultInlineContentSpecs,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
import {
getCalloutReactSlashMenuItems,
getDividerReactSlashMenuItems,
getPdfReactSlashMenuItems,
} from './custom-blocks';
import { useGetInterlinkingMenuItems } from './custom-inline-content';
import XLMultiColumn from './xl-multi-column';
Expand All @@ -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(() => {
Expand All @@ -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
Expand Down
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems to have some drag and drop issue with the embed tag, not sure why.

-.Docs.1.webm

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure either.

Original file line number Diff line number Diff line change
@@ -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'),
},
];
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { createReactBlockSpec } from '@blocknote/react';

import { Box, Text } from '@/components';

import Loader from '../../assets/loader.svg';
import Warning from '../../assets/warning.svg';

export const UploadLoaderBlock = createReactBlockSpec(
{
type: 'uploadLoader',
propSchema: {
information: { default: '' as const },
type: {
default: 'loading' as const,
values: ['loading', 'warning'] as const,
},
},
content: 'none',
},
{
render: ({ block }) => {
return (
<Box className="bn-visual-media-wrapper" $direction="row" $gap="0.5rem">
{block.props.type === 'warning' ? (
<Warning />
) : (
<Loader style={{ animation: 'spin 1.5s linear infinite' }} />
)}
<Text>{block.props.information}</Text>
</Box>
);
},
},
);
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
export * from './AccessibleImageBlock';
export * from './CalloutBlock';
export * from './DividerBlock';
export * from './PdfBlock';
export * from './UploadLoaderBlock';
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@ import { useMediaUrl } from '@/core/config';
import { sleep } from '@/utils';

import { checkDocMediaStatus, useCreateDocAttachment } from '../api';
import Loader from '../assets/loader.svg?url';
import Warning from '../assets/warning.svg?url';
import { DocsBlockNoteEditor } from '../types';

/**
Expand All @@ -33,52 +31,6 @@ const loopCheckDocMediaStatus = async (url: string) => {
}
};

const informationStatus = (src: string, text: string) => {
const loadingContainer = document.createElement('div');
loadingContainer.style.display = 'flex';
loadingContainer.style.alignItems = 'center';
loadingContainer.style.justifyContent = 'left';
loadingContainer.style.padding = '10px';
loadingContainer.style.color = '#666';
loadingContainer.className =
'bn-visual-media bn-audio bn-file-name-with-icon';

// Create an image element for the SVG
const imgElement = document.createElement('img');
imgElement.src = src;

// Create a text span
const textSpan = document.createElement('span');
textSpan.textContent = text;
textSpan.style.marginLeft = '8px';
textSpan.style.verticalAlign = 'middle';
imgElement.style.animation = 'spin 1.5s linear infinite';

// Add the spinner and text to the container
loadingContainer.appendChild(imgElement);
loadingContainer.appendChild(textSpan);

return loadingContainer;
};

const replaceUploadContent = (blockId: string, elementReplace: HTMLElement) => {
const blockEl = document.body.querySelector(
`.bn-block[data-id="${blockId}"]`,
);

blockEl
?.querySelector('.bn-visual-media-wrapper .bn-visual-media')
?.replaceWith(elementReplace);

blockEl
?.querySelector('.bn-file-block-content-wrapper .bn-audio')
?.replaceWith(elementReplace);

blockEl
?.querySelector('.bn-file-block-content-wrapper .bn-file-name-with-icon')
?.replaceWith(elementReplace);
};

export const useUploadFile = (docId: string) => {
const {
mutateAsync: createDocAttachment,
Expand Down Expand Up @@ -122,35 +74,55 @@ export const useUploadStatus = (editor: DocsBlockNoteEditor) => {

// Delay to let the time to the dom to be rendered
const timoutId = setTimeout(() => {
replaceUploadContent(
blockId,
informationStatus(Loader.src, t('Analyzing file...')),
// Replace the resource block by a loading block
const { insertedBlocks, removedBlocks } = editor.replaceBlocks(
[blockId],
[
{
type: 'uploadLoader',
props: {
information: t('Analyzing file...'),
type: 'loading',
},
},
],
);

loopCheckDocMediaStatus(url)
.then((response) => {
const block = editor.getBlock(blockId);
if (!block) {
if (insertedBlocks.length === 0 || removedBlocks.length === 0) {
return;
}

block.props = {
...block.props,
const loadingBlockId = insertedBlocks[0].id;
const removedBlock = removedBlocks[0];

removedBlock.props = {
...removedBlock.props,
url: `${mediaUrl}${response.file}`,
};

editor.updateBlock(blockId, block);
// Replace the loading block with the resource block (image, audio, video, pdf ...)
editor.replaceBlocks([loadingBlockId], [removedBlock]);
})
.catch((error) => {
console.error('Error analyzing file:', error);

replaceUploadContent(
blockId,
informationStatus(
Warning.src,
t('The antivirus has detected an anomaly in your file.'),
const loadingBlock = insertedBlocks[0];

if (!loadingBlock) {
return;
}

loadingBlock.props = {
...loadingBlock.props,
type: 'warning',
information: t(
'The antivirus has detected an anomaly in your file.',
),
);
};

editor.updateBlock(loadingBlock.id, loadingBlock);
});
}, 250);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,5 @@ export * from './paragraphPDF';
export * from './quoteDocx';
export * from './quotePDF';
export * from './tablePDF';
export * from './uploadLoaderPDF';
export * from './uploadLoaderDocx';
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { Paragraph, TextRun } from 'docx';

import { DocsExporterDocx } from '../types';

export const blockMappingUploadLoaderDocx: DocsExporterDocx['mappings']['blockMapping']['uploadLoader'] =
(block) => {
return new Paragraph({
children: [
new TextRun(block.props.type === 'loading' ? '⏳' : '⚠️'),
new TextRun(' '),
new TextRun(block.props.information),
],
});
};
Loading
Loading