Skip to content

Commit 90def7f

Browse files
chittolinagchittolinacaio-pizzol
authored
SD-2440 - fix: toc not loading on paragraphs (#2706)
* fix: toc not loading on paragraphs * fix: don't decode every child twice Co-authored-by: Caio Pizzol <97641911+caio-pizzol@users.noreply.github.com> * fix: build * refactor: removed unused code * chore: small code tweaks * chore: small code tweaks * chore: removed unused code * test: add boundary unit tests and import regression test for field-based TOC (SD-2440) --------- Co-authored-by: Gabriel Chittolina <gabrielchittolina1@gmail.com> Co-authored-by: Caio Pizzol <97641911+caio-pizzol@users.noreply.github.com> Co-authored-by: Caio Pizzol <caio@harbourshare.com>
1 parent 4e4fc05 commit 90def7f

4 files changed

Lines changed: 112 additions & 5 deletions

File tree

packages/super-editor/src/editors/v1/core/super-converter/v3/handlers/sd/tableOfContents/tableOfContents-translator.js

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,16 +33,26 @@ const encode = (params) => {
3333
const { nodes = [], nodeListHandler } = params || {};
3434
const node = nodes[0];
3535

36-
const processedContent = nodeListHandler.handler({
36+
let processedContent = nodeListHandler.handler({
3737
...params,
3838
nodes: node.elements || [],
3939
});
40+
const hasParagraphBlocks = (processedContent || []).some((child) => child?.type === 'paragraph');
41+
if (!hasParagraphBlocks) {
42+
processedContent = [
43+
{
44+
type: 'paragraph',
45+
content: processedContent.filter((child) => Boolean(child && child.type)),
46+
},
47+
];
48+
}
49+
const attrs = {
50+
instruction: node.attributes?.instruction || '',
51+
};
52+
attrs.rightAlignPageNumbers = deriveRightAlignPageNumbers(processedContent);
4053
const processedNode = {
4154
type: 'tableOfContents',
42-
attrs: {
43-
instruction: node.attributes?.instruction || '',
44-
rightAlignPageNumbers: deriveRightAlignPageNumbers(processedContent),
45-
},
55+
attrs,
4656
content: processedContent,
4757
};
4858

packages/super-editor/src/editors/v1/core/super-converter/v3/handlers/sd/tableOfContents/tableOfContents-translator.test.js

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,73 @@ describe('sd:tableOfContents translator', () => {
9090
const result = config.encode(params);
9191
expect(result.attrs.rightAlignPageNumbers).toBe(false);
9292
});
93+
94+
it('wraps inline children into a paragraph when parent accepts blocks', () => {
95+
const mockNodeListHandler = {
96+
handler: vi.fn(() => [{ type: 'text', text: 'Inline content' }]),
97+
};
98+
const params = {
99+
nodes: [
100+
{
101+
name: 'sd:tableOfContents',
102+
attributes: { instruction: 'TOC \\h' },
103+
elements: [{ name: 'w:r', elements: [] }],
104+
},
105+
],
106+
nodeListHandler: mockNodeListHandler,
107+
};
108+
109+
const result = config.encode(params);
110+
expect(result).toEqual({
111+
type: 'tableOfContents',
112+
attrs: { instruction: 'TOC \\h', rightAlignPageNumbers: true },
113+
content: [{ type: 'paragraph', content: [{ type: 'text', text: 'Inline content' }] }],
114+
});
115+
});
116+
117+
it('does not wrap when content already contains paragraph blocks', () => {
118+
const mockNodeListHandler = {
119+
handler: vi.fn(() => [
120+
{ type: 'paragraph', content: [{ type: 'text', text: 'Para' }] },
121+
{ type: 'text', text: 'trailing inline' },
122+
]),
123+
};
124+
const params = {
125+
nodes: [
126+
{
127+
name: 'sd:tableOfContents',
128+
attributes: { instruction: 'TOC \\o "1-3"' },
129+
elements: [{ name: 'w:p', elements: [] }],
130+
},
131+
],
132+
nodeListHandler: mockNodeListHandler,
133+
};
134+
135+
const result = config.encode(params);
136+
expect(result.content).toEqual([
137+
{ type: 'paragraph', content: [{ type: 'text', text: 'Para' }] },
138+
{ type: 'text', text: 'trailing inline' },
139+
]);
140+
});
141+
142+
it('filters out null and typeless children when wrapping', () => {
143+
const mockNodeListHandler = {
144+
handler: vi.fn(() => [null, { type: 'text', text: 'valid' }, undefined, {}]),
145+
};
146+
const params = {
147+
nodes: [
148+
{
149+
name: 'sd:tableOfContents',
150+
attributes: { instruction: 'TOC \\h' },
151+
elements: [{ name: 'w:r', elements: [] }],
152+
},
153+
],
154+
nodeListHandler: mockNodeListHandler,
155+
};
156+
157+
const result = config.encode(params);
158+
expect(result.content).toEqual([{ type: 'paragraph', content: [{ type: 'text', text: 'valid' }] }]);
159+
});
93160
});
94161

95162
describe('decode', () => {

packages/super-editor/src/editors/v1/extensions/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,7 @@ export {
274274
TableCell,
275275
TableHeader,
276276
DocumentIndex,
277+
TableOfContents,
277278
IndexEntry,
278279
TableOfContentsEntry,
279280
TocPageNumber,
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import fs from 'node:fs';
2+
import path from 'node:path';
3+
import { fileURLToPath } from 'node:url';
4+
import { test, expect } from '../../fixtures/superdoc.js';
5+
import { assertDocumentApiReady, getDocumentText } from '../../helpers/document-api.js';
6+
7+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
8+
const DOC_PATH = path.resolve(
9+
__dirname,
10+
'../../test-data/rendering/sd-2440-field-based-toc-list-of-tables-figures.docx',
11+
);
12+
13+
test.skip(!fs.existsSync(DOC_PATH), 'Test document not available — run pnpm corpus:pull');
14+
15+
test.use({ config: { toolbar: 'full', comments: 'off' } });
16+
17+
test('loads document with field-based TOC list of tables/figures without schema errors (SD-2440)', async ({
18+
superdoc,
19+
}) => {
20+
await superdoc.loadDocument(DOC_PATH);
21+
await superdoc.waitForStable();
22+
await assertDocumentApiReady(superdoc.page);
23+
24+
const text = await getDocumentText(superdoc.page);
25+
expect(text.length).toBeGreaterThan(0);
26+
27+
await expect(superdoc.page.locator('.superdoc-page').first()).toBeVisible();
28+
await expect(superdoc.page.locator('.superdoc-line').first()).toBeVisible();
29+
});

0 commit comments

Comments
 (0)