diff --git a/.nycrc b/.nycrc index 785b724a8c..532628ce04 100644 --- a/.nycrc +++ b/.nycrc @@ -1,9 +1,9 @@ { "check-coverage": true, - "statements": 99.32, - "branches": 96.27, - "functions": 99.11, - "lines": 99.32, + "statements": 99.43, + "branches": 96.6, + "functions": 99.47, + "lines": 99.43, "include": [ "src/**/*.ts" ], diff --git a/demo/73-comment.ts b/demo/73-comments.ts similarity index 68% rename from demo/73-comment.ts rename to demo/73-comments.ts index 09b6059b50..fffe22df93 100644 --- a/demo/73-comment.ts +++ b/demo/73-comments.ts @@ -1,10 +1,12 @@ // Simple example to add comments to a document // Import from 'docx' rather than '../build' if you install from npm import * as fs from "fs"; -import { Document, Packer, Paragraph, TextRun, CommentRangeStart, CommentRangeEnd, Comments, Comment, CommentReference } from "../build"; +import { Document, Packer, Paragraph, TextRun, CommentRangeStart, CommentRangeEnd, CommentReference } from "../build"; const doc = new Document({ - comments: new Comments([new Comment({ id: "0", author: "Ray Chen", date: new Date().toISOString() }, "comment text content")]), + comments: { + children: [{ id: 0, author: "Ray Chen", date: new Date(), text: "comment text content" }], + }, sections: [ { properties: {}, @@ -12,14 +14,14 @@ const doc = new Document({ new Paragraph({ children: [ new TextRun("Hello World"), - new CommentRangeStart({ id: "0" }), + new CommentRangeStart(0), new TextRun({ text: "Foo Bar", bold: true, }), - new CommentRangeEnd({ id: "0" }), + new CommentRangeEnd(0), new TextRun({ - children: [new CommentReference({ id: "0" })], + children: [new CommentReference(0)], bold: true, }), ], diff --git a/docs/usage/comments.md b/docs/usage/comments.md new file mode 100644 index 0000000000..d76a6bb3e1 --- /dev/null +++ b/docs/usage/comments.md @@ -0,0 +1,17 @@ +# Comments + +!> Comments requires an understanding of [Sections](usage/sections.md) and [Paragraphs](usage/paragraph.md). + +## Intro + +To add comments in `docx`, a `comments` block is specified in the `Document`. This block defines all the comments in your document. Each comment has an `id`, which you then reference later. + +In the spot you want to add a comment, you simply add a `CommentRangeStart` and a `CommentRangeEnd` to specify where the comment starts and ends. + +Alternatively, you can use `CommentReference` to specify a comment at a specific singular point. + +### Example + +[Example](https://raw.githubusercontent.com/dolanmiu/docx/master/demo/73-comments.ts ':include') + +_Source: https://github.com/dolanmiu/docx/blob/master/demo/73-comments.ts_ diff --git a/src/file/core-properties/properties.ts b/src/file/core-properties/properties.ts index 7269518bdd..44a6878272 100644 --- a/src/file/core-properties/properties.ts +++ b/src/file/core-properties/properties.ts @@ -1,4 +1,4 @@ -import { Comments } from "file/paragraph/run/comment-run"; +import { ICommentsOptions } from "file/paragraph/run/comment-run"; import { StringContainer, XmlComponent } from "file/xml-components"; import { ICustomPropertyOptions } from "../custom-properties"; @@ -23,7 +23,7 @@ export interface IPropertiesOptions { readonly externalStyles?: string; readonly styles?: IStylesOptions; readonly numbering?: INumberingOptions; - readonly comments?: Comments; + readonly comments?: ICommentsOptions; readonly footnotes?: { readonly [key: string]: { readonly children: Paragraph[]; diff --git a/src/file/file.spec.ts b/src/file/file.spec.ts index 321a03dc41..e759ee15c3 100644 --- a/src/file/file.spec.ts +++ b/src/file/file.spec.ts @@ -387,4 +387,30 @@ describe("File", () => { expect(tree["w:settings"][2]).to.deep.equal({ "w:evenAndOddHeaders": {} }); }); + + describe("#comments", () => { + it("should create comments", () => { + const doc = new File({ + comments: { + children: [], + }, + sections: [], + }); + + // tslint:disable-next-line: no-unused-expression + expect(doc.Comments).to.not.be.undefined; + }); + }); + + describe("#numbering", () => { + it("should create", () => { + const doc = new File({ + numbering: { config: [] }, + sections: [], + }); + + // tslint:disable-next-line: no-unused-expression + expect(doc.Numbering).to.not.be.undefined; + }); + }); }); diff --git a/src/file/file.ts b/src/file/file.ts index 20d29d0ed9..afd13e5b6f 100644 --- a/src/file/file.ts +++ b/src/file/file.ts @@ -72,7 +72,7 @@ export class File { ); if (options.comments) { - this.comments = options.comments; + this.comments = new Comments(options.comments); } this.fileRelationships = new Relationships(); diff --git a/src/file/paragraph/formatting/indent.spec.ts b/src/file/paragraph/formatting/indent.spec.ts new file mode 100644 index 0000000000..9a4991c56f --- /dev/null +++ b/src/file/paragraph/formatting/indent.spec.ts @@ -0,0 +1,42 @@ +import { expect } from "chai"; + +import { Formatter } from "export/formatter"; + +import { Indent } from "./indent"; + +describe("Indent", () => { + it("should create", () => { + const indent = new Indent({ + start: 10, + end: 10, + left: 10, + right: 10, + hanging: 10, + firstLine: 10, + }); + const tree = new Formatter().format(indent); + expect(tree).to.deep.equal({ + "w:ind": { + _attr: { + "w:start": 10, + "w:end": 10, + "w:firstLine": 10, + "w:hanging": 10, + "w:left": 10, + "w:right": 10, + }, + }, + }); + }); + + it("should create with no indent values", () => { + const indent = new Indent({}); + + const tree = new Formatter().format(indent); + expect(tree).to.deep.equal({ + "w:ind": { + _attr: {}, + }, + }); + }); +}); diff --git a/src/file/paragraph/run/comment-run.spec.ts b/src/file/paragraph/run/comment-run.spec.ts new file mode 100644 index 0000000000..d0e90aabc9 --- /dev/null +++ b/src/file/paragraph/run/comment-run.spec.ts @@ -0,0 +1,150 @@ +import { expect } from "chai"; + +import { Formatter } from "export/formatter"; +import { Comment, CommentRangeEnd, CommentRangeStart, CommentReference, Comments } from "./comment-run"; + +describe("CommentRangeStart", () => { + describe("#constructor()", () => { + it("should create", () => { + const component = new CommentRangeStart(0); + const tree = new Formatter().format(component); + expect(tree).to.deep.equal({ + "w:commentRangeStart": { _attr: { "w:id": 0 } }, + }); + }); + }); +}); + +describe("CommentRangeEnd", () => { + describe("#constructor()", () => { + it("should create", () => { + const component = new CommentRangeEnd(0); + const tree = new Formatter().format(component); + expect(tree).to.deep.equal({ + "w:commentRangeEnd": { _attr: { "w:id": 0 } }, + }); + }); + }); +}); + +describe("CommentReference", () => { + describe("#constructor()", () => { + it("should create", () => { + const component = new CommentReference(0); + const tree = new Formatter().format(component); + expect(tree).to.deep.equal({ + "w:commentReference": { _attr: { "w:id": 0 } }, + }); + }); + }); +}); + +describe("Comment", () => { + describe("#constructor()", () => { + it("should create", () => { + const component = new Comment({ + id: 0, + text: "test-comment", + date: new Date(1999, 0, 1), + }); + const tree = new Formatter().format(component); + expect(tree).to.deep.equal({ + "w:comment": [ + { _attr: { "w:id": 0, "w:date": "1999-01-01T00:00:00.000Z" } }, + { + "w:p": [ + { + "w:r": [ + { + "w:t": [ + { + _attr: { + "xml:space": "preserve", + }, + }, + "test-comment", + ], + }, + ], + }, + ], + }, + ], + }); + }); + }); +}); + +describe("Comments", () => { + describe("#constructor()", () => { + it("should create", () => { + const component = new Comments({ + children: [ + { + id: 0, + text: "test-comment", + date: new Date(1999, 0, 1), + }, + { + id: 1, + text: "test-comment-2", + date: new Date(1999, 0, 1), + }, + ], + }); + const tree = new Formatter().format(component); + + expect(tree).to.deep.equal({ + "w:comments": [ + { + _attr: { + "xmlns:cx": "http://schemas.microsoft.com/office/drawing/2014/chartex", + "xmlns:cx1": "http://schemas.microsoft.com/office/drawing/2015/9/8/chartex", + "xmlns:cx2": "http://schemas.microsoft.com/office/drawing/2015/10/21/chartex", + "xmlns:cx3": "http://schemas.microsoft.com/office/drawing/2016/5/9/chartex", + "xmlns:cx4": "http://schemas.microsoft.com/office/drawing/2016/5/10/chartex", + "xmlns:cx5": "http://schemas.microsoft.com/office/drawing/2016/5/11/chartex", + "xmlns:cx6": "http://schemas.microsoft.com/office/drawing/2016/5/12/chartex", + "xmlns:cx7": "http://schemas.microsoft.com/office/drawing/2016/5/13/chartex", + "xmlns:cx8": "http://schemas.microsoft.com/office/drawing/2016/5/14/chartex", + "xmlns:mc": "http://schemas.openxmlformats.org/markup-compatibility/2006", + "xmlns:aink": "http://schemas.microsoft.com/office/drawing/2016/ink", + "xmlns:am3d": "http://schemas.microsoft.com/office/drawing/2017/model3d", + "xmlns:o": "urn:schemas-microsoft-com:office:office", + "xmlns:r": "http://schemas.openxmlformats.org/officeDocument/2006/relationships", + "xmlns:m": "http://schemas.openxmlformats.org/officeDocument/2006/math", + "xmlns:v": "urn:schemas-microsoft-com:vml", + "xmlns:wp14": "http://schemas.microsoft.com/office/word/2010/wordprocessingDrawing", + "xmlns:wp": "http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing", + "xmlns:w10": "urn:schemas-microsoft-com:office:word", + "xmlns:w": "http://schemas.openxmlformats.org/wordprocessingml/2006/main", + "xmlns:w14": "http://schemas.microsoft.com/office/word/2010/wordml", + "xmlns:w15": "http://schemas.microsoft.com/office/word/2012/wordml", + "xmlns:w16cex": "http://schemas.microsoft.com/office/word/2018/wordml/cex", + "xmlns:w16cid": "http://schemas.microsoft.com/office/word/2016/wordml/cid", + "xmlns:w16": "http://schemas.microsoft.com/office/word/2018/wordml", + "xmlns:w16sdtdh": "http://schemas.microsoft.com/office/word/2020/wordml/sdtdatahash", + "xmlns:w16se": "http://schemas.microsoft.com/office/word/2015/wordml/symex", + "xmlns:wpg": "http://schemas.microsoft.com/office/word/2010/wordprocessingGroup", + "xmlns:wpi": "http://schemas.microsoft.com/office/word/2010/wordprocessingInk", + "xmlns:wne": "http://schemas.microsoft.com/office/word/2006/wordml", + "xmlns:wps": "http://schemas.microsoft.com/office/word/2010/wordprocessingShape", + }, + }, + { + "w:comment": [ + { _attr: { "w:id": 0, "w:date": "1999-01-01T00:00:00.000Z" } }, + { "w:p": [{ "w:r": [{ "w:t": [{ _attr: { "xml:space": "preserve" } }, "test-comment"] }] }] }, + ], + }, + { + "w:comment": [ + { _attr: { "w:id": 1, "w:date": "1999-01-01T00:00:00.000Z" } }, + { "w:p": [{ "w:r": [{ "w:t": [{ _attr: { "xml:space": "preserve" } }, "test-comment-2"] }] }] }, + ], + }, + ], + }); + }); + }); +}); diff --git a/src/file/paragraph/run/comment-run.ts b/src/file/paragraph/run/comment-run.ts index dea19b6147..16054ad778 100644 --- a/src/file/paragraph/run/comment-run.ts +++ b/src/file/paragraph/run/comment-run.ts @@ -3,51 +3,63 @@ import { XmlAttributeComponent, XmlComponent } from "file/xml-components"; import { TextRun } from "./text-run"; -interface ICommentOptions { - readonly id: string; +export interface ICommentOptions { + readonly id: number; + readonly text: string; + readonly initials?: string; + readonly author?: string; + readonly date?: Date; +} + +export interface ICommentsOptions { + readonly children: ICommentOptions[]; +} + +class CommentAttributes extends XmlAttributeComponent<{ + readonly id: number; readonly initials?: string; readonly author?: string; readonly date?: string; -} - -class CommentAttributes extends XmlAttributeComponent { +}> { protected readonly xmlKeys = { id: "w:id", initials: "w:initials", author: "w:author", date: "w:date" }; } -const COMMENT_ATTRIBUTES_MAP = { - "xmlns:cx": "http://schemas.microsoft.com/office/drawing/2014/chartex", - "xmlns:cx1": "http://schemas.microsoft.com/office/drawing/2015/9/8/chartex", - "xmlns:cx2": "http://schemas.microsoft.com/office/drawing/2015/10/21/chartex", - "xmlns:cx3": "http://schemas.microsoft.com/office/drawing/2016/5/9/chartex", - "xmlns:cx4": "http://schemas.microsoft.com/office/drawing/2016/5/10/chartex", - "xmlns:cx5": "http://schemas.microsoft.com/office/drawing/2016/5/11/chartex", - "xmlns:cx6": "http://schemas.microsoft.com/office/drawing/2016/5/12/chartex", - "xmlns:cx7": "http://schemas.microsoft.com/office/drawing/2016/5/13/chartex", - "xmlns:cx8": "http://schemas.microsoft.com/office/drawing/2016/5/14/chartex", - "xmlns:mc": "http://schemas.openxmlformats.org/markup-compatibility/2006", - "xmlns:aink": "http://schemas.microsoft.com/office/drawing/2016/ink", - "xmlns:am3d": "http://schemas.microsoft.com/office/drawing/2017/model3d", - "xmlns:o": "urn:schemas-microsoft-com:office:office", - "xmlns:r": "http://schemas.openxmlformats.org/officeDocument/2006/relationships", - "xmlns:m": "http://schemas.openxmlformats.org/officeDocument/2006/math", - "xmlns:v": "urn:schemas-microsoft-com:vml", - "xmlns:wp14": "http://schemas.microsoft.com/office/word/2010/wordprocessingDrawing", - "xmlns:wp": "http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing", - "xmlns:w10": "urn:schemas-microsoft-com:office:word", - "xmlns:w": "http://schemas.openxmlformats.org/wordprocessingml/2006/main", - "xmlns:w14": "http://schemas.microsoft.com/office/word/2010/wordml", - "xmlns:w15": "http://schemas.microsoft.com/office/word/2012/wordml", - "xmlns:w16cex": "http://schemas.microsoft.com/office/word/2018/wordml/cex", - "xmlns:w16cid": "http://schemas.microsoft.com/office/word/2016/wordml/cid", - "xmlns:w16": "http://schemas.microsoft.com/office/word/2018/wordml", - "xmlns:w16sdtdh": "http://schemas.microsoft.com/office/word/2020/wordml/sdtdatahash", - "xmlns:w16se": "http://schemas.microsoft.com/office/word/2015/wordml/symex", - "xmlns:wpg": "http://schemas.microsoft.com/office/word/2010/wordprocessingGroup", - "xmlns:wpi": "http://schemas.microsoft.com/office/word/2010/wordprocessingInk", - "xmlns:wne": "http://schemas.microsoft.com/office/word/2006/wordml", - "xmlns:wps": "http://schemas.microsoft.com/office/word/2010/wordprocessingShape", -}; -class RootCommentsAttributes extends XmlAttributeComponent { +class CommentRangeAttributes extends XmlAttributeComponent<{ readonly id: number }> { + protected readonly xmlKeys = { id: "w:id" }; +} +class RootCommentsAttributes extends XmlAttributeComponent<{ + readonly "xmlns:cx"?: string; + readonly "xmlns:cx1"?: string; + readonly "xmlns:cx2"?: string; + readonly "xmlns:cx3"?: string; + readonly "xmlns:cx4"?: string; + readonly "xmlns:cx5"?: string; + readonly "xmlns:cx6"?: string; + readonly "xmlns:cx7"?: string; + readonly "xmlns:cx8"?: string; + readonly "xmlns:mc"?: string; + readonly "xmlns:aink"?: string; + readonly "xmlns:am3d"?: string; + readonly "xmlns:o"?: string; + readonly "xmlns:r"?: string; + readonly "xmlns:m"?: string; + readonly "xmlns:v"?: string; + readonly "xmlns:wp14"?: string; + readonly "xmlns:wp"?: string; + readonly "xmlns:w10"?: string; + readonly "xmlns:w"?: string; + readonly "xmlns:w14"?: string; + readonly "xmlns:w15"?: string; + readonly "xmlns:w16cex"?: string; + readonly "xmlns:w16cid"?: string; + readonly "xmlns:w16"?: string; + readonly "xmlns:w16sdtdh"?: string; + readonly "xmlns:w16se"?: string; + readonly "xmlns:wpg": string; + readonly "xmlns:wpi"?: string; + readonly "xmlns:wne"?: string; + readonly "xmlns:wps"?: string; +}> { protected readonly xmlKeys = { "xmlns:cx": "xmlns:cx", "xmlns:cx1": "xmlns:cx1", @@ -83,81 +95,88 @@ class RootCommentsAttributes extends XmlAttributeComponent { }; } -interface ICommentsAttrs { - readonly "xmlns:cx"?: string; - readonly "xmlns:cx1"?: string; - readonly "xmlns:cx2"?: string; - readonly "xmlns:cx3"?: string; - readonly "xmlns:cx4"?: string; - readonly "xmlns:cx5"?: string; - readonly "xmlns:cx6"?: string; - readonly "xmlns:cx7"?: string; - readonly "xmlns:cx8"?: string; - readonly "xmlns:mc"?: string; - readonly "xmlns:aink"?: string; - readonly "xmlns:am3d"?: string; - readonly "xmlns:o"?: string; - readonly "xmlns:r"?: string; - readonly "xmlns:m"?: string; - readonly "xmlns:v"?: string; - readonly "xmlns:wp14"?: string; - readonly "xmlns:wp"?: string; - readonly "xmlns:w10"?: string; - readonly "xmlns:w"?: string; - readonly "xmlns:w14"?: string; - readonly "xmlns:w15"?: string; - readonly "xmlns:w16cex"?: string; - readonly "xmlns:w16cid"?: string; - readonly "xmlns:w16"?: string; - readonly "xmlns:w16sdtdh"?: string; - readonly "xmlns:w16se"?: string; - readonly "xmlns:wpg": string; - readonly "xmlns:wpi"?: string; - readonly "xmlns:wne"?: string; - readonly "xmlns:wps"?: string; -} - export class CommentRangeStart extends XmlComponent { - constructor(options: ICommentOptions) { + constructor(id: number) { super("w:commentRangeStart"); - this.root.push(new CommentAttributes(options)); + this.root.push(new CommentRangeAttributes({ id })); } } export class CommentRangeEnd extends XmlComponent { - constructor(options: ICommentOptions) { + constructor(id: number) { super("w:commentRangeEnd"); - this.root.push(new CommentAttributes(options)); + this.root.push(new CommentRangeAttributes({ id })); } } export class CommentReference extends XmlComponent { - constructor(options: ICommentOptions) { + constructor(id: number) { super("w:commentReference"); - this.root.push(new CommentAttributes(options)); + this.root.push(new CommentRangeAttributes({ id })); } } export class Comment extends XmlComponent { - constructor(options: ICommentOptions, text: string) { + constructor({ id, initials, author, date = new Date(), text }: ICommentOptions) { super("w:comment"); - this.root.push(new CommentAttributes(options)); + this.root.push( + new CommentAttributes({ + id, + initials, + author, + date: date.toISOString(), + }), + ); - this.addChildElement(new Paragraph({ children: [new TextRun(text)] })); + this.root.push(new Paragraph({ children: [new TextRun(text)] })); } } export class Comments extends XmlComponent { - constructor(comments: Comment[]) { + constructor({ children }: ICommentsOptions) { super("w:comments"); - this.root.push(new RootCommentsAttributes(COMMENT_ATTRIBUTES_MAP)); + this.root.push( + new RootCommentsAttributes({ + "xmlns:cx": "http://schemas.microsoft.com/office/drawing/2014/chartex", + "xmlns:cx1": "http://schemas.microsoft.com/office/drawing/2015/9/8/chartex", + "xmlns:cx2": "http://schemas.microsoft.com/office/drawing/2015/10/21/chartex", + "xmlns:cx3": "http://schemas.microsoft.com/office/drawing/2016/5/9/chartex", + "xmlns:cx4": "http://schemas.microsoft.com/office/drawing/2016/5/10/chartex", + "xmlns:cx5": "http://schemas.microsoft.com/office/drawing/2016/5/11/chartex", + "xmlns:cx6": "http://schemas.microsoft.com/office/drawing/2016/5/12/chartex", + "xmlns:cx7": "http://schemas.microsoft.com/office/drawing/2016/5/13/chartex", + "xmlns:cx8": "http://schemas.microsoft.com/office/drawing/2016/5/14/chartex", + "xmlns:mc": "http://schemas.openxmlformats.org/markup-compatibility/2006", + "xmlns:aink": "http://schemas.microsoft.com/office/drawing/2016/ink", + "xmlns:am3d": "http://schemas.microsoft.com/office/drawing/2017/model3d", + "xmlns:o": "urn:schemas-microsoft-com:office:office", + "xmlns:r": "http://schemas.openxmlformats.org/officeDocument/2006/relationships", + "xmlns:m": "http://schemas.openxmlformats.org/officeDocument/2006/math", + "xmlns:v": "urn:schemas-microsoft-com:vml", + "xmlns:wp14": "http://schemas.microsoft.com/office/word/2010/wordprocessingDrawing", + "xmlns:wp": "http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing", + "xmlns:w10": "urn:schemas-microsoft-com:office:word", + "xmlns:w": "http://schemas.openxmlformats.org/wordprocessingml/2006/main", + "xmlns:w14": "http://schemas.microsoft.com/office/word/2010/wordml", + "xmlns:w15": "http://schemas.microsoft.com/office/word/2012/wordml", + "xmlns:w16cex": "http://schemas.microsoft.com/office/word/2018/wordml/cex", + "xmlns:w16cid": "http://schemas.microsoft.com/office/word/2016/wordml/cid", + "xmlns:w16": "http://schemas.microsoft.com/office/word/2018/wordml", + "xmlns:w16sdtdh": "http://schemas.microsoft.com/office/word/2020/wordml/sdtdatahash", + "xmlns:w16se": "http://schemas.microsoft.com/office/word/2015/wordml/symex", + "xmlns:wpg": "http://schemas.microsoft.com/office/word/2010/wordprocessingGroup", + "xmlns:wpi": "http://schemas.microsoft.com/office/word/2010/wordprocessingInk", + "xmlns:wne": "http://schemas.microsoft.com/office/word/2006/wordml", + "xmlns:wps": "http://schemas.microsoft.com/office/word/2010/wordprocessingShape", + }), + ); - comments.forEach((comment) => { - this.addChildElement(comment); - }); + for (const child of children) { + this.root.push(new Comment(child)); + } } } diff --git a/src/file/xml-components/default-attributes.ts b/src/file/xml-components/default-attributes.ts index 04d6a27ab8..5f4240c102 100644 --- a/src/file/xml-components/default-attributes.ts +++ b/src/file/xml-components/default-attributes.ts @@ -24,8 +24,4 @@ export abstract class XmlAttributeComponent extends BaseXmlComponent { }); return { _attr: attrs }; } - - public set(properties: T): void { - this.root = properties; - } }