Skip to content
Merged
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 @@ -11,6 +11,7 @@ import {
prepareCommentParaIds,
prepareCommentsXmlFilesForExport,
} from './v2/exporter/commentsExporter.js';
import { HYPERLINK_RELATIONSHIP_TYPE } from './constants.js';

class SuperConverter {
static allowedElements = Object.freeze({
Expand Down Expand Up @@ -504,15 +505,16 @@ class SuperConverter {
const existingId = rel.attributes.Id;
const existingTarget = relationships.elements.find((el) => el.attributes.Target === rel.attributes.Target);
const isNewMedia = rel.attributes.Target?.startsWith('media/') && existingId.length > 6;

if (existingTarget && !isNewMedia) {
const isNewHyperlink = rel.attributes.Type === HYPERLINK_RELATIONSHIP_TYPE && existingId.length > 6;

if (existingTarget && !isNewMedia && !isNewHyperlink) {
return;
}

// Update the target to escape ampersands
rel.attributes.Target = rel.attributes?.Target?.replace(/&/g, '&');

// Update the ID. If we've assigned a long ID (ie: images) we leave it alone
// Update the ID. If we've assigned a long ID (ie: images, links) we leave it alone
rel.attributes.Id = existingId.length > 6 ? existingId : `rId${++largestId}`;
Copy link
Copy Markdown
Contributor Author

@artem-harbour artem-harbour May 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the main issue why the links did not work when exporting. We create a new id for the link node every time, but we don't create a new relationship for this id.

Note:

  • Previous relationships are not deleted, but this does not seem to be an issue. It can also be quite risky to delete prev relationships by checking the value. In any case, we can consider this later.


newRels.push(rel);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const HYPERLINK_RELATIONSHIP_TYPE = 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink';
55 changes: 52 additions & 3 deletions packages/super-editor/src/core/super-converter/exporter.js
Original file line number Diff line number Diff line change
Expand Up @@ -601,25 +601,69 @@ function translateLinkNode(params) {

const linkMark = node.marks.find((m) => m.type === 'link');
const link = linkMark.attrs.href;

let rId = linkMark.attrs.rId;
if (!rId) {
rId = addNewLinkRelationship(params, link);
}

node.marks = node.marks.filter((m) => m.type !== 'link');

const outputNode = exportSchemaToJson({ ...params, node });
const contentNode = processLinkContentNode(outputNode);

const newNode = {
name: 'w:hyperlink',
type: 'element',
attributes: {
'r:id': rId,
},
elements: [outputNode],
elements: [contentNode],
};

return newNode;
}

function processLinkContentNode(node) {
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add default properties and color to the link node so it looks like a link in a document.

if (!node) return node;

const contentNode = carbonCopy(node);
if (!contentNode) return contentNode;

const hyperlinkStyle = {
name: 'w:rStyle',
attributes: { 'w:val': 'Hyperlink' },
};
const color = {
name: 'w:color',
attributes: { 'w:val': '467886' },
};

if (contentNode.name === 'w:r') {
const runProps = contentNode.elements.find((el) => el.name === 'w:rPr');

if (runProps) {
const foundColor = runProps.elements.find((el) => el.name === 'w:color');
const foundHyperlinkStyle = runProps.elements.find((el) => el.name === 'w:rStyle');
if (!foundColor) runProps.elements.unshift(color);
if (!foundHyperlinkStyle) runProps.elements.unshift(hyperlinkStyle);
} else {
// we don't add underline by default
const runProps = {
name: 'w:rPr',
elements: [
hyperlinkStyle,
color,
],
};

contentNode.elements.unshift(runProps);
}
}

return contentNode;
}

/**
* Create a new link relationship and add it to the relationships array
*
Expand All @@ -630,7 +674,10 @@ function translateLinkNode(params) {
function addNewLinkRelationship(params, link) {
const newId = 'rId' + generateDocxRandomId();

if (!params.relationships || !Array.isArray(params.relationships)) params.relationships = [];
if (!params.relationships || !Array.isArray(params.relationships)) {
params.relationships = [];
}

params.relationships.push({
type: 'element',
name: 'Relationship',
Expand All @@ -641,6 +688,7 @@ function addNewLinkRelationship(params, link) {
TargetMode: 'External',
},
});

return newId;
}

Expand Down Expand Up @@ -1817,6 +1865,7 @@ function prepareUrlAnnotation(params) {
const newId = addNewLinkRelationship(params, attrs.linkUrl);

const linkTextNode = getTextNodeForExport(attrs.linkUrl, marks, params);
const contentNode = processLinkContentNode(linkTextNode);
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same for url annotation.


return {
name: 'w:hyperlink',
Expand All @@ -1825,7 +1874,7 @@ function prepareUrlAnnotation(params) {
'r:id': newId,
'w:history': 1,
},
elements: [linkTextNode],
elements: [contentNode],
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,10 @@ describe('AnnotationNodeExporter', async () => {
const shortFieldType = fieldElements.find((f) => f.name === 'w:fieldTypeShort');
expect(shortFieldType.attributes['w:val']).toBe('link');

const text = getTextFromNode(body.elements[10].elements[1].elements[1].elements[0]);
const node = body.elements[10].elements[1].elements[1].elements[0];
const run = node.elements.find((el) => el.name === 'w:r');
const text = run?.elements[1].elements[0].text;

expect(text).toEqual('https://vitest.dev/guide/coverage');
expect(params.relationships[2].attributes.Target).toBe('https://vitest.dev/guide/coverage');
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,10 @@ describe('AnnotationNodeExporter for final doc', async () => {

it('export url annotation correctly', async() => {
const hyperLinkNode = body.elements[10].elements[1];
const text = getTextFromNode(hyperLinkNode);

const run = hyperLinkNode.elements.find((el) => el.name === 'w:r');
const text = run?.elements[1].elements[0].text;

expect(text).toBe('https://vitest.dev/guide/coverage');
expect(hyperLinkNode.attributes['r:id']).toBe(params.relationships[2].attributes.Id);
});
Expand Down
Loading