Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ export const getCommentDefinition = (comment, commentId, allComments, editor) =>
const attributes = {
'w:id': String(commentId),
'w:author': comment.creatorName || comment.importedAuthor?.name,
'w:email': comment.creatorEmail || comment.importedAuthor?.email,
'w:date': toIsoNoFractional(comment.createdTime),
'w:initials': getInitials(comment.creatorName),
'w:done': comment.resolvedTime ? '1' : '0',
Expand All @@ -48,6 +47,7 @@ export const getCommentDefinition = (comment, commentId, allComments, editor) =>
'custom:trackedChangeType': comment.trackedChangeType,
'custom:trackedChangeDisplayType': comment.trackedChangeDisplayType || null,
'custom:trackedDeletedText': comment.deletedText || null,
'custom:authorEmail': comment.creatorEmail || comment.importedAuthor?.email,
};

// Add the w15:paraIdParent attribute if the comment has a parent
Expand Down Expand Up @@ -132,7 +132,6 @@ export const updateCommentsXml = (commentDefs = [], commentsXml) => {
commentDef.attributes = {
'w:id': commentDef.attributes['w:id'],
'w:author': commentDef.attributes['w:author'],
'w:email': commentDef.attributes['w:email'],
'w:date': commentDef.attributes['w:date'],
'w:initials': commentDef.attributes['w:initials'],
'custom:internalId': commentDef.attributes['custom:internalId'],
Expand All @@ -141,6 +140,7 @@ export const updateCommentsXml = (commentDefs = [], commentsXml) => {
'custom:trackedChangeType': commentDef.attributes['custom:trackedChangeType'],
'custom:trackedChangeDisplayType': commentDef.attributes['custom:trackedChangeDisplayType'],
'custom:trackedDeletedText': commentDef.attributes['custom:trackedDeletedText'],
'custom:authorEmail': commentDef.attributes['custom:authorEmail'],
'xmlns:custom': 'http://schemas.openxmlformats.org/wordprocessingml/2006/main',
};
});
Expand Down Expand Up @@ -400,10 +400,14 @@ export const prepareCommentsXmlFilesForExport = ({
relationships.push(generateRelationship('comments.xml'));
emittedTargets.add('comments.xml');

const forceWordThreadingProfile =
threadingProfile?.defaultStyle === 'range-based' && exportStrategy !== 'google-docs';
const effectiveThreadingProfile = forceWordThreadingProfile ? 'word' : threadingProfile || exportStrategy;

const commentsExtendedXml = updateCommentsExtendedXml(
commentsWithParaIds,
updatedXml['word/commentsExtended.xml'],
threadingProfile || exportStrategy,
effectiveThreadingProfile,
);

// Only add the file and relationship if we're actually generating commentsExtended.xml
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -381,12 +381,65 @@ describe('prepareCommentsXmlFilesForExport', () => {
expect(result.removedTargets).toHaveLength(0);
});
});

describe('threading profile overrides', () => {
it('forces Word-style threading when export strategy is Word but profile is range-based', () => {
const threadingProfile = {
defaultStyle: 'range-based',
mixed: false,
fileSet: {
hasCommentsExtended: false,
hasCommentsExtensible: false,
hasCommentsIds: false,
},
};

const result = prepareCommentsXmlFilesForExport({
convertedXml: makeConvertedXml(),
defs,
commentsWithParaIds,
exportType: 'external',
threadingProfile,
});

expect(result.documentXml['word/commentsExtended.xml']).toBeDefined();
const rel = result.relationships.find((r) => r.attributes.Target === 'commentsExtended.xml');
expect(rel).toBeDefined();
});

it('still honors Google Docs export strategy when all comments originate from Google Docs', () => {
const threadingProfile = {
defaultStyle: 'range-based',
mixed: false,
fileSet: {
hasCommentsExtended: false,
hasCommentsExtensible: false,
hasCommentsIds: false,
},
};

const googleComments = [makeComment({ origin: 'google-docs' })];

const result = prepareCommentsXmlFilesForExport({
convertedXml: makeConvertedXml(),
defs,
commentsWithParaIds: googleComments,
exportType: 'external',
threadingProfile,
});

expect(result.documentXml['word/commentsExtended.xml']).toBeUndefined();
const rel = result.relationships.find((r) => r.attributes.Target === 'commentsExtended.xml');
expect(rel).toBeUndefined();
});
});
});

