diff --git a/CHANGELOG.md b/CHANGELOG.md index c28c6f1dac..f3524666ed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -84,6 +84,7 @@ and this project adheres to - 🛂(backend) let users allowed to comment list each other's accesses #2447 - ⬆️(frontend) update BlockNote preview packages to 83153fda2e +- 🐛(frontend) adapt to the BlockNote 0.56 editor and exporter APIs - ♻️(frontend) restore bounded editor history through native yhub rollback - ♻️(frontend) use standalone YHub version storage and history extension - ♻️(frontend) migrate the history panel to RenderInPortalElement diff --git a/deploy/dev-instance/README.md b/deploy/dev-instance/README.md index 57af6ccdb5..aabd82ca5d 100644 --- a/deploy/dev-instance/README.md +++ b/deploy/dev-instance/README.md @@ -81,7 +81,14 @@ pull request. cd src/frontend && yarn install --ignore-engines ``` - `yarn.lock` must change only the `@blocknote/*` entries. + `yarn.lock` must change only the `@blocknote/*` entries. A new BlockNote + version can change its API, and the deploy fails in the frontend build. + So type-check before you open the pull request: + + ```bash + cd src/frontend/apps/impress && npx tsc --noEmit -p . + ``` + 4. Commit as `⬆️(frontend) update BlockNote preview packages to ` (signed off), open a pull request against `dev`, and merge it. The merge deploys. diff --git a/src/frontend/apps/impress/src/features/docs/doc-editor/components/BlockNoteToolBar/BlockNoteToolbar.tsx b/src/frontend/apps/impress/src/features/docs/doc-editor/components/BlockNoteToolBar/BlockNoteToolbar.tsx index 0c98b2de5a..1b1013d3e0 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-editor/components/BlockNoteToolBar/BlockNoteToolbar.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-editor/components/BlockNoteToolBar/BlockNoteToolbar.tsx @@ -6,7 +6,6 @@ import { blockTypeSelectItems, getFormattingToolbarItems, useBlockNoteEditor, - useDictionary, } from '@blocknote/react'; import dynamic from 'next/dynamic'; import React, { useCallback, useMemo, useState } from 'react'; @@ -34,7 +33,6 @@ const AIToolbarButton = BlockNoteAI?.AIToolbarButton; export const BlockNoteToolbar = ({ aiAllowed }: { aiAllowed: boolean }) => { const editor = useBlockNoteEditor(); - const dict = useDictionary(); const [confirmOpen, setIsConfirmOpen] = useState(false); const [onConfirm, setOnConfirm] = useState<() => void | Promise>(); const { t } = useTranslation(); @@ -42,7 +40,7 @@ export const BlockNoteToolbar = ({ aiAllowed }: { aiAllowed: boolean }) => { const toolbarItems = useMemo(() => { let toolbarItems = getFormattingToolbarItems([ - ...blockTypeSelectItems(dict), + ...blockTypeSelectItems(editor), getCalloutFormattingToolbarItems(t), ...getMathBlockTypeSelectItems(editor), ...getDiagramBlockTypeSelectItems(editor), @@ -81,7 +79,7 @@ export const BlockNoteToolbar = ({ aiAllowed }: { aiAllowed: boolean }) => { }); return toolbarItems; - }, [dict, editor, t]); + }, [editor, t]); const formattingToolbar = useCallback(() => { return ( diff --git a/src/frontend/apps/impress/src/features/docs/doc-editor/components/custom-blocks/CodeBlock.tsx b/src/frontend/apps/impress/src/features/docs/doc-editor/components/custom-blocks/CodeBlock.tsx index 85c655f411..c5909eec04 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-editor/components/custom-blocks/CodeBlock.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-editor/components/custom-blocks/CodeBlock.tsx @@ -53,15 +53,19 @@ export const createSafeCodeBlockSpec = (): ReturnType< > => { const spec = createCodeBlockSpec(codeBlockOptions); const baseRender = spec.implementation.render; + if (!baseRender) { + throw new Error('The BlockNote code block spec has no render function'); + } + type Render = NonNullable; return { ...spec, implementation: { ...spec.implementation, render( - this: ThisParameterType, - ...args: Parameters - ): ReturnType { + this: ThisParameterType, + ...args: Parameters + ): ReturnType { const [block, editor] = args; const language = resolveCodeBlockLanguage(block.props.language); diff --git a/src/frontend/apps/impress/src/features/docs/doc-export/blocks-mapping/imagePDF.tsx b/src/frontend/apps/impress/src/features/docs/doc-export/blocks-mapping/imagePDF.tsx index 4e4aa5f652..5af26787b2 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-export/blocks-mapping/imagePDF.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-export/blocks-mapping/imagePDF.tsx @@ -25,50 +25,53 @@ const imageBoxStyle = { * @returns A react-pdf View element, or an empty View when the blob is not an * image or the conversion fails. */ -export const blockMappingImagePDF: DocsExporterPDF['mappings']['blockMapping']['image'] = - async (block, exporter) => { - const blob = await exporter.resolveFile(block.props.url); - let result: { png: string; width: number; height: number } | undefined; - const previewWidth = block.props.previewWidth - ? Math.min(block.props.previewWidth, MAX_WIDTH) - : undefined; +// The plain function form of the mapping, so that tests can call it directly. +export const blockMappingImagePDF: Extract< + DocsExporterPDF['mappings']['blockMapping']['image'], + (...args: never[]) => unknown +> = async (block, exporter) => { + const blob = await exporter.resolveFile(block.props.url); + let result: { png: string; width: number; height: number } | undefined; + const previewWidth = block.props.previewWidth + ? Math.min(block.props.previewWidth, MAX_WIDTH) + : undefined; - if (!blob.type.includes('image')) { - return ; - } + if (!blob.type.includes('image')) { + return ; + } - try { - if (blob.type.includes('svg')) { - const svgText = await blob.text(); - result = await convertSvgToPng(svgText, previewWidth); - } else { - result = await convertBlobToPng(blob, previewWidth); - } - } catch { - return ; + try { + if (blob.type.includes('svg')) { + const svgText = await blob.text(); + result = await convertSvgToPng(svgText, previewWidth); + } else { + result = await convertBlobToPng(blob, previewWidth); } + } catch { + return ; + } - if (!result || !result.png) { - return ; - } + if (!result || !result.png) { + return ; + } - // Ensure the final width never exceeds MAX_WIDTH to prevent images - // from overflowing the page width in the exported document - const finalWidth = Math.min(previewWidth || result.width, MAX_WIDTH); + // Ensure the final width never exceeds MAX_WIDTH to prevent images + // from overflowing the page width in the exported document + const finalWidth = Math.min(previewWidth || result.width, MAX_WIDTH); - return ( - - - {caption(block.props)} - - ); - }; + return ( + + + {caption(block.props)} + + ); +}; /** * Renders an optional caption below an image in the PDF. 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 2ab0e328ae..ce1048b7f5 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 @@ -32,7 +32,7 @@ export const getDocxDocsSchemaMappings = ( quote: blockMappingQuoteDocx, image: blockMappingImageDocx, uploadLoader: blockMappingUploadLoaderDocx, - table: (block, exporter, nestedLevel, numberedListIndex, children) => { + table: (block, exporter, nestedLevel, numberedListIndex) => { /** * Nan values are not supported, so we need to replace them with undefined * to avoid issues during the export. @@ -47,13 +47,17 @@ export const getDocxDocsSchemaMappings = ( ); } - return docxDefaultSchemaMappings.blockMapping.table( - block, - exporter, - nestedLevel, - numberedListIndex, - children, - ); + const tableMapping = docxDefaultSchemaMappings.blockMapping.table; + // A table has no child blocks. + return typeof tableMapping === 'function' + ? tableMapping(block, exporter, nestedLevel, numberedListIndex) + : tableMapping.withChildren( + block, + exporter, + nestedLevel, + numberedListIndex, + [], + ); }, }, inlineContentMapping: {