From cae6405d9a534afba21115988371c0fe05bc8204 Mon Sep 17 00:00:00 2001 From: Thomas Jansen Date: Wed, 7 Oct 2020 11:44:23 +0200 Subject: [PATCH] improved signature for deleted text runs, added demo 54 and added documentation for change tracking --- demo/54-track-revisions.ts | 132 +++++++ docs/_sidebar.md | 2 +- docs/usage/change-tracking.md | 58 +++ src/file/track-revision/index.ts | 3 +- .../deleted-page-number.spec.ts | 30 ++ .../deleted-page-number.ts | 30 ++ .../deleted-text-run.spec.ts | 371 ++++++++++++++++++ .../deleted-text-run.ts | 83 ++++ .../deleted-text.spec.ts | 15 + .../track-revision-components/deleted-text.ts | 15 + .../inserted-text-run.spec.ts | 37 ++ .../inserted-text-run.ts | 19 + .../track-revision/track-revision.spec.ts | 76 ---- src/file/track-revision/track-revision.ts | 63 +-- 14 files changed, 796 insertions(+), 138 deletions(-) create mode 100644 demo/54-track-revisions.ts create mode 100644 docs/usage/change-tracking.md create mode 100644 src/file/track-revision/track-revision-components/deleted-page-number.spec.ts create mode 100644 src/file/track-revision/track-revision-components/deleted-page-number.ts create mode 100644 src/file/track-revision/track-revision-components/deleted-text-run.spec.ts create mode 100644 src/file/track-revision/track-revision-components/deleted-text-run.ts create mode 100644 src/file/track-revision/track-revision-components/deleted-text.spec.ts create mode 100644 src/file/track-revision/track-revision-components/deleted-text.ts create mode 100644 src/file/track-revision/track-revision-components/inserted-text-run.spec.ts create mode 100644 src/file/track-revision/track-revision-components/inserted-text-run.ts delete mode 100644 src/file/track-revision/track-revision.spec.ts diff --git a/demo/54-track-revisions.ts b/demo/54-track-revisions.ts new file mode 100644 index 0000000000..01d96082b8 --- /dev/null +++ b/demo/54-track-revisions.ts @@ -0,0 +1,132 @@ +// Track Revisions aka. "Track Changes" +// Import from 'docx' rather than '../build' if you install from npm +import * as fs from "fs"; +import { Document, Packer, Paragraph, TextRun, ShadingType, DeletedTextRun, InsertedTextRun, Footer, PageNumber, AlignmentType, FootnoteReferenceRun } from "../build"; + +/* + For reference, see + - https://docs.microsoft.com/en-us/dotnet/api/documentformat.openxml.wordprocessing.insertedrun + - https://docs.microsoft.com/en-us/dotnet/api/documentformat.openxml.wordprocessing.deletedrun + + The method `addTrackRevisions()` adds an element `` to the `settings.xml` file. This specifies that the application shall track *new* revisions made to the existing document. + See also https://docs.microsoft.com/en-us/dotnet/api/documentformat.openxml.wordprocessing.trackrevisions + + Note that this setting enables to track *new changes* after teh file is generated, so this example will still show inserted and deleted text runs when you remove it. +*/ + +const doc = new Document({ + footnotes: [ + new Paragraph({ + children:[ + new TextRun("This is a footnote"), + new DeletedTextRun({ + text: " with some extra text which was deleted", + id: 0, + author: "Firstname Lastname", + date: "2020-10-06T09:05:00Z", + }), + new InsertedTextRun({ + text: " and new content", + id: 1, + author: "Firstname Lastname", + date: "2020-10-06T09:05:00Z", + }) + ] + }), + ], +}); + +doc.Settings.addTrackRevisions() + +const paragraph = new Paragraph({ + children: [ + new TextRun("This is a simple demo "), + new TextRun({ + text: "on how to " + }), + new InsertedTextRun({ + text: "mark a text as an insertion ", + id: 0, + author: "Firstname Lastname", + date: "2020-10-06T09:00:00Z", + }), + new DeletedTextRun({ + text: "or a deletion.", + id: 1, + author: "Firstname Lastname", + date: "2020-10-06T09:00:00Z", + }) + ], +}); + +doc.addSection({ + properties: {}, + children: [ + paragraph, + new Paragraph({ + children: [ + new TextRun("This is a demo "), + new DeletedTextRun({ + text: "in order", + color: "red", + bold: true, + size: 24, + font: { + name: "Garamond", + }, + shading: { + type: ShadingType.REVERSE_DIAGONAL_STRIPE, + color: "00FFFF", + fill: "FF0000", + }, + id: 2, + author: "Firstname Lastname", + date: "2020-10-06T09:00:00Z", + }).break(), + new InsertedTextRun({ + text: "to show how to ", + bold: false, + id: 3, + author: "Firstname Lastname", + date: "2020-10-06T09:05:00Z", + }), + new TextRun({ + bold: true, + children: [ "\tuse Inserted and Deleted TextRuns.", new FootnoteReferenceRun(1) ], + }), + ], + }), + ], + footers: { + default: new Footer({ + children: [ + new Paragraph({ + alignment: AlignmentType.CENTER, + children: [ + new TextRun("Awesome LLC"), + new TextRun({ + children: ["Page Number: ", PageNumber.CURRENT], + }), + new DeletedTextRun({ + children: [" to ", PageNumber.TOTAL_PAGES], + id: 4, + author: "Firstname Lastname", + date: "2020-10-06T09:05:00Z", + }), + new InsertedTextRun({ + children: [" from ", PageNumber.TOTAL_PAGES], + bold: true, + id: 5, + author: "Firstname Lastname", + date: "2020-10-06T09:05:00Z", + }), + ], + }), + ], + }), + }, +}); + +Packer.toBuffer(doc).then((buffer) => { + fs.writeFileSync("My Document.docx", buffer); +}); diff --git a/docs/_sidebar.md b/docs/_sidebar.md index a63970dff9..5d2ba31551 100644 --- a/docs/_sidebar.md +++ b/docs/_sidebar.md @@ -20,6 +20,7 @@ * [Tab Stops](usage/tab-stops.md) * [Table of Contents](usage/table-of-contents.md) * [Page Numbers](usage/page-numbers.md) + * [Change Tracking](usage/change-tracking.md) * Styling * [Styling with JS](usage/styling-with-js.md) * [Styling with XML](usage/styling-with-xml.md) @@ -28,4 +29,3 @@ * [Packers](usage/packers.md) * [Contribution Guidelines](contribution-guidelines.md) - diff --git a/docs/usage/change-tracking.md b/docs/usage/change-tracking.md new file mode 100644 index 0000000000..6f81e4d0d7 --- /dev/null +++ b/docs/usage/change-tracking.md @@ -0,0 +1,58 @@ +# Change Tracking + +> Instead of adding a `TextRun` into a `Paragraph`, you can also add an `InsertedTextRun` or `DeletedTextRun` where you need to supply an `id`, `author` and `date` for the change. + +```ts +import { Paragraph, TextRun, InsertedTextRun, DeletedTextRun } from "docx"; + +const paragraph = new Paragraph({ + children: [ + new TextRun("This is a simple demo "), + new TextRun({ + text: "on how to " + }), + new InsertedTextRun({ + text: "mark a text as an insertion ", + id: 0, + author: "Firstname Lastname", + date: "2020-10-06T09:00:00Z", + }), + new DeletedTextRun({ + text: "or a deletion.", + id: 1, + author: "Firstname Lastname", + date: "2020-10-06T09:00:00Z", + }) + ], +}); +``` + +Note that for a `InsertedTextRun` and `DeletedTextRun`, it is not possible to simply call it with only a text as in `new TextRun("some text")`, since the additonal fields for change tracking need to be provided. Similar to a normal `TextRun` you can add additional text properties. + +```ts +import { Paragraph, TextRun, InsertedTextRun, DeletedTextRun } from "docx"; + +const paragraph = new Paragraph({ + children: [ + new TextRun("This is a simple demo"), + new DeletedTextRun({ + text: "with a deletion.", + color: "red", + bold: true, + size: 24, + id: 0, + author: "Firstname Lastname", + date: "2020-10-06T09:00:00Z", + }) + ], +}); +``` + +In addtion to marking text as inserted or deleted, change tracking can also be added via the document settings. This will enable new changes to be tracked as well. + +```ts +import { Document } from "docx"; + +const doc = new Document({}); +doc.Settings.addTrackRevisions() +``` \ No newline at end of file diff --git a/src/file/track-revision/index.ts b/src/file/track-revision/index.ts index 20bb87a229..eb2465d8fe 100644 --- a/src/file/track-revision/index.ts +++ b/src/file/track-revision/index.ts @@ -1 +1,2 @@ -export * from "./track-revision"; +export * from "./track-revision-components/inserted-text-run"; +export * from "./track-revision-components/deleted-text-run"; diff --git a/src/file/track-revision/track-revision-components/deleted-page-number.spec.ts b/src/file/track-revision/track-revision-components/deleted-page-number.spec.ts new file mode 100644 index 0000000000..5e0238da96 --- /dev/null +++ b/src/file/track-revision/track-revision-components/deleted-page-number.spec.ts @@ -0,0 +1,30 @@ +import { expect } from "chai"; +import { Formatter } from "export/formatter"; +import { DeletedNumberOfPages, DeletedNumberOfPagesSection, DeletedPage } from "./deleted-page-number"; + +describe("Deleted Page", () => { + describe("#constructor()", () => { + it("uses the font name for both ascii and hAnsi", () => { + const tree = new Formatter().format(new DeletedPage()); + expect(tree).to.deep.equal({ "w:delInstrText": [{ _attr: { "xml:space": "preserve" } }, "PAGE"] }); + }); + }); +}); + +describe("Delted NumberOfPages", () => { + describe("#constructor()", () => { + it("uses the font name for both ascii and hAnsi", () => { + const tree = new Formatter().format(new DeletedNumberOfPages()); + expect(tree).to.deep.equal({ "w:delInstrText": [{ _attr: { "xml:space": "preserve" } }, "NUMPAGES"] }); + }); + }); +}); + +describe("Deleted NumberOfPagesSection", () => { + describe("#constructor()", () => { + it("uses the font name for both ascii and hAnsi", () => { + const tree = new Formatter().format(new DeletedNumberOfPagesSection()); + expect(tree).to.deep.equal({ "w:delInstrText": [{ _attr: { "xml:space": "preserve" } }, "SECTIONPAGES"] }); + }); + }); +}); diff --git a/src/file/track-revision/track-revision-components/deleted-page-number.ts b/src/file/track-revision/track-revision-components/deleted-page-number.ts new file mode 100644 index 0000000000..6ce6266f13 --- /dev/null +++ b/src/file/track-revision/track-revision-components/deleted-page-number.ts @@ -0,0 +1,30 @@ +import { SpaceType } from "file/space-type"; +import { XmlAttributeComponent, XmlComponent } from "file/xml-components"; + +class TextAttributes extends XmlAttributeComponent<{ readonly space: SpaceType }> { + protected readonly xmlKeys = { space: "xml:space" }; +} + +export class DeletedPage extends XmlComponent { + constructor() { + super("w:delInstrText"); + this.root.push(new TextAttributes({ space: SpaceType.PRESERVE })); + this.root.push("PAGE"); + } +} + +export class DeletedNumberOfPages extends XmlComponent { + constructor() { + super("w:delInstrText"); + this.root.push(new TextAttributes({ space: SpaceType.PRESERVE })); + this.root.push("NUMPAGES"); + } +} + +export class DeletedNumberOfPagesSection extends XmlComponent { + constructor() { + super("w:delInstrText"); + this.root.push(new TextAttributes({ space: SpaceType.PRESERVE })); + this.root.push("SECTIONPAGES"); + } +} diff --git a/src/file/track-revision/track-revision-components/deleted-text-run.spec.ts b/src/file/track-revision/track-revision-components/deleted-text-run.spec.ts new file mode 100644 index 0000000000..7931e50406 --- /dev/null +++ b/src/file/track-revision/track-revision-components/deleted-text-run.spec.ts @@ -0,0 +1,371 @@ +import { expect } from "chai"; +import { Formatter } from "export/formatter"; +import { DeletedTextRun } from "./deleted-text-run"; +import { FootnoteReferenceRun, PageNumber } from "../../index"; + +describe("DeletedTextRun", () => { + describe("#constructor", () => { + it("should create a deleted text run", () => { + const deletedTextRun = new DeletedTextRun({ text: "some text", id: 0, date: "123", author: "Author" }); + const tree = new Formatter().format(deletedTextRun); + expect(tree).to.deep.equal({ + "w:del": [ + { + _attr: { + "w:author": "Author", + "w:date": "123", + "w:id": 0, + }, + }, + { + "w:r": [ + { + "w:delText": [ + { + _attr: { + "xml:space": "preserve", + }, + }, + "some text", + ], + }, + ], + }, + ], + }); + }); + }); + + describe("#constructor with formatting", () => { + it("should create a deleted text run", () => { + const deletedTextRun = new DeletedTextRun({ text: "some text", bold: true, id: 0, date: "123", author: "Author" }); + const tree = new Formatter().format(deletedTextRun); + expect(tree).to.deep.equal({ + "w:del": [ + { + _attr: { + "w:author": "Author", + "w:date": "123", + "w:id": 0, + }, + }, + { + "w:r": [ + { + "w:rPr": [ + { + "w:b": { + _attr: { + "w:val": true, + }, + }, + }, + { + "w:bCs": { + _attr: { + "w:val": true, + }, + }, + }, + ], + }, + { + "w:delText": [ + { + _attr: { + "xml:space": "preserve", + }, + }, + "some text", + ], + }, + ], + }, + ], + }); + }); + }); + + describe("#break()", () => { + it("should add a break", () => { + const deletedTextRun = new DeletedTextRun({ + children: ["some text"], + id: 0, + date: "123", + author: "Author", + }).break(); + const tree = new Formatter().format(deletedTextRun); + expect(tree).to.deep.equal({ + "w:del": [ + { + _attr: { + "w:author": "Author", + "w:date": "123", + "w:id": 0, + }, + }, + { + "w:r": [ + { + "w:br": {}, + }, + { + "w:delText": [ + { + _attr: { + "xml:space": "preserve", + }, + }, + "some text", + ], + }, + ], + }, + ], + }); + }); + }); + + describe("page numbering", () => { + it("should be able to delete the total pages", () => { + const deletedTextRun = new DeletedTextRun({ + children: [" to ", PageNumber.TOTAL_PAGES], + id: 0, + date: "123", + author: "Author", + }); + const tree = new Formatter().format(deletedTextRun); + expect(tree).to.deep.equal({ + "w:del": [ + { + _attr: { + "w:author": "Author", + "w:date": "123", + "w:id": 0, + }, + }, + { + "w:r": [ + { + "w:delText": [ + { + _attr: { + "xml:space": "preserve", + }, + }, + " to ", + ], + }, + { + "w:fldChar": { + _attr: { + "w:fldCharType": "begin", + }, + }, + }, + { + "w:delInstrText": [ + { + _attr: { + "xml:space": "preserve", + }, + }, + "NUMPAGES", + ], + }, + { + "w:fldChar": { + _attr: { + "w:fldCharType": "separate", + }, + }, + }, + { + "w:fldChar": { + _attr: { + "w:fldCharType": "end", + }, + }, + }, + ], + }, + ], + }); + }); + + it("should be able to delete the total pages in section", () => { + const deletedTextRun = new DeletedTextRun({ + children: [" to ", PageNumber.TOTAL_PAGES_IN_SECTION], + id: 0, + date: "123", + author: "Author", + }); + const tree = new Formatter().format(deletedTextRun); + expect(tree).to.deep.equal({ + "w:del": [ + { + _attr: { + "w:author": "Author", + "w:date": "123", + "w:id": 0, + }, + }, + { + "w:r": [ + { + "w:delText": [ + { + _attr: { + "xml:space": "preserve", + }, + }, + " to ", + ], + }, + { + "w:fldChar": { + _attr: { + "w:fldCharType": "begin", + }, + }, + }, + { + "w:delInstrText": [ + { + _attr: { + "xml:space": "preserve", + }, + }, + "SECTIONPAGES", + ], + }, + { + "w:fldChar": { + _attr: { + "w:fldCharType": "separate", + }, + }, + }, + { + "w:fldChar": { + _attr: { + "w:fldCharType": "end", + }, + }, + }, + ], + }, + ], + }); + }); + + it("should be able to delete the current page", () => { + const deletedTextRun = new DeletedTextRun({ + children: [" to ", PageNumber.CURRENT], + id: 0, + date: "123", + author: "Author", + }); + const tree = new Formatter().format(deletedTextRun); + expect(tree).to.deep.equal({ + "w:del": [ + { + _attr: { + "w:author": "Author", + "w:date": "123", + "w:id": 0, + }, + }, + { + "w:r": [ + { + "w:delText": [ + { + _attr: { + "xml:space": "preserve", + }, + }, + " to ", + ], + }, + { + "w:fldChar": { + _attr: { + "w:fldCharType": "begin", + }, + }, + }, + { + "w:delInstrText": [ + { + _attr: { + "xml:space": "preserve", + }, + }, + "PAGE", + ], + }, + { + "w:fldChar": { + _attr: { + "w:fldCharType": "separate", + }, + }, + }, + { + "w:fldChar": { + _attr: { + "w:fldCharType": "end", + }, + }, + }, + ], + }, + ], + }); + }); + }); + + describe("footnote references", () => { + it("should add a valid footnote reference", () => { + const deletedTextRun = new DeletedTextRun({ + children: ["some text", new FootnoteReferenceRun(1)], + id: 0, + date: "123", + author: "Author", + }); + const tree = new Formatter().format(deletedTextRun); + expect(tree).to.deep.equal({ + "w:del": [ + { + _attr: { + "w:author": "Author", + "w:date": "123", + "w:id": 0, + }, + }, + { + "w:r": [ + { + "w:delText": [ + { + _attr: { + "xml:space": "preserve", + }, + }, + "some text", + ], + }, + { + "w:r": [ + { "w:rPr": [{ "w:rStyle": { _attr: { "w:val": "FootnoteReference" } } }] }, + { "w:footnoteReference": { _attr: { "w:id": 1 } } }, + ], + }, + ], + }, + ], + }); + }); + }); +}); diff --git a/src/file/track-revision/track-revision-components/deleted-text-run.ts b/src/file/track-revision/track-revision-components/deleted-text-run.ts new file mode 100644 index 0000000000..2342f70104 --- /dev/null +++ b/src/file/track-revision/track-revision-components/deleted-text-run.ts @@ -0,0 +1,83 @@ +import { IChangedAttributesProperties, ChangeAttributes } from "../track-revision"; +import { XmlComponent } from "file/xml-components"; +import { IRunOptions, RunProperties, IRunPropertiesOptions, FootnoteReferenceRun } from "../../index"; + +import { Break } from "../../paragraph/run/break"; +import { Begin, Separate, End } from "../../paragraph/run/field"; +import { PageNumber } from "../../paragraph/run/run"; + +import { DeletedPage, DeletedNumberOfPages, DeletedNumberOfPagesSection } from "./deleted-page-number"; +import { DeletedText } from "./deleted-text"; + +interface IDeletedRunOptions extends IRunPropertiesOptions, IChangedAttributesProperties { + readonly children?: (Begin | Separate | End | PageNumber | FootnoteReferenceRun | string)[]; + readonly text?: string; +} + +export class DeletedTextRun extends XmlComponent { + protected readonly deletedTextRunWrapper: DeletedTextRunWrapper; + + constructor(options: IDeletedRunOptions) { + super("w:del"); + this.root.push( + new ChangeAttributes({ + id: options.id, + author: options.author, + date: options.date, + }), + ); + this.deletedTextRunWrapper = new DeletedTextRunWrapper(options as IRunOptions); + this.addChildElement(this.deletedTextRunWrapper); + } + + public break(): DeletedTextRun { + this.deletedTextRunWrapper.break(); + return this; + } +} + +class DeletedTextRunWrapper extends XmlComponent { + constructor(options: IRunOptions) { + super("w:r"); + this.root.push(new RunProperties(options)); + + if (options.children) { + for (const child of options.children) { + if (typeof child === "string") { + switch (child) { + case PageNumber.CURRENT: + this.root.push(new Begin()); + this.root.push(new DeletedPage()); + this.root.push(new Separate()); + this.root.push(new End()); + break; + case PageNumber.TOTAL_PAGES: + this.root.push(new Begin()); + this.root.push(new DeletedNumberOfPages()); + this.root.push(new Separate()); + this.root.push(new End()); + break; + case PageNumber.TOTAL_PAGES_IN_SECTION: + this.root.push(new Begin()); + this.root.push(new DeletedNumberOfPagesSection()); + this.root.push(new Separate()); + this.root.push(new End()); + break; + default: + this.root.push(new DeletedText(child)); + break; + } + continue; + } + + this.root.push(child); + } + } else if (options.text) { + this.root.push(new DeletedText(options.text)); + } + } + + public break(): void { + this.root.splice(1, 0, new Break()); + } +} diff --git a/src/file/track-revision/track-revision-components/deleted-text.spec.ts b/src/file/track-revision/track-revision-components/deleted-text.spec.ts new file mode 100644 index 0000000000..d9c8de46bf --- /dev/null +++ b/src/file/track-revision/track-revision-components/deleted-text.spec.ts @@ -0,0 +1,15 @@ +import { expect } from "chai"; +import { Formatter } from "export/formatter"; +import { DeletedText } from "./deleted-text"; + +describe("Deleted Text", () => { + describe("#constructor", () => { + it("adds the passed in text to the component", () => { + const t = new DeletedText(" this is\n text"); + const f = new Formatter().format(t); + expect(f).to.deep.equal({ + "w:delText": [{ _attr: { "xml:space": "preserve" } }, " this is\n text"], + }); + }); + }); +}); diff --git a/src/file/track-revision/track-revision-components/deleted-text.ts b/src/file/track-revision/track-revision-components/deleted-text.ts new file mode 100644 index 0000000000..408b47304d --- /dev/null +++ b/src/file/track-revision/track-revision-components/deleted-text.ts @@ -0,0 +1,15 @@ +import { SpaceType } from "file/space-type"; +import { XmlAttributeComponent, XmlComponent } from "file/xml-components"; + +class TextAttributes extends XmlAttributeComponent<{ readonly space: SpaceType }> { + protected readonly xmlKeys = { space: "xml:space" }; +} + +export class DeletedText extends XmlComponent { + constructor(text: string) { + super("w:delText"); + this.root.push(new TextAttributes({ space: SpaceType.PRESERVE })); + + this.root.push(text); + } +} diff --git a/src/file/track-revision/track-revision-components/inserted-text-run.spec.ts b/src/file/track-revision/track-revision-components/inserted-text-run.spec.ts new file mode 100644 index 0000000000..c23069fbba --- /dev/null +++ b/src/file/track-revision/track-revision-components/inserted-text-run.spec.ts @@ -0,0 +1,37 @@ +import { expect } from "chai"; +import { Formatter } from "export/formatter"; +import { InsertedTextRun } from "./inserted-text-run"; + +describe("InsertedTextRun", () => { + describe("#constructor", () => { + it("should create a inserted text run", () => { + const insertedTextRun = new InsertedTextRun({ text: "some text", id: 0, date: "123", author: "Author" }); + const tree = new Formatter().format(insertedTextRun); + expect(tree).to.deep.equal({ + "w:ins": [ + { + _attr: { + "w:author": "Author", + "w:date": "123", + "w:id": 0, + }, + }, + { + "w:r": [ + { + "w:t": [ + { + _attr: { + "xml:space": "preserve", + }, + }, + "some text", + ], + }, + ], + }, + ], + }); + }); + }); +}); diff --git a/src/file/track-revision/track-revision-components/inserted-text-run.ts b/src/file/track-revision/track-revision-components/inserted-text-run.ts new file mode 100644 index 0000000000..49bd7f53ae --- /dev/null +++ b/src/file/track-revision/track-revision-components/inserted-text-run.ts @@ -0,0 +1,19 @@ +import { IChangedAttributesProperties, ChangeAttributes } from "../track-revision"; +import { XmlComponent } from "file/xml-components"; +import { TextRun, IRunOptions } from "../../index"; + +interface IInsertedRunOptions extends IChangedAttributesProperties, IRunOptions {} + +export class InsertedTextRun extends XmlComponent { + constructor(options: IInsertedRunOptions) { + super("w:ins"); + this.root.push( + new ChangeAttributes({ + id: options.id, + author: options.author, + date: options.date, + }), + ); + this.addChildElement(new TextRun(options as IRunOptions)); + } +} diff --git a/src/file/track-revision/track-revision.spec.ts b/src/file/track-revision/track-revision.spec.ts deleted file mode 100644 index 30a684fe9c..0000000000 --- a/src/file/track-revision/track-revision.spec.ts +++ /dev/null @@ -1,76 +0,0 @@ -import { expect } from "chai"; - -import { Formatter } from "export/formatter"; - -import { InsertedTextRun, DeletedTextRun } from "./track-revision"; -import { TextRun } from "../paragraph"; - -describe("InsertedTestRun", () => { - describe("#constructor", () => { - it("should create a inserted text run", () => { - const textRun = new TextRun({ - text: "some text", - }); - const insertedTextRun = new InsertedTextRun({ child: textRun, id: 0, date: "123", author: "Author" }); - const tree = new Formatter().format(insertedTextRun); - expect(tree).to.deep.equal({ - "w:ins": [ - { - _attr: { - "w:author": "Author", - "w:date": "123", - "w:id": 0, - }, - }, - { - "w:r": [ - { - "w:t": [ - { - _attr: { - "xml:space": "preserve", - }, - }, - "some text", - ], - }, - ], - }, - ], - }); - }); - }); -}); -describe("DeletedTestRun", () => { - describe("#constructor", () => { - it("should create a deleted text run", () => { - const insertedParagraph = new DeletedTextRun({ text: "some text", id: 0, date: "123", author: "Author" }); - const tree = new Formatter().format(insertedParagraph); - expect(tree).to.deep.equal({ - "w:del": [ - { - _attr: { - "w:author": "Author", - "w:date": "123", - "w:id": 0, - }, - }, - { - "w:r": [ - { - "w:delText": [ - { - _attr: { - "xml:space": "preserve", - }, - }, - "some text", - ], - }, - ], - }, - ], - }); - }); - }); -}); diff --git a/src/file/track-revision/track-revision.ts b/src/file/track-revision/track-revision.ts index 052c7e29ea..4318e9a468 100644 --- a/src/file/track-revision/track-revision.ts +++ b/src/file/track-revision/track-revision.ts @@ -1,72 +1,15 @@ -import { SpaceType } from "file/space-type"; -import { XmlComponent, XmlAttributeComponent } from "file/xml-components"; -import { TextRun } from "../index"; +import { XmlAttributeComponent } from "file/xml-components"; -export interface ITrackRevisionAttributesProperties { +export interface IChangedAttributesProperties { readonly id: number; readonly author: string; readonly date: string; } -export class TrackRevisionAttributes extends XmlAttributeComponent { +export class ChangeAttributes extends XmlAttributeComponent { protected readonly xmlKeys = { id: "w:id", author: "w:author", date: "w:date", }; } - -export interface IInsertedTextRunOptions extends ITrackRevisionAttributesProperties { - readonly child: TextRun; -} - -export interface IDeletedTextRunOptions extends ITrackRevisionAttributesProperties { - readonly text: string; -} - -export class InsertedTextRun extends XmlComponent { - constructor(options: IInsertedTextRunOptions) { - super("w:ins"); - this.root.push( - new TrackRevisionAttributes({ - id: options.id, - author: options.author, - date: options.date, - }), - ); - this.addChildElement(options.child); - } -} - -export class DeletedTextRunWrapper extends XmlComponent { - constructor(text: string) { - super("w:r"); - this.root.push(new DeletedText(text)); - } -} - -class TextAttributes extends XmlAttributeComponent<{ readonly space: SpaceType }> { - protected readonly xmlKeys = { space: "xml:space" }; -} - -export class DeletedText extends XmlComponent { - constructor(text: string) { - super("w:delText"); - this.root.push(new TextAttributes({ space: SpaceType.PRESERVE })); - this.root.push(text); - } -} - -export class DeletedTextRun extends XmlComponent { - constructor(options: IDeletedTextRunOptions) { - super("w:del"); - this.root.push( - new TrackRevisionAttributes({ - id: options.id, - author: options.author, - date: options.date, - }), - ); - this.addChildElement(new DeletedTextRunWrapper(options.text)); - } -}