Compare commits

...

2 Commits

Author SHA1 Message Date
Anthony LC
0e7edb9239 🚚(frontend) move Blocknote styles
Move Blocknote styles to separate file.
2025-02-05 15:51:04 +01:00
Anthony LC
48cb2587be 💄(frontend) improve styles export pdf
When exporting a document to PDF, the headings
spacings were too small, the break lines were
not displayed. This commit fixes these issues
by extending PDFExporter.
2025-02-05 15:51:04 +01:00
6 changed files with 205 additions and 103 deletions

View File

@@ -10,8 +10,14 @@ and this project adheres to
## [Unreleased]
## Added
- 📝(doc) Add security.md and codeofconduct.md #604
## Fixed
- 💄improve export spacings PDF #613
## [2.1.0] - 2025-01-29
## Added

View File

@@ -6,7 +6,6 @@ import { useCreateBlockNote } from '@blocknote/react';
import { HocuspocusProvider } from '@hocuspocus/provider';
import { useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import { css } from 'styled-components';
import * as Y from 'yjs';
import { Box, TextErrors } from '@/components';
@@ -17,96 +16,11 @@ import { useUploadFile } from '../hook';
import { useHeadings } from '../hook/useHeadings';
import useSaveDoc from '../hook/useSaveDoc';
import { useEditorStore } from '../stores';
import { cssEditor } from '../styles';
import { randomColor } from '../utils';
import { BlockNoteToolbar } from './BlockNoteToolbar';
const cssEditor = (readonly: boolean) => css`
&,
& > .bn-container,
& .ProseMirror {
height: 100%;
.bn-side-menu[data-block-type='heading'][data-level='1'] {
height: 50px;
}
.bn-side-menu[data-block-type='heading'][data-level='2'] {
height: 43px;
}
.bn-side-menu[data-block-type='heading'][data-level='3'] {
height: 35px;
}
h1 {
font-size: 1.875rem;
}
h2 {
font-size: 1.5rem;
}
h3 {
font-size: 1.25rem;
}
a {
color: var(--c--theme--colors--greyscale-500);
cursor: pointer;
}
.bn-block-group
.bn-block-group
.bn-block-outer:not([data-prev-depth-changed]):before {
border-left: none;
}
}
.bn-editor {
color: var(--c--theme--colors--greyscale-700);
}
.bn-block-outer:not(:first-child) {
&:has(h1) {
padding-top: 32px;
}
&:has(h2) {
padding-top: 24px;
}
&:has(h3) {
padding-top: 16px;
}
}
& .bn-inline-content code {
background-color: gainsboro;
padding: 2px;
border-radius: 4px;
}
@media screen and (width <= 560px) {
& .bn-editor {
${readonly && `padding-left: 10px;`}
}
.bn-side-menu[data-block-type='heading'][data-level='1'] {
height: 46px;
}
.bn-side-menu[data-block-type='heading'][data-level='2'] {
height: 40px;
}
.bn-side-menu[data-block-type='heading'][data-level='3'] {
height: 40px;
}
& .bn-editor h1 {
font-size: 1.6rem;
}
& .bn-editor h2 {
font-size: 1.35rem;
}
& .bn-editor h3 {
font-size: 1.2rem;
}
.bn-block-content[data-is-empty-and-focused][data-content-type='paragraph']
.bn-inline-content:has(> .ProseMirror-trailingBreak:only-child)::before {
font-size: 14px;
}
}
`;
interface BlockNoteEditorProps {
doc: Doc;
provider: HocuspocusProvider;

View File

@@ -0,0 +1,87 @@
import { css } from 'styled-components';
export const cssEditor = (readonly: boolean) => css`
&,
& > .bn-container,
& .ProseMirror {
height: 100%;
.bn-side-menu[data-block-type='heading'][data-level='1'] {
height: 50px;
}
.bn-side-menu[data-block-type='heading'][data-level='2'] {
height: 43px;
}
.bn-side-menu[data-block-type='heading'][data-level='3'] {
height: 35px;
}
h1 {
font-size: 1.875rem;
}
h2 {
font-size: 1.5rem;
}
h3 {
font-size: 1.25rem;
}
a {
color: var(--c--theme--colors--greyscale-500);
cursor: pointer;
}
.bn-block-group
.bn-block-group
.bn-block-outer:not([data-prev-depth-changed]):before {
border-left: none;
}
}
.bn-editor {
color: var(--c--theme--colors--greyscale-700);
}
.bn-block-outer:not(:first-child) {
&:has(h1) {
padding-top: 32px;
}
&:has(h2) {
padding-top: 24px;
}
&:has(h3) {
padding-top: 16px;
}
}
& .bn-inline-content code {
background-color: gainsboro;
padding: 2px;
border-radius: 4px;
}
@media screen and (width <= 560px) {
& .bn-editor {
${readonly && `padding-left: 10px;`}
}
.bn-side-menu[data-block-type='heading'][data-level='1'] {
height: 46px;
}
.bn-side-menu[data-block-type='heading'][data-level='2'] {
height: 40px;
}
.bn-side-menu[data-block-type='heading'][data-level='3'] {
height: 40px;
}
& .bn-editor h1 {
font-size: 1.6rem;
}
& .bn-editor h2 {
font-size: 1.35rem;
}
& .bn-editor h3 {
font-size: 1.2rem;
}
.bn-block-content[data-is-empty-and-focused][data-content-type='paragraph']
.bn-inline-content:has(> .ProseMirror-trailingBreak:only-child)::before {
font-size: 14px;
}
}
`;

View File

@@ -1,3 +1,5 @@
import { BlockNoteEditor, BlockNoteSchema } from '@blocknote/core';
export interface DocAttachment {
file: string;
}
@@ -12,3 +14,19 @@ export type HeadingBlock = {
level: number;
};
};
export const blockNoteInstance = BlockNoteSchema.create();
export type DocsBlockSchema = typeof blockNoteInstance.blockSchema;
export type DocsInlineContentSchema =
typeof blockNoteInstance.inlineContentSchema;
export type DocsStyleSchema = typeof blockNoteInstance.styleSchema;
export type DocsBlockNoteEditor = BlockNoteEditor<
DocsBlockSchema,
DocsInlineContentSchema,
DocsStyleSchema
>;
export type DocsBlockNoteSchema = BlockNoteSchema<
DocsBlockSchema,
DocsInlineContentSchema,
DocsStyleSchema
>;

View File

@@ -2,10 +2,6 @@ import {
DOCXExporter,
docxDefaultSchemaMappings,
} from '@blocknote/xl-docx-exporter';
import {
PDFExporter,
pdfDefaultSchemaMappings,
} from '@blocknote/xl-pdf-exporter';
import {
Button,
Loader,
@@ -25,6 +21,7 @@ import { useEditorStore } from '@/features/docs/doc-editor';
import { Doc } from '@/features/docs/doc-management';
import { TemplatesOrdering, useTemplates } from '../api/useTemplates';
import { DocsPDFExporter } from '../libs/DocsPDFExporter';
import { downloadFile, exportResolveFileUrl } from '../utils';
enum DocDownloadFormat {
@@ -91,19 +88,12 @@ export const ModalExport = ({ onClose, doc }: ModalExportProps) => {
let blobExport: Blob;
if (format === DocDownloadFormat.PDF) {
const defaultExporter = new PDFExporter(
editor.schema,
pdfDefaultSchemaMappings,
);
const defaultExporter = new DocsPDFExporter(editor.schema);
const exporter = new DocsPDFExporter(editor.schema, {
resolveFileUrl: async (url) =>
exportResolveFileUrl(url, defaultExporter.options.resolveFileUrl),
});
const exporter = new PDFExporter(
editor.schema,
pdfDefaultSchemaMappings,
{
resolveFileUrl: async (url) =>
exportResolveFileUrl(url, defaultExporter.options.resolveFileUrl),
},
);
const pdfDocument = await exporter.toReactPDFDocument(exportDocument);
blobExport = await pdf(pdfDocument).toBlob();
} else {

View File

@@ -0,0 +1,87 @@
import { Block, DefaultProps, ExporterOptions } from '@blocknote/core';
import {
PDFExporter,
pdfDefaultSchemaMappings,
} from '@blocknote/xl-pdf-exporter';
import { Font } from '@react-pdf/renderer';
import {
DocsBlockNoteSchema,
DocsBlockSchema,
DocsInlineContentSchema,
DocsStyleSchema,
} from '@/features/docs/doc-editor';
type Options = ExporterOptions & {
emojiSource: false | ReturnType<typeof Font.getEmojiSource>;
};
type DocsDefaultProps = DefaultProps & {
level?: number;
};
export class DocsPDFExporter extends PDFExporter<
DocsBlockSchema,
DocsStyleSchema,
DocsInlineContentSchema
> {
constructor(
protected readonly schemaMappings: DocsBlockNoteSchema,
options?: Partial<Options>,
) {
super(schemaMappings, pdfDefaultSchemaMappings, options);
}
/**
* Breaklines are not displayed in PDFs, by adding a space we ensure that the line is not ignored
* @param blocks
* @param nestingLevel
* @returns
*/
public transformBlocks(
blocks: Block<DocsBlockSchema, DocsInlineContentSchema, DocsStyleSchema>[], // Or BlockFromConfig<B[keyof B], I, S>?
nestingLevel?: number,
) {
blocks.forEach((block) => {
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 super.transformBlocks(blocks, nestingLevel);
}
/**
* Override the method to add our custom styles
* @param props
* @returns
*/
public blocknoteDefaultPropsToReactPDFStyle(
props: Partial<DocsDefaultProps>,
) {
let styles = super.blocknoteDefaultPropsToReactPDFStyle(props);
// Add margin to headings
if (props.level) {
styles = {
marginTop: 15,
marginBottom: 15,
...styles,
};
}
return styles;
}
}