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
13 changed files with 162 additions and 970 deletions
-1
View File
@@ -6,7 +6,6 @@ on:
push:
branches:
- 'main'
- 'feature/blocknote-ai'
tags:
- 'v*'
pull_request:
+6
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
+3 -8
View File
@@ -15,14 +15,9 @@
"test:watch": "jest --watch"
},
"dependencies": {
"ai": "^4.1.18",
"zod": "^3.24.1",
"@ai-sdk/openai": "^1.1.9",
"@blocknote/core": "*",
"@blocknote/mantine": "*",
"@blocknote/react": "*",
"@blocknote/xl-ai": "*",
"vitest": "^2.0.3",
"@blocknote/core": "0.21.0",
"@blocknote/mantine": "0.21.0",
"@blocknote/react": "0.21.0",
"@blocknote/xl-docx-exporter": "0.21.0",
"@blocknote/xl-pdf-exporter": "0.21.0",
"@gouvfr-lasuite/integration": "1.0.2",
@@ -95,7 +95,7 @@ export function AIGroupButton() {
return (
<Components.Generic.Menu.Root>
<Components.Generic.Menu.Trigger>
<Components.Toolbar.Button
<Components.FormattingToolbar.Button
className="bn-button bn-menu-item"
data-test="ai-actions"
label="AI"
@@ -1,31 +1,8 @@
import { createOpenAI } from '@ai-sdk/openai';
import {
BlockNoteEditor as BNEditor,
BlockConfig,
Dictionary,
InlineContentSchema,
StyleSchema,
filterSuggestionItems,
locales,
} from '@blocknote/core';
import { Dictionary, locales } from '@blocknote/core';
import '@blocknote/core/fonts/inter.css';
import { BlockNoteView } from '@blocknote/mantine';
import '@blocknote/mantine/style.css';
import {
SuggestionMenuController,
getDefaultReactSlashMenuItems,
useCreateBlockNote,
} from '@blocknote/react';
import {
AIShowSelectionPlugin,
BlockNoteAIContextProvider,
BlockNoteAIUI,
locales as aiLocales,
createBlockNoteAIClient,
getAISlashMenuItems,
useBlockNoteAIContext,
} from '@blocknote/xl-ai';
import '@blocknote/xl-ai/style.css';
import { useCreateBlockNote } from '@blocknote/react';
import { HocuspocusProvider } from '@hocuspocus/provider';
import { useEffect } from 'react';
import { useTranslation } from 'react-i18next';
@@ -44,34 +21,6 @@ import { randomColor } from '../utils';
import { BlockNoteToolbar } from './BlockNoteToolbar';
const blocknoteAIClient = createBlockNoteAIClient({
apiKey: 'BLOCKNOTE-API-KEY-CURRENTLY-NOT-NEEDED',
baseURL: 'https://blocknote-esy4.onrender.com/ai',
});
const model = createOpenAI({
baseURL: 'https://albert.api.staging.etalab.gouv.fr/v1',
...blocknoteAIClient.getProviderSettings('albert-etalab'),
compatibility: 'compatible',
})('neuralmagic/Meta-Llama-3.1-70B-Instruct-FP8');
// We call the model via a proxy server (see above) that has the API key,
// but we could also call the model directly from the frontend.
// i.e., this should work as well (but it would leak your albert key to the frontend):
/*
return createOpenAI({
baseURL: 'https://albert.api.staging.etalab.gouv.fr/v1',
apiKey: 'ALBERT-API-KEY',
compatibility: 'compatible',
})('albert-etalab/neuralmagic/Meta-Llama-3.1-70B-Instruct-FP8');
*/
export type DocsBlockNoteEditor = BNEditor<
Record<string, BlockConfig>,
InlineContentSchema,
StyleSchema
>;
interface BlockNoteEditorProps {
doc: Doc;
provider: HocuspocusProvider;
@@ -95,10 +44,6 @@ export const BlockNoteEditor = ({ doc, provider }: BlockNoteEditorProps) => {
const editor = useCreateBlockNote(
{
_extensions: {
aiSelection: new AIShowSelectionPlugin(),
},
collaboration: {
provider,
fragment: provider.document.getXmlFragment('document-store'),
@@ -132,10 +77,7 @@ export const BlockNoteEditor = ({ doc, provider }: BlockNoteEditorProps) => {
return cursor;
},
},
dictionary: {
...(locales[lang as keyof typeof locales] as Dictionary),
ai: aiLocales['en'] as unknown as Dictionary,
},
dictionary: locales[lang as keyof typeof locales] as Dictionary,
uploadFile,
},
[collabName, lang, provider, uploadFile],
@@ -171,42 +113,13 @@ export const BlockNoteEditor = ({ doc, provider }: BlockNoteEditorProps) => {
formattingToolbar={false}
editable={!readOnly}
theme="light"
slashMenu={false}
>
<BlockNoteAIContextProvider
model={model}
dataFormat="markdown"
stream={false}
>
<BlockNoteAIUI />
<BlockNoteToolbar />
<SuggestionMenu editor={editor as unknown as DocsBlockNoteEditor} />
</BlockNoteAIContextProvider>
<BlockNoteToolbar />
</BlockNoteView>
</Box>
);
};
function SuggestionMenu(props: { editor: DocsBlockNoteEditor }) {
const ctx = useBlockNoteAIContext();
return (
<SuggestionMenuController
triggerCharacter="/"
getItems={async (query) =>
Promise.resolve(
filterSuggestionItems(
[
...getDefaultReactSlashMenuItems(props.editor),
...getAISlashMenuItems(props.editor, ctx),
],
query,
),
)
}
/>
);
}
interface BlockNoteEditorVersionProps {
initialContent: Y.XmlFragment;
}
@@ -5,9 +5,9 @@ import {
FormattingToolbarProps,
getFormattingToolbarItems,
} from '@blocknote/react';
import { AIToolbarButton } from '@blocknote/xl-ai';
import { useCallback } from 'react';
import React, { useCallback } from 'react';
import { AIGroupButton } from './AIButton';
import { MarkdownButton } from './MarkdownButton';
export const BlockNoteToolbar = () => {
@@ -17,8 +17,7 @@ export const BlockNoteToolbar = () => {
{getFormattingToolbarItems(blockTypeSelectItems)}
{/* Extra button to do some AI powered actions */}
{/* <AIGroupButton key="AIButton" /> */}
<AIToolbarButton key="AIButton" />
<AIGroupButton key="AIButton" />
{/* Extra button to convert from markdown to json */}
<MarkdownButton key="customButton" />
@@ -5,7 +5,7 @@ import {
useSelectedBlocks,
} from '@blocknote/react';
import { forEach, isArray } from 'lodash';
import { useMemo } from 'react';
import React, { useMemo } from 'react';
import { useTranslation } from 'react-i18next';
type Block = {
@@ -80,11 +80,11 @@ export function MarkdownButton() {
}
return (
<Components.Toolbar.Button
<Components.FormattingToolbar.Button
mainTooltip={t('Convert Markdown')}
onClick={handleConvertMarkdown}
>
M
</Components.Toolbar.Button>
</Components.FormattingToolbar.Button>
);
}
@@ -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
>;
@@ -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 {
@@ -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;
}
}
+1 -5
View File
@@ -35,10 +35,6 @@
"eslint": "8.57.0",
"react": "18.3.1",
"react-dom": "18.3.1",
"typescript": "5.7.3",
"@blocknote/core": "https://gitpkg.vercel.app/typecellOS/blocknote/packages/core?ai-block-built&v=3",
"@blocknote/mantine": "https://gitpkg.vercel.app/typecellOS/blocknote/packages/mantine?ai-block-built&v=3",
"@blocknote/react": "https://gitpkg.vercel.app/typecellOS/blocknote/packages/react?ai-block-built&v=3",
"@blocknote/xl-ai": "https://gitpkg.vercel.app/typecellOS/blocknote/packages/xl-ai?ai-block-built&v=3"
"typescript": "5.7.3"
}
}
+30 -841
View File
File diff suppressed because it is too large Load Diff