@docx4j/core-ts 0.1.5 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +76 -0
- package/dist/model/content/Body.d.mts +36 -2
- package/dist/model/content/Body.mjs +79 -4
- package/dist/model/content/Comment.d.mts +15 -5
- package/dist/model/content/Comment.mjs +70 -22
- package/dist/model/content/List.d.mts +13 -2
- package/dist/model/content/List.mjs +48 -9
- package/dist/model/content/Paragraph.d.mts +11 -1
- package/dist/model/content/Paragraph.mjs +18 -7
- package/dist/model/content/Range.d.mts +7 -2
- package/dist/model/content/Range.mjs +21 -8
- package/dist/model/content/binder.d.mts +44 -0
- package/dist/model/content/binder.mjs +95 -0
- package/dist/model/content/comments.d.mts +19 -1
- package/dist/model/content/index.d.mts +5 -3
- package/dist/model/content/index.mjs +3 -1
- package/dist/model/content/search.d.mts +27 -2
- package/dist/model/content/search.mjs +59 -3
- package/dist/model/content/stylesFacade.d.mts +37 -0
- package/dist/model/content/stylesFacade.mjs +114 -0
- package/dist/model/content/tracking.d.mts +57 -2
- package/dist/model/content/tracking.mjs +47 -15
- package/dist/model/customxml/CustomXmlPartCollection.d.mts +7 -0
- package/dist/model/customxml/CustomXmlPartCollection.mjs +40 -6
- package/dist/model/customxml/xpath.d.mts +18 -3
- package/dist/model/customxml/xpath.mjs +18 -3
- package/dist/office-js/index.d.mts +3 -1
- package/dist/office-js/run.d.mts +12 -2
- package/dist/office-js/run.mjs +8 -1
- package/dist/office-js/shimmed.d.mts +46 -0
- package/dist/office-js/shimmed.mjs +1 -0
- package/dist/opc/exceptions.d.mts +25 -0
- package/dist/opc/exceptions.mjs +20 -0
- package/dist/opc/index.d.mts +1 -1
- package/dist/opc/index.mjs +1 -1
- package/dist/opc/mce/understood.mjs +37 -8
- package/dist/packages/WordprocessingMLPackage.d.mts +92 -2
- package/dist/packages/WordprocessingMLPackage.mjs +144 -1
- package/dist/parts/XmlPart.d.mts +44 -0
- package/dist/parts/XmlPart.mjs +86 -3
- package/dist/parts/sml/index.d.mts +9 -8
- package/dist/parts/sml/index.mjs +23 -19
- package/dist/parts/wml/index.d.mts +10 -2
- package/dist/parts/wml/index.mjs +11 -3
- package/dist/parts/wml/spliceableStyles.d.mts +1 -0
- package/dist/parts/wml/spliceableStyles.mjs +122 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -103,6 +103,20 @@ p?.insertParagraph('Inserted after the fourth block', 'After');
|
|
|
103
103
|
(await pkg.paragraphAt({ contains: 'Chapter 2' }))?.delete();
|
|
104
104
|
```
|
|
105
105
|
|
|
106
|
+
`search` and `replaceText` take Office JS's `matchCase`, `matchWholeWord` and `matchWildcards`
|
|
107
|
+
(Word's `?`, `*`, `[a-z]`, `<`, `>`, `@` and `(...)` groups, with `\1` in the replacement), and
|
|
108
|
+
`matchRegExp` for an ECMAScript regular expression, with `$1` in the replacement. A match never
|
|
109
|
+
crosses a paragraph.
|
|
110
|
+
|
|
111
|
+
```ts
|
|
112
|
+
body.replaceText('(\\d+) pages', '$1 pp.', { matchRegExp: true });
|
|
113
|
+
body.replaceText('(quick) (fox)', '\\2 \\1', { matchWildcards: true });
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
A style the document lacks is spliced in from docx4j's defaults, with the styles it is based on and
|
|
117
|
+
linked to: `await pkg.styles.ensure(['FootnoteText', 'FootnoteReference'])` returns the ids it
|
|
118
|
+
added, and a document that already has them keeps its styles part byte for byte.
|
|
119
|
+
|
|
106
120
|
Docx4j's names are there as aliases (`addParagraphOfText`, `addStyledParagraphOfText`,
|
|
107
121
|
`addObject`, `getContent`), and the tree stays reachable: `paragraph.p` is the `P`,
|
|
108
122
|
`body.content` the live array.
|
|
@@ -173,6 +187,12 @@ list.setLevelStartingNumber(0, 5);
|
|
|
173
187
|
const again = p4.restartList(); // the same list, numbering again from its start
|
|
174
188
|
```
|
|
175
189
|
|
|
190
|
+
Without a paragraph, `pkg.numbering.newList()` makes a definition and returns its `w:numId`, and
|
|
191
|
+
`pkg.numbering.restart(numId)` one that numbers again from the start. Both take the identifiers to
|
|
192
|
+
use when the next free one is the wrong answer - two sessions allocating in one co-edited
|
|
193
|
+
document - checked unused, a clash throwing rather than renumbering:
|
|
194
|
+
`await pkg.numbering.newList({ numId: 4242, abstractNumId: 77 })`.
|
|
195
|
+
|
|
176
196
|
A level writer changes the `w:abstractNum`, so it copies that definition first when another
|
|
177
197
|
`w:num` shares it — the change stays local to this list. `startNewList()` is asynchronous here
|
|
178
198
|
(Office JS's is not): it copies its definition from docx4j's default `numbering.xml` and adds
|
|
@@ -208,6 +228,21 @@ body.acceptAll(); // or rejectAll(), or accept()/reject() one at a time
|
|
|
208
228
|
`getTrackedChanges()` is also on `Paragraph` and `Range`. Text and searches read the accepted
|
|
209
229
|
view throughout, so an agent sees the document as it will read once the changes are taken.
|
|
210
230
|
|
|
231
|
+
To record one caller's edits without touching the document's own setting - an agent working in a
|
|
232
|
+
document whose `w:trackRevisions` belongs to its owner - use `withTracking`. Nothing is written
|
|
233
|
+
to the settings part, and the author, date and mode are put back when `fn` returns or throws:
|
|
234
|
+
|
|
235
|
+
```ts
|
|
236
|
+
await pkg.withTracking({ author: { name: 'Agent' } }, () => {
|
|
237
|
+
body.replaceText('colour', 'color');
|
|
238
|
+
});
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
Calls nest. While two overlap, the later one's author and mode apply to every edit, since nothing
|
|
242
|
+
can tell which asynchronous call an edit came from: await one before starting another when they
|
|
243
|
+
must be recorded as different authors. A revision is dated to the minute, as Word dates them, and
|
|
244
|
+
its id is above every annotation id in the document, including those in parts nobody has read.
|
|
245
|
+
|
|
211
246
|
### Comments
|
|
212
247
|
|
|
213
248
|
`getComments()` on the body, a paragraph or a range, and `insertComment` on a range or a
|
|
@@ -230,6 +265,15 @@ for (const c of await body.getComments()) { // document order, replies nested
|
|
|
230
265
|
}
|
|
231
266
|
```
|
|
232
267
|
|
|
268
|
+
`insertComment` and `reply` take the author, initials, date and `w:id` when they are not the
|
|
269
|
+
package's - a comment made by another person in a co-edited document - and paragraphs as well as
|
|
270
|
+
a string. A chosen id is checked unused, and a clash throws:
|
|
271
|
+
|
|
272
|
+
```ts
|
|
273
|
+
await hit.insertComment('Checked', { author: { name: 'Grace Hopper' }, date: new Date('2026-09-27T10:15:00Z'), id: 42 });
|
|
274
|
+
await hit.insertComment([p1, p2]); // w:p elements, for rich content
|
|
275
|
+
```
|
|
276
|
+
|
|
233
277
|
### XML in
|
|
234
278
|
|
|
235
279
|
A whole `word/document.xml` becomes the main document part's contents with `setXml`, and a
|
|
@@ -253,6 +297,33 @@ import * as el from '@docx4j/generated-objects-ts/el/org_docx4j_wml';
|
|
|
253
297
|
body.addObject(el.p({ content: [el.r({ content: [el.t({ value: 'Hello World' })] })] }));
|
|
254
298
|
```
|
|
255
299
|
|
|
300
|
+
A part that cannot be unmarshalled throws `PartUnmarshalException`, whose message names the part
|
|
301
|
+
and, where the parser supplies a position (xmldom in Node; not a browser), the element or attribute
|
|
302
|
+
and its line: `/word/styles.xml: w:sz/@w:val (line 7): Argument [NaN] must be an integer, but it is not a number.`
|
|
303
|
+
`partName`, `location`, `line` and `column` are fields of their own.
|
|
304
|
+
|
|
305
|
+
### XPath over the document
|
|
306
|
+
|
|
307
|
+
`pkg.selectObjects(xpath)` evaluates an XPath over a part - the main document part unless you pass
|
|
308
|
+
another - and answers as docx4j's `getJAXBNodesViaXPath` does: for each hit, the node, the tree
|
|
309
|
+
object behind it and its path. `body.select(xpath)` gives the content-API view instead, where the
|
|
310
|
+
element has one (`Paragraph`, `Table`, `TableRow`, `TableCell`, `ContentControl`, a `Range` for a
|
|
311
|
+
run):
|
|
312
|
+
|
|
313
|
+
```ts
|
|
314
|
+
const hits = await pkg.selectObjects("//w:p[w:pPr/w:pStyle/@w:val='Heading1']");
|
|
315
|
+
hits[0].object; // the P itself, not a copy: change it and the document changes
|
|
316
|
+
hits[0].path; // '/w:document/w:body[1]/w:p[3]'
|
|
317
|
+
|
|
318
|
+
for (const heading of await body.select("//w:p[w:pPr/w:pStyle/@w:val='Heading1']")) {
|
|
319
|
+
if (heading instanceof Paragraph) heading.styleBuiltIn = 'Heading2';
|
|
320
|
+
}
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
The prefixes are the objects package's (`w`, `r`, `wp`, `a`, ...), and `namespaces` adds others.
|
|
324
|
+
Each call evaluates a fresh snapshot of the part, so what it selects is never stale and changing the
|
|
325
|
+
snapshot's DOM changes nothing. The answers are held to docx4j's own by the XPath goldens (CR-006).
|
|
326
|
+
|
|
256
327
|
### Tables, pictures and content controls
|
|
257
328
|
|
|
258
329
|
`Table`, `TableRow` and `TableCell` are Office JS shapes too; a cell is a `Body` of its own, so
|
|
@@ -350,6 +421,7 @@ document part (Word drops a custom XML part the main part does not relate to):
|
|
|
350
421
|
|
|
351
422
|
```ts
|
|
352
423
|
const part = pkg.customXmlParts.add('<greeting xmlns="http://example.com/g"><to>World</to></greeting>');
|
|
424
|
+
// or with the itemID and part name to use, checked unused: { itemID: '{4B0E8F1A-...}', partName: '/customXml/item7.xml' }
|
|
353
425
|
control.xmlMapping.setMapping('/ns0:greeting[1]/ns0:to[1]', "xmlns:ns0='http://example.com/g'", part);
|
|
354
426
|
control.xmlMapping.setMappingByNode(part.selectSingleNode('/ns0:greeting/ns0:to', "xmlns:ns0='http://example.com/g'"));
|
|
355
427
|
```
|
|
@@ -475,6 +547,10 @@ await Word.run(pkg, async (context) => {
|
|
|
475
547
|
await writeFile('edited.docx', await pkg.save());
|
|
476
548
|
```
|
|
477
549
|
|
|
550
|
+
The callback's `context.document` is typed `ShimmedDocument`, so add-in code that uses
|
|
551
|
+
`paragraphs.items`, `load('items/text')`, `getFirst()` or `getCount().value` type-checks here as it
|
|
552
|
+
does against Office JS.
|
|
553
|
+
|
|
478
554
|
`Word.supported` is the set of `Class.member` strings this package implements
|
|
479
555
|
(`Word.supported.has('Body.insertParagraph')`), generated from the compile-time subset, so a
|
|
480
556
|
tool can check a script before running it. The selection an add-in edits is passed in:
|
|
@@ -5,10 +5,16 @@ import type { OpcPackage } from '../../packages/OpcPackage.mjs';
|
|
|
5
5
|
import { type Element, type TextViewOptions } from './tree.mjs';
|
|
6
6
|
import { Paragraph } from './Paragraph.mjs';
|
|
7
7
|
import { Range } from './Range.mjs';
|
|
8
|
-
import { Table } from './Table.mjs';
|
|
8
|
+
import { Table, type TableRow, type TableCell } from './Table.mjs';
|
|
9
9
|
import { ContentControl, type ContentControlType } from './ContentControl.mjs';
|
|
10
10
|
import { InlinePicture, type InlinePictureOptions } from './InlinePicture.mjs';
|
|
11
|
-
import type
|
|
11
|
+
import { type SearchOptions } from './search.mjs';
|
|
12
|
+
import type { Bound } from './binder.mjs';
|
|
13
|
+
/**
|
|
14
|
+
* What `Body.select` returns for one hit: the content-API view of the selected element where there
|
|
15
|
+
* is one, and the `Bound` itself otherwise (CR-006 section 2).
|
|
16
|
+
*/
|
|
17
|
+
export type SelectResult = Paragraph | Table | TableRow | TableCell | ContentControl | Range | Bound;
|
|
12
18
|
import type { Comment } from './Comment.mjs';
|
|
13
19
|
import { type ChangeTracker } from './tracking.mjs';
|
|
14
20
|
import { type TrackedChange } from './TrackedChange.mjs';
|
|
@@ -75,6 +81,34 @@ export declare class Body {
|
|
|
75
81
|
get content(): Element[];
|
|
76
82
|
/** Every paragraph in document order, descending into tables and content controls. */
|
|
77
83
|
get paragraphs(): Paragraph[];
|
|
84
|
+
/**
|
|
85
|
+
* The content-API views an XPath selects (CR-006): a `ContentControl`, `Table`, `TableRow`,
|
|
86
|
+
* `TableCell` or `Paragraph` where the selected element has one, at any depth - an inline content
|
|
87
|
+
* control is a `ContentControl`, a nested table a `Table`; a `Range` over the whole paragraph for a
|
|
88
|
+
* `w:r` or anything in one (`w:t`, `w:tab`), wherever the run sits (a hyperlink, an insertion, an
|
|
89
|
+
* inline control); and the `Bound` itself for anything else - an attribute, a text node, a `w:pPr`,
|
|
90
|
+
* a `w:hyperlink`, a wildcard child. An attribute or a text node is always the `Bound`, never its
|
|
91
|
+
* element's view, so that what matched is not lost. A caller that wants the tree objects rather
|
|
92
|
+
* than views uses `pkg.selectObjects`.
|
|
93
|
+
*
|
|
94
|
+
* The expression is evaluated over a snapshot of the part this body belongs to, so it sees the
|
|
95
|
+
* whole part - `//w:p` from a header's body selects that header's paragraphs. Every call marshals
|
|
96
|
+
* the part afresh (CR-006 section 2).
|
|
97
|
+
*/
|
|
98
|
+
select(xpath: string, options?: {
|
|
99
|
+
namespaces?: Record<string, string>;
|
|
100
|
+
}): Promise<SelectResult[]>;
|
|
101
|
+
/**
|
|
102
|
+
* Every view this body can make, keyed by the tree object behind it, so that a selected object
|
|
103
|
+
* becomes the view a caller can act on. Built per select: the views are cheap wrappers and nothing
|
|
104
|
+
* is cached but the `Body` itself (CR-002's "views, not a model"). The first view put for an object
|
|
105
|
+
* is the one it keeps, so the most specific kinds go first: a content control before the
|
|
106
|
+
* paragraph that holds an inline one, a table at any depth before the paragraphs in its cells, and
|
|
107
|
+
* a paragraph before the runs in it. A `w:r` and what is in it map to a `Range` over the paragraph,
|
|
108
|
+
* which is the nearest thing the content API has - a run is not addressable on its own in Office
|
|
109
|
+
* JS. A run in a text box is not one of the paragraph's, and stays a `Bound`.
|
|
110
|
+
*/
|
|
111
|
+
private viewsByObject;
|
|
78
112
|
/** The tables directly in this body, as Office JS reports them (nested tables are a cell's). */
|
|
79
113
|
get tables(): Table[];
|
|
80
114
|
/** Every content control in this body, in document order, nested ones included. */
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Docx4JException, PropertyResolverNotCreatedException } from '../../opc/exceptions.mjs';
|
|
2
2
|
import { typeNameOf, childrenOf, linkParents, BLOCK_LEVEL_TYPES, textOf, textOfView, rowsOf, cellsOf } from './tree.mjs';
|
|
3
3
|
import { r as textRun, br as breakItem, tbl as tableOf } from '@docx4j/generated-objects-ts/builders/wml';
|
|
4
|
-
import { runOf, paragraphOf } from './tree.mjs';
|
|
4
|
+
import { runOf, paragraphOf, runsOf } from './tree.mjs';
|
|
5
5
|
import { Paragraph } from './Paragraph.mjs';
|
|
6
6
|
import { Table } from './Table.mjs';
|
|
7
7
|
import { ContentControl, collectControls } from './ContentControl.mjs';
|
|
@@ -9,6 +9,7 @@ import { sdt as sdtOf, nextSdtId } from '@docx4j/generated-objects-ts/builders/w
|
|
|
9
9
|
import { controlIdScope, sdtKindFor } from '../customxml/insert.mjs';
|
|
10
10
|
import { InlinePicture, addImage, writableWidthEmu } from './InlinePicture.mjs';
|
|
11
11
|
import { contentOf } from './ooxml.mjs';
|
|
12
|
+
import { expandReplacement } from './search.mjs';
|
|
12
13
|
import { commentApi } from './comments.mjs';
|
|
13
14
|
import { trackerOf, trackInsertedParagraph, trackInsertedTable } from './tracking.mjs';
|
|
14
15
|
import { trackedChangesOfRow } from './TrackedChange.mjs';
|
|
@@ -80,6 +81,78 @@ export class Body {
|
|
|
80
81
|
visit(this.content);
|
|
81
82
|
return out;
|
|
82
83
|
}
|
|
84
|
+
/**
|
|
85
|
+
* The content-API views an XPath selects (CR-006): a `ContentControl`, `Table`, `TableRow`,
|
|
86
|
+
* `TableCell` or `Paragraph` where the selected element has one, at any depth - an inline content
|
|
87
|
+
* control is a `ContentControl`, a nested table a `Table`; a `Range` over the whole paragraph for a
|
|
88
|
+
* `w:r` or anything in one (`w:t`, `w:tab`), wherever the run sits (a hyperlink, an insertion, an
|
|
89
|
+
* inline control); and the `Bound` itself for anything else - an attribute, a text node, a `w:pPr`,
|
|
90
|
+
* a `w:hyperlink`, a wildcard child. An attribute or a text node is always the `Bound`, never its
|
|
91
|
+
* element's view, so that what matched is not lost. A caller that wants the tree objects rather
|
|
92
|
+
* than views uses `pkg.selectObjects`.
|
|
93
|
+
*
|
|
94
|
+
* The expression is evaluated over a snapshot of the part this body belongs to, so it sees the
|
|
95
|
+
* whole part - `//w:p` from a header's body selects that header's paragraphs. Every call marshals
|
|
96
|
+
* the part afresh (CR-006 section 2).
|
|
97
|
+
*/
|
|
98
|
+
async select(xpath, options = {}) {
|
|
99
|
+
const pkg = this.package_;
|
|
100
|
+
if ((pkg === null || pkg === void 0 ? void 0 : pkg.selectObjects) === undefined)
|
|
101
|
+
throw new Docx4JException('select needs the package that owns this body');
|
|
102
|
+
const bounds = await pkg.selectObjects(xpath, { part: this.part, namespaces: options.namespaces });
|
|
103
|
+
const views = this.viewsByObject();
|
|
104
|
+
return bounds.map((bound) => { var _a; return (_a = (bound.node.nodeType === 1 ? views.get(bound.object) : undefined)) !== null && _a !== void 0 ? _a : bound; });
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Every view this body can make, keyed by the tree object behind it, so that a selected object
|
|
108
|
+
* becomes the view a caller can act on. Built per select: the views are cheap wrappers and nothing
|
|
109
|
+
* is cached but the `Body` itself (CR-002's "views, not a model"). The first view put for an object
|
|
110
|
+
* is the one it keeps, so the most specific kinds go first: a content control before the
|
|
111
|
+
* paragraph that holds an inline one, a table at any depth before the paragraphs in its cells, and
|
|
112
|
+
* a paragraph before the runs in it. A `w:r` and what is in it map to a `Range` over the paragraph,
|
|
113
|
+
* which is the nearest thing the content API has - a run is not addressable on its own in Office
|
|
114
|
+
* JS. A run in a text box is not one of the paragraph's, and stays a `Bound`.
|
|
115
|
+
*/
|
|
116
|
+
viewsByObject() {
|
|
117
|
+
var _a;
|
|
118
|
+
const out = new Map();
|
|
119
|
+
const put = (value, view) => {
|
|
120
|
+
if (typeof value === 'object' && value !== null && !out.has(value))
|
|
121
|
+
out.set(value, view);
|
|
122
|
+
};
|
|
123
|
+
const controls = this.contentControls;
|
|
124
|
+
for (const control of controls)
|
|
125
|
+
put(control.element.value, control);
|
|
126
|
+
const putTables = (tables) => {
|
|
127
|
+
for (const table of tables) {
|
|
128
|
+
put(table.element.value, table);
|
|
129
|
+
for (const row of table.rows) {
|
|
130
|
+
put(row.element.value, row);
|
|
131
|
+
for (const cell of row.cells) {
|
|
132
|
+
put(cell.element.value, cell);
|
|
133
|
+
putTables(cell.tables);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
putTables(this.tables);
|
|
139
|
+
for (const control of controls)
|
|
140
|
+
if (control.form === 'Block')
|
|
141
|
+
putTables(control.tables);
|
|
142
|
+
for (const paragraph of this.paragraphs) {
|
|
143
|
+
put(paragraph.element.value, paragraph);
|
|
144
|
+
const range = paragraph.getRange();
|
|
145
|
+
// both views, so that a run in a w:del is found as well as one in a w:ins
|
|
146
|
+
for (const view of ['accepted', 'original']) {
|
|
147
|
+
for (const run of runsOf(paragraph.element.value, { view })) {
|
|
148
|
+
put(run.value, range);
|
|
149
|
+
for (const item of (_a = run.value.content) !== null && _a !== void 0 ? _a : [])
|
|
150
|
+
put(item.value, range);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
return out;
|
|
155
|
+
}
|
|
83
156
|
/** The tables directly in this body, as Office JS reports them (nested tables are a cell's). */
|
|
84
157
|
get tables() {
|
|
85
158
|
return this.content.filter((el) => typeNameOf(el) === 'org_docx4j_wml.Tbl').map((element) => new Table(element, this.content, this));
|
|
@@ -282,9 +355,11 @@ export class Body {
|
|
|
282
355
|
* the package's `changeTrackingMode` is on.
|
|
283
356
|
*/
|
|
284
357
|
replaceText(find, replace, options) {
|
|
285
|
-
const matches = this.
|
|
286
|
-
for (let i = matches.length - 1; i >= 0; i--)
|
|
287
|
-
matches[i]
|
|
358
|
+
const matches = this.paragraphs.flatMap((p) => p.searchMatches(find, options));
|
|
359
|
+
for (let i = matches.length - 1; i >= 0; i--) {
|
|
360
|
+
const { range, match } = matches[i];
|
|
361
|
+
range.insertText(expandReplacement(replace, match, options), 'Replace');
|
|
362
|
+
}
|
|
288
363
|
return matches.length;
|
|
289
364
|
}
|
|
290
365
|
/**
|
|
@@ -2,7 +2,7 @@ import type * as wml from '@docx4j/generated-objects-ts/modules/org_docx4j_wml';
|
|
|
2
2
|
import { Body } from './Body.mjs';
|
|
3
3
|
import { Paragraph } from './Paragraph.mjs';
|
|
4
4
|
import { Range } from './Range.mjs';
|
|
5
|
-
import { type CommentParts } from './comments.mjs';
|
|
5
|
+
import { type CommentContent, type CommentOptions, type CommentParts } from './comments.mjs';
|
|
6
6
|
/**
|
|
7
7
|
* A subset of Office JS `Word.Comment` over a `w:comment` and the markers that anchor it. A view:
|
|
8
8
|
* the tree is the state, and a view may be created and discarded freely. Replies are `Comment`s
|
|
@@ -51,8 +51,11 @@ export declare class Comment {
|
|
|
51
51
|
get replies(): Comment[];
|
|
52
52
|
/** The comment this one replies to (extension). */
|
|
53
53
|
get parent(): Comment | undefined;
|
|
54
|
-
/**
|
|
55
|
-
|
|
54
|
+
/**
|
|
55
|
+
* A reply in the same thread, anchored to the same range, as Word writes them. `options` are
|
|
56
|
+
* `insertComment`'s (CR-002 section 28.3).
|
|
57
|
+
*/
|
|
58
|
+
reply(content: CommentContent, options?: CommentOptions): Promise<Comment>;
|
|
56
59
|
/** Removes the comment and its replies: the markers, the reference runs and every side-part entry. */
|
|
57
60
|
delete(): Promise<void>;
|
|
58
61
|
/**
|
|
@@ -68,8 +71,15 @@ export declare class Comment {
|
|
|
68
71
|
/** Used by `commentsOf` while threading. */
|
|
69
72
|
addReply(reply: Comment): void;
|
|
70
73
|
}
|
|
71
|
-
/**
|
|
72
|
-
|
|
74
|
+
/**
|
|
75
|
+
* `Range.insertComment` (CR-002 section 3.8): the markers, the comment and the side-part entries.
|
|
76
|
+
*
|
|
77
|
+
* `options` (CR-002 section 28.3) name the author, the initials, the date and the `w:id`: in a
|
|
78
|
+
* co-editing room the author is the peer who made the comment and the date is when they made it,
|
|
79
|
+
* neither of which is a property of whoever is saving. A given `id` is checked unused and a clash
|
|
80
|
+
* throws.
|
|
81
|
+
*/
|
|
82
|
+
export declare function insertComment(range: Range, content: CommentContent, options?: CommentOptions): Promise<Comment>;
|
|
73
83
|
/**
|
|
74
84
|
* `getComments()` on a body, a paragraph or a range: the comments anchored there in document
|
|
75
85
|
* order, replies nested under their parent (a reply is in the array only when its parent is not).
|
|
@@ -105,12 +105,16 @@ export class Comment {
|
|
|
105
105
|
get parent() {
|
|
106
106
|
return this.parentComment;
|
|
107
107
|
}
|
|
108
|
-
/**
|
|
109
|
-
|
|
108
|
+
/**
|
|
109
|
+
* A reply in the same thread, anchored to the same range, as Word writes them. `options` are
|
|
110
|
+
* `insertComment`'s (CR-002 section 28.3).
|
|
111
|
+
*/
|
|
112
|
+
async reply(content, options) {
|
|
113
|
+
const paragraphs = paragraphsOfContent(content);
|
|
110
114
|
const parts = await this.parts.ensureAll();
|
|
111
|
-
const id =
|
|
115
|
+
const id = commentId(parts, this.body, options);
|
|
112
116
|
placeAfter(this.body, this.id, id);
|
|
113
|
-
const reply = addComment(parts, this.body, id,
|
|
117
|
+
const reply = addComment(parts, this.body, id, paragraphs, this, options);
|
|
114
118
|
this.replyList.push(reply);
|
|
115
119
|
reply.parentComment = this;
|
|
116
120
|
return reply;
|
|
@@ -291,6 +295,10 @@ function placeAfter(body, parentId, id) {
|
|
|
291
295
|
const start = markers.find((m) => m.kind === 'start');
|
|
292
296
|
const end = [...markers].reverse().find((m) => m.kind === 'end');
|
|
293
297
|
const reference = [...markers].reverse().find((m) => m.kind === 'reference');
|
|
298
|
+
if (!((reference === null || reference === void 0 ? void 0 : reference.run) && reference.runOwner) && !end) {
|
|
299
|
+
// refused before anything is written, so that no marker is left naming a comment never made
|
|
300
|
+
throw new Docx4JException(`Comment ${parentId} has no markers in this body; cannot anchor a reply`);
|
|
301
|
+
}
|
|
294
302
|
if (start)
|
|
295
303
|
insertAt(start.owner, start.owner.indexOf(start.item) + 1, [el.commentRangeStart(wmlFactory.createCommentRangeStart({ id }))], parentOf(start.item));
|
|
296
304
|
if (end)
|
|
@@ -301,37 +309,37 @@ function placeAfter(body, parentId, id) {
|
|
|
301
309
|
else if (end) {
|
|
302
310
|
insertAt(end.owner, end.owner.indexOf(end.item) + 1, [referenceRun(id)], parentOf(end.item));
|
|
303
311
|
}
|
|
304
|
-
else {
|
|
305
|
-
throw new Docx4JException(`Comment ${parentId} has no markers in this body; cannot anchor a reply`);
|
|
306
|
-
}
|
|
307
312
|
}
|
|
308
|
-
/**
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
313
|
+
/**
|
|
314
|
+
* The `w:comment` and the side-part entries of a new comment; the markers are already written. The
|
|
315
|
+
* paragraphs come checked, by `paragraphsOfContent` before the markers were placed.
|
|
316
|
+
*/
|
|
317
|
+
function addComment(parts, body, id, paragraphs, parent, options) {
|
|
318
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
319
|
+
var _h, _j;
|
|
320
|
+
const author = (_a = options === null || options === void 0 ? void 0 : options.author) !== null && _a !== void 0 ? _a : parts.author;
|
|
314
321
|
const paraId = randomHexId((candidate) => paraIdTaken(parts, body.container, candidate));
|
|
315
322
|
paragraphs[0].value.paraId = paraId;
|
|
316
323
|
const comment = wmlFactory.createCommentsComment({
|
|
317
|
-
id, author: author.name, initials:
|
|
324
|
+
id, author: author.name, initials: (_b = options === null || options === void 0 ? void 0 : options.initials) !== null && _b !== void 0 ? _b : initialsOf(author),
|
|
325
|
+
date: dateToCalendar((_c = options === null || options === void 0 ? void 0 : options.date) !== null && _c !== void 0 ? _c : new Date()),
|
|
318
326
|
content: paragraphs,
|
|
319
327
|
});
|
|
320
|
-
const comments = ((
|
|
328
|
+
const comments = ((_d = (_h = parts.comments).comment) !== null && _d !== void 0 ? _d : (_h.comment = []));
|
|
321
329
|
comments.push(comment);
|
|
322
330
|
linkParents(comment, parts.comments);
|
|
323
331
|
const extended = parts.ensureCommentsEx();
|
|
324
332
|
const entry = w15Factory.createCTCommentEx({ paraId, done: '0' });
|
|
325
333
|
if (parent)
|
|
326
334
|
entry.paraIdParent = parent.ensureParaId();
|
|
327
|
-
((
|
|
335
|
+
((_e = extended.commentEx) !== null && _e !== void 0 ? _e : (extended.commentEx = [])).push(entry);
|
|
328
336
|
linkParents(entry, extended);
|
|
329
337
|
if (parts.commentsIds) {
|
|
330
338
|
const durableId = randomHexId((candidate) => { var _a; return ((_a = parts.commentsIds.commentId) !== null && _a !== void 0 ? _a : []).some((e) => e.durableId === candidate); });
|
|
331
339
|
const commentId = w16cidFactory.createCTCommentId({ paraId, durableId });
|
|
332
|
-
((
|
|
340
|
+
((_f = (_j = parts.commentsIds).commentId) !== null && _f !== void 0 ? _f : (_j.commentId = [])).push(commentId);
|
|
333
341
|
linkParents(commentId, parts.commentsIds);
|
|
334
|
-
(
|
|
342
|
+
(_g = parts.extensible) === null || _g === void 0 ? void 0 : _g.set(durableId, new Date());
|
|
335
343
|
}
|
|
336
344
|
addPerson(parts, author.name, author.email);
|
|
337
345
|
return new Comment(comment, parts, body);
|
|
@@ -354,13 +362,53 @@ function addPerson(parts, name, email) {
|
|
|
354
362
|
list.push(person);
|
|
355
363
|
linkParents(person, people);
|
|
356
364
|
}
|
|
357
|
-
/**
|
|
358
|
-
|
|
365
|
+
/**
|
|
366
|
+
* `Range.insertComment` (CR-002 section 3.8): the markers, the comment and the side-part entries.
|
|
367
|
+
*
|
|
368
|
+
* `options` (CR-002 section 28.3) name the author, the initials, the date and the `w:id`: in a
|
|
369
|
+
* co-editing room the author is the peer who made the comment and the date is when they made it,
|
|
370
|
+
* neither of which is a property of whoever is saving. A given `id` is checked unused and a clash
|
|
371
|
+
* throws.
|
|
372
|
+
*/
|
|
373
|
+
export async function insertComment(range, content, options) {
|
|
374
|
+
// Everything that can refuse the call runs before anything is written: the content, then the id
|
|
375
|
+
// once the parts can say which are taken. A refusal after the markers were placed left them naming
|
|
376
|
+
// a comment that does not exist (CR-002 section 31).
|
|
377
|
+
const paragraphs = paragraphsOfContent(content);
|
|
359
378
|
const body = range.paragraph.parentBody;
|
|
360
379
|
const parts = await commentPartsAccess().create(body);
|
|
361
|
-
const id =
|
|
380
|
+
const id = commentId(parts, body, options);
|
|
362
381
|
placeAround(range, id);
|
|
363
|
-
return addComment(parts, body, id,
|
|
382
|
+
return addComment(parts, body, id, paragraphs, undefined, options);
|
|
383
|
+
}
|
|
384
|
+
/**
|
|
385
|
+
* The `w:id` for a new comment: the caller's when given, checked unused across the comments part and
|
|
386
|
+
* the markers in the story, else the next free one. A clash throws rather than renumbering, as a
|
|
387
|
+
* chosen list `numId` does: the caller is asserting something about a room's state (section 28.3).
|
|
388
|
+
*/
|
|
389
|
+
function commentId(parts, body, options) {
|
|
390
|
+
var _a;
|
|
391
|
+
if ((options === null || options === void 0 ? void 0 : options.id) === undefined)
|
|
392
|
+
return nextCommentId(parts.comments, body.container);
|
|
393
|
+
const id = options.id;
|
|
394
|
+
if (!Number.isInteger(id) || id < 0)
|
|
395
|
+
throw new Docx4JException(`A comment id must be a non-negative integer, not ${id}`);
|
|
396
|
+
if (((_a = parts.comments.comment) !== null && _a !== void 0 ? _a : []).some((c) => c.id === id)) {
|
|
397
|
+
throw new Docx4JException(`The comment id ${id} is already in use in this document's comments part`);
|
|
398
|
+
}
|
|
399
|
+
if (markersOf(body.container).some((m) => m.id === id)) {
|
|
400
|
+
throw new Docx4JException(`The comment id ${id} is already named by a marker in this story`);
|
|
401
|
+
}
|
|
402
|
+
return id;
|
|
403
|
+
}
|
|
404
|
+
/** The paragraphs of a comment's content: a string splits on newlines, elements are taken as they are. */
|
|
405
|
+
function paragraphsOfContent(content) {
|
|
406
|
+
if (typeof content === 'string')
|
|
407
|
+
return content.split('\n').map((line, i) => commentParagraph(line, i === 0));
|
|
408
|
+
const paragraphs = [...content];
|
|
409
|
+
if (paragraphs.length === 0)
|
|
410
|
+
throw new Docx4JException('A comment needs at least one paragraph');
|
|
411
|
+
return paragraphs;
|
|
364
412
|
}
|
|
365
413
|
/**
|
|
366
414
|
* `getComments()` on a body, a paragraph or a range: the comments anchored there in document
|
|
@@ -307,7 +307,7 @@ export declare class NumberingFacade {
|
|
|
307
307
|
*/
|
|
308
308
|
newList(options?: {
|
|
309
309
|
bullet?: boolean;
|
|
310
|
-
}): Promise<string>;
|
|
310
|
+
} & ChosenListIds): Promise<string>;
|
|
311
311
|
/**
|
|
312
312
|
* A `w:num` that numbers as `numId` does but starts again, and its own `w:numId`:
|
|
313
313
|
* `List.restart()` without needing a `Body`. The counters are shared by abstract definition,
|
|
@@ -317,5 +317,16 @@ export declare class NumberingFacade {
|
|
|
317
317
|
* Synchronous, so the numbering part must have been read: `await pkg.getPropertyResolver()`,
|
|
318
318
|
* `newList()` or any list read does it.
|
|
319
319
|
*/
|
|
320
|
-
restart(numId: string | number
|
|
320
|
+
restart(numId: string | number, options?: {
|
|
321
|
+
newNumId?: string | number;
|
|
322
|
+
}): string;
|
|
323
|
+
}
|
|
324
|
+
/**
|
|
325
|
+
* The identifiers a caller may choose for a new list definition, when the next integer is the wrong
|
|
326
|
+
* answer: in a co-editing room two sessions allocate at once, so the editor picks random ids and
|
|
327
|
+
* needs them honoured rather than renumbered (CR-002 section 24).
|
|
328
|
+
*/
|
|
329
|
+
export interface ChosenListIds {
|
|
330
|
+
numId?: string | number | undefined;
|
|
331
|
+
abstractNumId?: string | number | undefined;
|
|
321
332
|
}
|
|
@@ -669,23 +669,29 @@ export async function startNewList(paragraph, options) {
|
|
|
669
669
|
* `w:numId`. The paragraph half of `startNewList` is separate because it is separable: nothing
|
|
670
670
|
* about building a list definition needs one.
|
|
671
671
|
*/
|
|
672
|
-
async function addListDefinition(part, bullet) {
|
|
672
|
+
async function addListDefinition(part, bullet, ids) {
|
|
673
673
|
var _a, _b, _c;
|
|
674
674
|
const numbering = await part.getContents();
|
|
675
675
|
const defaults = await defaultNumbering();
|
|
676
676
|
const template = ((_a = defaults.abstractNum) !== null && _a !== void 0 ? _a : [])[bullet ? 0 : 1];
|
|
677
677
|
if (template === undefined)
|
|
678
678
|
throw new Docx4JException('The default numbering definitions are missing an abstractNum');
|
|
679
|
+
// both identifiers are settled before anything is written, so a clash leaves the part as it was
|
|
680
|
+
// (CR-002 section 31: an orphan w:abstractNum was saved for every refused numId)
|
|
681
|
+
const abstractNumId = (ids === null || ids === void 0 ? void 0 : ids.abstractNumId) === undefined
|
|
682
|
+
? nextAbstractNumId(numbering)
|
|
683
|
+
: requireFreeId(numbering, 'abstractNumId', ids.abstractNumId);
|
|
684
|
+
const numId = (ids === null || ids === void 0 ? void 0 : ids.numId) === undefined ? nextNumId(numbering) : requireFreeId(numbering, 'numId', ids.numId);
|
|
679
685
|
const abstract = deepCopy(template);
|
|
680
|
-
abstract.abstractNumId =
|
|
686
|
+
abstract.abstractNumId = abstractNumId;
|
|
681
687
|
abstract.nsid = f.createCTLongHexNumber({ val: hexId() });
|
|
682
688
|
delete abstract.styleLink;
|
|
683
689
|
delete abstract.numStyleLink;
|
|
684
690
|
((_b = numbering.abstractNum) !== null && _b !== void 0 ? _b : (numbering.abstractNum = [])).push(abstract);
|
|
685
691
|
linkParents(abstract, numbering);
|
|
686
692
|
const num = f.createNumberingNum({
|
|
687
|
-
numId
|
|
688
|
-
abstractNumId: f.createNumberingNumAbstractNumId({ val:
|
|
693
|
+
numId,
|
|
694
|
+
abstractNumId: f.createNumberingNumAbstractNumId({ val: abstractNumId }),
|
|
689
695
|
});
|
|
690
696
|
((_c = numbering.num) !== null && _c !== void 0 ? _c : (numbering.num = [])).push(num);
|
|
691
697
|
linkParents(num, numbering);
|
|
@@ -697,10 +703,10 @@ async function addListDefinition(part, bullet) {
|
|
|
697
703
|
* list (CR-001 section 15.2), and it is what {@link List.restart} and
|
|
698
704
|
* {@link NumberingFacade.restart} both do.
|
|
699
705
|
*/
|
|
700
|
-
function addRestartedNum(numbering, abstractNumId, start) {
|
|
706
|
+
function addRestartedNum(numbering, abstractNumId, start, newNumId) {
|
|
701
707
|
var _a;
|
|
702
708
|
const created = f.createNumberingNum({
|
|
703
|
-
numId: nextNumId(numbering),
|
|
709
|
+
numId: newNumId === undefined ? nextNumId(numbering) : requireFreeId(numbering, 'numId', newNumId),
|
|
704
710
|
abstractNumId: f.createNumberingNumAbstractNumId({ val: abstractNumId }),
|
|
705
711
|
lvlOverride: [f.createNumberingNumLvlOverride({
|
|
706
712
|
ilvl: 0,
|
|
@@ -776,9 +782,14 @@ export class NumberingFacade {
|
|
|
776
782
|
* caller writes `w:numPr` itself.
|
|
777
783
|
*/
|
|
778
784
|
async newList(options) {
|
|
785
|
+
// the form is checked before a numbering part can be created for a call that is then refused
|
|
786
|
+
if ((options === null || options === void 0 ? void 0 : options.numId) !== undefined)
|
|
787
|
+
requireIdForm('numId', options.numId);
|
|
788
|
+
if ((options === null || options === void 0 ? void 0 : options.abstractNumId) !== undefined)
|
|
789
|
+
requireIdForm('abstractNumId', options.abstractNumId);
|
|
779
790
|
const main = this.pkg.getMainDocumentPart();
|
|
780
791
|
const part = await ensureNumberingPartOf(this.pkg, main);
|
|
781
|
-
const numId = await addListDefinition(part, (options === null || options === void 0 ? void 0 : options.bullet) === true);
|
|
792
|
+
const numId = await addListDefinition(part, (options === null || options === void 0 ? void 0 : options.bullet) === true, options);
|
|
782
793
|
this.pkg.refreshPropertyResolver();
|
|
783
794
|
return String(numId);
|
|
784
795
|
}
|
|
@@ -791,7 +802,7 @@ export class NumberingFacade {
|
|
|
791
802
|
* Synchronous, so the numbering part must have been read: `await pkg.getPropertyResolver()`,
|
|
792
803
|
* `newList()` or any list read does it.
|
|
793
804
|
*/
|
|
794
|
-
restart(numId) {
|
|
805
|
+
restart(numId, options) {
|
|
795
806
|
var _a, _b;
|
|
796
807
|
const part = this.pkg.getMainDocumentPart().numberingDefinitionsPart;
|
|
797
808
|
if (part === undefined)
|
|
@@ -799,7 +810,7 @@ export class NumberingFacade {
|
|
|
799
810
|
const definition = part.definitions.list(numId);
|
|
800
811
|
if (definition === undefined)
|
|
801
812
|
throw new ItemNotFound(`No w:num for numId ${numId}`);
|
|
802
|
-
const created = addRestartedNum(part.makeLive(), definition.num.abstractNumId.val, (_b = (_a = definition.level('0')) === null || _a === void 0 ? void 0 : _a.start) !== null && _b !== void 0 ? _b : 1);
|
|
813
|
+
const created = addRestartedNum(part.makeLive(), definition.num.abstractNumId.val, (_b = (_a = definition.level('0')) === null || _a === void 0 ? void 0 : _a.start) !== null && _b !== void 0 ? _b : 1, options === null || options === void 0 ? void 0 : options.newNumId);
|
|
803
814
|
this.pkg.refreshPropertyResolver();
|
|
804
815
|
return String(created);
|
|
805
816
|
}
|
|
@@ -840,6 +851,34 @@ function nextNumId(numbering) {
|
|
|
840
851
|
var _a;
|
|
841
852
|
return ((_a = numbering.num) !== null && _a !== void 0 ? _a : []).reduce((max, num) => Math.max(max, Number(num.numId)), 0) + 1;
|
|
842
853
|
}
|
|
854
|
+
/**
|
|
855
|
+
* A chosen identifier in range: a `w:numId` from 1, since 0 is what a `w:numPr` says to mean "not
|
|
856
|
+
* numbered" (`detachFromList` writes it, and Word and the Emulator read it so), and a
|
|
857
|
+
* `w:abstractNumId` from 0 (CR-002 section 31).
|
|
858
|
+
*/
|
|
859
|
+
function requireIdForm(kind, chosen) {
|
|
860
|
+
const id = Number(chosen);
|
|
861
|
+
const least = kind === 'numId' ? 1 : 0;
|
|
862
|
+
if (!Number.isInteger(id) || id < least) {
|
|
863
|
+
throw new Docx4JException(`A ${kind} must be an integer from ${least}, not ${String(chosen)}`);
|
|
864
|
+
}
|
|
865
|
+
return id;
|
|
866
|
+
}
|
|
867
|
+
/**
|
|
868
|
+
* A chosen identifier, checked unused, as `addExternalRelationship`'s proposed id is. A clash throws
|
|
869
|
+
* rather than renumbering: a caller who names an id is asserting something about a room's state, and
|
|
870
|
+
* quietly moving it would break the peer that named it (CR-002 section 24).
|
|
871
|
+
*/
|
|
872
|
+
function requireFreeId(numbering, kind, chosen) {
|
|
873
|
+
var _a, _b;
|
|
874
|
+
const id = requireIdForm(kind, chosen);
|
|
875
|
+
const taken = kind === 'numId'
|
|
876
|
+
? ((_a = numbering.num) !== null && _a !== void 0 ? _a : []).some((num) => Number(num.numId) === id)
|
|
877
|
+
: ((_b = numbering.abstractNum) !== null && _b !== void 0 ? _b : []).some((a) => Number(a.abstractNumId) === id);
|
|
878
|
+
if (taken)
|
|
879
|
+
throw new Docx4JException(`The ${kind} ${id} is already in use in this document's numbering part`);
|
|
880
|
+
return id;
|
|
881
|
+
}
|
|
843
882
|
/** A free `w:abstractNumId`. */
|
|
844
883
|
function nextAbstractNumId(numbering) {
|
|
845
884
|
var _a;
|
|
@@ -7,6 +7,7 @@ import type { Body, BlockElement } from './Body.mjs';
|
|
|
7
7
|
import { type TableCell } from './Table.mjs';
|
|
8
8
|
import { ContentControl, type ContentControlType } from './ContentControl.mjs';
|
|
9
9
|
import { InlinePicture, type InlinePictureOptions } from './InlinePicture.mjs';
|
|
10
|
+
import { type CommentContent, type CommentOptions } from './comments.mjs';
|
|
10
11
|
import type { Comment } from './Comment.mjs';
|
|
11
12
|
import { type ChangeTracker } from './tracking.mjs';
|
|
12
13
|
import { TrackedChange } from './TrackedChange.mjs';
|
|
@@ -160,6 +161,15 @@ export declare class Paragraph {
|
|
|
160
161
|
insertOoxml(ooxml: string, location: 'Before' | 'After' | 'Start' | 'End' | 'Replace'): Promise<(Paragraph | BlockElement)[]>;
|
|
161
162
|
/** Matches within this paragraph. */
|
|
162
163
|
search(text: string, options?: SearchOptions): Range[];
|
|
164
|
+
/**
|
|
165
|
+
* `search`, keeping each match's capture groups, which is what a replacement's `$1` or Word's `\1`
|
|
166
|
+
* expands from (CR-002 section 26). Not an Office JS member; `replaceText` is its only caller here
|
|
167
|
+
* and `Body.replaceText` the other.
|
|
168
|
+
*/
|
|
169
|
+
searchMatches(text: string, options?: SearchOptions): {
|
|
170
|
+
range: Range;
|
|
171
|
+
match: RegExpExecArray;
|
|
172
|
+
}[];
|
|
163
173
|
/**
|
|
164
174
|
* Replaces every match of `find` with `replace`, last match first so that the offsets of the
|
|
165
175
|
* ones still to do stay valid (CR-002 section 3.7). Returns the number replaced; tracked when
|
|
@@ -261,7 +271,7 @@ export declare class Paragraph {
|
|
|
261
271
|
/** The comments anchored in this paragraph, replies nested under their parent. */
|
|
262
272
|
getComments(): Promise<Comment[]>;
|
|
263
273
|
/** Comments the whole paragraph (sugar over `getRange().insertComment`). */
|
|
264
|
-
insertComment(
|
|
274
|
+
insertComment(content: CommentContent, options?: CommentOptions): Promise<Comment>;
|
|
265
275
|
/**
|
|
266
276
|
* Wraps this paragraph in a new block-level content control (Office JS
|
|
267
277
|
* `Paragraph.insertContentControl`), typed for the kind when one is given. The `w:id` is free in
|