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 3eb1a71e..68d7c269 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
@@ -38,6 +38,7 @@ import {
CalloutBlock,
DividerBlock,
PdfBlock,
+ UploadLoaderBlock,
} from './custom-blocks';
import {
InterlinkingLinkInlineContent,
@@ -56,6 +57,7 @@ const baseBlockNoteSchema = withPageBreak(
divider: DividerBlock,
image: AccessibleImageBlock,
pdf: PdfBlock,
+ uploadLoader: UploadLoaderBlock,
},
inlineContentSpecs: {
...defaultInlineContentSpecs,
diff --git a/src/frontend/apps/impress/src/features/docs/doc-editor/components/custom-blocks/UploadLoaderBlock.tsx b/src/frontend/apps/impress/src/features/docs/doc-editor/components/custom-blocks/UploadLoaderBlock.tsx
new file mode 100644
index 00000000..297b70bf
--- /dev/null
+++ b/src/frontend/apps/impress/src/features/docs/doc-editor/components/custom-blocks/UploadLoaderBlock.tsx
@@ -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 (
+
+ {block.props.type === 'warning' ? (
+
+ ) : (
+
+ )}
+ {block.props.information}
+
+ );
+ },
+ },
+);
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 1a2ea21e..7aad893b 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
@@ -2,3 +2,4 @@ export * from './AccessibleImageBlock';
export * from './CalloutBlock';
export * from './DividerBlock';
export * from './PdfBlock';
+export * from './UploadLoaderBlock';
diff --git a/src/frontend/apps/impress/src/features/docs/doc-editor/hook/useUploadFile.tsx b/src/frontend/apps/impress/src/features/docs/doc-editor/hook/useUploadFile.tsx
index 9ba2b483..d8a0878f 100644
--- a/src/frontend/apps/impress/src/features/docs/doc-editor/hook/useUploadFile.tsx
+++ b/src/frontend/apps/impress/src/features/docs/doc-editor/hook/useUploadFile.tsx
@@ -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';
/**
@@ -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,
@@ -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);
diff --git a/src/frontend/apps/impress/src/features/docs/doc-export/blocks-mapping/index.ts b/src/frontend/apps/impress/src/features/docs/doc-export/blocks-mapping/index.ts
index e3e766db..e8d7328a 100644
--- a/src/frontend/apps/impress/src/features/docs/doc-export/blocks-mapping/index.ts
+++ b/src/frontend/apps/impress/src/features/docs/doc-export/blocks-mapping/index.ts
@@ -9,3 +9,5 @@ export * from './paragraphPDF';
export * from './quoteDocx';
export * from './quotePDF';
export * from './tablePDF';
+export * from './uploadLoaderPDF';
+export * from './uploadLoaderDocx';
diff --git a/src/frontend/apps/impress/src/features/docs/doc-export/blocks-mapping/uploadLoaderDocx.tsx b/src/frontend/apps/impress/src/features/docs/doc-export/blocks-mapping/uploadLoaderDocx.tsx
new file mode 100644
index 00000000..57b26eb5
--- /dev/null
+++ b/src/frontend/apps/impress/src/features/docs/doc-export/blocks-mapping/uploadLoaderDocx.tsx
@@ -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),
+ ],
+ });
+ };
diff --git a/src/frontend/apps/impress/src/features/docs/doc-export/blocks-mapping/uploadLoaderPDF.tsx b/src/frontend/apps/impress/src/features/docs/doc-export/blocks-mapping/uploadLoaderPDF.tsx
new file mode 100644
index 00000000..48e83814
--- /dev/null
+++ b/src/frontend/apps/impress/src/features/docs/doc-export/blocks-mapping/uploadLoaderPDF.tsx
@@ -0,0 +1,13 @@
+import { Text, View } from '@react-pdf/renderer';
+
+import { DocsExporterPDF } from '../types';
+
+export const blockMappingUploadLoaderPDF: DocsExporterPDF['mappings']['blockMapping']['uploadLoader'] =
+ (block) => {
+ return (
+
+ {block.props.type === 'loading' ? '⏳' : '⚠️'}
+ {block.props.information}
+
+ );
+ };
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 73e66d11..0a26644a 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
@@ -6,6 +6,7 @@ import {
blockMappingDividerDocx,
blockMappingImageDocx,
blockMappingQuoteDocx,
+ blockMappingUploadLoaderDocx,
} from './blocks-mapping';
import { inlineContentMappingInterlinkingLinkDocx } from './inline-content-mapping';
import { DocsExporterDocx } from './types';
@@ -22,6 +23,7 @@ export const docxDocsSchemaMappings: DocsExporterDocx['mappings'] = {
pdf: docxDefaultSchemaMappings.blockMapping.file as any,
quote: blockMappingQuoteDocx,
image: blockMappingImageDocx,
+ uploadLoader: blockMappingUploadLoaderDocx,
},
inlineContentMapping: {
...docxDefaultSchemaMappings.inlineContentMapping,
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 1b72ab1c..ed100e84 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
@@ -8,6 +8,7 @@ import {
blockMappingParagraphPDF,
blockMappingQuotePDF,
blockMappingTablePDF,
+ blockMappingUploadLoaderPDF,
} from './blocks-mapping';
import { inlineContentMappingInterlinkingLinkPDF } from './inline-content-mapping';
import { DocsExporterPDF } from './types';
@@ -27,6 +28,7 @@ export const pdfDocsSchemaMappings: DocsExporterPDF['mappings'] = {
// 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,
+ uploadLoader: blockMappingUploadLoaderPDF,
},
inlineContentMapping: {
...pdfDefaultSchemaMappings.inlineContentMapping,