diff --git a/plugins/talk-plugin-rich-text/package.json b/plugins/talk-plugin-rich-text/package.json index 7ef08ec80..e933626f6 100644 --- a/plugins/talk-plugin-rich-text/package.json +++ b/plugins/talk-plugin-rich-text/package.json @@ -8,6 +8,7 @@ "license": "Apache-2.0", "dependencies": { "dompurify": "^1.0.3", - "jsdom": "^11.6.2" + "jsdom": "^11.6.2", + "linkifyjs": "^2.1.5" } } diff --git a/plugins/talk-plugin-rich-text/server/config.js b/plugins/talk-plugin-rich-text/server/config.js index eec559ce0..bb28dca32 100644 --- a/plugins/talk-plugin-rich-text/server/config.js +++ b/plugins/talk-plugin-rich-text/server/config.js @@ -1,4 +1,16 @@ const config = { + // Highlight Links + highlightLinks: true, + + // Linkify Settings + linkify: { + className: 'talk-plugin-rich-text-link', + tagName: 'a', + target: { + url: '_blank', + }, + }, + // Super strict rules to make sure users only submit the tags they are allowed dompurify: { ALLOWED_TAGS: ['b', 'i', 'blockquote'] }, diff --git a/plugins/talk-plugin-rich-text/server/hooks.js b/plugins/talk-plugin-rich-text/server/hooks.js index cd4beac99..0dfcd054c 100644 --- a/plugins/talk-plugin-rich-text/server/hooks.js +++ b/plugins/talk-plugin-rich-text/server/hooks.js @@ -1,31 +1,40 @@ const { merge, get } = require('lodash'); const DOMPurify = require('./DOMPurify'); +const linkify = require('linkifyjs/html'); +const config = require('./config'); + +const inputCleanup = ({ htmlBody }) => { + // htmlBody needs to be present in the request + if (!htmlBody) { + throw new Error('htmlBody not present in the request'); + } + + // Let's sanitize the body + let cleanInput = DOMPurify.sanitize(htmlBody); + + // Highlighting links + if (config.highlightLinks) { + cleanInput = linkify(cleanInput, config.linkify); + } + + return cleanInput; +}; module.exports = { RootMutation: { createComment: { async pre(_, { input }, _context, _info) { - // Let's sanitize the body - const dirtyInput = input.htmlBody; - - const cleanInput = DOMPurify.sanitize(dirtyInput); - // Adding the clean body to the comment.metadata field input.metadata = merge(get(input, 'metadata'), { - htmlBody: cleanInput, + htmlBody: inputCleanup(input), }); }, }, editComment: { async pre(_, { edit }, _context, _info) { - // Let's sanitize the body - const dirtyInput = edit.htmlBody; - - const cleanInput = DOMPurify.sanitize(dirtyInput); - - // Adding the clean body to the comment.metadata field + // Adding the clean body to the coment.metadata field edit.metadata = merge(get(edit, 'metadata'), { - htmlBody: cleanInput, + htmlBody: inputCleanup(edit), }); }, }, diff --git a/yarn.lock b/yarn.lock index d24c5fa58..78b96c795 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4997,6 +4997,10 @@ joi@^6.10.1: moment "2.x.x" topo "1.x.x" +jquery@>=1.9.0: + version "3.3.1" + resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.3.1.tgz#958ce29e81c9790f31be7792df5d4d95fc57fbca" + js-base64@^2.1.9: version "2.3.2" resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.3.2.tgz#a79a923666372b580f8e27f51845c6f7e8fbfbaf" @@ -5366,6 +5370,14 @@ linkify-it@^2.0.3: dependencies: uc.micro "^1.0.1" +linkifyjs@^2.1.5: + version "2.1.5" + resolved "https://registry.yarnpkg.com/linkifyjs/-/linkifyjs-2.1.5.tgz#effc9f01e4aeafbbdbef21a45feab38b9516f93e" + optionalDependencies: + jquery ">=1.9.0" + react ">=0.14.0" + react-dom ">=0.14.0" + load-json-file@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-1.1.0.tgz#956905708d58b4bab4c2261b04f59f31c99374c0" @@ -7808,6 +7820,15 @@ react-apollo@^1.4.12: object-assign "^4.0.1" prop-types "^15.5.8" +react-dom@>=0.14.0: + version "16.2.0" + resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.2.0.tgz#69003178601c0ca19b709b33a83369fe6124c044" + dependencies: + fbjs "^0.8.16" + loose-envify "^1.1.0" + object-assign "^4.1.1" + prop-types "^15.6.0" + react-dom@^15.3.1, react-dom@^15.4.2: version "15.6.2" resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-15.6.2.tgz#41cfadf693b757faf2708443a1d1fd5a02bef730" @@ -7937,6 +7958,15 @@ react-virtualized@9.13.0: loose-envify "^1.3.0" prop-types "^15.5.4" +react@>=0.14.0: + version "16.2.0" + resolved "https://registry.yarnpkg.com/react/-/react-16.2.0.tgz#a31bd2dab89bff65d42134fa187f24d054c273ba" + dependencies: + fbjs "^0.8.16" + loose-envify "^1.1.0" + object-assign "^4.1.1" + prop-types "^15.6.0" + react@^15.3.1, react@^15.4.2: version "15.6.2" resolved "https://registry.yarnpkg.com/react/-/react-15.6.2.tgz#dba0434ab439cfe82f108f0f511663908179aa72"