diff --git a/src/component/annotator/AnnotatorContent.tsx b/src/component/annotator/AnnotatorContent.tsx index 1ec43025..f54a6bbc 100644 --- a/src/component/annotator/AnnotatorContent.tsx +++ b/src/component/annotator/AnnotatorContent.tsx @@ -35,8 +35,16 @@ const PREPROCESSING_INSTRUCTIONS = [ shouldPreprocessNode: (node: any): boolean => node.name && node.name === "a", preprocessNode: (node: any) => { - node.attribs["data-href"] = node.attribs.href; - delete node.attribs.href; + // Remove href from relative links, absolute links will open in blank tab + if (!Utils.isLink(node.attribs.href)) { + node.attribs["data-href"] = node.attribs.href; + delete node.attribs.href; + } else { + node.attribs["data-target"] = node.attribs.target; + node.attribs["target"] = "_blank"; + node.attribs["data-rel"] = node.attribs.rel; + node.attribs["rel"] = "noopener noreferrer"; + } }, }, ]; diff --git a/src/component/annotator/HtmlParserUtils.ts b/src/component/annotator/HtmlParserUtils.ts index e0e20842..469d91ef 100644 --- a/src/component/annotator/HtmlParserUtils.ts +++ b/src/component/annotator/HtmlParserUtils.ts @@ -4,12 +4,33 @@ import render from "dom-serializer"; const RDF_ATTRIBUTE_NAMES = ["about", "property", "resource", "typeof"]; +function removeAddedAttributes(elem: Element) { + // Restore links to their original state - see AnnotatorContent.PREPROCESSING_INSTRUCTIONS + if (elem.tagName === "a") { + if (elem.attribs["data-href"]) { + elem.attribs.href = elem.attribs["data-href"]; + delete elem.attribs["data-href"]; + } + delete elem.attribs["target"]; + delete elem.attribs["rel"]; + if (elem.attribs["data-rel"]) { + elem.attribs.rel = elem.attribs["data-rel"]; + delete elem.attribs["data-rel"]; + } + if (elem.attribs["data-target"]) { + elem.attribs.target = elem.attribs["data-target"]; + delete elem.attribs["data-target"]; + } + } +} + const HtmlParserUtils = { html2dom(html: string): Node[] { // Do not decode HTML entities (e.g., <) when parsing content for object representation, it caused issues // with rendering const options = { decodeEntities: false }; - const handler = new DomHandler(); + // @ts-ignore + const handler = new DomHandler(null, null, removeAddedAttributes); const parser = new HtmlParser(handler, options); parser.parseComplete(html); return handler.dom as Node[]; diff --git a/src/component/annotator/__tests__/Annotator.test.tsx b/src/component/annotator/__tests__/Annotator.test.tsx index 31af1e03..99226224 100644 --- a/src/component/annotator/__tests__/Annotator.test.tsx +++ b/src/component/annotator/__tests__/Annotator.test.tsx @@ -118,7 +118,7 @@ describe("Annotator", () => { expect(wrapper.html().includes(sampleContent)).toBe(true); }); - it("renders body of provided html content with replaced anchor hrefs", () => { + it("preserves absolute URL href anchors", () => { const htmlContent = surroundWithHtml( 'This is a link' ); @@ -138,7 +138,28 @@ describe("Annotator", () => { ) ); const sampleOutput = - 'This is a link'; + 'This is a link'; + expect(wrapper.html().includes(sampleOutput)).toBe(true); + }); + + it("renders body of provided html content with replaced relative anchor hrefs", () => { + const htmlContent = surroundWithHtml('This is a link'); + + const wrapper = mountWithIntl( + withWebSocket( + + + + ) + ); + const sampleOutput = 'This is a link'; expect(wrapper.html().includes(sampleOutput)).toBe(true); }); diff --git a/src/component/annotator/__tests__/HtmlParserUtils.test.ts b/src/component/annotator/__tests__/HtmlParserUtils.test.ts new file mode 100644 index 00000000..620db37f --- /dev/null +++ b/src/component/annotator/__tests__/HtmlParserUtils.test.ts @@ -0,0 +1,22 @@ +import { Element } from "domhandler"; +import HtmlParserUtils from "../HtmlParserUtils"; + +describe("HtmlParserUtils", () => { + describe("html2dom", () => { + it("remove target and rel attributes added when rendering HTML", () => { + const html = + 'Example'; + const nodes = HtmlParserUtils.html2dom(html); + expect((nodes[0] as Element).attribs.href).toEqual("http://example.com"); + expect((nodes[0] as Element).attribs.target).not.toBeDefined(); + expect((nodes[0] as Element).attribs.rel).not.toBeDefined(); + }); + + it("set href attribute to data-href attribute created when rendering HTML", () => { + const html = 'Example'; + const nodes = HtmlParserUtils.html2dom(html); + expect((nodes[0] as Element).attribs.href).toEqual("./about.html"); + expect((nodes[0] as Element).attribs["data-href"]).not.toBeDefined(); + }); + }); +}); diff --git a/src/util/Utils.ts b/src/util/Utils.ts index 45b54beb..b4fd2cb9 100644 --- a/src/util/Utils.ts +++ b/src/util/Utils.ts @@ -51,10 +51,11 @@ const Utils = { */ isLink(str: string): boolean { return ( - str.startsWith("http://") || - str.startsWith("https://") || - str.startsWith("ftp://") || - str.startsWith("sftp://") + str !== undefined && + (str.startsWith("http://") || + str.startsWith("https://") || + str.startsWith("ftp://") || + str.startsWith("sftp://")) ); },