From b2a09b512fab0c92c099e68edaa8eae0d985e824 Mon Sep 17 00:00:00 2001 From: Chen Yuncai Date: Thu, 3 Mar 2022 09:59:09 +0800 Subject: [PATCH 1/7] implement sample version of Comment feature. --- demo/72-comment.ts | 41 +++++ src/export/packer/next-compiler.ts | 25 ++- src/export/packer/packer.ts | 2 +- src/file/content-types/content-types.ts | 1 + src/file/document/document-attributes.ts | 32 ++++ src/file/document/document.ts | 16 ++ src/file/file.ts | 16 +- src/file/paragraph/paragraph.ts | 8 +- src/file/paragraph/run/comment-run.ts | 155 ++++++++++++++++++ src/file/paragraph/run/index.ts | 1 + .../relationship/relationship.ts | 3 +- src/import-dotx/import-dotx.ts | 12 +- 12 files changed, 300 insertions(+), 12 deletions(-) create mode 100644 demo/72-comment.ts create mode 100644 src/file/paragraph/run/comment-run.ts diff --git a/demo/72-comment.ts b/demo/72-comment.ts new file mode 100644 index 0000000000..787cfe0e63 --- /dev/null +++ b/demo/72-comment.ts @@ -0,0 +1,41 @@ +// Simple example to add text 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"; + +const doc = new Document({ + sections: [ + { + properties: {}, + children: [ + new Paragraph({ + children: [ + new TextRun("Hello World"), + new CommentRangeStart({ id: "0" }), + new TextRun({ + text: "Foo Bar", + bold: true, + }), + new CommentRangeEnd({ id: "0" }), + new TextRun({ + children: [ + new CommentReference({ id: "0" }) + ], + bold: true, + }), + ], + }), + ], + }, + ], +}, { + template: { + currentRelationshipId: 1, + // global comments data, every comment has a unique id + comments: new Comments([new Comment({ id: '0', author: 'Ray Chen', date: new Date().toISOString() }, 'comment text content')]), + } +} +); +Packer.toBuffer(doc, ' ').then((buffer) => { + fs.writeFileSync("document-comments.docx", buffer); +}); diff --git a/src/export/packer/next-compiler.ts b/src/export/packer/next-compiler.ts index 2afe0fde98..88ec2cfa56 100644 --- a/src/export/packer/next-compiler.ts +++ b/src/export/packer/next-compiler.ts @@ -28,6 +28,7 @@ interface IXmlifyedFileMapping { readonly FootNotes: IXmlifyedFile; readonly FootNotesRelationships: IXmlifyedFile; readonly Settings: IXmlifyedFile; + readonly Comments?: IXmlifyedFile; } export class Compiler { @@ -41,7 +42,7 @@ export class Compiler { this.numberingReplacer = new NumberingReplacer(); } - public compile(file: File, prettifyXml?: boolean): JSZip { + public compile(file: File, prettifyXml?: boolean | string): JSZip { const zip = new JSZip(); const xmlifiedFileMapping = this.xmlifyFile(file, prettifyXml); const map = new Map(Object.entries(xmlifiedFileMapping)); @@ -64,7 +65,7 @@ export class Compiler { return zip; } - private xmlifyFile(file: File, prettify?: boolean): IXmlifyedFileMapping { + private xmlifyFile(file: File, prettify?: boolean | string): IXmlifyedFileMapping { const documentRelationshipCount = file.Document.Relationships.RelationshipCount + 1; const documentXmlData = xml( @@ -112,7 +113,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", @@ -399,6 +399,25 @@ export class Compiler { ), path: "word/settings.xml", }, + Comments: { + data: (() => { + 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..d9be2315aa 100644 --- a/src/export/packer/packer.ts +++ b/src/export/packer/packer.ts @@ -2,7 +2,7 @@ import { File } from "file"; import { Compiler } from "./next-compiler"; export class Packer { - public static async toBuffer(file: File, prettify?: boolean): Promise { + public static async toBuffer(file: File, prettify?: boolean | string): Promise { const zip = this.compiler.compile(file, prettify); const zipData = await zip.generateAsync({ type: "nodebuffer", 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/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 { + protected readonly xmlKeys = { id: "w:id", initias: "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 { + 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", + }; +} + +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) { + super("w:commentRangeStart"); + this.root.push(new CommentAttributes(options)); + } +} + +export class CommentRangeEnd extends XmlComponent { + constructor(options: ICommentOptions) { + super("w:commentRangeEnd"); + this.root.push(new CommentAttributes(options)); + } +} + +export class CommentReference extends XmlComponent { + constructor(options: ICommentOptions) { + super("w:commentReference"); + this.root.push(new CommentAttributes(options)); + } +} + +export class Comment extends XmlComponent { + constructor(options: ICommentOptions, text: string) { + super("w:comment"); + this.root.push(new CommentAttributes(options)); + this.addChildElement(new Paragraph({ children: [new TextRun(text)] })); + } +} +export class Comments extends XmlComponent { + constructor(comments: Comment[]) { + super("w:comments"); + this.root.push(new RootCommentsAttributes(COMMENT_ATTRIBUTES_MAP)); + comments.forEach((comment) => { + this.addChildElement(comment); + }); + } +} 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/import-dotx/import-dotx.ts b/src/import-dotx/import-dotx.ts index 3ef73208d8..774f27a534 100644 --- a/src/import-dotx/import-dotx.ts +++ b/src/import-dotx/import-dotx.ts @@ -1,3 +1,4 @@ +import { Comments } from "./../file/paragraph/run/comment-run"; import * as JSZip from "jszip"; import { Element as XMLElement, ElementCompact as XMLElementCompact, xml2js } from "xml-js"; @@ -37,11 +38,12 @@ interface IRelationshipFileInfo { // https://fileinfo.com/extension/dotx export interface IDocumentTemplate { readonly currentRelationshipId: number; - readonly headers: IDocumentHeader[]; - readonly footers: IDocumentFooter[]; - readonly styles: string; - readonly titlePageIsDefined: boolean; - readonly media: Media; + readonly headers?: IDocumentHeader[]; + readonly footers?: IDocumentFooter[]; + readonly styles?: string; + readonly titlePageIsDefined?: boolean; + readonly media?: Media; + readonly comments?: Comments; } export class ImportDotx { From b81086ab7ad25423081c5180ea75b0a23007404b Mon Sep 17 00:00:00 2001 From: Chen Yuncai Date: Mon, 7 Mar 2022 20:02:14 +0800 Subject: [PATCH 2/7] feat: transfer comments property to File CoreProperties like numbering. --- demo/72-comment.ts | 13 ++++--------- src/export/packer/next-compiler.ts | 5 +++-- src/export/packer/packer.ts | 16 +++++++++++++--- src/file/core-properties/properties.ts | 2 ++ src/file/file.ts | 7 ++++--- src/file/paragraph/run/comment-run.ts | 2 +- src/import-dotx/import-dotx.ts | 12 +++++------- 7 files changed, 32 insertions(+), 25 deletions(-) diff --git a/demo/72-comment.ts b/demo/72-comment.ts index 787cfe0e63..0b2d1f4952 100644 --- a/demo/72-comment.ts +++ b/demo/72-comment.ts @@ -1,9 +1,10 @@ // Simple example to add text 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, Comments, Comment, CommentReference, PrettityType } from "../build"; const doc = new Document({ + comments: new Comments([new Comment({ id: '0', author: 'Ray Chen', date: new Date().toISOString() }, 'comment text content')]), sections: [ { properties: {}, @@ -28,14 +29,8 @@ const doc = new Document({ ], }, ], -}, { - template: { - currentRelationshipId: 1, - // global comments data, every comment has a unique id - comments: new Comments([new Comment({ id: '0', author: 'Ray Chen', date: new Date().toISOString() }, 'comment text content')]), - } -} +}, ); -Packer.toBuffer(doc, ' ').then((buffer) => { +Packer.toBuffer(doc, PrettityType.WITH_2_BLANKS).then((buffer) => { fs.writeFileSync("document-comments.docx", buffer); }); diff --git a/src/export/packer/next-compiler.ts b/src/export/packer/next-compiler.ts index 88ec2cfa56..b571852b10 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; @@ -42,7 +43,7 @@ export class Compiler { this.numberingReplacer = new NumberingReplacer(); } - public compile(file: File, prettifyXml?: boolean | string): 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)); @@ -65,7 +66,7 @@ export class Compiler { return zip; } - private xmlifyFile(file: File, prettify?: boolean | string): IXmlifyedFileMapping { + private xmlifyFile(file: File, prettify?: boolean | PrettityType): IXmlifyedFileMapping { const documentRelationshipCount = file.Document.Relationships.RelationshipCount + 1; const documentXmlData = xml( diff --git a/src/export/packer/packer.ts b/src/export/packer/packer.ts index d9be2315aa..87ee47666b 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 | string): 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/core-properties/properties.ts b/src/file/core-properties/properties.ts index f323c94981..5c3dbd42d8 100644 --- a/src/file/core-properties/properties.ts +++ b/src/file/core-properties/properties.ts @@ -1,3 +1,4 @@ +import { Comments } from './../paragraph/run/comment-run'; import { StringContainer, XmlComponent } from "file/xml-components"; import { ICustomPropertyOptions } from "../custom-properties"; import { IDocumentBackgroundOptions } from "../document"; @@ -21,6 +22,7 @@ export interface IPropertiesOptions { readonly externalStyles?: string; readonly styles?: IStylesOptions; readonly numbering?: INumberingOptions; + readonly comments?: Comments; readonly footnotes?: { readonly [key: string]: { readonly children: Paragraph[]; diff --git a/src/file/file.ts b/src/file/file.ts index 658514d1d0..48dd71b844 100644 --- a/src/file/file.ts +++ b/src/file/file.ts @@ -71,6 +71,10 @@ export class File { }, ); + if (options.comments) { + this.comments = options.comments; + } + this.fileRelationships = new Relationships(); this.customProperties = new CustomProperties(options.customProperties ?? []); this.appProperties = new AppProperties(); @@ -136,9 +140,6 @@ export class File { this.footnotesWrapper.View.createFootNote(parseFloat(key), options.footnotes[key].children); } } - if (fileProperties.template && fileProperties.template.comments) { - this.comments = fileProperties.template.comments; - } } private addSection({ headers = {}, footers = {}, children, properties }: ISectionOptions): void { diff --git a/src/file/paragraph/run/comment-run.ts b/src/file/paragraph/run/comment-run.ts index 6f5176f521..f65bb08c5b 100644 --- a/src/file/paragraph/run/comment-run.ts +++ b/src/file/paragraph/run/comment-run.ts @@ -10,7 +10,7 @@ interface ICommentOptions { } class CommentAttributes extends XmlAttributeComponent { - protected readonly xmlKeys = { id: "w:id", initias: "w:initials", author: "w:author", date: "w:date" }; + protected readonly xmlKeys = { id: "w:id", initials: "w:initials", author: "w:author", date: "w:date" }; } const COMMENT_ATTRIBUTES_MAP = { diff --git a/src/import-dotx/import-dotx.ts b/src/import-dotx/import-dotx.ts index 774f27a534..3ef73208d8 100644 --- a/src/import-dotx/import-dotx.ts +++ b/src/import-dotx/import-dotx.ts @@ -1,4 +1,3 @@ -import { Comments } from "./../file/paragraph/run/comment-run"; import * as JSZip from "jszip"; import { Element as XMLElement, ElementCompact as XMLElementCompact, xml2js } from "xml-js"; @@ -38,12 +37,11 @@ interface IRelationshipFileInfo { // https://fileinfo.com/extension/dotx export interface IDocumentTemplate { readonly currentRelationshipId: number; - readonly headers?: IDocumentHeader[]; - readonly footers?: IDocumentFooter[]; - readonly styles?: string; - readonly titlePageIsDefined?: boolean; - readonly media?: Media; - readonly comments?: Comments; + readonly headers: IDocumentHeader[]; + readonly footers: IDocumentFooter[]; + readonly styles: string; + readonly titlePageIsDefined: boolean; + readonly media: Media; } export class ImportDotx { From 233a29bfefcef906b8d2a3808f6cbfa7519ee5fe Mon Sep 17 00:00:00 2001 From: Dolan Miu Date: Sun, 19 Jun 2022 00:31:36 +0100 Subject: [PATCH 3/7] Fix tests and linting --- src/export/packer/next-compiler.spec.ts | 5 +++-- src/export/packer/next-compiler.ts | 4 ++++ src/export/packer/packer.ts | 2 +- src/file/content-types/content-types.spec.ts | 8 ++++---- src/file/core-properties/properties.ts | 3 ++- src/file/document/document.spec.ts | 16 ++++++++++++++++ src/file/file.ts | 2 +- src/file/paragraph/paragraph.ts | 3 +-- src/file/paragraph/run/comment-run.ts | 12 ++++++++++-- 9 files changed, 42 insertions(+), 13 deletions(-) diff --git a/src/export/packer/next-compiler.spec.ts b/src/export/packer/next-compiler.spec.ts index e5336cbcf5..3ffdda7953 100644 --- a/src/export/packer/next-compiler.spec.ts +++ b/src/export/packer/next-compiler.spec.ts @@ -23,7 +23,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(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 +33,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 +77,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 fd6110601b..90d08a2232 100644 --- a/src/export/packer/next-compiler.ts +++ b/src/export/packer/next-compiler.ts @@ -406,6 +406,10 @@ export class Compiler { }, Comments: { data: (() => { + if (!file.Comments) { + return; + } + const data = xml( this.formatter.format(file.Comments, { viewWrapper: file.Document, diff --git a/src/export/packer/packer.ts b/src/export/packer/packer.ts index 87ee47666b..b32cd0aa8d 100644 --- a/src/export/packer/packer.ts +++ b/src/export/packer/packer.ts @@ -8,7 +8,7 @@ export enum PrettityType { NONE = "", WITH_2_BLANKS = " ", WITH_4_BLANKS = " ", - WITH_TAB = "\t" + WITH_TAB = "\t", } export class Packer { 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/core-properties/properties.ts b/src/file/core-properties/properties.ts index 5c3dbd42d8..7269518bdd 100644 --- a/src/file/core-properties/properties.ts +++ b/src/file/core-properties/properties.ts @@ -1,5 +1,6 @@ -import { Comments } from './../paragraph/run/comment-run'; +import { Comments } from "file/paragraph/run/comment-run"; import { StringContainer, XmlComponent } from "file/xml-components"; + import { ICustomPropertyOptions } from "../custom-properties"; import { IDocumentBackgroundOptions } from "../document"; diff --git a/src/file/document/document.spec.ts b/src/file/document/document.spec.ts index f6137bde70..6cea17d009 100644 --- a/src/file/document/document.spec.ts +++ b/src/file/document/document.spec.ts @@ -31,11 +31,27 @@ describe("Document", () => { "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/file.ts b/src/file/file.ts index 48dd71b844..20d29d0ed9 100644 --- a/src/file/file.ts +++ b/src/file/file.ts @@ -1,4 +1,3 @@ -import { Comments } from "./paragraph/run/comment-run"; import { AppProperties } from "./app-properties/app-properties"; import { ContentTypes } from "./content-types/content-types"; import { CoreProperties, IPropertiesOptions } from "./core-properties"; @@ -13,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"; diff --git a/src/file/paragraph/paragraph.ts b/src/file/paragraph/paragraph.ts index 4f3919196d..4257816375 100644 --- a/src/file/paragraph/paragraph.ts +++ b/src/file/paragraph/paragraph.ts @@ -1,6 +1,4 @@ -import { CommentRangeStart, CommentRangeEnd, CommentReference, Comments, Comment } from "./run/comment-run"; // 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"; @@ -12,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 diff --git a/src/file/paragraph/run/comment-run.ts b/src/file/paragraph/run/comment-run.ts index f65bb08c5b..dea19b6147 100644 --- a/src/file/paragraph/run/comment-run.ts +++ b/src/file/paragraph/run/comment-run.ts @@ -1,6 +1,7 @@ +import { Paragraph } from "file/paragraph"; +import { XmlAttributeComponent, XmlComponent } from "file/xml-components"; + import { TextRun } from "./text-run"; -import { Paragraph } from "./../paragraph"; -import { XmlAttributeComponent, XmlComponent } from "../../../file/xml-components"; interface ICommentOptions { readonly id: string; @@ -119,6 +120,7 @@ interface ICommentsAttrs { export class CommentRangeStart extends XmlComponent { constructor(options: ICommentOptions) { super("w:commentRangeStart"); + this.root.push(new CommentAttributes(options)); } } @@ -126,6 +128,7 @@ export class CommentRangeStart extends XmlComponent { export class CommentRangeEnd extends XmlComponent { constructor(options: ICommentOptions) { super("w:commentRangeEnd"); + this.root.push(new CommentAttributes(options)); } } @@ -133,6 +136,7 @@ export class CommentRangeEnd extends XmlComponent { export class CommentReference extends XmlComponent { constructor(options: ICommentOptions) { super("w:commentReference"); + this.root.push(new CommentAttributes(options)); } } @@ -140,14 +144,18 @@ export class CommentReference extends XmlComponent { export class Comment extends XmlComponent { constructor(options: ICommentOptions, text: string) { super("w:comment"); + this.root.push(new CommentAttributes(options)); + this.addChildElement(new Paragraph({ children: [new TextRun(text)] })); } } export class Comments extends XmlComponent { constructor(comments: Comment[]) { super("w:comments"); + this.root.push(new RootCommentsAttributes(COMMENT_ATTRIBUTES_MAP)); + comments.forEach((comment) => { this.addChildElement(comment); }); From 0acb9cf7a9896509c45be40bf6415d674980f07e Mon Sep 17 00:00:00 2001 From: Dolan Miu Date: Sun, 19 Jun 2022 02:01:54 +0100 Subject: [PATCH 4/7] Format demo --- demo/72-comment.ts | 36 ------------------------------------ demo/73-comment.ts | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 36 deletions(-) delete mode 100644 demo/72-comment.ts create mode 100644 demo/73-comment.ts diff --git a/demo/72-comment.ts b/demo/72-comment.ts deleted file mode 100644 index 0b2d1f4952..0000000000 --- a/demo/72-comment.ts +++ /dev/null @@ -1,36 +0,0 @@ -// Simple example to add text 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, PrettityType } from "../build"; - -const doc = new Document({ - comments: new Comments([new Comment({ id: '0', author: 'Ray Chen', date: new Date().toISOString() }, 'comment text content')]), - sections: [ - { - properties: {}, - children: [ - new Paragraph({ - children: [ - new TextRun("Hello World"), - new CommentRangeStart({ id: "0" }), - new TextRun({ - text: "Foo Bar", - bold: true, - }), - new CommentRangeEnd({ id: "0" }), - new TextRun({ - children: [ - new CommentReference({ id: "0" }) - ], - bold: true, - }), - ], - }), - ], - }, - ], -}, -); -Packer.toBuffer(doc, PrettityType.WITH_2_BLANKS).then((buffer) => { - fs.writeFileSync("document-comments.docx", buffer); -}); diff --git a/demo/73-comment.ts b/demo/73-comment.ts new file mode 100644 index 0000000000..d0f3b08005 --- /dev/null +++ b/demo/73-comment.ts @@ -0,0 +1,34 @@ +// 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"; + +const doc = new Document({ + comments: new Comments([new Comment({ id: "0", author: "Ray Chen", date: new Date().toISOString() }, "comment text content")]), + sections: [ + { + properties: {}, + children: [ + new Paragraph({ + children: [ + new TextRun("Hello World"), + new CommentRangeStart({ id: "0" }), + new TextRun({ + text: "Foo Bar", + bold: true, + }), + new CommentRangeEnd({ id: "0" }), + new TextRun({ + children: [new CommentReference({ id: "0" })], + bold: true, + }), + ], + }), + ], + }, + ], +}); + +Packer.toBuffer(doc).then((buffer) => { + fs.writeFileSync("document-comments.docx", buffer); +}); From 5b7c62685dcae285cb64716fe05d2b0c4d1c7641 Mon Sep 17 00:00:00 2001 From: Dolan Miu Date: Sun, 19 Jun 2022 13:12:59 +0100 Subject: [PATCH 5/7] Export to My Document.docx --- demo/73-comment.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/demo/73-comment.ts b/demo/73-comment.ts index d0f3b08005..09b6059b50 100644 --- a/demo/73-comment.ts +++ b/demo/73-comment.ts @@ -30,5 +30,5 @@ const doc = new Document({ }); Packer.toBuffer(doc).then((buffer) => { - fs.writeFileSync("document-comments.docx", buffer); + fs.writeFileSync("My Document.docx", buffer); }); From 9827ed30bba5529360019c8c43da0d13fc5d1fce Mon Sep 17 00:00:00 2001 From: Dolan Miu Date: Wed, 22 Jun 2022 23:35:46 +0100 Subject: [PATCH 6/7] Write tests, improve API, create documentation --- .nycrc | 8 +- demo/{73-comment.ts => 73-comments.ts} | 12 +- docs/usage/comments.md | 17 ++ src/file/core-properties/properties.ts | 4 +- src/file/file.spec.ts | 26 +++ src/file/file.ts | 2 +- src/file/paragraph/formatting/indent.spec.ts | 42 ++++ src/file/paragraph/run/comment-run.spec.ts | 150 ++++++++++++++ src/file/paragraph/run/comment-run.ts | 193 ++++++++++-------- src/file/xml-components/default-attributes.ts | 4 - 10 files changed, 355 insertions(+), 103 deletions(-) rename demo/{73-comment.ts => 73-comments.ts} (68%) create mode 100644 docs/usage/comments.md create mode 100644 src/file/paragraph/formatting/indent.spec.ts create mode 100644 src/file/paragraph/run/comment-run.spec.ts 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; - } } From c38e70e25dd6b092e49dfc279a18a25af44c05d9 Mon Sep 17 00:00:00 2001 From: Dolan Miu Date: Sat, 25 Jun 2022 13:08:20 +0100 Subject: [PATCH 7/7] Update tests --- src/export/packer/next-compiler.spec.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/export/packer/next-compiler.spec.ts b/src/export/packer/next-compiler.spec.ts index 3ffdda7953..727c617007 100644 --- a/src/export/packer/next-compiler.spec.ts +++ b/src/export/packer/next-compiler.spec.ts @@ -18,6 +18,9 @@ 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);