# Tracked changes and reviewed text

Tracked insertions and deletions listed, accepted and rejected one at a time or all at once, with the text read as it was and as it will be.

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:

```bash
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:

```js
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 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 `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.

---

Source: https://www.rystic.ai/docs/simulators/word/features/tracked-changes · Markdown: https://www.rystic.ai/docs/simulators/word/features/tracked-changes.md
