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 <dolan_miu@hotmail.com>
This commit is contained in:
Terry Sargent
2025-04-16 15:45:36 +08:00
committed by GitHub
parent 5af1045a59
commit b19025881b
4 changed files with 77 additions and 19 deletions

View File

@ -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",
}),

View File

@ -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", () => {

View File

@ -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, {

View File

@ -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;
}
}