Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix for numbered list item when exporting to PDF, DOCX #1357

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
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
19 changes: 18 additions & 1 deletion examples/05-interoperability/05-converting-blocks-to-pdf/App.tsx
Original file line number Diff line number Diff line change
@@ -170,7 +170,24 @@ export default function App() {
},
{
type: "numberedListItem",
content: "Numbered List Item",
content: "Numbered List Item starting at 10",
props: {
start: 10,
},
},
{
type: "numberedListItem",
content: "Numbered List Item continuing from 10",
children: [
{
type: "numberedListItem",
content: "Numbered List Item Nested 1",
},
{
type: "numberedListItem",
content: "Numbered List Item Nested 2",
},
],
},
{
type: "checkListItem",
Original file line number Diff line number Diff line change
@@ -166,7 +166,24 @@ export default function App() {
},
{
type: "numberedListItem",
content: "Numbered List Item",
content: "Numbered List Item starting at 10",
props: {
start: 10,
},
},
{
type: "numberedListItem",
content: "Numbered List Item continuing from 10",
children: [
{
type: "numberedListItem",
content: "Numbered List Item Nested 1",
},
{
type: "numberedListItem",
content: "Numbered List Item Nested 2",
},
],
},
{
type: "checkListItem",
14 changes: 12 additions & 2 deletions packages/core/src/exporter/Exporter.ts
Original file line number Diff line number Diff line change
@@ -44,6 +44,8 @@ export abstract class Exporter<
RS,
TS
> {
public numberingSectionStarts: Set<number> = new Set();

public constructor(
_schema: BlockNoteSchema<B, I, S>, // only used for type inference
protected readonly mappings: {
@@ -86,16 +88,24 @@ export abstract class Exporter<

public abstract transformStyledText(styledText: StyledText<S>): TS;

public addNumberingSectionStart(number: number) {
this.numberingSectionStarts.add(number);
}

public async mapBlock(
block: BlockFromConfig<B[keyof B], I, S>,
nestingLevel: number,
numberedListIndex: number
numberedListIndex?: number,
numberedListStart?: number,
numberedListIntance?: number
) {
return this.mappings.blockMapping[block.type](
block,
this,
nestingLevel,
numberedListIndex
numberedListIndex,
numberedListStart,
numberedListIntance
);
}
}
4 changes: 3 additions & 1 deletion packages/core/src/exporter/mapping.ts
Original file line number Diff line number Diff line change
@@ -26,7 +26,9 @@ export type BlockMapping<
// this is why there are many `any` types here (same for types below)
exporter: Exporter<any, any, any, RB, RI, any, any>,
nestingLevel: number,
numberedListIndex?: number
numberedListIndex?: number,
numberedListStart?: number,
numberedListIntance?: number
) => RB | Promise<RB>;
};

Original file line number Diff line number Diff line change
@@ -241,11 +241,47 @@
<w:pStyle w:val="ListParagraph"/>
<w:numPr>
<w:ilvl w:val="0"/>
<w:numId w:val="4"/>
</w:numPr>
</w:pPr>
<w:r>
<w:t xml:space="preserve">Numbered List Item starting at 10</w:t>
</w:r>
</w:p>
<w:p>
<w:pPr>
<w:pStyle w:val="ListParagraph"/>
<w:numPr>
<w:ilvl w:val="0"/>
<w:numId w:val="4"/>
</w:numPr>
</w:pPr>
<w:r>
<w:t xml:space="preserve">Numbered List Item continuing from 10</w:t>
</w:r>
</w:p>
<w:p>
<w:pPr>
<w:pStyle w:val="ListParagraph"/>
<w:numPr>
<w:ilvl w:val="1"/>
<w:numId w:val="3"/>
</w:numPr>
</w:pPr>
<w:r>
<w:t xml:space="preserve">Numbered List Item Nested 1</w:t>
</w:r>
</w:p>
<w:p>
<w:pPr>
<w:pStyle w:val="ListParagraph"/>
<w:numPr>
<w:ilvl w:val="1"/>
<w:numId w:val="3"/>
</w:numPr>
</w:pPr>
<w:r>
<w:t xml:space="preserve">Numbered List Item</w:t>
<w:t xml:space="preserve">Numbered List Item Nested 2</w:t>
</w:r>
</w:p>
<w:p>
14 changes: 12 additions & 2 deletions packages/xl-docx-exporter/src/docx/defaultSchema/blocks.ts
Original file line number Diff line number Diff line change
@@ -77,13 +77,23 @@ export const docxBlockMappingForDefaultSchema: BlockMapping<
},
});
},
numberedListItem: (block, exporter, nestingLevel) => {
numberedListItem: (
block,
exporter,
nestingLevel,
_numberedListIndex,
numberedListStart,
numberedListInstance
) => {
exporter.addNumberingSectionStart(numberedListStart!);

return new Paragraph({
...blockPropsToStyles(block.props, exporter.options.colors),
children: exporter.transformInlineContent(block.content),
numbering: {
reference: "blocknote-numbered-list",
reference: `blocknote-numbered-list-${numberedListStart}`,
level: nestingLevel,
instance: numberedListInstance,
},
});
},
66 changes: 46 additions & 20 deletions packages/xl-docx-exporter/src/docx/docxExporter.ts
Original file line number Diff line number Diff line change
@@ -111,8 +111,25 @@ export class DOCXExporter<
nestingLevel = 0
): Promise<Array<Paragraph | Table>> {
const ret: Array<Paragraph | Table> = [];
let currentNumberingStart = 1;
let numberingInstanceCount = 0;
let isFirstNumberedListItem = false;

for (const b of blocks) {
if (b.type === "numberedListItem") {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code is a bit messy and could definitely be improved, but its the best I could come up with to ensure a unique instance id is given to each numbered list. In theory this id should guarantee no weird continuations of non adjacent lists happen, but for some reason they still are. I would not merge this as is

if (!isFirstNumberedListItem) {
currentNumberingStart = 1;
numberingInstanceCount += 1;
isFirstNumberedListItem = true;
}
if (b.props.start !== undefined) {
currentNumberingStart = b.props.start as number;
numberingInstanceCount += 1;
}
} else {
isFirstNumberedListItem = false;
}

let children = await this.transformBlocks(b.children, nestingLevel + 1);
children = children.map((c, _i) => {
// NOTE: nested tables not supported (we can't insert the new Tab before a table)
@@ -128,7 +145,13 @@ export class DOCXExporter<
}
return c;
});
const self = await this.mapBlock(b as any, nestingLevel, 0 /*unused*/); // TODO: any
const self = await this.mapBlock(
b as any,
nestingLevel,
0 /* unused */,
currentNumberingStart,
numberingInstanceCount
); // TODO: any
if (Array.isArray(self)) {
ret.push(...self, ...children);
} else {
@@ -178,27 +201,29 @@ export class DOCXExporter<
.default;

const bullets = ["•"]; //, "◦", "▪"]; (these don't look great, just use solid bullet for now)
const generateNumberingConfig = (start: number) => ({
reference: `blocknote-numbered-list-${start}`,
levels: Array.from({ length: 9 }, (_, i) => ({
start,
level: i,
format: LevelFormat.DECIMAL,
text: `%${i + 1}.`,
alignment: AlignmentType.LEFT,
style: {
paragraph: {
indent: {
left: DEFAULT_TAB_STOP * (i + 1),
hanging: DEFAULT_TAB_STOP,
},
},
},
})),
});

return {
numbering: {
config: [
{
reference: "blocknote-numbered-list",
levels: Array.from({ length: 9 }, (_, i) => ({
start: 1,
level: i,
format: LevelFormat.DECIMAL,
text: `%${i + 1}.`,
alignment: AlignmentType.LEFT,
style: {
paragraph: {
indent: {
left: DEFAULT_TAB_STOP * (i + 1),
hanging: DEFAULT_TAB_STOP,
},
},
},
})),
},
...[...this.numberingSectionStarts].map(generateNumberingConfig),
{
reference: "blocknote-bullet-list",
levels: Array.from({ length: 9 }, (_, i) => ({
@@ -264,12 +289,13 @@ export class DOCXExporter<
documentOptions: {},
}
) {
const transformedBlocks = await this.transformBlocks(blocks);
const doc = new Document({
...(await this.createDefaultDocumentOptions()),
...options.documentOptions,
sections: [
{
children: await this.transformBlocks(blocks),
children: transformedBlocks,
...options.sectionOptions,
},
],
Loading
Oops, something went wrong.
Loading
Oops, something went wrong.