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-comments.ts b/demo/73-comments.ts new file mode 100644 index 0000000000..fffe22df93 --- /dev/null +++ b/demo/73-comments.ts @@ -0,0 +1,36 @@ +// 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, CommentReference } from "../build"; + +const doc = new Document({ + comments: { + children: [{ id: 0, author: "Ray Chen", date: new Date(), text: "comment text content" }], + }, + sections: [ + { + properties: {}, + children: [ + new Paragraph({ + children: [ + new TextRun("Hello World"), + new CommentRangeStart(0), + new TextRun({ + text: "Foo Bar", + bold: true, + }), + new CommentRangeEnd(0), + new TextRun({ + children: [new CommentReference(0)], + bold: true, + }), + ], + }), + ], + }, + ], +}); + +Packer.toBuffer(doc).then((buffer) => { + fs.writeFileSync("My Document.docx", buffer); +}); 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/export/packer/next-compiler.spec.ts b/src/export/packer/next-compiler.spec.ts index e5336cbcf5..727c617007 100644 --- a/src/export/packer/next-compiler.spec.ts +++ b/src/export/packer/next-compiler.spec.ts @@ -18,12 +18,15 @@ describe("Compiler", () => { this.timeout(99999999); const file = new File({ sections: [], + comments: { + children: [], + }, }); const zipFile = compiler.compile(file); const fileNames = Object.keys(zipFile.files).map((f) => zipFile.files[f].name); expect(fileNames).is.an.instanceof(Array); - expect(fileNames).has.length(16); + expect(fileNames).has.length(17); expect(fileNames).to.include("word/document.xml"); expect(fileNames).to.include("word/styles.xml"); expect(fileNames).to.include("docProps/core.xml"); @@ -33,6 +36,7 @@ describe("Compiler", () => { expect(fileNames).to.include("word/footnotes.xml"); expect(fileNames).to.include("word/_rels/footnotes.xml.rels"); expect(fileNames).to.include("word/settings.xml"); + expect(fileNames).to.include("word/comments.xml"); expect(fileNames).to.include("word/_rels/document.xml.rels"); expect(fileNames).to.include("[Content_Types].xml"); expect(fileNames).to.include("_rels/.rels"); @@ -76,7 +80,7 @@ describe("Compiler", () => { const fileNames = Object.keys(zipFile.files).map((f) => zipFile.files[f].name); expect(fileNames).is.an.instanceof(Array); - expect(fileNames).has.length(24); + expect(fileNames).has.length(25); expect(fileNames).to.include("word/header1.xml"); expect(fileNames).to.include("word/_rels/header1.xml.rels"); diff --git a/src/export/packer/next-compiler.ts b/src/export/packer/next-compiler.ts index 4d7ce6ffd2..90d08a2232 100644 --- a/src/export/packer/next-compiler.ts +++ b/src/export/packer/next-compiler.ts @@ -5,6 +5,7 @@ import { File } from "file"; import { Formatter } from "../formatter"; import { ImageReplacer } from "./image-replacer"; import { NumberingReplacer } from "./numbering-replacer"; +import { PrettityType } from "./packer"; interface IXmlifyedFile { readonly data: string; @@ -28,6 +29,7 @@ interface IXmlifyedFileMapping { readonly FootNotes: IXmlifyedFile; readonly FootNotesRelationships: IXmlifyedFile; readonly Settings: IXmlifyedFile; + readonly Comments?: IXmlifyedFile; } export class Compiler { @@ -41,7 +43,7 @@ export class Compiler { this.numberingReplacer = new NumberingReplacer(); } - public compile(file: File, prettifyXml?: boolean): JSZip { + public compile(file: File, prettifyXml?: boolean | PrettityType): JSZip { const zip = new JSZip(); const xmlifiedFileMapping = this.xmlifyFile(file, prettifyXml); const map = new Map(Object.entries(xmlifiedFileMapping)); @@ -64,7 +66,7 @@ export class Compiler { return zip; } - private xmlifyFile(file: File, prettify?: boolean): IXmlifyedFileMapping { + private xmlifyFile(file: File, prettify?: boolean | PrettityType): IXmlifyedFileMapping { const documentRelationshipCount = file.Document.Relationships.RelationshipCount + 1; const documentXmlData = xml( @@ -112,7 +114,6 @@ export class Compiler { data: (() => { const xmlData = this.imageReplacer.replace(documentXmlData, documentMediaDatas, documentRelationshipCount); const referenedXmlData = this.numberingReplacer.replace(xmlData, file.Numbering.ConcreteNumbering); - return referenedXmlData; })(), path: "word/document.xml", @@ -403,6 +404,29 @@ export class Compiler { ), path: "word/settings.xml", }, + Comments: { + data: (() => { + if (!file.Comments) { + return; + } + + const data = xml( + this.formatter.format(file.Comments, { + viewWrapper: file.Document, + file, + }), + { + indent: prettify, + declaration: { + standalone: "yes", + encoding: "UTF-8", + }, + }, + ); + return data; + })(), + path: "word/comments.xml", + }, }; } } diff --git a/src/export/packer/packer.ts b/src/export/packer/packer.ts index 1b5a219190..b32cd0aa8d 100644 --- a/src/export/packer/packer.ts +++ b/src/export/packer/packer.ts @@ -1,8 +1,18 @@ import { File } from "file"; import { Compiler } from "./next-compiler"; +/** + * Use blanks to prettify + */ +export enum PrettityType { + NONE = "", + WITH_2_BLANKS = " ", + WITH_4_BLANKS = " ", + WITH_TAB = "\t", +} + export class Packer { - public static async toBuffer(file: File, prettify?: boolean): Promise { + public static async toBuffer(file: File, prettify?: boolean | PrettityType): Promise { const zip = this.compiler.compile(file, prettify); const zipData = await zip.generateAsync({ type: "nodebuffer", @@ -13,7 +23,7 @@ export class Packer { return zipData; } - public static async toBase64String(file: File, prettify?: boolean): Promise { + public static async toBase64String(file: File, prettify?: boolean | PrettityType): Promise { const zip = this.compiler.compile(file, prettify); const zipData = await zip.generateAsync({ type: "base64", @@ -24,7 +34,7 @@ export class Packer { return zipData; } - public static async toBlob(file: File, prettify?: boolean): Promise { + public static async toBlob(file: File, prettify?: boolean | PrettityType): Promise { const zip = this.compiler.compile(file, prettify); const zipData = await zip.generateAsync({ type: "blob", diff --git a/src/file/content-types/content-types.spec.ts b/src/file/content-types/content-types.spec.ts index 57d992f83d..93fca7c8e1 100644 --- a/src/file/content-types/content-types.spec.ts +++ b/src/file/content-types/content-types.spec.ts @@ -102,7 +102,7 @@ describe("ContentTypes", () => { contentTypes.addFooter(102); const tree = new Formatter().format(contentTypes); - expect(tree["Types"][16]).to.deep.equal({ + expect(tree["Types"][17]).to.deep.equal({ Override: { _attr: { ContentType: "application/vnd.openxmlformats-officedocument.wordprocessingml.footer+xml", @@ -111,7 +111,7 @@ describe("ContentTypes", () => { }, }); - expect(tree["Types"][17]).to.deep.equal({ + expect(tree["Types"][18]).to.deep.equal({ Override: { _attr: { ContentType: "application/vnd.openxmlformats-officedocument.wordprocessingml.footer+xml", @@ -128,7 +128,7 @@ describe("ContentTypes", () => { contentTypes.addHeader(202); const tree = new Formatter().format(contentTypes); - expect(tree["Types"][16]).to.deep.equal({ + expect(tree["Types"][17]).to.deep.equal({ Override: { _attr: { ContentType: "application/vnd.openxmlformats-officedocument.wordprocessingml.header+xml", @@ -137,7 +137,7 @@ describe("ContentTypes", () => { }, }); - expect(tree["Types"][17]).to.deep.equal({ + expect(tree["Types"][18]).to.deep.equal({ Override: { _attr: { ContentType: "application/vnd.openxmlformats-officedocument.wordprocessingml.header+xml", diff --git a/src/file/content-types/content-types.ts b/src/file/content-types/content-types.ts index 34e495d3c0..d2331c2cbd 100644 --- a/src/file/content-types/content-types.ts +++ b/src/file/content-types/content-types.ts @@ -32,6 +32,7 @@ export class ContentTypes extends XmlComponent { this.root.push(new Override("application/vnd.openxmlformats-officedocument.wordprocessingml.numbering+xml", "/word/numbering.xml")); this.root.push(new Override("application/vnd.openxmlformats-officedocument.wordprocessingml.footnotes+xml", "/word/footnotes.xml")); this.root.push(new Override("application/vnd.openxmlformats-officedocument.wordprocessingml.settings+xml", "/word/settings.xml")); + this.root.push(new Override("application/vnd.openxmlformats-officedocument.wordprocessingml.comments+xml", "/word/comments.xml")); } public addFooter(index: number): void { diff --git a/src/file/core-properties/properties.ts b/src/file/core-properties/properties.ts index f323c94981..44a6878272 100644 --- a/src/file/core-properties/properties.ts +++ b/src/file/core-properties/properties.ts @@ -1,4 +1,6 @@ +import { ICommentsOptions } from "file/paragraph/run/comment-run"; import { StringContainer, XmlComponent } from "file/xml-components"; + import { ICustomPropertyOptions } from "../custom-properties"; import { IDocumentBackgroundOptions } from "../document"; @@ -21,6 +23,7 @@ export interface IPropertiesOptions { readonly externalStyles?: string; readonly styles?: IStylesOptions; readonly numbering?: INumberingOptions; + readonly comments?: ICommentsOptions; readonly footnotes?: { readonly [key: string]: { readonly children: Paragraph[]; diff --git a/src/file/document/document-attributes.ts b/src/file/document/document-attributes.ts index a61b37eba0..dc759faea0 100644 --- a/src/file/document/document-attributes.ts +++ b/src/file/document/document-attributes.ts @@ -24,6 +24,22 @@ export interface IDocumentAttributesProperties { readonly dcmitype?: string; readonly xsi?: string; readonly type?: string; + readonly cx?: string; + readonly cx1?: string; + readonly cx2?: string; + readonly cx3?: string; + readonly cx4?: string; + readonly cx5?: string; + readonly cx6?: string; + readonly cx7?: string; + readonly cx8?: string; + readonly aink?: string; + readonly am3d?: string; + readonly w16cex?: string; + readonly w16cid?: string; + readonly w16?: string; + readonly w16sdtdh?: string; + readonly w16se?: string; } export class DocumentAttributes extends XmlAttributeComponent { @@ -51,5 +67,21 @@ export class DocumentAttributes extends XmlAttributeComponent { "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:w16": "http://schemas.microsoft.com/office/word/2018/wordml", + "xmlns:w16cex": "http://schemas.microsoft.com/office/word/2018/wordml/cex", + "xmlns:w16cid": "http://schemas.microsoft.com/office/word/2016/wordml/cid", + "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", "mc:Ignorable": "w14 w15 wp14", + "xmlns:aink": "http://schemas.microsoft.com/office/drawing/2016/ink", + "xmlns:am3d": "http://schemas.microsoft.com/office/drawing/2017/model3d", + "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", }, }, { diff --git a/src/file/document/document.ts b/src/file/document/document.ts index 4059eb5183..8296d50436 100644 --- a/src/file/document/document.ts +++ b/src/file/document/document.ts @@ -53,6 +53,22 @@ export class Document extends XmlComponent { wpi: "http://schemas.microsoft.com/office/word/2010/wordprocessingInk", wne: "http://schemas.microsoft.com/office/word/2006/wordml", wps: "http://schemas.microsoft.com/office/word/2010/wordprocessingShape", + cx: "http://schemas.microsoft.com/office/drawing/2014/chartex", + cx1: "http://schemas.microsoft.com/office/drawing/2015/9/8/chartex", + cx2: "http://schemas.microsoft.com/office/drawing/2015/10/21/chartex", + cx3: "http://schemas.microsoft.com/office/drawing/2016/5/9/chartex", + cx4: "http://schemas.microsoft.com/office/drawing/2016/5/10/chartex", + cx5: "http://schemas.microsoft.com/office/drawing/2016/5/11/chartex", + cx6: "http://schemas.microsoft.com/office/drawing/2016/5/12/chartex", + cx7: "http://schemas.microsoft.com/office/drawing/2016/5/13/chartex", + cx8: "http://schemas.microsoft.com/office/drawing/2016/5/14/chartex", + aink: "http://schemas.microsoft.com/office/drawing/2016/ink", + am3d: "http://schemas.microsoft.com/office/drawing/2017/model3d", + w16cex: "http://schemas.microsoft.com/office/word/2018/wordml/cex", + w16cid: "http://schemas.microsoft.com/office/word/2016/wordml/cid", + w16: "http://schemas.microsoft.com/office/word/2018/wordml", + w16sdtdh: "http://schemas.microsoft.com/office/word/2020/wordml/sdtdatahash", + w16se: "http://schemas.microsoft.com/office/word/2015/wordml/symex", Ignorable: "w14 w15 wp14", }), ); 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 00e91efdd5..afd13e5b6f 100644 --- a/src/file/file.ts +++ b/src/file/file.ts @@ -12,6 +12,7 @@ import { HeaderWrapper, IDocumentHeader } from "./header-wrapper"; import { Media } from "./media"; import { Numbering } from "./numbering"; import { Paragraph } from "./paragraph"; +import { Comments } from "./paragraph/run/comment-run"; import { Relationships } from "./relationships"; import { Settings } from "./settings"; import { Styles } from "./styles"; @@ -52,6 +53,7 @@ export class File { private readonly customProperties: CustomProperties; private readonly appProperties: AppProperties; private readonly styles: Styles; + private readonly comments: Comments; constructor(options: IPropertiesOptions, fileProperties: IFileProperties = {}) { this.coreProperties = new CoreProperties({ @@ -69,6 +71,10 @@ export class File { }, ); + if (options.comments) { + this.comments = new Comments(options.comments); + } + this.fileRelationships = new Relationships(); this.customProperties = new CustomProperties(options.customProperties ?? []); this.appProperties = new AppProperties(); @@ -92,7 +98,7 @@ export class File { if (fileProperties.template && options.externalStyles) { throw Error("can not use both template and external styles"); } - if (fileProperties.template) { + if (fileProperties.template && fileProperties.template.styles) { const stylesFactory = new ExternalStylesFactory(); this.styles = stylesFactory.newInstance(fileProperties.template.styles); } else if (options.externalStyles) { @@ -240,6 +246,11 @@ export class File { "http://schemas.openxmlformats.org/officeDocument/2006/relationships/settings", "settings.xml", ); + this.documentWrapper.Relationships.createRelationship( + this.currentRelationshipId++, + "http://schemas.openxmlformats.org/officeDocument/2006/relationships/comments", + "comments.xml", + ); } public get Document(): DocumentWrapper { @@ -293,4 +304,8 @@ export class File { public get Settings(): Settings { return this.settings; } + + public get Comments(): Comments { + return this.comments; + } } 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/paragraph.ts b/src/file/paragraph/paragraph.ts index 2f73f7cc65..4257816375 100644 --- a/src/file/paragraph/paragraph.ts +++ b/src/file/paragraph/paragraph.ts @@ -1,5 +1,4 @@ // http://officeopenxml.com/WPparagraph.php - import { uniqueId } from "convenience-functions"; import { FootnoteReferenceRun } from "file/footnotes/footnote/run/reference-run"; import { IContext, IXmlableObject, XmlComponent } from "file/xml-components"; @@ -11,6 +10,7 @@ import { Bookmark, ConcreteHyperlink, ExternalHyperlink, InternalHyperlink } fro import { Math } from "./math"; import { IParagraphPropertiesOptions, ParagraphProperties } from "./properties"; import { ImageRun, Run, SequentialIdentifier, SimpleField, SimpleMailMergeField, SymbolRun, TextRun } from "./run"; +import { Comment, CommentRangeEnd, CommentRangeStart, CommentReference, Comments } from "./run/comment-run"; export type ParagraphChild = | TextRun @@ -27,7 +27,12 @@ export type ParagraphChild = | DeletedTextRun | Math | SimpleField - | SimpleMailMergeField; + | SimpleMailMergeField + | Comments + | Comment + | CommentRangeStart + | CommentRangeEnd + | CommentReference; export interface IParagraphOptions extends IParagraphPropertiesOptions { readonly text?: string; 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 new file mode 100644 index 0000000000..16054ad778 --- /dev/null +++ b/src/file/paragraph/run/comment-run.ts @@ -0,0 +1,182 @@ +import { Paragraph } from "file/paragraph"; +import { XmlAttributeComponent, XmlComponent } from "file/xml-components"; + +import { TextRun } from "./text-run"; + +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; +}> { + protected readonly xmlKeys = { id: "w:id", initials: "w:initials", author: "w:author", date: "w:date" }; +} + +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", + "xmlns:cx2": "xmlns:cx2", + "xmlns:cx3": "xmlns:cx3", + "xmlns:cx4": "xmlns:cx4", + "xmlns:cx5": "xmlns:cx5", + "xmlns:cx6": "xmlns:cx6", + "xmlns:cx7": "xmlns:cx7", + "xmlns:cx8": "xmlns:cx8", + "xmlns:mc": "xmlns:mc", + "xmlns:aink": "xmlns:aink", + "xmlns:am3d": "xmlns:am3d", + "xmlns:o": "xmlns:o", + "xmlns:r": "xmlns:r", + "xmlns:m": "xmlns:m", + "xmlns:v": "xmlns:v", + "xmlns:wp14": "xmlns:wp14", + "xmlns:wp": "xmlns:wp", + "xmlns:w10": "xmlns:w10", + "xmlns:w": "xmlns:w", + "xmlns:w14": "xmlns:w14", + "xmlns:w15": "xmlns:w15", + "xmlns:w16cex": "xmlns:w16cex", + "xmlns:w16cid": "xmlns:w16cid", + "xmlns:w16": "xmlns:w16", + "xmlns:w16sdtdh": "xmlns:w16sdtdh", + "xmlns:w16se": "xmlns:w16se", + "xmlns:wpg": "xmlns:wpg", + "xmlns:wpi": "xmlns:wpi", + "xmlns:wne": "xmlns:wne", + "xmlns:wps": "xmlns:wps", + }; +} + +export class CommentRangeStart extends XmlComponent { + constructor(id: number) { + super("w:commentRangeStart"); + + this.root.push(new CommentRangeAttributes({ id })); + } +} + +export class CommentRangeEnd extends XmlComponent { + constructor(id: number) { + super("w:commentRangeEnd"); + + this.root.push(new CommentRangeAttributes({ id })); + } +} + +export class CommentReference extends XmlComponent { + constructor(id: number) { + super("w:commentReference"); + + this.root.push(new CommentRangeAttributes({ id })); + } +} + +export class Comment extends XmlComponent { + constructor({ id, initials, author, date = new Date(), text }: ICommentOptions) { + super("w:comment"); + + this.root.push( + new CommentAttributes({ + id, + initials, + author, + date: date.toISOString(), + }), + ); + + this.root.push(new Paragraph({ children: [new TextRun(text)] })); + } +} +export class Comments extends XmlComponent { + constructor({ children }: ICommentsOptions) { + super("w:comments"); + + 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", + }), + ); + + for (const child of children) { + this.root.push(new Comment(child)); + } + } +} diff --git a/src/file/paragraph/run/index.ts b/src/file/paragraph/run/index.ts index da4b9e4f2e..34819e2c02 100644 --- a/src/file/paragraph/run/index.ts +++ b/src/file/paragraph/run/index.ts @@ -9,3 +9,4 @@ export * from "./underline"; export * from "./emphasis-mark"; export * from "./tab"; export * from "./simple-field"; +export * from "./comment-run"; diff --git a/src/file/relationships/relationship/relationship.ts b/src/file/relationships/relationship/relationship.ts index 2b928de890..79204e4ff8 100644 --- a/src/file/relationships/relationship/relationship.ts +++ b/src/file/relationships/relationship/relationship.ts @@ -16,7 +16,8 @@ export type RelationshipType = | "http://schemas.openxmlformats.org/officeDocument/2006/relationships/custom-properties" | "http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties" | "http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink" - | "http://schemas.openxmlformats.org/officeDocument/2006/relationships/footnotes"; + | "http://schemas.openxmlformats.org/officeDocument/2006/relationships/footnotes" + | "http://schemas.openxmlformats.org/officeDocument/2006/relationships/comments"; export enum TargetModeType { EXTERNAL = "External", 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; - } }