diff --git a/app/payment-links/start/start.njk b/app/payment-links/start/start.njk index 7805a3c9c..1a8fbc637 100644 --- a/app/payment-links/start/start.njk +++ b/app/payment-links/start/start.njk @@ -15,7 +15,7 @@

{{ product.name }}

{% if product.description %} -

{{ product.description | striptags(true) | escape | nl2br }}

+
{{ product.description | striptags(true) | formatMarkdown | safe }}
{% endif %} {{ govukButton({ diff --git a/app/utils/format-markdown.js b/app/utils/format-markdown.js new file mode 100644 index 000000000..f72f08d5b --- /dev/null +++ b/app/utils/format-markdown.js @@ -0,0 +1,68 @@ +/** + * Credit: https://github.com/ministryofjustice/opg-performance-data/blob/63fac0c26a701f37cfa2d4d8229cf82937ad5047/markdown-it-gds.js#L4 + */ + +'use strict' + +const markdownIt = require('markdown-it') + +const linkExternalStack = [] + +/** + * Rules that can be enabled/disabled for markdown-it can be found here: + * https://github.com/markdown-it/markdown-it/blob/0fe7ccb4b7f30236fb05f623be6924961d296d3d/lib/parser_block.mjs + * https://github.com/markdown-it/markdown-it/blob/HEAD/lib/parser_inline.mjs + */ +const md = markdownIt({ + html: false, + breaks: true +}) + .disable(['heading', 'lheading', 'image', 'table', 'code', 'fence', 'blockquote', 'hr', 'html_block', 'reference', 'emphasis', 'backticks', 'strikethrough', 'html_inline', 'autolink', 'entity']) + +function defaultRender (tokens, idx, options, env, self) { + return self.renderToken(tokens, idx, options); +} + +md.renderer.rules.paragraph_open = function (tokens, idx, options, env, self) { + tokens[idx].attrPush(["class", "govuk-body"]); + return defaultRender(tokens, idx, options, env, self); +} + +md.renderer.rules.link_open = function (tokens, idx, options, env, self) { + let externalLink = false; + tokens[idx].attrPush(["class", "govuk-link"]); + if (tokens[idx].attrGet("href").indexOf("http") === 0) { + externalLink = true; + tokens[idx].attrPush(["target", "_blank"]); + tokens[idx].attrPush(["rel", "noreferrer noopener"]); + + linkExternalStack.push(externalLink); + } + return defaultRender(tokens, idx, options, env, self); +} + +md.renderer.rules.link_close = function (tokens, idx, options, env, self) { + const externalLink = linkExternalStack.pop(); + if (externalLink) { + return `(opens in new tab)`; + } + return defaultRender(tokens, idx, options, env, self); +}; + +md.renderer.rules.bullet_list_open = function (tokens, idx, options, env, self) { + tokens[idx].attrPush(["class", "govuk-list govuk-list--bullet"]); + return defaultRender(tokens, idx, options, env, self); +} + +md.renderer.rules.ordered_list_open = function (tokens, idx, options, env, self) { + tokens[idx].attrPush(["class", "govuk-list govuk-list--number"]); + return defaultRender(tokens, idx, options, env, self); +} + +function formatMarkdown (rawText) { + return md.render(rawText) +} + +module.exports = { + formatMarkdown +} \ No newline at end of file diff --git a/config/server.js b/config/server.js index 27032fcf3..7b42a1444 100644 --- a/config/server.js +++ b/config/server.js @@ -28,6 +28,7 @@ const loggingMiddleware = require('../app/middleware/logging-middleware') const { requestContextMiddleware } = require('../app/clients/base/request-context') const Sentry = require('../app/utils/sentry.js').initialiseSentry() const replaceParamsInPath = require('../app/utils/replace-params-in-path') +const { formatMarkdown } = require('../app/utils/format-markdown') // Global constants const JAVASCRIPT_PATH = staticify.getVersionedPath('/js/application.min.js') @@ -106,6 +107,9 @@ function initialiseTemplateEngine (app) { // if it's not production we want to re-evaluate the assets on each file change nunjucksEnvironment.addGlobal('css_path', staticify.getVersionedPath('/stylesheets/application.min.css')) nunjucksEnvironment.addGlobal('js_path', NODE_ENV === 'production' ? JAVASCRIPT_PATH : staticify.getVersionedPath('/js/application.js')) + + // Load custom Nunjucks filters + nunjucksEnvironment.addFilter('formatMarkdown', formatMarkdown) } function initialisePublic (app) {