@docx4j/core-ts 0.2.1 → 0.3.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 +3 -1
- package/dist/model/content/Body.d.mts +15 -3
- package/dist/model/content/Body.mjs +62 -20
- package/dist/model/content/Comment.d.mts +21 -2
- package/dist/model/content/Comment.mjs +104 -19
- package/dist/model/content/Font.mjs +2 -0
- package/dist/model/content/List.mjs +9 -6
- package/dist/model/content/Paragraph.d.mts +36 -1
- package/dist/model/content/Paragraph.mjs +172 -34
- package/dist/model/content/Range.d.mts +9 -4
- package/dist/model/content/Range.mjs +95 -15
- package/dist/model/content/TrackedChange.d.mts +103 -6
- package/dist/model/content/TrackedChange.mjs +713 -20
- package/dist/model/content/comments.d.mts +20 -3
- package/dist/model/content/comments.mjs +1 -9
- package/dist/model/content/index.d.mts +3 -3
- package/dist/model/content/index.mjs +2 -2
- package/dist/model/content/search.d.mts +8 -0
- package/dist/model/content/search.mjs +22 -1
- package/dist/model/content/tableRevisions.d.mts +30 -0
- package/dist/model/content/tableRevisions.mjs +207 -0
- package/dist/model/content/tracking.d.mts +58 -8
- package/dist/model/content/tracking.mjs +116 -10
- package/dist/packages/WordprocessingMLPackage.d.mts +12 -0
- package/dist/packages/WordprocessingMLPackage.mjs +22 -0
- package/dist/parts/wml/comments.mjs +47 -0
- package/package.json +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { deepCopy, deepCopyAsSync } from '@docx4j/generated-objects-ts';
|
|
2
2
|
import { createCTTrackChange, createCTRPrChange, createCTRPrChangeRPr, createCTPPrChange, createDelText, createPPr, createParaRPr, createTrPr, } from '@docx4j/generated-objects-ts/factory/org_docx4j_wml';
|
|
3
|
-
import { rPrToElements } from '@docx4j/generated-objects-ts/builders/wml';
|
|
3
|
+
import { rPrToElements, rPrFromElements } from '@docx4j/generated-objects-ts/builders/wml';
|
|
4
4
|
import { Docx4JException } from '../../opc/exceptions.mjs';
|
|
5
5
|
import { W_NS, linkParents, walk, typeNameOf, revisionKindOf } from './tree.mjs';
|
|
6
6
|
/**
|
|
@@ -39,24 +39,25 @@ export function trackerOf(pkg) {
|
|
|
39
39
|
return pkg === null || pkg === void 0 ? void 0 : pkg.changeTracker;
|
|
40
40
|
}
|
|
41
41
|
/**
|
|
42
|
-
* Now, with the seconds and milliseconds dropped, which is what Word stamps a revision with
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
42
|
+
* Now, with the seconds and milliseconds dropped, which is what Word stamps a revision with
|
|
43
|
+
* (CR-002 section 28.2). A caller that sets `trackedChangeDate` gets exactly the date it asked for,
|
|
44
|
+
* truncated or not. (Section 28.2 gave a second reason, that Word groups adjacent revisions by their
|
|
45
|
+
* timestamp; check 23 found otherwise for Office JS's list, where an hour apart is still one change,
|
|
46
|
+
* CR-002 section 29.)
|
|
46
47
|
*/
|
|
47
48
|
export function nowToTheMinute() {
|
|
48
49
|
const now = new Date();
|
|
49
50
|
now.setUTCSeconds(0, 0);
|
|
50
51
|
return now;
|
|
51
52
|
}
|
|
52
|
-
/** An `XmlCalendar` in UTC,
|
|
53
|
+
/** An `XmlCalendar` in UTC, ending in `Z`: `w16du:dateUtc`, or any xsd:dateTime meant as UTC. */
|
|
53
54
|
export function calendarOf(date) {
|
|
54
55
|
return {
|
|
55
56
|
year: date.getUTCFullYear(), month: date.getUTCMonth() + 1, day: date.getUTCDate(),
|
|
56
57
|
hour: date.getUTCHours(), minute: date.getUTCMinutes(), second: date.getUTCSeconds(), timezone: 0,
|
|
57
58
|
};
|
|
58
59
|
}
|
|
59
|
-
/** The `Date` of
|
|
60
|
+
/** The `Date` of an xsd:dateTime read as it says - a missing offset as UTC - or undefined when there is none. */
|
|
60
61
|
export function dateOf(cal) {
|
|
61
62
|
var _a, _b, _c, _d, _e, _f;
|
|
62
63
|
if (!cal || cal.year === undefined)
|
|
@@ -64,6 +65,63 @@ export function dateOf(cal) {
|
|
|
64
65
|
const ms = Date.UTC(cal.year, ((_a = cal.month) !== null && _a !== void 0 ? _a : 1) - 1, (_b = cal.day) !== null && _b !== void 0 ? _b : 1, (_c = cal.hour) !== null && _c !== void 0 ? _c : 0, (_d = cal.minute) !== null && _d !== void 0 ? _d : 0, (_e = cal.second) !== null && _e !== void 0 ? _e : 0);
|
|
65
66
|
return new Date(ms - ((_f = cal.timezone) !== null && _f !== void 0 ? _f : 0) * 60000);
|
|
66
67
|
}
|
|
68
|
+
/**
|
|
69
|
+
* A revision's `w:date` as Word writes it: the local wall-clock time, with a `Z` that does not mean
|
|
70
|
+
* UTC (CR-002 section 29, check 23: Office JS read `01:00:00Z` as 15:00 UTC the day before, at
|
|
71
|
+
* UTC+10; Word's own files carry the true UTC beside it in `w16du:dateUtc`). "Local" is this
|
|
72
|
+
* process's time zone, as it is the add-in's for Office JS.
|
|
73
|
+
*/
|
|
74
|
+
export function wordCalendarOf(date) {
|
|
75
|
+
return {
|
|
76
|
+
year: date.getFullYear(), month: date.getMonth() + 1, day: date.getDate(),
|
|
77
|
+
hour: date.getHours(), minute: date.getMinutes(), second: date.getSeconds(), timezone: 0,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* A revision's date as Word means it: `w16du:dateUtc` when there is one, which is UTC; else
|
|
82
|
+
* `w:date` read as local wall-clock time, its `Z` notwithstanding, as Office JS reads it (check 23).
|
|
83
|
+
* A `w:date` with a non-zero offset, which Word does not write, is read as it says.
|
|
84
|
+
*/
|
|
85
|
+
export function revisionDateOf(value) {
|
|
86
|
+
var _a, _b, _c, _d, _e, _f;
|
|
87
|
+
if (((_a = value.dateUtc) === null || _a === void 0 ? void 0 : _a.year) !== undefined)
|
|
88
|
+
return dateOf(value.dateUtc);
|
|
89
|
+
const cal = value.date;
|
|
90
|
+
if (!cal || cal.year === undefined)
|
|
91
|
+
return undefined;
|
|
92
|
+
if (cal.timezone !== undefined && cal.timezone !== 0)
|
|
93
|
+
return dateOf(cal);
|
|
94
|
+
return new Date(cal.year, ((_b = cal.month) !== null && _b !== void 0 ? _b : 1) - 1, (_c = cal.day) !== null && _c !== void 0 ? _c : 1, (_d = cal.hour) !== null && _d !== void 0 ? _d : 0, (_e = cal.minute) !== null && _e !== void 0 ? _e : 0, (_f = cal.second) !== null && _f !== void 0 ? _f : 0);
|
|
95
|
+
}
|
|
96
|
+
/** Properties compared as values: keys sorted, `PARENT`, `TYPE_NAME` and absent values left out. */
|
|
97
|
+
export function canonical(value) {
|
|
98
|
+
return JSON.stringify(value, (key, v) => {
|
|
99
|
+
if (key === 'PARENT' || key === 'TYPE_NAME' || v === undefined)
|
|
100
|
+
return undefined;
|
|
101
|
+
if (v && typeof v === 'object' && !Array.isArray(v))
|
|
102
|
+
return Object.fromEntries(Object.keys(v).sort().map((k) => [k, v[k]]));
|
|
103
|
+
return v;
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Makes sure the part's root lists `w16du` in `mc:Ignorable`, appending it when absent, so that
|
|
108
|
+
* `w16du:dateUtc` may be written in it; the facade then declares the prefix. This is docx4j's
|
|
109
|
+
* pattern - the writer of the content adds its prefix (`Paginate.declareW14Ignorable`) - and Jason's
|
|
110
|
+
* decision (CR-002 section 29); objects CR-008 would do it for every writer. False when there is no
|
|
111
|
+
* unmarshalled root to put it on. Every part a `Body` is over (document, header, footer, notes,
|
|
112
|
+
* comments) has a root that binds `mc:Ignorable`.
|
|
113
|
+
*/
|
|
114
|
+
function declareDateUtc(part) {
|
|
115
|
+
var _a;
|
|
116
|
+
const contents = part === null || part === void 0 ? void 0 : part.contents;
|
|
117
|
+
if (!contents || typeof contents !== 'object')
|
|
118
|
+
return false;
|
|
119
|
+
const root = (contents.value && typeof contents.value === 'object' ? contents.value : contents);
|
|
120
|
+
const prefixes = ((_a = root.ignorable) !== null && _a !== void 0 ? _a : '').split(/\s+/).filter((prefix) => prefix.length > 0);
|
|
121
|
+
if (!prefixes.includes('w16du'))
|
|
122
|
+
root.ignorable = [...prefixes, 'w16du'].join(' ');
|
|
123
|
+
return true;
|
|
124
|
+
}
|
|
67
125
|
function element(localPart, value) {
|
|
68
126
|
return { name: { namespaceURI: W_NS, localPart }, value };
|
|
69
127
|
}
|
|
@@ -76,9 +134,27 @@ function element(localPart, value) {
|
|
|
76
134
|
* The name follows Office JS's vocabulary (`Word.ChangeTrackingMode`).
|
|
77
135
|
*/
|
|
78
136
|
export class ChangeTracker {
|
|
79
|
-
constructor(host, mode
|
|
137
|
+
constructor(host, mode,
|
|
138
|
+
/** The part this tracker writes into, when known: it decides whether `w16du:dateUtc` is written. */
|
|
139
|
+
part) {
|
|
80
140
|
this.host = host;
|
|
81
141
|
this.mode = mode;
|
|
142
|
+
this.part = part;
|
|
143
|
+
this.perPart = new WeakMap();
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* This tracker for revisions written into one part (a `Body` asks for its own). The id counter is
|
|
147
|
+
* the host's, so every tracker over a document draws ids from one space.
|
|
148
|
+
*/
|
|
149
|
+
forPart(part) {
|
|
150
|
+
if (!part || part === this.part)
|
|
151
|
+
return this;
|
|
152
|
+
let tracker = this.perPart.get(part);
|
|
153
|
+
if (!tracker) {
|
|
154
|
+
tracker = new ChangeTracker(this.host, this.mode, part);
|
|
155
|
+
this.perPart.set(part, tracker);
|
|
156
|
+
}
|
|
157
|
+
return tracker;
|
|
82
158
|
}
|
|
83
159
|
/** The `w:author` a new revision carries: the package author's name. */
|
|
84
160
|
get author() {
|
|
@@ -106,10 +182,20 @@ export class ChangeTracker {
|
|
|
106
182
|
host.annotationIdCounter = counter;
|
|
107
183
|
return counter;
|
|
108
184
|
}
|
|
109
|
-
/**
|
|
185
|
+
/**
|
|
186
|
+
* The `w:id`, `w:author` and `w:date` a new revision carries, `w:date` in Word's local wall-clock
|
|
187
|
+
* convention (`wordCalendarOf`), and `w16du:dateUtc`, the true UTC, as Word writes them both. The
|
|
188
|
+
* part's root is made to list `w16du` in `mc:Ignorable` first (`declareDateUtc`): a consumer that
|
|
189
|
+
* does not know `w16du` must reject it unless it is declared ignorable. A tracker bound to no part -
|
|
190
|
+
* the package's own - writes `w:date` alone (CR-002 section 29).
|
|
191
|
+
*/
|
|
110
192
|
markup() {
|
|
111
193
|
var _a;
|
|
112
|
-
|
|
194
|
+
const when = (_a = this.host.trackedChangeDate) !== null && _a !== void 0 ? _a : nowToTheMinute();
|
|
195
|
+
const markup = { id: this.nextId(), author: this.author, date: wordCalendarOf(when) };
|
|
196
|
+
if (declareDateUtc(this.part))
|
|
197
|
+
markup.dateUtc = calendarOf(when);
|
|
198
|
+
return markup;
|
|
113
199
|
}
|
|
114
200
|
/** A `w:ins` / `w:del` attribute set: the id, this package's author and the date. */
|
|
115
201
|
trackChange() {
|
|
@@ -147,6 +233,26 @@ export class ChangeTracker {
|
|
|
147
233
|
rPr.rPrChange = createCTRPrChange({ ...this.markup(), rPr: original });
|
|
148
234
|
linkParents(rPr.rPrChange, rPr);
|
|
149
235
|
}
|
|
236
|
+
/**
|
|
237
|
+
* After a tracked formatting write: the run's `w:rPrChange` goes when its properties are back to
|
|
238
|
+
* what it recorded, since Word keeps no formatting change that changes nothing, whoever changed the
|
|
239
|
+
* formatting back (`test/README.md` check 24: bold then unbold by the same author, and by another; a
|
|
240
|
+
* character style applied then cleared; CR-002 section 29). An `w:rPr` then left empty goes too, as
|
|
241
|
+
* Word writes none.
|
|
242
|
+
*/
|
|
243
|
+
settleRPrChange(run) {
|
|
244
|
+
var _a, _b;
|
|
245
|
+
const rPr = run.rPr;
|
|
246
|
+
const change = rPr === null || rPr === void 0 ? void 0 : rPr.rPrChange;
|
|
247
|
+
if (!rPr || !change)
|
|
248
|
+
return;
|
|
249
|
+
const { rPrChange: _recorded, ...current } = rPr;
|
|
250
|
+
if (canonical(current) !== canonical(rPrFromElements((_b = (_a = change.rPr) === null || _a === void 0 ? void 0 : _a.egrPrBase) !== null && _b !== void 0 ? _b : [])))
|
|
251
|
+
return;
|
|
252
|
+
delete rPr.rPrChange;
|
|
253
|
+
if (Object.keys(rPr).every((key) => key === 'TYPE_NAME'))
|
|
254
|
+
delete run.rPr;
|
|
255
|
+
}
|
|
150
256
|
/** Records the paragraph's properties as they are now in `w:pPrChange`, once. */
|
|
151
257
|
recordPPrChange(pPr) {
|
|
152
258
|
if (pPr.pPrChange)
|
|
@@ -232,6 +232,18 @@ export declare class WordprocessingMLPackage extends OpcPackage implements Track
|
|
|
232
232
|
* bytes cannot change; bytes that were set are read again when they are set again.
|
|
233
233
|
*/
|
|
234
234
|
seedAnnotationIds(): Promise<number>;
|
|
235
|
+
/**
|
|
236
|
+
* Records the highest annotation id of a part the caller has read itself, so that
|
|
237
|
+
* `seedAnnotationIds()` does not read it again. An editor that imports the main document part into
|
|
238
|
+
* a model of its own knows that part's ids already, and seeding would parse it a second time -
|
|
239
|
+
* 1.06 s for a 255-page document (CR-002 section 34). The floor rises to `highest` at once, and
|
|
240
|
+
* the part counts as read for the bytes it holds now: set its bytes again and the next seed reads
|
|
241
|
+
* it afresh; unmarshal it and the counter's own scan of it covers it.
|
|
242
|
+
*
|
|
243
|
+
* The caller vouches for the number: one lower than the part's highest id lets a new revision
|
|
244
|
+
* reuse an id. Asynchronous only because a part whose bytes were set is keyed on those bytes.
|
|
245
|
+
*/
|
|
246
|
+
noteAnnotationIds(part: XmlPart<unknown>, highest: number): Promise<void>;
|
|
235
247
|
/** TrackingHost: the trees a new revision id must be above, which is every part already unmarshalled. */
|
|
236
248
|
markupRoots(): object[];
|
|
237
249
|
/**
|
|
@@ -448,6 +448,28 @@ export class WordprocessingMLPackage extends OpcPackage {
|
|
|
448
448
|
}
|
|
449
449
|
return this.annotationIdFloor;
|
|
450
450
|
}
|
|
451
|
+
/**
|
|
452
|
+
* Records the highest annotation id of a part the caller has read itself, so that
|
|
453
|
+
* `seedAnnotationIds()` does not read it again. An editor that imports the main document part into
|
|
454
|
+
* a model of its own knows that part's ids already, and seeding would parse it a second time -
|
|
455
|
+
* 1.06 s for a 255-page document (CR-002 section 34). The floor rises to `highest` at once, and
|
|
456
|
+
* the part counts as read for the bytes it holds now: set its bytes again and the next seed reads
|
|
457
|
+
* it afresh; unmarshal it and the counter's own scan of it covers it.
|
|
458
|
+
*
|
|
459
|
+
* The caller vouches for the number: one lower than the part's highest id lets a new revision
|
|
460
|
+
* reuse an id. Asynchronous only because a part whose bytes were set is keyed on those bytes.
|
|
461
|
+
*/
|
|
462
|
+
async noteAnnotationIds(part, highest) {
|
|
463
|
+
if (!Number.isInteger(highest) || highest < 0) {
|
|
464
|
+
throw new Docx4JException(`The highest annotation id must be a non-negative integer, not ${String(highest)}`);
|
|
465
|
+
}
|
|
466
|
+
if (this.parts.get(part.partName.name) !== part) {
|
|
467
|
+
throw new Docx4JException(`${part.partName.name} is not a part of this package`);
|
|
468
|
+
}
|
|
469
|
+
this.annotationIdFloor = Math.max(this.annotationIdFloor, highest);
|
|
470
|
+
if (!part.isUnmarshalled)
|
|
471
|
+
this.seededParts.set(part, part.isUntouched ? true : await part.getBytes());
|
|
472
|
+
}
|
|
451
473
|
/** TrackingHost: the trees a new revision id must be above, which is every part already unmarshalled. */
|
|
452
474
|
markupRoots() {
|
|
453
475
|
const out = [];
|
|
@@ -3,8 +3,10 @@ import * as w15Factory from '@docx4j/generated-objects-ts/factory/org_docx4j_w15
|
|
|
3
3
|
import * as w16cidFactory from '@docx4j/generated-objects-ts/factory/org_docx4j_w16cid';
|
|
4
4
|
import { linkParents } from '@docx4j/generated-objects-ts/builders/wml';
|
|
5
5
|
import { Docx4JException } from '../../opc/exceptions.mjs';
|
|
6
|
+
import { parseXml } from '../../xml/dom.mjs';
|
|
6
7
|
import { MainDocumentPart, CommentsPart, CommentsExtendedPart, CommentsIdsPart, PeoplePart } from './index.mjs';
|
|
7
8
|
import { COMMENT_REFERENCE_STYLE, COMMENT_TEXT_STYLE, setCommentPartsAccess, dateToCalendar, } from '../../model/content/comments.mjs';
|
|
9
|
+
import { dateOf } from '../../model/content/tracking.mjs';
|
|
8
10
|
// Loaded for its side effect: the Comment views register themselves with the content API.
|
|
9
11
|
import '../../model/content/Comment.mjs';
|
|
10
12
|
/** The comment parts of one document. */
|
|
@@ -14,6 +16,7 @@ class DocumentCommentParts {
|
|
|
14
16
|
this.commentsPart = commentsPart;
|
|
15
17
|
this.comments = comments;
|
|
16
18
|
this.author = author;
|
|
19
|
+
this.datesUtc = new Map();
|
|
17
20
|
this.loadedAll = false;
|
|
18
21
|
}
|
|
19
22
|
ensureCommentsEx() {
|
|
@@ -66,8 +69,52 @@ async function partsFor(main, commentsPart, author) {
|
|
|
66
69
|
parts.commentsEx = await main.commentsExtendedPart.getContents();
|
|
67
70
|
if (main.peoplePart)
|
|
68
71
|
parts.people = await main.peoplePart.getContents();
|
|
72
|
+
await readDatesUtc(main, parts.datesUtc);
|
|
69
73
|
return parts;
|
|
70
74
|
}
|
|
75
|
+
const W16CID_NS = 'http://schemas.microsoft.com/office/word/2016/wordml/cid';
|
|
76
|
+
const W16CEX_NS = 'http://schemas.microsoft.com/office/word/2018/wordml/cex';
|
|
77
|
+
/**
|
|
78
|
+
* Each comment's `w16cex:dateUtc`, by the paragraph id `w16cid:commentId` pairs its durable id with.
|
|
79
|
+
* From the parts' contents when they are unmarshalled, else from their XML, which leaves them
|
|
80
|
+
* untouched: no view needs them otherwise, and an unmarshalled part is re-marshalled on save.
|
|
81
|
+
*/
|
|
82
|
+
async function readDatesUtc(main, into) {
|
|
83
|
+
var _a, _b, _c;
|
|
84
|
+
const idsPart = main.commentsIdsPart;
|
|
85
|
+
const extensiblePart = main.commentsExtensiblePart;
|
|
86
|
+
if (!idsPart || !extensiblePart)
|
|
87
|
+
return;
|
|
88
|
+
const paraIdOf = new Map();
|
|
89
|
+
if (idsPart.isUnmarshalled) {
|
|
90
|
+
for (const e of (_a = idsPart.contents.commentId) !== null && _a !== void 0 ? _a : [])
|
|
91
|
+
if (e.durableId && e.paraId)
|
|
92
|
+
paraIdOf.set(e.durableId.toUpperCase(), e.paraId.toUpperCase());
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
for (const e of Array.from(parseXml(await idsPart.getXml()).getElementsByTagNameNS(W16CID_NS, 'commentId'))) {
|
|
96
|
+
const durableId = e.getAttributeNS(W16CID_NS, 'durableId');
|
|
97
|
+
const paraId = e.getAttributeNS(W16CID_NS, 'paraId');
|
|
98
|
+
if (durableId && paraId)
|
|
99
|
+
paraIdOf.set(durableId.toUpperCase(), paraId.toUpperCase());
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
const add = (durableId, date) => {
|
|
103
|
+
const paraId = durableId ? paraIdOf.get(durableId.toUpperCase()) : undefined;
|
|
104
|
+
if (paraId && date && !Number.isNaN(date.getTime()))
|
|
105
|
+
into.set(paraId, date);
|
|
106
|
+
};
|
|
107
|
+
if (extensiblePart.isUnmarshalled) {
|
|
108
|
+
for (const e of (_b = extensiblePart.contents.commentExtensible) !== null && _b !== void 0 ? _b : [])
|
|
109
|
+
add(e.durableId, dateOf(e.dateUtc));
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
for (const e of Array.from(parseXml(await extensiblePart.getXml()).getElementsByTagNameNS(W16CEX_NS, 'commentExtensible'))) {
|
|
113
|
+
const value = e.getAttributeNS(W16CEX_NS, 'dateUtc');
|
|
114
|
+
add((_c = e.getAttributeNS(W16CEX_NS, 'durableId')) !== null && _c !== void 0 ? _c : undefined, value ? new Date(value) : undefined);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
71
118
|
async function ensurePeople(main) {
|
|
72
119
|
if (main.peoplePart)
|
|
73
120
|
return main.peoplePart.getContents();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@docx4j/core-ts",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "docx4j-core for TypeScript: Open Packaging (docx/pptx/xlsx zip and flat OPC), typed parts and relationships, style, numbering and font resolution over @docx4j/generated-objects-ts. Node, browsers, Word add-ins.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.mjs",
|