Skip to content

Commit eeafd64

Browse files
committed
Merge remote-tracking branch 'upstream/main' into feat/tiff-image-support
2 parents f538843 + 08ffc41 commit eeafd64

123 files changed

Lines changed: 9095 additions & 684 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/ci-examples.yml

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,31 @@ jobs:
1212
strategy:
1313
fail-fast: false
1414
matrix:
15-
example: [react, vue, vanilla, cdn, angular]
15+
example: [react, vue, vanilla, cdn, angular, nuxt, laravel]
1616
steps:
1717
- uses: actions/checkout@v6
1818

19+
- name: Setup PHP
20+
if: matrix.example == 'laravel'
21+
uses: shivammathur/setup-php@v2
22+
with:
23+
php-version: '8.2'
24+
1925
- name: Setup Node.js
2026
uses: actions/setup-node@v6
2127
with:
2228
node-version: '20'
2329

30+
- name: Install Composer dependencies
31+
if: matrix.example == 'laravel'
32+
working-directory: examples/getting-started/laravel
33+
run: composer install --no-interaction --prefer-dist
34+
35+
- name: Prepare Laravel environment
36+
if: matrix.example == 'laravel'
37+
working-directory: examples/getting-started/laravel
38+
run: cp .env.example .env && php artisan key:generate
39+
2440
- name: Install example dependencies
2541
if: matrix.example != 'cdn'
2642
working-directory: examples/getting-started/${{ matrix.example }}

.github/workflows/pr-labels.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,9 @@ jobs:
9090
if: >-
9191
github.event.action == 'opened' &&
9292
github.event.pull_request.author_association != 'MEMBER' &&
93-
github.event.pull_request.author_association != 'OWNER'
93+
github.event.pull_request.author_association != 'OWNER' &&
94+
github.event.pull_request.author_association != 'COLLABORATOR' &&
95+
github.event.pull_request.user.type != 'Bot'
9496
uses: actions/github-script@v7
9597
with:
9698
script: |

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,8 @@ Special thanks to these community members who have contributed code to SuperDoc:
144144
<a href="https://github.com/asumaran"><img src="https://github.com/asumaran.png" width="50" height="50" alt="asumaran" title="Alfredo Sumaran" /></a>
145145
<a href="https://github.com/J-Michalek"><img src="https://github.com/J-Michalek.png" width="50" height="50" alt="J-Michalek" title="Jakub Michálek" /></a>
146146
<a href="https://github.com/gm1357"><img src="https://github.com/gm1357.png" width="50" height="50" alt="gm1357" title="Gabriel Machado" /></a>
147+
<a href="https://github.com/roncallyt"><img src="https://github.com/roncallyt.png" width="50" height="50" alt="roncallyt" title="Thomerson Roncally" /></a>
148+
<a href="https://github.com/gpardhivvarma"><img src="https://github.com/gpardhivvarma.png" width="50" height="50" alt="gpardhivvarma" title="G Pardhiv Varma" /></a>
147149

148150
Want to see your avatar here? Check the [Contributing Guide](CONTRIBUTING.md) to get started.
149151

