Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,11 @@ and this project adheres to
### Added

- ✨(backend) support creating subdoc from file #1987
- ✨(frontend) justified text alignment in the formatting toolbar

### Fixed

- 🐛(frontend) DOCX export helpers map justified text to OOXML `both` for blocks using app-provided paragraph mappings (e.g. callout, quote, image caption)
- 🐛(docs) run migration 0027 without superuser role


Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {
FormattingToolbar,
FormattingToolbarController,
TextAlignButton,
blockTypeSelectItems,
getFormattingToolbarItems,
useDictionary,
Expand Down Expand Up @@ -74,6 +75,25 @@ export const BlockNoteToolbar = ({ aiAllowed }: { aiAllowed: boolean }) => {
return true;
});

const rightAlignIndex = toolbarItems.findIndex(
(item) =>
typeof item === 'object' &&
item !== null &&
'key' in item &&
item.key === 'textAlignRightButton',
);

if (rightAlignIndex !== -1) {
toolbarItems.splice(
rightAlignIndex + 1,
0,
<TextAlignButton
key="textAlignJustifyButton"
textAlignment="justify"
/>,
);
}

return toolbarItems;
}, [dict, t]);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import {
COLORS_DEFAULT,
DefaultProps,
UnreachableCaseError,
} from '@blocknote/core';
import { IParagraphOptions, ShadingType } from 'docx';

/**
* Same semantics as `@blocknote/xl-docx-exporter` `blockPropsToStyles`, but
* `textAlignment: justify` maps to OOXML `both` (normal justified paragraphs).
* Upstream maps justify to `distribute`, which uses “distribute all characters equally”
* and does not match browser/Word paragraph justification.
*/
export function blockNoteDocxBlockPropsToStyles(
props: Partial<DefaultProps>,
colors: typeof COLORS_DEFAULT,
): IParagraphOptions {
return {
shading:
props.backgroundColor === 'default' || !props.backgroundColor
? undefined
: {
type: ShadingType.CLEAR,
fill: (() => {
const color = colors[props.backgroundColor]?.background;
if (!color) {
return undefined;
}
return color.slice(1);
})(),
},
run:
props.textColor === 'default' || !props.textColor
? undefined
: {
color: (() => {
const color = colors[props.textColor]?.text;
if (!color) {
return undefined;
}
return color.slice(1);
})(),
},
alignment:
!props.textAlignment || props.textAlignment === 'left'
? undefined
: props.textAlignment === 'center'
? 'center'
: props.textAlignment === 'right'
? 'right'
: props.textAlignment === 'justify'
? 'both'
: (() => {
throw new UnreachableCaseError(props.textAlignment);
})(),
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { CheckBox, Paragraph, TextRun } from 'docx';

import { DocsExporterDocx } from '../types';

import { blockNoteDocxBlockPropsToStyles } from './blockNoteDocxBlockProps';

/** Default-schema DOCX blocks with correct `justify` → `both` (see blockNoteDocxBlockProps). */

export const blockMappingParagraphDocxJustifyBoth: DocsExporterDocx['mappings']['blockMapping']['paragraph'] =
(block, exporter) =>
new Paragraph({
...blockNoteDocxBlockPropsToStyles(block.props, exporter.options.colors),
children: exporter.transformInlineContent(block.content),
});

export const blockMappingHeadingDocxJustifyBoth: DocsExporterDocx['mappings']['blockMapping']['heading'] =
(block, exporter) =>
new Paragraph({
...blockNoteDocxBlockPropsToStyles(block.props, exporter.options.colors),
children: exporter.transformInlineContent(block.content),
heading: `Heading${block.props.level as 1 | 2 | 3 | 4 | 5 | 6}`,
});

export const blockMappingBulletListItemDocxJustifyBoth: DocsExporterDocx['mappings']['blockMapping']['bulletListItem'] =
(block, exporter, nestingLevel) =>
new Paragraph({
...blockNoteDocxBlockPropsToStyles(block.props, exporter.options.colors),
children: exporter.transformInlineContent(block.content),
numbering: {
reference: 'blocknote-bullet-list',
level: nestingLevel,
},
});

export const blockMappingNumberedListItemDocxJustifyBoth: DocsExporterDocx['mappings']['blockMapping']['numberedListItem'] =
(block, exporter, nestingLevel) =>
new Paragraph({
...blockNoteDocxBlockPropsToStyles(block.props, exporter.options.colors),
children: exporter.transformInlineContent(block.content),
numbering: {
reference: 'blocknote-numbered-list',
level: nestingLevel,
},
});

export const blockMappingToggleListItemDocxJustifyBoth: DocsExporterDocx['mappings']['blockMapping']['toggleListItem'] =
(block, exporter) =>
new Paragraph({
...blockNoteDocxBlockPropsToStyles(block.props, exporter.options.colors),
children: [
new TextRun({
children: ['> '],
}),
...exporter.transformInlineContent(block.content),
],
});

export const blockMappingCheckListItemDocxJustifyBoth: DocsExporterDocx['mappings']['blockMapping']['checkListItem'] =
(block, exporter) =>
new Paragraph({
...blockNoteDocxBlockPropsToStyles(block.props, exporter.options.colors),
children: [
new CheckBox({ checked: block.props.checked }),
new TextRun({
children: [' '],
}),
...exporter.transformInlineContent(block.content),
],
});
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ function blockPropsToStyles(
: props.textAlignment === 'right'
? 'right'
: props.textAlignment === 'justify'
? 'distribute'
? 'both'
: (() => {
throw new UnreachableCaseError(props.textAlignment);
})(),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
export * from './blockNoteDocxBlockProps';
export * from './calloutDocx';
export * from './defaultBlocksDocxJustify';
export * from './calloutODT';
export * from './calloutPDF';
export * from './headingPDF';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
import { docxDefaultSchemaMappings } from '@blocknote/xl-docx-exporter';

import {
blockMappingBulletListItemDocxJustifyBoth,
blockMappingCalloutDocx,
blockMappingCheckListItemDocxJustifyBoth,
blockMappingHeadingDocxJustifyBoth,
blockMappingImageDocx,
blockMappingNumberedListItemDocxJustifyBoth,
blockMappingParagraphDocxJustifyBoth,
blockMappingQuoteDocx,
blockMappingToggleListItemDocxJustifyBoth,
blockMappingUploadLoaderDocx,
} from './blocks-mapping';
import { inlineContentMappingInterlinkingLinkDocx } from './inline-content-mapping';
Expand All @@ -13,6 +19,12 @@ export const docxDocsSchemaMappings: DocsExporterDocx['mappings'] = {
...docxDefaultSchemaMappings,
blockMapping: {
...docxDefaultSchemaMappings.blockMapping,
paragraph: blockMappingParagraphDocxJustifyBoth,
heading: blockMappingHeadingDocxJustifyBoth,
bulletListItem: blockMappingBulletListItemDocxJustifyBoth,
numberedListItem: blockMappingNumberedListItemDocxJustifyBoth,
toggleListItem: blockMappingToggleListItemDocxJustifyBoth,
checkListItem: blockMappingCheckListItemDocxJustifyBoth,
callout: blockMappingCalloutDocx,
// We're reusing the file block mapping for PDF blocks; both share the same
// implementation signature, so we can reuse the handler directly.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ export function docxBlockPropsToStyles(
: props.textAlignment === 'right'
? 'right'
: props.textAlignment === 'justify'
? 'distribute'
? 'both'
: (() => {
throw new UnreachableCaseError(props.textAlignment);
})(),
Expand Down