From 8aa8517d1d17f6e1f791643531c85e1bc58d099e Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Mon, 19 Mar 2018 00:29:01 +0100 Subject: [PATCH 01/28] Implement Coral RTE --- .gitignore | 1 + plugins/talk-plugin-rich-text-coral/README.md | 47 ++++++ .../client/.eslintrc.json | 3 + .../client/components/Button.css | 20 +++ .../client/components/Button.js | 29 ++++ .../client/components/CommentContent.js | 23 +++ .../client/components/Editor.css | 12 ++ .../client/components/Editor.js | 137 ++++++++++++++++++ .../client/components/Toolbar.css | 7 + .../client/components/Toolbar.js | 17 +++ .../client/constants.js | 1 + .../client/containers/CommentContent.js | 12 ++ .../client/containers/Editor.js | 12 ++ .../client/index.js | 70 +++++++++ .../client/utils.js | 31 ++++ plugins/talk-plugin-rich-text-coral/index.js | 1 + .../talk-plugin-rich-text-coral/package.json | 12 ++ .../talk-plugin-rich-text/server/config.js | 5 +- yarn.lock | 4 + 19 files changed, 443 insertions(+), 1 deletion(-) create mode 100644 plugins/talk-plugin-rich-text-coral/README.md create mode 100644 plugins/talk-plugin-rich-text-coral/client/.eslintrc.json create mode 100644 plugins/talk-plugin-rich-text-coral/client/components/Button.css create mode 100644 plugins/talk-plugin-rich-text-coral/client/components/Button.js create mode 100644 plugins/talk-plugin-rich-text-coral/client/components/CommentContent.js create mode 100644 plugins/talk-plugin-rich-text-coral/client/components/Editor.css create mode 100644 plugins/talk-plugin-rich-text-coral/client/components/Editor.js create mode 100644 plugins/talk-plugin-rich-text-coral/client/components/Toolbar.css create mode 100644 plugins/talk-plugin-rich-text-coral/client/components/Toolbar.js create mode 100644 plugins/talk-plugin-rich-text-coral/client/constants.js create mode 100644 plugins/talk-plugin-rich-text-coral/client/containers/CommentContent.js create mode 100644 plugins/talk-plugin-rich-text-coral/client/containers/Editor.js create mode 100644 plugins/talk-plugin-rich-text-coral/client/index.js create mode 100644 plugins/talk-plugin-rich-text-coral/client/utils.js create mode 100644 plugins/talk-plugin-rich-text-coral/index.js create mode 100644 plugins/talk-plugin-rich-text-coral/package.json diff --git a/.gitignore b/.gitignore index 7c0007dc9..230acae6e 100644 --- a/.gitignore +++ b/.gitignore @@ -62,6 +62,7 @@ plugins/* !plugins/talk-plugin-toxic-comments !plugins/talk-plugin-viewing-options !plugins/talk-plugin-rich-text +!plugins/talk-plugin-rich-text-coral !plugins/talk-plugin-rich-text-pell **/node_modules/* diff --git a/plugins/talk-plugin-rich-text-coral/README.md b/plugins/talk-plugin-rich-text-coral/README.md new file mode 100644 index 000000000..f953a65cd --- /dev/null +++ b/plugins/talk-plugin-rich-text-coral/README.md @@ -0,0 +1,47 @@ +--- +title: talk-plugin-rich-text-coral +permalink: /plugin/talk-plugin-rich-text-coral/ +layout: plugin +plugin: + name: talk-plugin-rich-text-coral + depends: + - name: talk-plugin-rich-text + provides: + - Client +--- + +Enables rich text support client-side by using a simple RTE. + +## Installation + +Add `"talk-plugin-rich-text-coral"` to the `plugins.json` in your Talk +installation. Remember to add this in the `client` property since this plugin +only covers the client side. To add server support, please use +[talk-plugin-rich-text](/talk/plugin/talk-plugin-rich-text). + +_Note: Ensure that you don't have any other plugins utilizing the +`commentContent` slot, as it would result in duplicate comments._ + +## How does this work? + +This plugin contains 2 important components: + +- The Editor (`./components/Editor.js`) +- The Comment Content Renderer (`./components/CommentContent.js`) + +The editor component uses [contentEditable](https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/Editable_content). + +If you check our `index.js` you will notice that we inject this editor in the +`commentBox` slot. We do this to replace the core comment box with this one. + +Now, in order to render the new styled comments we need a comment renderer. For +this task we will have to replace our core comment renderer by using the +`commentContent` slot. + +If you are not familiar with GraphQL `client/index.js` will look complicated, +but fear not! With those functions we specify what to expect from the server +schema, how to perform optimistic updates and how keep the client store updated +with the latest changes. + +We encourage you to see the files and check how easy is to build plugins! If you +have any feedback, please let us know. diff --git a/plugins/talk-plugin-rich-text-coral/client/.eslintrc.json b/plugins/talk-plugin-rich-text-coral/client/.eslintrc.json new file mode 100644 index 000000000..c8a6db18a --- /dev/null +++ b/plugins/talk-plugin-rich-text-coral/client/.eslintrc.json @@ -0,0 +1,3 @@ +{ + "extends": "@coralproject/eslint-config-talk/client" +} diff --git a/plugins/talk-plugin-rich-text-coral/client/components/Button.css b/plugins/talk-plugin-rich-text-coral/client/components/Button.css new file mode 100644 index 000000000..f9f1ba186 --- /dev/null +++ b/plugins/talk-plugin-rich-text-coral/client/components/Button.css @@ -0,0 +1,20 @@ +.button > i { + vertical-align: middle; +} + +.button { + background-color: transparent; + padding: 3px; + border: none; + color: #4e4e4e; + margin-right: 3px; +} + +.button:hover{ + cursor: pointer; + border-radius: 3px; + background-color: #eae8e8; +} +.icon { + font-size: 20px; +} diff --git a/plugins/talk-plugin-rich-text-coral/client/components/Button.js b/plugins/talk-plugin-rich-text-coral/client/components/Button.js new file mode 100644 index 000000000..330e3d993 --- /dev/null +++ b/plugins/talk-plugin-rich-text-coral/client/components/Button.js @@ -0,0 +1,29 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import styles from './Button.css'; +import { Icon, BareButton } from 'plugin-api/beta/client/components/ui'; +import cn from 'classnames'; + +class Button extends React.Component { + render() { + const { className, icon, title, onClick } = this.props; + return ( + + + + ); + } +} + +Button.propTypes = { + icon: PropTypes.string.isRequired, + className: PropTypes.string, + title: PropTypes.string, + onClick: PropTypes.func, +}; + +export default Button; diff --git a/plugins/talk-plugin-rich-text-coral/client/components/CommentContent.js b/plugins/talk-plugin-rich-text-coral/client/components/CommentContent.js new file mode 100644 index 000000000..a0e045c6f --- /dev/null +++ b/plugins/talk-plugin-rich-text-coral/client/components/CommentContent.js @@ -0,0 +1,23 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { PLUGIN_NAME } from '../constants'; + +class CommentContent extends React.Component { + render() { + const { comment } = this.props; + return comment.richTextBody ? ( +
+ ) : ( +
{comment.body}
+ ); + } +} + +CommentContent.propTypes = { + comment: PropTypes.object.isRequired, +}; + +export default CommentContent; diff --git a/plugins/talk-plugin-rich-text-coral/client/components/Editor.css b/plugins/talk-plugin-rich-text-coral/client/components/Editor.css new file mode 100644 index 000000000..f102067a5 --- /dev/null +++ b/plugins/talk-plugin-rich-text-coral/client/components/Editor.css @@ -0,0 +1,12 @@ +.contentEditable { + background: #fff; + border: solid 1px #bbb; + min-height: 120px; + box-sizing: border-box; + outline: 0; + overflow-y: auto; + width: 100%; + padding: 10px; + font-style: unset; +} + diff --git a/plugins/talk-plugin-rich-text-coral/client/components/Editor.js b/plugins/talk-plugin-rich-text-coral/client/components/Editor.js new file mode 100644 index 000000000..f6491572b --- /dev/null +++ b/plugins/talk-plugin-rich-text-coral/client/components/Editor.js @@ -0,0 +1,137 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import styles from './Editor.css'; +import cn from 'classnames'; +import { PLUGIN_NAME } from '../constants'; +import { htmlNormalizer } from '../utils'; +import ContentEditable from 'react-contenteditable'; +import Toolbar from './Toolbar'; +import Button from './Button'; +import bowser from 'bowser'; + +class Editor extends React.Component { + ref = null; + handleRef = ref => (this.ref = ref); + state = { + html: + !this.props.isReply && this.props.comment + ? this.props.comment.richTextBody || this.props.comment.body || '' + : '', + }; + + handleChange = evt => { + const html = evt.target.value; + this.setState({ html }); + this.props.onChange(this.ref.htmlEl.innerText, { + richTextBody: htmlNormalizer(html), + }); + }; + + componentDidMount() { + if (this.props.registerHook) { + this.clearInputHook = this.props.registerHook( + 'postSubmit', + (res, handleBodyChange) => { + this.setState({ html: '' }); + handleBodyChange('', { richTextBody: '' }); + } + ); + } + } + + shouldComponentUpdate(nextProps) { + if (this.props.value !== nextProps.value) { + return false; + } + return true; + } + + componentWillUnmount() { + this.props.unregisterHook(this.clearInputHook); + } + + getCurrentTagName() { + const sel = window.getSelection(); + const range = sel.getRangeAt(0); + if (range.startContainer.nodeName !== '#text') { + return range.startContainer.nodeName; + } + return range.startContainer.parentNode.tagName; + } + + formatBold = () => { + document.execCommand('bold'); + this.ref.htmlEl.focus(); + }; + + formatItalic = () => { + document.execCommand('italic'); + this.ref.htmlEl.focus(); + }; + + formatBlockquote = () => { + const currentTag = this.getCurrentTagName(); + if (currentTag === 'BLOCKQUOTE') { + document.execCommand('outdent'); + } else { + if (bowser.msie) { + document.execCommand('indent'); + } else { + document.execCommand('formatBlock', false, 'blockquote'); + } + } + this.ref.htmlEl.focus(); + }; + + outdentOnEnter = e => { + if (e.key === 'Enter' && !e.shiftKey) { + setTimeout(() => document.execCommand('outdent')); + } + }; + + render() { + const { id } = this.props; + return ( +
+ +
+ ); + } +} + +Editor.propTypes = { + rows: PropTypes.number, // TODO: should not be passed. + id: PropTypes.string, // TODO: should not be passed. + value: PropTypes.string, + placeholder: PropTypes.string, + onChange: PropTypes.func, + disabled: PropTypes.bool, + comment: PropTypes.object, + classNames: PropTypes.object, + registerHook: PropTypes.func, + unregisterHook: PropTypes.func, + isReply: PropTypes.bool, +}; + +export default Editor; diff --git a/plugins/talk-plugin-rich-text-coral/client/components/Toolbar.css b/plugins/talk-plugin-rich-text-coral/client/components/Toolbar.css new file mode 100644 index 000000000..f4880f432 --- /dev/null +++ b/plugins/talk-plugin-rich-text-coral/client/components/Toolbar.css @@ -0,0 +1,7 @@ +.toolbar { + user-select: none; + padding: 5px 10px; + border-top: 1px solid #bbb; + border-left: 1px solid #bbb; + border-right: 1px solid #bbb; +} diff --git a/plugins/talk-plugin-rich-text-coral/client/components/Toolbar.js b/plugins/talk-plugin-rich-text-coral/client/components/Toolbar.js new file mode 100644 index 000000000..f3112e813 --- /dev/null +++ b/plugins/talk-plugin-rich-text-coral/client/components/Toolbar.js @@ -0,0 +1,17 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import styles from './Toolbar.css'; +import cn from 'classnames'; + +class Toolbar extends React.Component { + render() { + const { className, ...rest } = this.props; + return
; + } +} + +Toolbar.propTypes = { + className: PropTypes.string, +}; + +export default Toolbar; diff --git a/plugins/talk-plugin-rich-text-coral/client/constants.js b/plugins/talk-plugin-rich-text-coral/client/constants.js new file mode 100644 index 000000000..40ea81053 --- /dev/null +++ b/plugins/talk-plugin-rich-text-coral/client/constants.js @@ -0,0 +1 @@ +export const PLUGIN_NAME = 'talk-plugin-rich-text-coral'; diff --git a/plugins/talk-plugin-rich-text-coral/client/containers/CommentContent.js b/plugins/talk-plugin-rich-text-coral/client/containers/CommentContent.js new file mode 100644 index 000000000..99b65c22c --- /dev/null +++ b/plugins/talk-plugin-rich-text-coral/client/containers/CommentContent.js @@ -0,0 +1,12 @@ +import { gql } from 'react-apollo'; +import { withFragments } from 'plugin-api/beta/client/hocs'; +import CommentContent from '../components/CommentContent'; + +export default withFragments({ + comment: gql` + fragment TalkPluginRichTextCoral_CommentContent_comment on Comment { + body + richTextBody + } + `, +})(CommentContent); diff --git a/plugins/talk-plugin-rich-text-coral/client/containers/Editor.js b/plugins/talk-plugin-rich-text-coral/client/containers/Editor.js new file mode 100644 index 000000000..f748e7344 --- /dev/null +++ b/plugins/talk-plugin-rich-text-coral/client/containers/Editor.js @@ -0,0 +1,12 @@ +import { gql } from 'react-apollo'; +import { withFragments } from 'plugin-api/beta/client/hocs'; +import Editor from '../components/Editor'; + +export default withFragments({ + comment: gql` + fragment TalkPluginRichTextCoral_Editor_comment on Comment { + body + richTextBody + } + `, +})(Editor); diff --git a/plugins/talk-plugin-rich-text-coral/client/index.js b/plugins/talk-plugin-rich-text-coral/client/index.js new file mode 100644 index 000000000..8cab68f8d --- /dev/null +++ b/plugins/talk-plugin-rich-text-coral/client/index.js @@ -0,0 +1,70 @@ +import Editor from './containers/Editor'; +import CommentContent from './containers/CommentContent'; +import { gql } from 'react-apollo'; + +export default { + slots: { + draftArea: [Editor], + commentContent: [CommentContent], + adminCommentContent: [CommentContent], + userDetailCommentContent: [CommentContent], + }, + fragments: { + CreateCommentResponse: gql` + fragment TalkRichTextCoral_CreateCommentResponse on CreateCommentResponse { + comment { + richTextBody + } + } + `, + EditCommentResponse: gql` + fragment TalkRichTextCoral_EditCommentResponse on EditCommentResponse { + comment { + richTextBody + } + } + `, + }, + mutations: { + PostComment: ({ variables: { input } }) => { + return { + optimisticResponse: { + createComment: { + comment: { + richTextBody: input.richTextBody, + }, + }, + }, + }; + }, + EditComment: ({ variables: { id, edit } }) => { + return { + optimisticResponse: { + editComment: { + comment: { + richTextBody: edit.richTextBody, + }, + }, + }, + update: proxy => { + const editCommentFragment = gql` + fragment TalkRichTextCoral_EditComment on Comment { + richTextBody + } + `; + + const fragmentId = `Comment_${id}`; + + proxy.writeFragment({ + fragment: editCommentFragment, + id: fragmentId, + data: { + __typename: 'Comment', + richTextBody: edit.richTextBody, + }, + }); + }, + }; + }, + }, +}; diff --git a/plugins/talk-plugin-rich-text-coral/client/utils.js b/plugins/talk-plugin-rich-text-coral/client/utils.js new file mode 100644 index 000000000..a617d618e --- /dev/null +++ b/plugins/talk-plugin-rich-text-coral/client/utils.js @@ -0,0 +1,31 @@ +export function htmlNormalizer(htmlInput) { + let str = htmlInput; + // We are normalizing the input from contenteditable of each browser, also removing unnecesary html tags + // https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/Editable_content#Differences_in_markup_generation + + // Old browsers uses `p` normalize to `div` instead. + str = str + .replace(/

/g, '

') // IE and old browsers outputs

instead of

s + .replace(/<\/p>/g, '
'); // IE and old browsers outputs

instead of

s + + // Harmonize all to tag. + str = str + .replace(//g, '') // IE + .replace(/<\/strong>/g, ''); // IE + + // Harmonize all to tag. + str = str + .replace(//g, '') // IE + .replace(/<\/em>/g, ''); // IE + + // Remove first opening tag, otherwise + // with the following transformation below + // we might add an unintended first empty line. + if (str.startsWith('
')) { + str = str.replace('
', ''); + } + + // Normalize
s to
. + // return str.replace(/
/g, '
').replace(/<\/div>/g, ''); + return str; +} diff --git a/plugins/talk-plugin-rich-text-coral/index.js b/plugins/talk-plugin-rich-text-coral/index.js new file mode 100644 index 000000000..f053ebf79 --- /dev/null +++ b/plugins/talk-plugin-rich-text-coral/index.js @@ -0,0 +1 @@ +module.exports = {}; diff --git a/plugins/talk-plugin-rich-text-coral/package.json b/plugins/talk-plugin-rich-text-coral/package.json new file mode 100644 index 000000000..f15b565e7 --- /dev/null +++ b/plugins/talk-plugin-rich-text-coral/package.json @@ -0,0 +1,12 @@ +{ + "name": "@coralproject/talk-plugin-rich-text-coral", + "pluginName": "talk-plugin-rich-text-coral", + "version": "0.0.1", + "description": "Simple Rich Text Editor for Talk", + "main": "index.js", + "author": "The Coral Project Team ", + "license": "Apache-2.0", + "dependencies": { + "react-contenteditable": "^2.0.7" + } +} diff --git a/plugins/talk-plugin-rich-text/server/config.js b/plugins/talk-plugin-rich-text/server/config.js index 8d0fc87d3..347502d08 100644 --- a/plugins/talk-plugin-rich-text/server/config.js +++ b/plugins/talk-plugin-rich-text/server/config.js @@ -13,7 +13,10 @@ const config = { // 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'] }, + dompurify: { + ALLOWED_TAGS: ['b', 'i', 'blockquote', 'br', 'div'], + ALLOWED_ATTR: [], + }, // Secure config for jsdom even when DOMPurify creates a document without a browsing context jsdom: { diff --git a/yarn.lock b/yarn.lock index 135719b51..e8093adf1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9142,6 +9142,10 @@ react-apollo@^1.4.12: object-assign "^4.0.1" prop-types "^15.5.8" +react-contenteditable@^2.0.7: + version "2.0.7" + resolved "https://registry.yarnpkg.com/react-contenteditable/-/react-contenteditable-2.0.7.tgz#a8d1c1d7b9a393f336c5ecdb74e5e336d786676b" + react-dom@>=0.14.0: version "16.2.0" resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.2.0.tgz#69003178601c0ca19b709b33a83369fe6124c044" From 3821477ef0114eb16bcb6097af4907c3343d99b2 Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Mon, 19 Mar 2018 00:32:06 +0100 Subject: [PATCH 02/28] Remove some comments --- .../talk-plugin-rich-text-coral/client/components/Editor.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/talk-plugin-rich-text-coral/client/components/Editor.js b/plugins/talk-plugin-rich-text-coral/client/components/Editor.js index f6491572b..b22fd0df4 100644 --- a/plugins/talk-plugin-rich-text-coral/client/components/Editor.js +++ b/plugins/talk-plugin-rich-text-coral/client/components/Editor.js @@ -111,9 +111,9 @@ class Editor extends React.Component { className={styles.contentEditable} id={id} ref={this.handleRef} - html={this.state.html} // innerHTML of the editable div - disabled={false} // use true to disable edition - onChange={this.handleChange} // handle innerHTML change + html={this.state.html} + disabled={false} + onChange={this.handleChange} />
); From 497225139dd1a7bf09d4d82f6fe3120dca7a5b55 Mon Sep 17 00:00:00 2001 From: Kim Gardner Date: Tue, 20 Mar 2018 10:14:41 -0400 Subject: [PATCH 03/28] Adding upvote and downvote plugins and sorting plugins for each --- .gitignore | 5 +- .../client/.eslintrc.json | 23 ++++++++ .../client/components/DownvoteButton.css | 39 +++++++++++++ .../client/components/DownvoteButton.js | 56 +++++++++++++++++++ .../client/components/Icon.js | 9 +++ plugins/talk-plugin-downvote/client/index.js | 25 +++++++++ plugins/talk-plugin-downvote/index.js | 2 + .../client/.eslintrc.json | 3 + .../client/index.js | 19 +++++++ .../client/translations.yml | 3 + .../talk-plugin-sort-most-downvoted/index.js | 1 + .../client/.eslintrc.json | 3 + .../client/index.js | 19 +++++++ .../client/translations.yml | 3 + .../talk-plugin-sort-most-upvoted/index.js | 1 + .../talk-plugin-upvote/client/.eslintrc.json | 23 ++++++++ .../client/components/Icon.js | 6 ++ .../client/components/UpvoteButton.css | 39 +++++++++++++ .../client/components/UpvoteButton.js | 54 ++++++++++++++++++ plugins/talk-plugin-upvote/client/index.js | 25 +++++++++ plugins/talk-plugin-upvote/index.js | 2 + 21 files changed, 359 insertions(+), 1 deletion(-) create mode 100644 plugins/talk-plugin-downvote/client/.eslintrc.json create mode 100644 plugins/talk-plugin-downvote/client/components/DownvoteButton.css create mode 100644 plugins/talk-plugin-downvote/client/components/DownvoteButton.js create mode 100644 plugins/talk-plugin-downvote/client/components/Icon.js create mode 100644 plugins/talk-plugin-downvote/client/index.js create mode 100644 plugins/talk-plugin-downvote/index.js create mode 100644 plugins/talk-plugin-sort-most-downvoted/client/.eslintrc.json create mode 100644 plugins/talk-plugin-sort-most-downvoted/client/index.js create mode 100644 plugins/talk-plugin-sort-most-downvoted/client/translations.yml create mode 100644 plugins/talk-plugin-sort-most-downvoted/index.js create mode 100644 plugins/talk-plugin-sort-most-upvoted/client/.eslintrc.json create mode 100644 plugins/talk-plugin-sort-most-upvoted/client/index.js create mode 100644 plugins/talk-plugin-sort-most-upvoted/client/translations.yml create mode 100644 plugins/talk-plugin-sort-most-upvoted/index.js create mode 100644 plugins/talk-plugin-upvote/client/.eslintrc.json create mode 100644 plugins/talk-plugin-upvote/client/components/Icon.js create mode 100644 plugins/talk-plugin-upvote/client/components/UpvoteButton.css create mode 100644 plugins/talk-plugin-upvote/client/components/UpvoteButton.js create mode 100644 plugins/talk-plugin-upvote/client/index.js create mode 100644 plugins/talk-plugin-upvote/index.js diff --git a/.gitignore b/.gitignore index 7c0007dc9..50e490095 100644 --- a/.gitignore +++ b/.gitignore @@ -30,6 +30,7 @@ plugins/* !plugins/talk-plugin-author-menu !plugins/talk-plugin-comment-content !plugins/talk-plugin-deep-reply-count +!plugins/talk-plugin-downvote !plugins/talk-plugin-facebook-auth !plugins/talk-plugin-featured-comments !plugins/talk-plugin-flag-details @@ -52,14 +53,16 @@ plugins/* !plugins/talk-plugin-remember-sort !plugins/talk-plugin-respect !plugins/talk-plugin-slack-notifications -!plugins/talk-plugin-sort-most-liked +!plugins/talk-plugin-sort-most-downvoted !plugins/talk-plugin-sort-most-loved !plugins/talk-plugin-sort-most-replied !plugins/talk-plugin-sort-most-respected +!plugins/talk-plugin-sort-most-upvoted !plugins/talk-plugin-sort-newest !plugins/talk-plugin-sort-oldest !plugins/talk-plugin-subscriber !plugins/talk-plugin-toxic-comments +!plugins/talk-plugin-upvote !plugins/talk-plugin-viewing-options !plugins/talk-plugin-rich-text !plugins/talk-plugin-rich-text-pell diff --git a/plugins/talk-plugin-downvote/client/.eslintrc.json b/plugins/talk-plugin-downvote/client/.eslintrc.json new file mode 100644 index 000000000..9fe56bd14 --- /dev/null +++ b/plugins/talk-plugin-downvote/client/.eslintrc.json @@ -0,0 +1,23 @@ +{ + "env": { + "browser": true, + "es6": true, + "mocha": true + }, + "parserOptions": { + "sourceType": "module", + "ecmaFeatures": { + "experimentalObjectRestSpread": true, + "jsx": true + } + }, + "parser": "babel-eslint", + "plugins": [ + "react" + ], + "rules": { + "react/jsx-uses-react": "error", + "react/jsx-uses-vars": "error", + "no-console": ["warn", { "allow": ["warn", "error"] }] + } +} diff --git a/plugins/talk-plugin-downvote/client/components/DownvoteButton.css b/plugins/talk-plugin-downvote/client/components/DownvoteButton.css new file mode 100644 index 000000000..71090306b --- /dev/null +++ b/plugins/talk-plugin-downvote/client/components/DownvoteButton.css @@ -0,0 +1,39 @@ +.container { + display: inline-block; +} + +.button { + color: #2a2a2a; + margin: 5px 10px 5px 0px; + background: none; + padding: 0px; + border: none; + font-size: inherit; + vertical-align: middle; + + &:hover { + color: #767676; + cursor: pointer; + } + + &.downvoted { + color: #cc0000; + + &:hover { + color: #ff3232; + cursor: pointer; + } + } +} + +.icon { + font-size: 12px; + padding: 0 3px; +} + +@media (max-width: 425px) { + .label { + display: none; + } +} + diff --git a/plugins/talk-plugin-downvote/client/components/DownvoteButton.js b/plugins/talk-plugin-downvote/client/components/DownvoteButton.js new file mode 100644 index 000000000..2bf34260b --- /dev/null +++ b/plugins/talk-plugin-downvote/client/components/DownvoteButton.js @@ -0,0 +1,56 @@ +import React from 'react'; +import Icon from './Icon'; +import styles from './DownvoteButton.css'; +import { withReaction } from 'plugin-api/beta/client/hocs'; +import cn from 'classnames'; + +const plugin = 'talk-plugin-downvote'; + +class DownvoteButton extends React.Component { + handleClick = () => { + const { + postReaction, + deleteReaction, + showSignInDialog, + alreadyReacted, + user, + } = this.props; + + // If the current user does not exist, trigger sign in dialog. + if (!user) { + showSignInDialog(); + return; + } + + if (alreadyReacted) { + deleteReaction(); + } else { + postReaction(); + } + }; + + render() { + const { count, alreadyReacted } = this.props; + return ( +
+ +
+ ); + } +} + +export default withReaction('downvote')(DownvoteButton); diff --git a/plugins/talk-plugin-downvote/client/components/Icon.js b/plugins/talk-plugin-downvote/client/components/Icon.js new file mode 100644 index 000000000..13d91fa9c --- /dev/null +++ b/plugins/talk-plugin-downvote/client/components/Icon.js @@ -0,0 +1,9 @@ +import React from 'react'; +import cn from 'classnames'; + +export default ({ className }) => ( +