Skip to Content
SimulatorsWordTracked changes and reviewed text

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 insertText puts in;
  • the mark of the paragraph body.insertParagraph adds, 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 paragraphWhat the call does with it
a text changesettles nothing; the change stays listed
a change holding that paragraph’s markthrows 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 listingAccepting the mark’s deletion
shorter, as after an accepted deletion of all or part of it, whoever made either change, or a rejected insertionthrows GeneralException, drops the mark’s deletion and leaves the paragraph in place, untracked
longer, as after an insertText, tracked or notthrows 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 Added change. 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.

Last updated on