apps/cli/scripts/export-sdk-contract.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ const INTENT_NAMES = {
8989
'doc.sections.setLinkToPrevious': 'set_section_link_to_previous',
9090
'doc.sections.setPageBorders': 'set_section_page_borders',
9191
'doc.sections.clearPageBorders': 'clear_section_page_borders',
92+
'doc.create.tableOfContents': 'create_table_of_contents',
9293
'doc.lists.list': 'list_lists',
9394
'doc.lists.get': 'get_list',
9495
'doc.lists.insert': 'insert_list',
@@ -105,6 +106,11 @@ const INTENT_NAMES = {
105106
'doc.trackChanges.list': 'list_tracked_changes',
106107
'doc.trackChanges.get': 'get_tracked_change',
107108
'doc.trackChanges.decide': 'decide_tracked_change',
109+
'doc.toc.list': 'list_table_of_contents',
110+
'doc.toc.get': 'get_table_of_contents',
111+
'doc.toc.configure': 'configure_table_of_contents',
112+
'doc.toc.update': 'update_table_of_contents',
113+
'doc.toc.remove': 'remove_table_of_contents',
108114
'doc.query.match': 'query_match',
109115
'doc.mutations.preview': 'preview_mutations',
110116
'doc.mutations.apply': 'apply_mutations',

apps/cli/src/__tests__/conformance/harness.ts

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { copyFile, mkdtemp, mkdir, rm } from 'node:fs/promises';
22
import { tmpdir } from 'node:os';
33
import path from 'node:path';
44
import { run } from '../../index';
5-
import { resolveListDocFixture, resolveSourceDocFixture } from '../fixtures';
5+
import { resolveListDocFixture, resolveSourceDocFixture, resolveTocDocFixture } from '../fixtures';
66

77
type RunResult = {
88
code: number;
@@ -48,6 +48,12 @@ export type ListItemAddress = {
4848
nodeId: string;
4949
};
5050

51+
export type TocAddress = {
52+
kind: 'block';
53+
nodeType: 'tableOfContents';
54+
nodeId: string;
55+
};
56+
5157
function parseEnvelope(raw: RunResult): CommandEnvelope {
5258
const source = raw.stdout.trim() || raw.stderr.trim();
5359
if (!source) {
@@ -119,6 +125,36 @@ export class ConformanceHarness {
119125
return filePath;
120126
}
121127

128+
async copyTocFixtureDoc(label: string, stateDir: string): Promise<string> {
129+
const filePath = path.join(this.docsDir, `${this.nextId()}-${label}.docx`);
130+
131+
try {
132+
await copyFile(await resolveTocDocFixture(), filePath);
133+
const probe = await this.runCli(['toc', 'list', filePath, '--limit', '1'], stateDir);
134+
if (probe.result.code === 0) {
135+
return filePath;
136+
}
137+
} catch {
138+
// Fall back to creating a TOC fixture from the generic source doc.
139+
}
140+
141+
const sourceDoc = await this.copyFixtureDoc(`${label}-seed`);
142+
const seededPath = path.join(this.docsDir, `${this.nextId()}-${label}-seeded.docx`);
143+
const { result, envelope } = await this.runCli(
144+
['create', 'table-of-contents', sourceDoc, '--out', seededPath],
145+
stateDir,
146+
);
147+
148+
if (result.code !== 0 || envelope.ok !== true) {
149+
const details = envelope.ok
150+
? 'unexpected non-success envelope'
151+
: `${envelope.error.code}: ${envelope.error.message}`;
152+
throw new Error(`Unable to seed TOC fixture for ${label}: ${details}`);
153+
}
154+
155+
return seededPath;
156+
}
157+
122158
createOutputPath(label: string): string {
123159
return path.join(this.docsDir, `${this.nextId()}-${label}.docx`);
124160
}
@@ -236,6 +272,27 @@ export class ConformanceHarness {
236272
return address;
237273
}
238274

275+
async firstTocAddress(docPath: string, stateDir: string): Promise<TocAddress> {
276+
const { result, envelope } = await this.runCli(['toc', 'list', docPath, '--limit', '1'], stateDir);
277+
if (result.code !== 0) {
278+
throw new Error(`Unable to resolve first table of contents for ${docPath}`);
279+
}
280+
281+
assertSuccessEnvelope(envelope);
282+
const data = envelope.data as {
283+
result?: {
284+
items?: Array<{
285+
address?: TocAddress;
286+
}>;
287+
};
288+
};
289+
const address = data.result?.items?.[0]?.address;
290+
if (!address) {
291+
throw new Error(`No table of contents address found in ${docPath}`);
292+
}
293+
return address;
294+
}
295+
239296
async addCommentFixture(
240297
stateDir: string,
241298
label: string,

apps/cli/src/__tests__/conformance/scenarios.ts

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -334,6 +334,43 @@ function tableScopedMutationScenario(
334334
};
335335
}
336336

337+
function tocMutationScenario(
338+
op: string,
339+
extraArgs: string[],
340+
): (harness: ConformanceHarness) => Promise<ScenarioInvocation> {
341+
return async (harness) => {
342+
const label = `toc-${op.replace(/\./g, '-')}`;
343+
const stateDir = await harness.createStateDir(`${label}-success`);
344+
const docPath = await harness.copyTocFixtureDoc(`${label}-source`, stateDir);
345+
const tocTarget = await harness.firstTocAddress(docPath, stateDir);
346+
return {
347+
stateDir,
348+
args: [
349+
...commandTokens(`doc.${op}` as CliOperationId),
350+
docPath,
351+
'--target-json',
352+
JSON.stringify(tocTarget),
353+
...extraArgs,
354+
'--out',
355+
harness.createOutputPath(`${label}-out`),
356+
],
357+
};
358+
};
359+
}
360+
361+
function tocReadWithTargetScenario(op: string): (harness: ConformanceHarness) => Promise<ScenarioInvocation> {
362+
return async (harness) => {
363+
const label = `toc-${op.replace(/\./g, '-')}`;
364+
const stateDir = await harness.createStateDir(`${label}-success`);
365+
const docPath = await harness.copyTocFixtureDoc(`${label}-source`, stateDir);
366+
const tocTarget = await harness.firstTocAddress(docPath, stateDir);
367+
return {
368+
stateDir,
369+
args: [...commandTokens(`doc.${op}` as CliOperationId), docPath, '--target-json', JSON.stringify(tocTarget)],
370+
};
371+
};
372+
}
373+
337374
export const SUCCESS_SCENARIOS = {
338375
'doc.open': async (harness: ConformanceHarness): Promise<ScenarioInvocation> => {
339376
const stateDir = await harness.createStateDir('doc-open-success');
@@ -582,6 +619,23 @@ export const SUCCESS_SCENARIOS = {
582619
],
583620
};
584621
},
622+
'doc.create.tableOfContents': async (harness: ConformanceHarness): Promise<ScenarioInvocation> => {
623+
const stateDir = await harness.createStateDir('doc-create-toc-success');
624+
const docPath = await harness.copyFixtureDoc('doc-create-toc');
625+
return {
626+
stateDir,
627+
args: [
628+
...commandTokens('doc.create.tableOfContents'),
629+
docPath,
630+
'--at-json',
631+
JSON.stringify({ kind: 'documentStart' }),
632+
'--config-json',
633+
JSON.stringify({ hyperlinks: true, outlineLevels: { from: 1, to: 3 } }),
634+
'--out',
635+
harness.createOutputPath('doc-create-toc-output'),
636+
],
637+
};
638+
},
585639
'doc.create.paragraph': async (harness: ConformanceHarness): Promise<ScenarioInvocation> => {
586640
const stateDir = await harness.createStateDir('doc-create-paragraph-success');
587641
const docPath = await harness.copyFixtureDoc('doc-create-paragraph');
@@ -1160,6 +1214,18 @@ export const SUCCESS_SCENARIOS = {
11601214
],
11611215
};
11621216
},
1217+
'doc.toc.list': async (harness: ConformanceHarness): Promise<ScenarioInvocation> => {
1218+
const stateDir = await harness.createStateDir('doc-toc-list-success');
1219+
const docPath = await harness.copyTocFixtureDoc('doc-toc-list', stateDir);
1220+
return {
1221+
stateDir,
1222+
args: [...commandTokens('doc.toc.list'), docPath, '--limit', '1'],
1223+
};
1224+
},
1225+
'doc.toc.get': tocReadWithTargetScenario('toc.get'),
1226+
'doc.toc.configure': tocMutationScenario('toc.configure', ['--patch-json', JSON.stringify({ hyperlinks: false })]),
1227+
'doc.toc.update': tocMutationScenario('toc.update', []),
1228+
'doc.toc.remove': tocMutationScenario('toc.remove', []),
11631229
'doc.session.list': async (harness: ConformanceHarness): Promise<ScenarioInvocation> => {
11641230
const stateDir = await harness.createStateDir('doc-session-list-success');
11651231
await harness.openSessionFixture(stateDir, 'doc-session-list', 'session-list-success');

apps/cli/src/__tests__/fixtures.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,15 @@ const LIST_SOURCE_DOC_CANDIDATES = [
1515
path.join(REPO_ROOT, 'e2e-tests/test-data/basic-documents/lists-complex-items.docx'),
1616
];
1717

18+
const TOC_SOURCE_DOC_CANDIDATES = [
19+
path.join(REPO_ROOT, 'test-corpus/basic/table-of-contents.docx'),
20+
path.join(REPO_ROOT, 'test-corpus/basic/table-of-contents-sdt.docx'),
21+
path.join(REPO_ROOT, 'test-corpus/layout/toc-with-heading2.docx'),
22+
];
23+
1824
let resolvedSourceDoc: string | null = null;
1925
let resolvedListSourceDoc: string | null = null;
26+
let resolvedTocSourceDoc: string | null = null;
2027

2128
async function resolveFixture(candidates: string[], fixtureLabel: string): Promise<string> {
2229
for (const candidate of candidates) {
@@ -42,3 +49,9 @@ export async function resolveListDocFixture(): Promise<string> {
4249
resolvedListSourceDoc = await resolveFixture(LIST_SOURCE_DOC_CANDIDATES, 'list');
4350
return resolvedListSourceDoc;
4451
}
52+
53+
export async function resolveTocDocFixture(): Promise<string> {
54+
if (resolvedTocSourceDoc != null) return resolvedTocSourceDoc;
55+
resolvedTocSourceDoc = await resolveFixture(TOC_SOURCE_DOC_CANDIDATES, 'table-of-contents');
56+
return resolvedTocSourceDoc;
57+
}

apps/cli/src/cli/operation-hints.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ export const SUCCESS_VERB: Record<CliExposedOperationId, string> = {
5757
'styles.apply': 'applied stylesheet defaults',
5858
'create.paragraph': 'created paragraph',
5959
'create.heading': 'created heading',
60+
'create.tableOfContents': 'created table of contents',
6061
'lists.list': 'listed items',
6162
'lists.get': 'resolved list item',
6263
'lists.insert': 'inserted list item',
@@ -73,6 +74,11 @@ export const SUCCESS_VERB: Record<CliExposedOperationId, string> = {
7374
'trackChanges.list': 'listed tracked changes',
7475
'trackChanges.get': 'resolved tracked change',
7576
'trackChanges.decide': 'reviewed tracked change',
77+
'toc.list': 'listed tables of contents',
78+
'toc.get': 'resolved table of contents',
79+
'toc.configure': 'configured table of contents',
80+
'toc.update': 'updated table of contents',
81+
'toc.remove': 'removed table of contents',
7682
'query.match': 'matched selectors',
7783
'mutations.preview': 'previewed mutations',
7884
'mutations.apply': 'applied mutations',
@@ -164,6 +170,7 @@ export const OUTPUT_FORMAT: Record<CliExposedOperationId, OutputFormat> = {
164170
'styles.apply': 'receipt',
165171
'create.paragraph': 'createResult',
166172
'create.heading': 'createResult',
173+
'create.tableOfContents': 'createResult',
167174
'lists.list': 'listResult',
168175
'lists.get': 'listItemInfo',
169176
'lists.insert': 'listsMutationResult',
@@ -180,6 +187,11 @@ export const OUTPUT_FORMAT: Record<CliExposedOperationId, OutputFormat> = {
180187
'trackChanges.list': 'trackChangeList',
181188
'trackChanges.get': 'trackChangeInfo',
182189
'trackChanges.decide': 'trackChangeMutationReceipt',
190+
'toc.list': 'plain',
191+
'toc.get': 'plain',
192+
'toc.configure': 'plain',
193+
'toc.update': 'plain',
194+
'toc.remove': 'plain',
183195
'query.match': 'plain',
184196
'mutations.preview': 'plain',
185197
'mutations.apply': 'plain',
@@ -255,6 +267,7 @@ export const RESPONSE_ENVELOPE_KEY: Record<CliExposedOperationId, string | null>
255267
'styles.apply': 'receipt',
256268
'create.paragraph': 'result',
257269
'create.heading': 'result',
270+
'create.tableOfContents': 'result',
258271
'lists.list': 'result',
259272
'lists.get': 'item',
260273
'lists.insert': 'result',
@@ -271,6 +284,11 @@ export const RESPONSE_ENVELOPE_KEY: Record<CliExposedOperationId, string | null>
271284
'trackChanges.list': 'result',
272285
'trackChanges.get': 'change',
273286
'trackChanges.decide': 'receipt',
287+
'toc.list': 'result',
288+
'toc.get': 'result',
289+
'toc.configure': 'result',
290+
'toc.update': 'result',
291+
'toc.remove': 'result',
274292
'query.match': 'result',
275293
'mutations.preview': 'result',
276294
'mutations.apply': 'result',
@@ -352,6 +370,7 @@ export type OperationFamily =
352370
| 'comments'
353371
| 'lists'
354372
| 'tables'
373+
| 'toc'
355374
| 'textMutation'
356375
| 'create'
357376
| 'blocks'
@@ -374,6 +393,7 @@ export const OPERATION_FAMILY: Record<CliExposedOperationId, OperationFamily> =
374393
'styles.apply': 'general',
375394
'create.paragraph': 'create',
376395
'create.heading': 'create',
396+
'create.tableOfContents': 'create',
377397
'lists.list': 'lists',
378398
'lists.get': 'lists',
379399
'lists.insert': 'lists',
@@ -390,6 +410,11 @@ export const OPERATION_FAMILY: Record<CliExposedOperationId, OperationFamily> =
390410
'trackChanges.list': 'trackChanges',
391411
'trackChanges.get': 'trackChanges',
392412
'trackChanges.decide': 'trackChanges',
413+
'toc.list': 'query',
414+
'toc.get': 'query',
415+
'toc.configure': 'toc',
416+
'toc.update': 'toc',
417+
'toc.remove': 'toc',
393418
'query.match': 'query',
394419
'mutations.preview': 'general',
395420
'mutations.apply': 'general',

0 commit comments

Comments
 (0)