Simulators
Tracked changes and reviewed text
Tracked changes are the redline Word records while change tracking is on: set changeTrackingMode to TrackAll or TrackMineOnly, edit, then list, accept or reject each change and read the text with or without them. The default scenario opens with six changes by Alice and Bob:
curl -s "$RYSTIC_URL/_rystic/inspect?view=document" | jq -c '{trackingMode, changes, authors}'
# -> {"trackingMode":"Off","changes":6,"authors":["Alice","Bob"]}
curl -s "$RYSTIC_URL/_rystic/inspect?view=document" | jq -c '.blocks[3].runs[1:3] | map({text, del: .del.author, ins: .ins.author})'
# -> [{"text":"New York","del":"Bob","ins":null},{"text":"Delaware","del":null,"ins":"Bob"}]
From a test, getTrackedChanges on the body, a paragraph or a range lists the changes with their type, author, date and text. Accept Alice’s changes, then read what is left and the governing-law clause as it stands and as it was:
await Word.run(async context => {
const changes = context.document.body.getTrackedChanges();
changes.load('items/type,items/author,items/text');
await context.sync();
changes.items.filter(c => c.author === 'Alice').forEach(c => c.accept());
await context.sync();
const left = context.document.body.getTrackedChanges();
left.load('items/author,items/type,items/text');
const current = context.document.body.getReviewedText('Current');
const original = context.document.body.getReviewedText('Original');
await context.sync();
// left.items -> Bob Deleted "New York", Bob Added "Delaware", Bob Deleted " and expenses"
// current.value.split('\r')[3] -> "This Agreement is governed by the laws of Delaware."
// original.value.split('\r')[3] -> "This Agreement is governed by the laws of New York."
});
accept and reject settle one change, and acceptAll and rejectAll settle a collection in the order it lists them. Every change of one getTrackedChanges() load stays addressable while others are accepted or rejected, so accepting each of one author’s changes in turn works. A range’s getTrackedChanges() lists only the changes in the paragraph it ends in. getReviewedText('Current') reads the text with every pending change accepted and getReviewedText('Original') with every one rejected.
Edit authors
Tracked edits are attributed to Test User, except changes recorded by insertOoxml, which have an empty author. A seeded document’s revisions and comments keep their own authors and dates.
The body’s insertText writes its text untracked
With tracking on, body.insertText writes its text with no insertion, at Start, at End or as a Replace. A Replace marks the old text deleted and writes the new text before it, so getTrackedChanges lists only the deletions. Through a paragraph or a range, the new text is a tracked insertion.
Just Mine tracks what For Everyone tracks, but for insertOoxml
Under TrackMineOnly, these are tracked, each attributed to Test User:
- the text a paragraph’s or a range’s
insertTextputs in; - the mark of the paragraph
body.insertParagraphadds, but not its text; - every deletion.
The body’s own insertText writes its text untracked in either mode. insertOoxml tracks nothing under TrackMineOnly: a Replace deletes the old text untracked, and the package keeps its own revisions.
acceptAll and rejectAll stop at a joined paragraph
acceptAll and rejectAll settle a collection’s changes in the order it lists them. Once one of them takes a paragraph mark out, that paragraph joins the next. A change listed in the next paragraph has then lost its paragraph:
| Change listed in the next paragraph | What the call does with it |
|---|---|
| a text change | settles nothing; the change stays listed |
| a change holding that paragraph’s mark | throws GeneralException, which ends the call; it and every later change stay tracked |
So rejectAll over two adjacent inserted paragraph marks rejects the first and refuses the second, whoever inserted them. Two insertParagraph calls at the body’s End make such marks. acceptAll over two adjacent deleted paragraphs accepts the first and refuses the second. Once the first has settled in that run, a single reject or accept of the second change throws ItemNotFound, text or mark.
Marks that are not adjacent settle every change. So do acceptAll over inserted marks and rejectAll over deleted paragraphs. Settling the second change from a fresh getTrackedChanges() in a later Word.run works.
Two text changes in one paragraph settle in that order too, and the second keeps the end it was listed with. Once the first’s settle took text out before it, the second’s takes as much again after its own text. So rejectAll over an insertion and a later Replace in one paragraph removes the replaced text and what follows it.
Accepting a mark’s deletion after its paragraph changed throws
Accepting the deletion of a paragraph mark that getTrackedChanges() listed on its own, by acceptAll or alone, throws GeneralException once the paragraph’s text is shorter or longer than at the listing:
| The paragraph’s text since the listing | Accepting the mark’s deletion |
|---|---|
| shorter, as after an accepted deletion of all or part of it, whoever made either change, or a rejected insertion | throws GeneralException, drops the mark’s deletion and leaves the paragraph in place, untracked |
longer, as after an insertText, tracked or not | throws GeneralException and keeps the deletion |
Differs from Word for the web: when text was inserted before the mark since the listing, Word’s GeneralException also writes the character at the mark’s listed offset again, untracked. The simulator leaves the text as it was.
Rejecting a mark’s deletion after the insertion before it was rejected goes through. So does accepting a mark’s insertion after the text’s deletion was accepted.
insertOoxml tracks with no author, and a package’s text only in its last paragraph
Under TrackAll, insertOoxml records its insertion, and the deletion a Replace makes, with an empty author. A package of two or more paragraphs tracks the marks it adds and its last paragraph’s text. The text before them goes in plain, at any location, so a later tracked deletion over it is a plain deletion.
A table in the package goes in tracked whole:
- Each row is marked inserted, and so are each cell’s paragraph marks and text. That includes a nested table, and a table in a block content control.
- A block content control’s paragraphs have their marks tracked as the package’s own do.
- Each cell paragraph lists as an
Addedchange. Its range, like any change’s in a table cell, reads its text without its mark. - Accepting or rejecting every change leaves the rows marked inserted, and a rejected cell empty.
A package that starts with a table leaves the mark it splits off before the table untracked.
Every insertion and deletion the package itself carries, on text, marks or rows, goes before the insert is tracked. So its deleted text goes in as text, and its inserted text as the package’s own. The last paragraph’s text goes under a later insertOoxml Replace, as the editor’s own insertion does, and nests under the account’s delete. An insertOoxml Replace’s deletion takes over a deletion already in its range.