Skip to content

Commit 7886b25

Browse files
committed
feat: add numbered list handling to export in docx
1 parent d069e7d commit 7886b25

File tree

5 files changed

+110
-26
lines changed

5 files changed

+110
-26
lines changed

packages/core/src/exporter/Exporter.ts

+12-2
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ export abstract class Exporter<
4444
RS,
4545
TS
4646
> {
47+
public numberingSectionStarts: Set<number> = new Set();
48+
4749
public constructor(
4850
_schema: BlockNoteSchema<B, I, S>, // only used for type inference
4951
protected readonly mappings: {
@@ -86,16 +88,24 @@ export abstract class Exporter<
8688

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

91+
public addNumberingSectionStart(number: number) {
92+
this.numberingSectionStarts.add(number);
93+
}
94+
8995
public async mapBlock(
9096
block: BlockFromConfig<B[keyof B], I, S>,
9197
nestingLevel: number,
92-
numberedListIndex: number
98+
numberedListIndex?: number,
99+
numberedListStart?: number,
100+
numberedListIntance?: number
93101
) {
94102
return this.mappings.blockMapping[block.type](
95103
block,
96104
this,
97105
nestingLevel,
98-
numberedListIndex
106+
numberedListIndex,
107+
numberedListStart,
108+
numberedListIntance
99109
);
100110
}
101111
}

packages/core/src/exporter/mapping.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,9 @@ export type BlockMapping<
2626
// this is why there are many `any` types here (same for types below)
2727
exporter: Exporter<any, any, any, RB, RI, any, any>,
2828
nestingLevel: number,
29-
numberedListIndex?: number
29+
numberedListIndex?: number,
30+
numberedListStart?: number,
31+
numberedListIntance?: number
3032
) => RB | Promise<RB>;
3133
};
3234

packages/xl-docx-exporter/src/docx/__snapshots__/basic/document.xml

+37-1
Original file line numberDiff line numberDiff line change
@@ -245,11 +245,47 @@ Line 2</w:t>
245245
<w:pStyle w:val="ListParagraph"/>
246246
<w:numPr>
247247
<w:ilvl w:val="0"/>
248+
<w:numId w:val="4"/>
249+
</w:numPr>
250+
</w:pPr>
251+
<w:r>
252+
<w:t xml:space="preserve">Numbered List Item starting at 10</w:t>
253+
</w:r>
254+
</w:p>
255+
<w:p>
256+
<w:pPr>
257+
<w:pStyle w:val="ListParagraph"/>
258+
<w:numPr>
259+
<w:ilvl w:val="0"/>
260+
<w:numId w:val="4"/>
261+
</w:numPr>
262+
</w:pPr>
263+
<w:r>
264+
<w:t xml:space="preserve">Numbered List Item continuing from 10</w:t>
265+
</w:r>
266+
</w:p>
267+
<w:p>
268+
<w:pPr>
269+
<w:pStyle w:val="ListParagraph"/>
270+
<w:numPr>
271+
<w:ilvl w:val="1"/>
272+
<w:numId w:val="3"/>
273+
</w:numPr>
274+
</w:pPr>
275+
<w:r>
276+
<w:t xml:space="preserve">Numbered List Item Nested 1</w:t>
277+
</w:r>
278+
</w:p>
279+
<w:p>
280+
<w:pPr>
281+
<w:pStyle w:val="ListParagraph"/>
282+
<w:numPr>
283+
<w:ilvl w:val="1"/>
248284
<w:numId w:val="3"/>
249285
</w:numPr>
250286
</w:pPr>
251287
<w:r>
252-
<w:t xml:space="preserve">Numbered List Item</w:t>
288+
<w:t xml:space="preserve">Numbered List Item Nested 2</w:t>
253289
</w:r>
254290
</w:p>
255291
<w:p>

packages/xl-docx-exporter/src/docx/defaultSchema/blocks.ts

