s to
.
+ return str.replace(/
/g, '
').replace(/<\/div>/g, '');
+}
diff --git a/plugins/talk-plugin-rich-text-pell/index.js b/plugins/talk-plugin-rich-text-pell/index.js
new file mode 100644
index 000000000..f053ebf79
--- /dev/null
+++ b/plugins/talk-plugin-rich-text-pell/index.js
@@ -0,0 +1 @@
+module.exports = {};
diff --git a/plugins/talk-plugin-rich-text-pell/package.json b/plugins/talk-plugin-rich-text-pell/package.json
new file mode 100644
index 000000000..6b35d5ea9
--- /dev/null
+++ b/plugins/talk-plugin-rich-text-pell/package.json
@@ -0,0 +1,12 @@
+{
+ "name": "@coralproject/talk-plugin-rich-text-pell",
+ "pluginName": "talk-plugin-rich-text-pell",
+ "version": "0.0.1",
+ "description": "Pell's Rich Text Editor for Talk",
+ "main": "index.js",
+ "author": "The Coral Project Team ",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "pell": "^0.7.0"
+ }
+}
diff --git a/plugins/talk-plugin-rich-text/README.md b/plugins/talk-plugin-rich-text/README.md
new file mode 100644
index 000000000..ce85a23b0
--- /dev/null
+++ b/plugins/talk-plugin-rich-text/README.md
@@ -0,0 +1,24 @@
+# Talk Plugin Rich Text
+Enables secure rich text support server-side.
+
+
+## Installation
+Add `talk-plugin-rich-text` to the `plugins.json` in your Talk installation. Remember to add this in the `server` property since this plugin only covers the server side. To add frontend support consider using `talk-plugin-rich-text-pell`.
+
+## How does this work?
+This plugin uses the `comment.metadata` field to store the `richTextBody`. By adding `richTextBody` to the schema we can later on resolve it as part of the comment. The original `comment.body` is never touched. Using the `metadata` field allow us to build plugins that are not invasive to the core and also test the capabilities of our plugin framework. We encourage you to see the files and check how easy is to build plugins! If you have any feedback, please let us know.
+
+## Configuration
+There is a `config.js` in the root folder. This file contains the recommended settings.
+
+### `highlightLinks`
+A `boolean` to highlight links. Set it to `false` to turn it off.
+
+### `linkify`
+Settings for highlighting links. These will only apply if `higlightLinks` is set to `true`.
+
+### `dompurify`
+Rules to sanitize html input. We use [DOMPurify] (https://github.com/cure53/DOMPurify) to prevent web attacks and XSS. Here is the complete list of [settings] (https://github.com/cure53/DOMPurify)
+
+## `jsdom`
+In order to run html in the server we need [jsdom](https://github.com/jsdom/jsdom). Usually you wouldn’t need to modify this settings.
diff --git a/plugins/talk-plugin-rich-text/index.js b/plugins/talk-plugin-rich-text/index.js
new file mode 100644
index 000000000..a318f8a11
--- /dev/null
+++ b/plugins/talk-plugin-rich-text/index.js
@@ -0,0 +1,13 @@
+const { readFileSync } = require('fs');
+const path = require('path');
+const hooks = require('./server/hooks');
+const resolvers = require('./server/resolvers');
+
+module.exports = {
+ typeDefs: readFileSync(
+ path.join(__dirname, 'server/typeDefs.graphql'),
+ 'utf8'
+ ),
+ hooks,
+ resolvers,
+};
diff --git a/plugins/talk-plugin-rich-text/package.json b/plugins/talk-plugin-rich-text/package.json
new file mode 100644
index 000000000..e933626f6
--- /dev/null
+++ b/plugins/talk-plugin-rich-text/package.json
@@ -0,0 +1,14 @@
+{
+ "name": "@coralproject/talk-plugin-rich-text",
+ "pluginName": "talk-plugin-rich-text",
+ "version": "0.0.1",
+ "description": "Rich Text Editor for Talk",
+ "main": "index.js",
+ "author": "The Coral Project Team ",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "dompurify": "^1.0.3",
+ "jsdom": "^11.6.2",
+ "linkifyjs": "^2.1.5"
+ }
+}
diff --git a/plugins/talk-plugin-rich-text/server/DOMPurify.js b/plugins/talk-plugin-rich-text/server/DOMPurify.js
new file mode 100644
index 000000000..faf45875e
--- /dev/null
+++ b/plugins/talk-plugin-rich-text/server/DOMPurify.js
@@ -0,0 +1,12 @@
+const createDOMPurify = require('dompurify');
+const { JSDOM } = require('jsdom');
+const config = require('./config');
+
+// Initializing JSDOM and DOMPurify
+const window = new JSDOM('', config.jsdom).window;
+const DOMPurify = createDOMPurify(window);
+
+// Setting our secure config
+DOMPurify.setConfig(config.dompurify);
+
+module.exports = DOMPurify;
diff --git a/plugins/talk-plugin-rich-text/server/config.js b/plugins/talk-plugin-rich-text/server/config.js
new file mode 100644
index 000000000..8d0fc87d3
--- /dev/null
+++ b/plugins/talk-plugin-rich-text/server/config.js
@@ -0,0 +1,27 @@
+const config = {
+ // Highlight Links
+ highlightLinks: true,
+
+ // Linkify Settings
+ linkify: {
+ className: 'talk-plugin-rich-text-link',
+ tagName: 'a',
+ target: {
+ url: '_blank',
+ },
+ },
+
+ // TODO: move to admin eventually
+ // Super strict rules to make sure users only submit the tags they are allowed
+ dompurify: { ALLOWED_TAGS: ['b', 'i', 'blockquote', 'br'] },
+
+ // Secure config for jsdom even when DOMPurify creates a document without a browsing context
+ jsdom: {
+ features: {
+ FetchExternalResources: false, // disables resource loading over HTTP / filesystem
+ ProcessExternalResources: false, // do not execute JS within script blocks
+ },
+ },
+};
+
+module.exports = config;
diff --git a/plugins/talk-plugin-rich-text/server/hooks.js b/plugins/talk-plugin-rich-text/server/hooks.js
new file mode 100644
index 000000000..2fb9e9a6e
--- /dev/null
+++ b/plugins/talk-plugin-rich-text/server/hooks.js
@@ -0,0 +1,37 @@
+const { merge, get } = require('lodash');
+const DOMPurify = require('./DOMPurify');
+const linkify = require('linkifyjs/html');
+const config = require('./config');
+
+const inputCleanup = ({ richTextBody }) => {
+ // Let's sanitize the body
+ let cleanInput = DOMPurify.sanitize(richTextBody);
+
+ // Highlighting links
+ if (config.highlightLinks) {
+ cleanInput = linkify(cleanInput, config.linkify);
+ }
+
+ return cleanInput;
+};
+
+module.exports = {
+ RootMutation: {
+ createComment: {
+ async pre(_, { input }) {
+ // Adding the clean body to the comment.metadata field
+ input.metadata = merge(get(input, 'metadata', {}), {
+ richTextBody: inputCleanup(input),
+ });
+ },
+ },
+ editComment: {
+ async pre(_, { edit }) {
+ // Adding the clean body to the comment.metadata field
+ edit.metadata = merge(get(edit, 'metadata', {}), {
+ richTextBody: inputCleanup(edit),
+ });
+ },
+ },
+ },
+};
diff --git a/plugins/talk-plugin-rich-text/server/resolvers.js b/plugins/talk-plugin-rich-text/server/resolvers.js
new file mode 100644
index 000000000..899ece49a
--- /dev/null
+++ b/plugins/talk-plugin-rich-text/server/resolvers.js
@@ -0,0 +1,8 @@
+const { get } = require('lodash');
+
+module.exports = {
+ Comment: {
+ // Get the richTextBody, or send null.
+ richTextBody: comment => get(comment, 'metadata.richTextBody', null),
+ },
+};
diff --git a/plugins/talk-plugin-rich-text/server/typeDefs.graphql b/plugins/talk-plugin-rich-text/server/typeDefs.graphql
new file mode 100644
index 000000000..afcb016ca
--- /dev/null
+++ b/plugins/talk-plugin-rich-text/server/typeDefs.graphql
@@ -0,0 +1,11 @@
+input CreateCommentInput {
+ richTextBody: String!
+}
+
+input EditCommentInput {
+ richTextBody: String!
+}
+
+type Comment {
+ richTextBody: String
+}
\ No newline at end of file
diff --git a/plugins/talk-plugin-slack-notifications/.eslintrc.json b/plugins/talk-plugin-slack-notifications/.eslintrc.json
deleted file mode 100644
index 78f7c2397..000000000
--- a/plugins/talk-plugin-slack-notifications/.eslintrc.json
+++ /dev/null
@@ -1,3 +0,0 @@
-{
- "extends": "@coralproject/eslint-config-talk"
-}
diff --git a/plugins/talk-plugin-slack-notifications/server/hooks.js b/plugins/talk-plugin-slack-notifications/server/hooks.js
index 4133f795d..eac0c1be4 100644
--- a/plugins/talk-plugin-slack-notifications/server/hooks.js
+++ b/plugins/talk-plugin-slack-notifications/server/hooks.js
@@ -10,7 +10,7 @@ if (process.env.NODE_ENV === 'test') {
module.exports = {
RootMutation: {
createComment: {
- async post(root, args, context, info, result) {
+ async post(_, _, context, info, result) {
debug(`Posting notification to Slack webhook: ${SLACK_WEBHOOK_URL}`);
const { comment: { body: text, created_at: createdAt } } = result;
const username = context.user.username;
diff --git a/plugins/talk-plugin-sort-most-liked/.eslintrc.json b/plugins/talk-plugin-sort-most-liked/.eslintrc.json
deleted file mode 100644
index 78f7c2397..000000000
--- a/plugins/talk-plugin-sort-most-liked/.eslintrc.json
+++ /dev/null
@@ -1,3 +0,0 @@
-{
- "extends": "@coralproject/eslint-config-talk"
-}
diff --git a/plugins/talk-plugin-sort-most-loved/.eslintrc.json b/plugins/talk-plugin-sort-most-loved/.eslintrc.json
deleted file mode 100644
index 78f7c2397..000000000
--- a/plugins/talk-plugin-sort-most-loved/.eslintrc.json
+++ /dev/null
@@ -1,3 +0,0 @@
-{
- "extends": "@coralproject/eslint-config-talk"
-}
diff --git a/plugins/talk-plugin-sort-most-replied/.eslintrc.json b/plugins/talk-plugin-sort-most-replied/.eslintrc.json
deleted file mode 100644
index 78f7c2397..000000000
--- a/plugins/talk-plugin-sort-most-replied/.eslintrc.json
+++ /dev/null
@@ -1,3 +0,0 @@
-{
- "extends": "@coralproject/eslint-config-talk"
-}
diff --git a/plugins/talk-plugin-sort-most-respected/.eslintrc.json b/plugins/talk-plugin-sort-most-respected/.eslintrc.json
deleted file mode 100644
index 78f7c2397..000000000
--- a/plugins/talk-plugin-sort-most-respected/.eslintrc.json
+++ /dev/null
@@ -1,3 +0,0 @@
-{
- "extends": "@coralproject/eslint-config-talk"
-}
diff --git a/plugins/talk-plugin-sort-newest/.eslintrc.json b/plugins/talk-plugin-sort-newest/.eslintrc.json
deleted file mode 100644
index 78f7c2397..000000000
--- a/plugins/talk-plugin-sort-newest/.eslintrc.json
+++ /dev/null
@@ -1,3 +0,0 @@
-{
- "extends": "@coralproject/eslint-config-talk"
-}
diff --git a/plugins/talk-plugin-sort-oldest/.eslintrc.json b/plugins/talk-plugin-sort-oldest/.eslintrc.json
deleted file mode 100644
index 78f7c2397..000000000
--- a/plugins/talk-plugin-sort-oldest/.eslintrc.json
+++ /dev/null
@@ -1,3 +0,0 @@
-{
- "extends": "@coralproject/eslint-config-talk"
-}
diff --git a/plugins/talk-plugin-subscriber/.eslintrc.json b/plugins/talk-plugin-subscriber/.eslintrc.json
deleted file mode 100644
index d7d291614..000000000
--- a/plugins/talk-plugin-subscriber/.eslintrc.json
+++ /dev/null
@@ -1,4 +0,0 @@
-{
- "extends": "@coralproject/eslint-config-talk"
- }
-
\ No newline at end of file
diff --git a/plugins/talk-plugin-toxic-comments/.eslintrc.json b/plugins/talk-plugin-toxic-comments/.eslintrc.json
deleted file mode 100644
index 78f7c2397..000000000
--- a/plugins/talk-plugin-toxic-comments/.eslintrc.json
+++ /dev/null
@@ -1,3 +0,0 @@
-{
- "extends": "@coralproject/eslint-config-talk"
-}
diff --git a/plugins/talk-plugin-toxic-comments/client/components/CheckToxicityHook.js b/plugins/talk-plugin-toxic-comments/client/components/CheckToxicityHook.js
index 528960310..4331e006b 100644
--- a/plugins/talk-plugin-toxic-comments/client/components/CheckToxicityHook.js
+++ b/plugins/talk-plugin-toxic-comments/client/components/CheckToxicityHook.js
@@ -49,6 +49,6 @@ export default class CheckToxicityHook extends React.Component {
CheckToxicityHook.propTypes = {
notify: PropTypes.func.isRequired,
- registerHook: PropTypes.func.isRequired,
- unregisterHook: PropTypes.func.isRequired,
+ registerHook: PropTypes.func,
+ unregisterHook: PropTypes.func,
};
diff --git a/plugins/talk-plugin-viewing-options/.eslintrc.json b/plugins/talk-plugin-viewing-options/.eslintrc.json
deleted file mode 100644
index 78f7c2397..000000000
--- a/plugins/talk-plugin-viewing-options/.eslintrc.json
+++ /dev/null
@@ -1,3 +0,0 @@
-{
- "extends": "@coralproject/eslint-config-talk"
-}
diff --git a/routes/api/v1/index.js b/routes/api/v1/index.js
index e331d09d8..46d5c0278 100644
--- a/routes/api/v1/index.js
+++ b/routes/api/v1/index.js
@@ -1,10 +1,11 @@
const express = require('express');
-const pkg = require('../../../package.json');
+const { version } = require('../../../package.json');
+const { REVISION_HASH } = require('../../../config');
const router = express.Router();
// Return the current version.
router.get('/', (req, res) => {
- res.json({ version: pkg.version });
+ res.json({ version, revision: REVISION_HASH });
});
router.use('/account', require('./account'));
diff --git a/scripts/docker.sh b/scripts/docker.sh
index f11aeb208..dc5759e5e 100755
--- a/scripts/docker.sh
+++ b/scripts/docker.sh
@@ -54,9 +54,16 @@ deploy_branch() {
docker push coralproject/talk:$CIRCLE_BRANCH-onbuild
}
+ARGS=""
+
+if [[ -n "$CIRCLE_SHA1" ]]
+then
+ ARGS="--build-arg REVISION_HASH=${CIRCLE_SHA1}"
+fi
+
# build the repo, including the onbuild tagged versions.
-docker build -t coralproject/talk:latest -f Dockerfile .
-docker build -t coralproject/talk:latest-onbuild -f Dockerfile.onbuild .
+docker build -t coralproject/talk:latest ${ARGS} -f Dockerfile .
+docker build -t coralproject/talk:latest-onbuild ${ARGS} -f Dockerfile.onbuild .
if [ "$1" = "deploy" ]
then
@@ -80,4 +87,4 @@ then
deploy_branch
fi
fi
-fi
\ No newline at end of file
+fi
diff --git a/services/comments.js b/services/comments.js
index fd6f2282d..a1f59c1e1 100644
--- a/services/comments.js
+++ b/services/comments.js
@@ -1,5 +1,5 @@
const CommentModel = require('../models/comment');
-
+const { dotize } = require('./utils');
const debug = require('debug')('talk:services:comments');
const SettingsService = require('./settings');
@@ -81,7 +81,7 @@ module.exports = {
* @param {String} body the new Comment body
* @param {String} status the new Comment status
*/
- edit: async ({ id, author_id, body, status }) => {
+ edit: async ({ id, author_id, body, status, metadata = {} }) => {
const EDITABLE_STATUSES = ['NONE', 'PREMOD', 'ACCEPTED'];
const created_at = new Date();
@@ -104,10 +104,11 @@ module.exports = {
};
const originalComment = await CommentModel.findOneAndUpdate(query, {
- $set: {
+ $set: dotize({
body,
status,
- },
+ metadata,
+ }),
$push: {
body_history: {
body,
@@ -161,11 +162,14 @@ module.exports = {
body,
created_at,
});
+
editedComment.status_history.push({
type: status,
created_at,
});
+ editedComment.metadata = merge(editedComment.metadata, metadata);
+
return editedComment;
},
diff --git a/yarn.lock b/yarn.lock
index 33bf7e56c..251ec050d 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2800,6 +2800,10 @@ domhandler@^2.3.0:
dependencies:
domelementtype "1"
+dompurify@^1.0.3:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-1.0.3.tgz#3f2f6ecb6ecd27599a506b410ff47d6eb90fd05d"
+
domutils@1.5, domutils@1.5.1:
version "1.5.1"
resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.5.1.tgz#dcd8488a26f563d61079e48c9f7b7e32373682cf"
@@ -5657,7 +5661,7 @@ jsbn@~0.1.0:
version "0.1.1"
resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513"
-jsdom@^11.5.1:
+jsdom@^11.5.1, jsdom@^11.6.2:
version "11.6.2"
resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-11.6.2.tgz#25d1ef332d48adf77fc5221fe2619967923f16bb"
dependencies:
@@ -7744,6 +7748,10 @@ pbkdf2@^3.0.3:
safe-buffer "^5.0.1"
sha.js "^2.4.8"
+pell@^0.7.0:
+ version "0.7.0"
+ resolved "https://registry.yarnpkg.com/pell/-/pell-0.7.0.tgz#46b3fcdfa8dd7e5999f73c550a337ecc80193dcc"
+
pend@~1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50"