describe('getCommentDefinition', () => {
it('preserves tracked change display metadata for exported tracked-change comments', () => {
const definition = getCommentDefinition(
makeComment({
creatorEmail: 'author@example.com',
trackedChange: true,
trackedChangeType: 'trackFormat',
trackedChangeText: 'https://example.com',
Expand All @@ -400,6 +453,8 @@ describe('getCommentDefinition', () => {
expect(definition.attributes['custom:trackedChangeType']).toBe('trackFormat');
expect(definition.attributes['custom:trackedChangeText']).toBe('https://example.com');
expect(definition.attributes['custom:trackedChangeDisplayType']).toBe('hyperlinkAdded');
expect(definition.attributes['custom:authorEmail']).toBe('author@example.com');
expect(definition.attributes['w:email']).toBeUndefined();
});
});

Expand Down Expand Up @@ -609,5 +664,31 @@ describe('updateCommentsXml', () => {
const lastParagraph = updatedComment.elements[updatedComment.elements.length - 1];

expect(lastParagraph.attributes['w14:paraId']).toBe('ABC12345');
expect(updatedComment.attributes['w:email']).toBeUndefined();
expect(updatedComment.attributes['custom:authorEmail']).toBeUndefined();
});

it('preserves custom author email attribute and omits w:email', () => {
const commentDef = {
type: 'element',
name: 'w:comment',
attributes: {
'w:id': '1',
'w:author': 'Author',
'w:initials': 'A',
'w15:paraId': 'EMAIL123',
'custom:authorEmail': 'author@example.com',
},
elements: [{ type: 'element', name: 'w:p', attributes: {}, elements: [] }],
};
const commentsXml = {
elements: [{ elements: [] }],
};

const result = updateCommentsXml([commentDef], commentsXml);
const updatedComment = result.elements[0].elements[0];

expect(updatedComment.attributes['w:email']).toBeUndefined();
expect(updatedComment.attributes['custom:authorEmail']).toBe('author@example.com');
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export function importCommentData({ docx, editor, converter }) {
const { attributes } = el;
const importedId = attributes['w:id'];
const authorName = attributes['w:author'];
const authorEmail = attributes['w:email'];
const authorEmail = attributes['w:email'] ?? attributes['custom:authorEmail'];
const initials = attributes['w:initials'];
const createdDate = attributes['w:date'];
const internalId = attributes['custom:internalId'];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ const buildDocx = ({ comments = [], extended = [], documentRanges = [] } = {}) =
'custom:trackedChangeType': comment.trackedChangeType,
'custom:trackedChangeDisplayType': comment.trackedChangeDisplayType,
'custom:trackedDeletedText': comment.trackedDeletedText,
...(comment.customAuthorEmail ? { 'custom:authorEmail': comment.customAuthorEmail } : {}),
},
elements: comment.elements ?? [{ fakeParaId: comment.paraId ?? `para-${comment.id}` }],
}));
Expand Down Expand Up @@ -257,6 +258,22 @@ describe('importCommentData metadata parsing', () => {
const [comment] = importCommentData({ docx });
expect(comment.elements).toHaveLength(2);
});

it('reads custom:authorEmail when w:email is absent', () => {
const docx = buildDocx({
comments: [
{
id: 6,
author: 'Custom Email',
customAuthorEmail: 'custom@example.com',
},
],
});
delete docx['word/comments.xml'].elements[0].elements[0].attributes['w:email'];

const [comment] = importCommentData({ docx });
expect(comment.creatorEmail).toBe('custom@example.com');
});
});

describe('importCommentData extended metadata', () => {
Expand Down
Loading