From b19025881b8acfb0fd42c3d5a44fb3b37ca46ea6 Mon Sep 17 00:00:00 2001 From: Terry Sargent <126881121+yrc2333@users.noreply.github.com> Date: Wed, 16 Apr 2025 15:45:36 +0800 Subject: [PATCH] feat(comments): Support comment pictures (#3032) * feat(comments): Support comment pictures * fix(demo): Fix the image path to load the image correctly * fix(test): Update the number of files asserted in the compiler test case * feat(comments): Support comment pictures * fix(demo): Fix the image path to load the image correctly * fix(test): Update the number of files asserted in the compiler test case * style(src): Format the code and remove extra blank lines --------- Co-authored-by: Dolan --- demo/73-comments.ts | 10 +++- src/export/packer/next-compiler.spec.ts | 8 +-- src/export/packer/next-compiler.ts | 69 ++++++++++++++++++++----- src/file/paragraph/run/comment-run.ts | 9 ++++ 4 files changed, 77 insertions(+), 19 deletions(-) diff --git a/demo/73-comments.ts b/demo/73-comments.ts index 54fc4def41..db21825f59 100644 --- a/demo/73-comments.ts +++ b/demo/73-comments.ts @@ -1,7 +1,7 @@ // Simple example to add comments to a document import * as fs from "fs"; -import { Document, Packer, Paragraph, TextRun, CommentRangeStart, CommentRangeEnd, CommentReference } from "docx"; +import { Document, Packer, Paragraph, TextRun, CommentRangeStart, CommentRangeEnd, CommentReference, ImageRun } from "docx"; const doc = new Document({ comments: { @@ -20,6 +20,14 @@ const doc = new Document({ }), new Paragraph({ children: [ + new ImageRun({ + type: "jpg", + data: fs.readFileSync("./demo/images/cat.jpg"), + transformation: { + width: 100, + height: 100, + }, + }), new TextRun({ text: "comment text content", }), diff --git a/src/export/packer/next-compiler.spec.ts b/src/export/packer/next-compiler.spec.ts index 8db6bc14f6..62a5d12582 100644 --- a/src/export/packer/next-compiler.spec.ts +++ b/src/export/packer/next-compiler.spec.ts @@ -36,7 +36,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(19); + expect(fileNames).has.length(20); expect(fileNames).to.include("word/document.xml"); expect(fileNames).to.include("word/styles.xml"); expect(fileNames).to.include("docProps/core.xml"); @@ -96,7 +96,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(27); + expect(fileNames).has.length(28); expect(fileNames).to.include("word/header1.xml"); expect(fileNames).to.include("word/_rels/header1.xml.rels"); @@ -131,7 +131,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(20); + expect(fileNames).has.length(21); expect(fileNames).to.include("word/comments.xml"); expect(fileNames).to.include("word/commentsExtended.xml"); @@ -163,7 +163,7 @@ describe("Compiler", () => { const spy = vi.spyOn(compiler["formatter"], "format"); compiler.compile(file); - expect(spy).toBeCalledTimes(15); + expect(spy).toBeCalledTimes(16); }); it("should work with media datas", () => { diff --git a/src/export/packer/next-compiler.ts b/src/export/packer/next-compiler.ts index 6eb1d5a11c..9f2e41e062 100644 --- a/src/export/packer/next-compiler.ts +++ b/src/export/packer/next-compiler.ts @@ -32,6 +32,7 @@ type IXmlifyedFileMapping = { readonly FootNotesRelationships: IXmlifyedFile; readonly Settings: IXmlifyedFile; readonly Comments?: IXmlifyedFile; + readonly CommentsRelationships?: IXmlifyedFile; readonly FontTable?: IXmlifyedFile; readonly FontTableRelationships?: IXmlifyedFile; }; @@ -104,7 +105,28 @@ export class Compiler { }, }, ); + + const commentRelationshipCount = file.Comments.Relationships.RelationshipCount + 1; + const commentXmlData = xml( + this.formatter.format(file.Comments, { + viewWrapper: { + View: file.Comments, + Relationships: file.Comments.Relationships, + }, + file, + stack: [], + }), + { + indent: prettify, + declaration: { + standalone: "yes", + encoding: "UTF-8", + }, + }, + ); + const documentMediaDatas = this.imageReplacer.getMediaData(documentXmlData, file.Media); + const commentMediaDatas = this.imageReplacer.getMediaData(commentXmlData, file.Media); return { Relationships: { @@ -450,22 +472,41 @@ export class Compiler { path: "word/settings.xml", }, Comments: { - data: xml( - this.formatter.format(file.Comments, { - viewWrapper: file.Document, - file, - stack: [], - }), - { - indent: prettify, - declaration: { - standalone: "yes", - encoding: "UTF-8", - }, - }, - ), + data: (() => { + const xmlData = this.imageReplacer.replace(commentXmlData, commentMediaDatas, commentRelationshipCount); + const referenedXmlData = this.numberingReplacer.replace(xmlData, file.Numbering.ConcreteNumbering); + return referenedXmlData; + })(), path: "word/comments.xml", }, + CommentsRelationships: { + data: (() => { + commentMediaDatas.forEach((mediaData, i) => { + file.Comments.Relationships.createRelationship( + commentRelationshipCount + i, + "http://schemas.openxmlformats.org/officeDocument/2006/relationships/image", + `media/${mediaData.fileName}`, + ); + }); + return xml( + this.formatter.format(file.Comments.Relationships, { + viewWrapper: { + View: file.Comments, + Relationships: file.Comments.Relationships, + }, + file, + stack: [], + }), + { + indent: prettify, + declaration: { + encoding: "UTF-8", + }, + }, + ); + })(), + path: "word/_rels/comments.xml.rels", + }, FontTable: { data: xml( this.formatter.format(file.FontTable.View, { diff --git a/src/file/paragraph/run/comment-run.ts b/src/file/paragraph/run/comment-run.ts index 0ffc9fe257..c65aaded1f 100644 --- a/src/file/paragraph/run/comment-run.ts +++ b/src/file/paragraph/run/comment-run.ts @@ -1,4 +1,5 @@ import { FileChild } from "@file/file-child"; +import { Relationships } from "@file/relationships"; import { XmlAttributeComponent, XmlComponent } from "@file/xml-components"; export type ICommentOptions = { @@ -136,6 +137,8 @@ export class Comment extends XmlComponent { } } export class Comments extends XmlComponent { + private readonly relationships: Relationships; + public constructor({ children }: ICommentsOptions) { super("w:comments"); @@ -178,5 +181,11 @@ export class Comments extends XmlComponent { for (const child of children) { this.root.push(new Comment(child)); } + + this.relationships = new Relationships(); + } + + public get Relationships(): Relationships { + return this.relationships; } }