✨(frontend) adapt export to quote block
We have a new block type, the quote block. We have to adapt the export to handle this new block type.
This commit is contained in:
@@ -11,6 +11,7 @@ and this project adheres to
|
|||||||
## Added
|
## Added
|
||||||
|
|
||||||
- 💄(frontend) add error pages #643
|
- 💄(frontend) add error pages #643
|
||||||
|
- ✨(frontend) Custom block quote with export #646
|
||||||
|
|
||||||
## Changed
|
## Changed
|
||||||
|
|
||||||
@@ -24,6 +25,7 @@ and this project adheres to
|
|||||||
- 🐛(backend) allow any type of extensions for media download #671
|
- 🐛(backend) allow any type of extensions for media download #671
|
||||||
- ♻️(frontend) improve table pdf rendering
|
- ♻️(frontend) improve table pdf rendering
|
||||||
|
|
||||||
|
|
||||||
## [2.2.0] - 2025-02-10
|
## [2.2.0] - 2025-02-10
|
||||||
|
|
||||||
## Added
|
## Added
|
||||||
|
|||||||
@@ -197,4 +197,49 @@ test.describe('Doc Export', () => {
|
|||||||
|
|
||||||
expect(pdfText).toContain('Hello World');
|
expect(pdfText).toContain('Hello World');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('it exports the doc with quotes', async ({ page, browserName }) => {
|
||||||
|
const [randomDoc] = await createDoc(page, 'export-quotes', browserName, 1);
|
||||||
|
|
||||||
|
const downloadPromise = page.waitForEvent('download', (download) => {
|
||||||
|
return download.suggestedFilename().includes(`${randomDoc}.pdf`);
|
||||||
|
});
|
||||||
|
|
||||||
|
const editor = page.locator('.ProseMirror');
|
||||||
|
// Trigger slash menu to show menu
|
||||||
|
await editor.click();
|
||||||
|
await editor.fill('/');
|
||||||
|
await page.getByText('Add a quote block').click();
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
editor.locator('.bn-block-content[data-content-type="quote"]'),
|
||||||
|
).toBeVisible();
|
||||||
|
|
||||||
|
await editor.fill('Hello World');
|
||||||
|
|
||||||
|
await expect(editor.getByText('Hello World')).toHaveCSS(
|
||||||
|
'font-style',
|
||||||
|
'italic',
|
||||||
|
);
|
||||||
|
|
||||||
|
await page
|
||||||
|
.getByRole('button', {
|
||||||
|
name: 'download',
|
||||||
|
})
|
||||||
|
.click();
|
||||||
|
|
||||||
|
await page
|
||||||
|
.getByRole('button', {
|
||||||
|
name: 'Download',
|
||||||
|
})
|
||||||
|
.click();
|
||||||
|
|
||||||
|
const download = await downloadPromise;
|
||||||
|
expect(download.suggestedFilename()).toBe(`${randomDoc}.pdf`);
|
||||||
|
|
||||||
|
const pdfBuffer = await cs.toBuffer(await download.createReadStream());
|
||||||
|
const pdfData = await pdf(pdfBuffer);
|
||||||
|
|
||||||
|
expect(pdfData.text).toContain('Hello World'); // This is the pdf text
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1 +1,2 @@
|
|||||||
export * from './DocEditor';
|
export * from './DocEditor';
|
||||||
|
export * from './custom-blocks/';
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
export * from './headingPDF';
|
export * from './headingPDF';
|
||||||
export * from './paragraphPDF';
|
export * from './paragraphPDF';
|
||||||
|
export * from './quoteDocx';
|
||||||
|
export * from './quotePDF';
|
||||||
export * from './tablePDF';
|
export * from './tablePDF';
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
import { Paragraph } from 'docx';
|
||||||
|
|
||||||
|
import { DocsExporterDocx } from '../types';
|
||||||
|
import { docxBlockPropsToStyles } from '../utils';
|
||||||
|
|
||||||
|
export const blockMappingQuoteDocx: DocsExporterDocx['mappings']['blockMapping']['quote'] =
|
||||||
|
(block, exporter) => {
|
||||||
|
if (Array.isArray(block.content)) {
|
||||||
|
block.content.forEach((content) => {
|
||||||
|
if (content.type === 'text') {
|
||||||
|
content.styles = {
|
||||||
|
...content.styles,
|
||||||
|
italic: true,
|
||||||
|
textColor: 'gray',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Paragraph({
|
||||||
|
...docxBlockPropsToStyles(block.props, exporter.options.colors),
|
||||||
|
spacing: { before: 10, after: 10 },
|
||||||
|
border: {
|
||||||
|
left: {
|
||||||
|
color: '#cecece',
|
||||||
|
space: 4,
|
||||||
|
style: 'thick',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
style: 'Normal',
|
||||||
|
children: exporter.transformInlineContent(block.content),
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import { Text } from '@react-pdf/renderer';
|
||||||
|
|
||||||
|
import { DocsExporterPDF } from '../types';
|
||||||
|
|
||||||
|
export const blockMappingQuotePDF: DocsExporterPDF['mappings']['blockMapping']['quote'] =
|
||||||
|
(block, exporter) => {
|
||||||
|
return (
|
||||||
|
<Text
|
||||||
|
style={{
|
||||||
|
fontStyle: 'italic',
|
||||||
|
marginVertical: 10,
|
||||||
|
paddingVertical: 5,
|
||||||
|
paddingLeft: 10,
|
||||||
|
borderLeft: '4px solid #cecece',
|
||||||
|
color: '#666',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{exporter.transformInlineContent(block.content)}
|
||||||
|
</Text>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -1,10 +1,12 @@
|
|||||||
import { docxDefaultSchemaMappings } from '@blocknote/xl-docx-exporter';
|
import { docxDefaultSchemaMappings } from '@blocknote/xl-docx-exporter';
|
||||||
|
|
||||||
|
import { blockMappingQuoteDocx } from './blocks-mapping/';
|
||||||
import { DocsExporterDocx } from './types';
|
import { DocsExporterDocx } from './types';
|
||||||
|
|
||||||
export const docxDocsSchemaMappings: DocsExporterDocx['mappings'] = {
|
export const docxDocsSchemaMappings: DocsExporterDocx['mappings'] = {
|
||||||
...docxDefaultSchemaMappings,
|
...docxDefaultSchemaMappings,
|
||||||
blockMapping: {
|
blockMapping: {
|
||||||
...docxDefaultSchemaMappings.blockMapping,
|
...docxDefaultSchemaMappings.blockMapping,
|
||||||
|
quote: blockMappingQuoteDocx,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { pdfDefaultSchemaMappings } from '@blocknote/xl-pdf-exporter';
|
|||||||
import {
|
import {
|
||||||
blockMappingHeadingPDF,
|
blockMappingHeadingPDF,
|
||||||
blockMappingParagraphPDF,
|
blockMappingParagraphPDF,
|
||||||
|
blockMappingQuotePDF,
|
||||||
blockMappingTablePDF,
|
blockMappingTablePDF,
|
||||||
} from './blocks-mapping';
|
} from './blocks-mapping';
|
||||||
import { DocsExporterPDF } from './types';
|
import { DocsExporterPDF } from './types';
|
||||||
@@ -13,6 +14,7 @@ export const pdfDocsSchemaMappings: DocsExporterPDF['mappings'] = {
|
|||||||
...pdfDefaultSchemaMappings.blockMapping,
|
...pdfDefaultSchemaMappings.blockMapping,
|
||||||
heading: blockMappingHeadingPDF,
|
heading: blockMappingHeadingPDF,
|
||||||
paragraph: blockMappingParagraphPDF,
|
paragraph: blockMappingParagraphPDF,
|
||||||
|
quote: blockMappingQuotePDF,
|
||||||
table: blockMappingTablePDF,
|
table: blockMappingTablePDF,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,3 +1,10 @@
|
|||||||
|
import {
|
||||||
|
COLORS_DEFAULT,
|
||||||
|
DefaultProps,
|
||||||
|
UnreachableCaseError,
|
||||||
|
} from '@blocknote/core';
|
||||||
|
import { IParagraphOptions, ShadingType } from 'docx';
|
||||||
|
|
||||||
export function downloadFile(blob: Blob, filename: string) {
|
export function downloadFile(blob: Blob, filename: string) {
|
||||||
const url = window.URL.createObjectURL(blob);
|
const url = window.URL.createObjectURL(blob);
|
||||||
const a = document.createElement('a');
|
const a = document.createElement('a');
|
||||||
@@ -30,3 +37,39 @@ export const exportResolveFileUrl = async (
|
|||||||
|
|
||||||
return url;
|
return url;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export function docxBlockPropsToStyles(
|
||||||
|
props: Partial<DefaultProps>,
|
||||||
|
colors: typeof COLORS_DEFAULT,
|
||||||
|
): IParagraphOptions {
|
||||||
|
return {
|
||||||
|
shading:
|
||||||
|
props.backgroundColor === 'default' || !props.backgroundColor
|
||||||
|
? undefined
|
||||||
|
: {
|
||||||
|
type: ShadingType.SOLID,
|
||||||
|
color:
|
||||||
|
colors[
|
||||||
|
props.backgroundColor as keyof typeof colors
|
||||||
|
].background.slice(1),
|
||||||
|
},
|
||||||
|
run:
|
||||||
|
props.textColor === 'default' || !props.textColor
|
||||||
|
? undefined
|
||||||
|
: {
|
||||||
|
color: colors[props.textColor as keyof typeof colors].text.slice(1),
|
||||||
|
},
|
||||||
|
alignment:
|
||||||
|
!props.textAlignment || props.textAlignment === 'left'
|
||||||
|
? undefined
|
||||||
|
: props.textAlignment === 'center'
|
||||||
|
? 'center'
|
||||||
|
: props.textAlignment === 'right'
|
||||||
|
? 'right'
|
||||||
|
: props.textAlignment === 'justify'
|
||||||
|
? 'distribute'
|
||||||
|
: (() => {
|
||||||
|
throw new UnreachableCaseError(props.textAlignment);
|
||||||
|
})(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user