+12-2
Original file line numberDiff line numberDiff line change
@@ -74,13 +74,23 @@ export const docxBlockMappingForDefaultSchema: BlockMapping<
7474
},
7575
});
7676
},
77-
numberedListItem: (block, exporter, nestingLevel) => {
77+
numberedListItem: (
78+
block,
79+
exporter,
80+
nestingLevel,
81+
_numberedListIndex,
82+
numberedListStart,
83+
numberedListInstance
84+
) => {
85+
exporter.addNumberingSectionStart(numberedListStart!);
86+
7887
return new Paragraph({
7988
...blockPropsToStyles(block.props, exporter.options.colors),
8089
children: exporter.transformInlineContent(block.content),
8190
numbering: {
82-
reference: "blocknote-numbered-list",
91+
reference: `blocknote-numbered-list-${numberedListStart}`,
8392
level: nestingLevel,
93+
instance: numberedListInstance,
8494
},
8595
});
8696
},

packages/xl-docx-exporter/src/docx/docxExporter.ts

+46-20
Original file line numberDiff line numberDiff line change
@@ -111,8 +111,25 @@ export class DOCXExporter<
111111
nestingLevel = 0
112112
): Promise<Array<Paragraph | Table>> {
113113
const ret: Array<Paragraph | Table> = [];
114+
let currentNumberingStart = 1;
115+
let numberingInstanceCount = 0;
116+
let isFirstNumberedListItem = false;
114117

115118
for (const b of blocks) {
119+
if (b.type === "numberedListItem") {
120+
if (!isFirstNumberedListItem) {
121+
currentNumberingStart = 1;
122+
numberingInstanceCount += 1;
123+
isFirstNumberedListItem = true;
124+
}
125+
if (b.props.start !== undefined) {
126+
currentNumberingStart = b.props.start as number;
127+
numberingInstanceCount += 1;
128+
}
129+
} else {
130+
isFirstNumberedListItem = false;
131+
}
132+
116133
let children = await this.transformBlocks(b.children, nestingLevel + 1);
117134
children = children.map((c, _i) => {
118135
// NOTE: nested tables not supported (we can't insert the new Tab before a table)
@@ -128,7 +145,13 @@ export class DOCXExporter<
128145
}
129146
return c;
130147
});
131-
const self = await this.mapBlock(b as any, nestingLevel, 0 /*unused*/); // TODO: any
148+
const self = await this.mapBlock(
149+
b as any,
150+
nestingLevel,
151+
0 /* unused */,
152+
currentNumberingStart,
153+
numberingInstanceCount
154+
); // TODO: any
132155
if (Array.isArray(self)) {
133156
ret.push(...self, ...children);
134157
} else {
@@ -160,27 +183,29 @@ export class DOCXExporter<
160183
.default;
161184

162185
const bullets = ["•"]; //, "◦", "▪"]; (these don't look great, just use solid bullet for now)
186+
const generateNumberingConfig = (start: number) => ({
187+
reference: `blocknote-numbered-list-${start}`,
188+
levels: Array.from({ length: 9 }, (_, i) => ({
189+
start,
190+
level: i,
191+
format: LevelFormat.DECIMAL,
192+
text: `%${i + 1}.`,
193+
alignment: AlignmentType.LEFT,
194+
style: {
195+
paragraph: {
196+
indent: {
197+
left: DEFAULT_TAB_STOP * (i + 1),
198+
hanging: DEFAULT_TAB_STOP,
199+
},
200+
},
201+
},
202+
})),
203+
});
204+
163205
return {
164206
numbering: {
165207
config: [
166-
{
167-
reference: "blocknote-numbered-list",
168-
levels: Array.from({ length: 9 }, (_, i) => ({
169-
start: 1,
170-
level: i,
171-
format: LevelFormat.DECIMAL,
172-
text: `%${i + 1}.`,
173-
alignment: AlignmentType.LEFT,
174-
style: {
175-
paragraph: {
176-
indent: {
177-
left: DEFAULT_TAB_STOP * (i + 1),
178-
hanging: DEFAULT_TAB_STOP,
179-
},
180-
},
181-
},
182-
})),
183-
},
208+
...[...this.numberingSectionStarts].map(generateNumberingConfig),
184209
{
185210
reference: "blocknote-bullet-list",
186211
levels: Array.from({ length: 9 }, (_, i) => ({
@@ -246,12 +271,13 @@ export class DOCXExporter<
246271
documentOptions: {},
247272
}
248273
) {
274+
const transformedBlocks = await this.transformBlocks(blocks);
249275
const doc = new Document({
250276
...(await this.createDefaultDocumentOptions()),
251277
...options.documentOptions,
252278
sections: [
253279
{
254-
children: await this.transformBlocks(blocks),
280+
children: transformedBlocks,
255281
...options.sectionOptions,
256282
},
257283
],

0 commit comments

Comments
 (0)