diff --git a/src/frontend/apps/impress/src/features/docs/doc-editor/types.tsx b/src/frontend/apps/impress/src/features/docs/doc-editor/types.tsx index 2bb97a4b..577c0176 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-editor/types.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-editor/types.tsx @@ -17,8 +17,12 @@ export type HeadingBlock = { }; }; +export type DocsBlockSchema = typeof blockNoteSchema.blockSchema; +export type DocsInlineContentSchema = + typeof blockNoteSchema.inlineContentSchema; +export type DocsStyleSchema = typeof blockNoteSchema.styleSchema; export type DocsBlockNoteEditor = BlockNoteEditor< - typeof blockNoteSchema.blockSchema, - typeof blockNoteSchema.inlineContentSchema, - typeof blockNoteSchema.styleSchema + DocsBlockSchema, + DocsInlineContentSchema, + DocsStyleSchema >; diff --git a/src/frontend/apps/impress/src/features/docs/doc-export/blocks-mapping/headingPDF.tsx b/src/frontend/apps/impress/src/features/docs/doc-export/blocks-mapping/headingPDF.tsx new file mode 100644 index 00000000..fca81d7b --- /dev/null +++ b/src/frontend/apps/impress/src/features/docs/doc-export/blocks-mapping/headingPDF.tsx @@ -0,0 +1,25 @@ +import { Text } from '@react-pdf/renderer'; + +import { DocsExporterPDF } from '../types'; + +export const blockMappingHeadingPDF: DocsExporterPDF['mappings']['blockMapping']['heading'] = + (block, exporter) => { + const PIXELS_PER_POINT = 0.75; + const MERGE_RATIO = 7.5; + const FONT_SIZE = 16; + const fontSizeEM = + block.props.level === 1 ? 2 : block.props.level === 2 ? 1.5 : 1.17; + return ( + + {exporter.transformInlineContent(block.content)} + + ); + }; 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 new file mode 100644 index 00000000..36f9c79f --- /dev/null +++ b/src/frontend/apps/impress/src/features/docs/doc-export/blocks-mapping/index.ts @@ -0,0 +1,3 @@ +export * from './headingPDF'; +export * from './paragraphPDF'; +export * from './tablePDF'; diff --git a/src/frontend/apps/impress/src/features/docs/doc-export/blocks-mapping/paragraphPDF.tsx b/src/frontend/apps/impress/src/features/docs/doc-export/blocks-mapping/paragraphPDF.tsx new file mode 100644 index 00000000..86d74877 --- /dev/null +++ b/src/frontend/apps/impress/src/features/docs/doc-export/blocks-mapping/paragraphPDF.tsx @@ -0,0 +1,31 @@ +import { Text } from '@react-pdf/renderer'; + +import { DocsExporterPDF } from '../types'; + +export const blockMappingParagraphPDF: DocsExporterPDF['mappings']['blockMapping']['paragraph'] = + (block, exporter) => { + /** + * Breakline in the editor are not rendered in the PDF + * By adding a space if the block is empty we ensure that the block is rendered + */ + if (Array.isArray(block.content)) { + block.content.forEach((content) => { + if (content.type === 'text' && !content.text) { + content.text = ' '; + } + }); + + if (!block.content.length) { + block.content.push({ + styles: {}, + text: ' ', + type: 'text', + }); + } + } + return ( + + {exporter.transformInlineContent(block.content)} + + ); + }; diff --git a/src/frontend/apps/impress/src/features/docs/doc-export/blocks-mapping/tablePDF.tsx b/src/frontend/apps/impress/src/features/docs/doc-export/blocks-mapping/tablePDF.tsx new file mode 100644 index 00000000..61117925 --- /dev/null +++ b/src/frontend/apps/impress/src/features/docs/doc-export/blocks-mapping/tablePDF.tsx @@ -0,0 +1,52 @@ +import { TD, TH, TR, Table } from '@ag-media/react-pdf-table'; +import { View } from '@react-pdf/renderer'; + +import { DocsExporterPDF } from '../types'; + +export const blockMappingTablePDF: DocsExporterPDF['mappings']['blockMapping']['table'] = + (block, exporter) => { + return ( + + {block.content.rows.map((row, index) => { + if (index === 0) { + return ( + + ); + })} + + ); + } + return ( + + {row.cells.map((cell, index) => { + // Make empty cells are rendered. + if (cell.length === 0) { + cell.push({ + styles: {}, + text: ' ', + type: 'text', + }); + } + return ( + + ); + })} + + ); + })} +
+ {row.cells.map((cell, index) => { + // Make empty cells are rendered. + if (cell.length === 0) { + cell.push({ + styles: {}, + text: ' ', + type: 'text', + }); + } + return ( + {exporter.transformInlineContent(cell)}
+ {exporter.transformInlineContent(cell)} +
+ ); + }; diff --git a/src/frontend/apps/impress/src/features/docs/doc-export/components/ModalExport.tsx b/src/frontend/apps/impress/src/features/docs/doc-export/components/ModalExport.tsx index bdad68d1..a2ba6f8a 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-export/components/ModalExport.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-export/components/ModalExport.tsx @@ -1,11 +1,5 @@ -import { - DOCXExporter, - docxDefaultSchemaMappings, -} from '@blocknote/xl-docx-exporter'; -import { - PDFExporter, - pdfDefaultSchemaMappings, -} from '@blocknote/xl-pdf-exporter'; +import { DOCXExporter } from '@blocknote/xl-docx-exporter'; +import { PDFExporter } from '@blocknote/xl-pdf-exporter'; import { Button, Loader, @@ -15,7 +9,7 @@ import { VariantType, useToastProvider, } from '@openfun/cunningham-react'; -import { Text as PDFText, pdf } from '@react-pdf/renderer'; +import { pdf } from '@react-pdf/renderer'; import { useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { css } from 'styled-components'; @@ -25,10 +19,10 @@ import { useEditorStore } from '@/features/docs/doc-editor'; import { Doc, useTrans } from '@/features/docs/doc-management'; import { TemplatesOrdering, useTemplates } from '../api/useTemplates'; +import { docxDocsSchemaMappings } from '../mappingDocx'; +import { pdfDocsSchemaMappings } from '../mappingPDF'; import { downloadFile, exportResolveFileUrl } from '../utils'; -import { Table } from './blocks/Table'; - enum DocDownloadFormat { PDF = 'pdf', DOCX = 'docx', @@ -96,91 +90,25 @@ export const ModalExport = ({ onClose, doc }: ModalExportProps) => { if (format === DocDownloadFormat.PDF) { const defaultExporter = new PDFExporter( editor.schema, - pdfDefaultSchemaMappings, + pdfDocsSchemaMappings, ); - const exporter = new PDFExporter( - editor.schema, - { - ...pdfDefaultSchemaMappings, - blockMapping: { - ...pdfDefaultSchemaMappings.blockMapping, - heading: (block, exporter) => { - const PIXELS_PER_POINT = 0.75; - const MERGE_RATIO = 7.5; - const FONT_SIZE = 16; - const fontSizeEM = - block.props.level === 1 - ? 2 - : block.props.level === 2 - ? 1.5 - : 1.17; - return ( - - {exporter.transformInlineContent(block.content)} - - ); - }, - paragraph: (block, exporter) => { - /** - * Breakline in the editor are not rendered in the PDF - * By adding a space if the block is empty we ensure that the block is rendered - */ - if (Array.isArray(block.content)) { - block.content.forEach((content) => { - if (content.type === 'text' && !content.text) { - content.text = ' '; - } - }); - - if (!block.content.length) { - block.content.push({ - styles: {}, - text: ' ', - type: 'text', - }); - } - } - return ( - - {exporter.transformInlineContent(block.content)} - - ); - }, - table: (block, transformer) => { - return ; - }, - }, - }, - { - resolveFileUrl: async (url) => - exportResolveFileUrl(url, defaultExporter.options.resolveFileUrl), - }, - ); + const exporter = new PDFExporter(editor.schema, pdfDocsSchemaMappings, { + resolveFileUrl: async (url) => + exportResolveFileUrl(url, defaultExporter.options.resolveFileUrl), + }); const pdfDocument = await exporter.toReactPDFDocument(exportDocument); blobExport = await pdf(pdfDocument).toBlob(); } else { const defaultExporter = new DOCXExporter( editor.schema, - docxDefaultSchemaMappings, + docxDocsSchemaMappings, ); - const exporter = new DOCXExporter( - editor.schema, - docxDefaultSchemaMappings, - { - resolveFileUrl: async (url) => - exportResolveFileUrl(url, defaultExporter.options.resolveFileUrl), - }, - ); + const exporter = new DOCXExporter(editor.schema, docxDocsSchemaMappings, { + resolveFileUrl: async (url) => + exportResolveFileUrl(url, defaultExporter.options.resolveFileUrl), + }); blobExport = await exporter.toBlob(exportDocument); } diff --git a/src/frontend/apps/impress/src/features/docs/doc-export/components/blocks/Table.tsx b/src/frontend/apps/impress/src/features/docs/doc-export/components/blocks/Table.tsx deleted file mode 100644 index 156c3eb6..00000000 --- a/src/frontend/apps/impress/src/features/docs/doc-export/components/blocks/Table.tsx +++ /dev/null @@ -1,76 +0,0 @@ -import { TD, TH, TR, Table as TablePDF } from '@ag-media/react-pdf-table'; -import { - DefaultBlockSchema, - Exporter, - InlineContentSchema, - StyleSchema, - TableContent, -} from '@blocknote/core'; -import { View } from '@react-pdf/renderer'; -import { ReactNode } from 'react'; - -export const Table = (props: { - data: TableContent; - transformer: Exporter< - DefaultBlockSchema, - InlineContentSchema, - StyleSchema, - unknown, - unknown, - unknown, - unknown - >; -}) => { - return ( - - {props.data.rows.map((row, index) => { - if (index === 0) { - return ( - - ); - })} - - ); - } - return ( - - {row.cells.map((cell, index) => { - // Make empty cells are rendered. - if (cell.length === 0) { - cell.push({ - styles: {}, - text: ' ', - type: 'text', - }); - } - return ( - - ); - })} - - ); - })} - - ); -}; 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 new file mode 100644 index 00000000..68b90803 --- /dev/null +++ b/src/frontend/apps/impress/src/features/docs/doc-export/mappingDocx.tsx @@ -0,0 +1,10 @@ +import { docxDefaultSchemaMappings } from '@blocknote/xl-docx-exporter'; + +import { DocsExporterDocx } from './types'; + +export const docxDocsSchemaMappings: DocsExporterDocx['mappings'] = { + ...docxDefaultSchemaMappings, + blockMapping: { + ...docxDefaultSchemaMappings.blockMapping, + }, +}; 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 new file mode 100644 index 00000000..19c9f204 --- /dev/null +++ b/src/frontend/apps/impress/src/features/docs/doc-export/mappingPDF.tsx @@ -0,0 +1,18 @@ +import { pdfDefaultSchemaMappings } from '@blocknote/xl-pdf-exporter'; + +import { + blockMappingHeadingPDF, + blockMappingParagraphPDF, + blockMappingTablePDF, +} from './blocks-mapping'; +import { DocsExporterPDF } from './types'; + +export const pdfDocsSchemaMappings: DocsExporterPDF['mappings'] = { + ...pdfDefaultSchemaMappings, + blockMapping: { + ...pdfDefaultSchemaMappings.blockMapping, + heading: blockMappingHeadingPDF, + paragraph: blockMappingParagraphPDF, + table: blockMappingTablePDF, + }, +}; diff --git a/src/frontend/apps/impress/src/features/docs/doc-export/types.ts b/src/frontend/apps/impress/src/features/docs/doc-export/types.ts index 70b62e56..42f8c156 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-export/types.ts +++ b/src/frontend/apps/impress/src/features/docs/doc-export/types.ts @@ -1,3 +1,18 @@ +import { Exporter } from '@blocknote/core'; +import { Link, Text, TextProps } from '@react-pdf/renderer'; +import { + IRunPropertiesOptions, + Paragraph, + ParagraphChild, + Table, + TextRun, +} from 'docx'; + +import { + DocsBlockSchema, + DocsInlineContentSchema, + DocsStyleSchema, +} from '../doc-editor'; import { Access } from '../doc-management'; export interface Template { @@ -16,3 +31,23 @@ export interface Template { css: string; code: string; } + +export type DocsExporterPDF = Exporter< + NoInfer, + NoInfer, + NoInfer, + React.ReactElement, + React.ReactElement | React.ReactElement, + TextProps['style'], + React.ReactElement +>; + +export type DocsExporterDocx = Exporter< + NoInfer, + NoInfer, + NoInfer, + Promise | Paragraph[] | Paragraph | Table, + ParagraphChild, + IRunPropertiesOptions, + TextRun +>;
- {row.cells.map((cell, index) => { - // Make empty cells are rendered. - if (cell.length === 0) { - cell.push({ - styles: {}, - text: ' ', - type: 'text', - }); - } - return ( - - {props.transformer.transformInlineContent(cell)} -
- - { - props.transformer.transformInlineContent( - cell, - ) as ReactNode - } - -