diff --git a/src/core/operations/RenderMarkdown.mjs b/src/core/operations/RenderMarkdown.mjs
index c656bf5b..20966cca 100644
--- a/src/core/operations/RenderMarkdown.mjs
+++ b/src/core/operations/RenderMarkdown.mjs
@@ -35,6 +35,11 @@ class RenderMarkdown extends Operation {
name: "Enable syntax highlighting",
type: "boolean",
value: true
+ },
+ {
+ name: "Open links in new tab.",
+ type: "boolean",
+ value: false
}
];
}
@@ -45,7 +50,7 @@ class RenderMarkdown extends Operation {
* @returns {html}
*/
run(input, args) {
- const [convertLinks, enableHighlighting] = args,
+ const [convertLinks, enableHighlighting, openLinksBlank] = args,
md = new MarkdownIt({
linkify: convertLinks,
html: false, // Explicitly disable HTML rendering
@@ -58,12 +63,38 @@ class RenderMarkdown extends Operation {
return "";
}
- }),
- rendered = md.render(input);
-
+ });
+ if (openLinksBlank) {
+ this.makeLinksOpenInNewTab(md);
+ }
+ const rendered = md.render(input);
return `
${rendered}
`;
}
+ /**
+ * Adds target="_blank" to links.
+ * @param {MarkdownIt} md
+ */
+ makeLinksOpenInNewTab(md) {
+ // Adapted from: https://github.com/markdown-it/markdown-it/blob/master/docs/architecture.md#renderer
+ // Remember old renderer, if overridden, or proxy to default renderer
+ const defaultRender = md.renderer.rules.link_open || function(tokens, idx, options, env, self) {
+ return self.renderToken(tokens, idx, options);
+ };
+
+ // eslint-disable-next-line camelcase
+ md.renderer.rules.link_open = function (tokens, idx, options, env, self) {
+ const token = tokens[idx];
+ if (token.attrIndex("target") >= 0) {
+ // Target attribute already set, do not replace.
+ return;
+ }
+ token.attrPush(["target", "_blank"]); // add new attribute
+
+ // pass token to default renderer.
+ return defaultRender(tokens, idx, options, env, self);
+ };
+ }
}
export default RenderMarkdown;
diff --git a/tests/operations/index.mjs b/tests/operations/index.mjs
index 420d0129..f40d840e 100644
--- a/tests/operations/index.mjs
+++ b/tests/operations/index.mjs
@@ -145,6 +145,7 @@ import "./tests/RAKE.mjs";
import "./tests/Regex.mjs";
import "./tests/Register.mjs";
import "./tests/RegularExpression.mjs";
+import "./tests/RenderMarkdown.mjs";
import "./tests/RisonEncodeDecode.mjs";
import "./tests/Rotate.mjs";
import "./tests/RSA.mjs";
diff --git a/tests/operations/tests/RenderMarkdown.mjs b/tests/operations/tests/RenderMarkdown.mjs
new file mode 100644
index 00000000..47781e5e
--- /dev/null
+++ b/tests/operations/tests/RenderMarkdown.mjs
@@ -0,0 +1,110 @@
+/**
+ * RenderMarkdown tests.
+ *
+ * @copyright Crown Copyright 2026
+ * @license Apache-2.0
+ */
+
+import TestRegister from "../../lib/TestRegister.mjs";
+
+TestRegister.addTests([
+ {
+ name: "Render Markdown: Nothing",
+ input: "",
+ expectedOutput: '',
+ recipeConfig: [
+ {
+ "op": "Render Markdown",
+ "args": []
+ }
+ ]
+ },
+ {
+ name: "Render Markdown: Basic Text",
+ input: "Hello World!",
+ expectedOutput: '',
+ recipeConfig: [
+ {
+ "op": "Render Markdown",
+ "args": []
+ }
+ ]
+ },
+ {
+ name: "Render Markdown: Simple Markdown",
+ input: "# Hello World!",
+ expectedOutput: 'Hello World!
\n',
+ recipeConfig: [
+ {
+ "op": "Render Markdown",
+ "args": []
+ }
+ ]
+ },
+ {
+ name: "Render Markdown: URL (not expanded)",
+ input: "https://gchq.github.io/CyberChef/",
+ expectedOutput: 'https://gchq.github.io/CyberChef/
\n
',
+ recipeConfig: [
+ {
+ "op": "Render Markdown",
+ "args": [false, false, false]
+ }
+ ]
+ },
+ {
+ name: "Render Markdown: URL (expanded)",
+ input: "https://gchq.github.io/CyberChef/",
+ expectedOutput: '',
+ recipeConfig: [
+ {
+ "op": "Render Markdown",
+ "args": [true, false, false]
+ }
+ ]
+ },
+ {
+ name: "Render Markdown: Link (not expanded)",
+ input: "[CyberChef](https://gchq.github.io/CyberChef/)",
+ expectedOutput: '',
+ recipeConfig: [
+ {
+ "op": "Render Markdown",
+ "args": [false, false, false]
+ }
+ ]
+ },
+ {
+ name: "Render Markdown: Link (expanded)",
+ input: "[CyberChef](https://gchq.github.io/CyberChef/)",
+ expectedOutput: '',
+ recipeConfig: [
+ {
+ "op": "Render Markdown",
+ "args": [true, false, false]
+ }
+ ]
+ },
+ {
+ name: "Render Markdown: Link (open in new window)",
+ input: "[CyberChef](https://gchq.github.io/CyberChef/)",
+ expectedOutput: '',
+ recipeConfig: [
+ {
+ "op": "Render Markdown",
+ "args": [true, false, true]
+ }
+ ]
+ },
+ {
+ name: "Render Markdown: URL (open in new window)",
+ input: "https://gchq.github.io/CyberChef/",
+ expectedOutput: '',
+ recipeConfig: [
+ {
+ "op": "Render Markdown",
+ "args": [true, false, true]
+ }
+ ]
+ },
+]);