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/18] 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/18] 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 bd2fcf837aec87dbea8affdfc12cc35406f9259a Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Wed, 21 Mar 2018 13:45:29 +0100 Subject: [PATCH 03/18] Move coral rte to talk-plugin-rich-text --- plugins/talk-plugin-rich-text-coral/index.js | 1 - plugins/talk-plugin-rich-text-coral/package.json | 12 ------------ .../README.client.md} | 0 .../client/.eslintrc.json | 0 .../client/components/Button.css | 0 .../client/components/Button.js | 0 .../client/components/CommentContent.js | 0 .../client/components/Editor.css | 0 .../client/components/Editor.js | 0 .../client/components/Toolbar.css | 0 .../client/components/Toolbar.js | 0 .../client/constants.js | 0 .../client/containers/CommentContent.js | 0 .../client/containers/Editor.js | 0 .../client/index.js | 0 .../client/utils.js | 0 plugins/talk-plugin-rich-text/package.json | 3 ++- 17 files changed, 2 insertions(+), 14 deletions(-) delete mode 100644 plugins/talk-plugin-rich-text-coral/index.js delete mode 100644 plugins/talk-plugin-rich-text-coral/package.json rename plugins/{talk-plugin-rich-text-coral/README.md => talk-plugin-rich-text/README.client.md} (100%) rename plugins/{talk-plugin-rich-text-coral => talk-plugin-rich-text}/client/.eslintrc.json (100%) rename plugins/{talk-plugin-rich-text-coral => talk-plugin-rich-text}/client/components/Button.css (100%) rename plugins/{talk-plugin-rich-text-coral => talk-plugin-rich-text}/client/components/Button.js (100%) rename plugins/{talk-plugin-rich-text-coral => talk-plugin-rich-text}/client/components/CommentContent.js (100%) rename plugins/{talk-plugin-rich-text-coral => talk-plugin-rich-text}/client/components/Editor.css (100%) rename plugins/{talk-plugin-rich-text-coral => talk-plugin-rich-text}/client/components/Editor.js (100%) rename plugins/{talk-plugin-rich-text-coral => talk-plugin-rich-text}/client/components/Toolbar.css (100%) rename plugins/{talk-plugin-rich-text-coral => talk-plugin-rich-text}/client/components/Toolbar.js (100%) rename plugins/{talk-plugin-rich-text-coral => talk-plugin-rich-text}/client/constants.js (100%) rename plugins/{talk-plugin-rich-text-coral => talk-plugin-rich-text}/client/containers/CommentContent.js (100%) rename plugins/{talk-plugin-rich-text-coral => talk-plugin-rich-text}/client/containers/Editor.js (100%) rename plugins/{talk-plugin-rich-text-coral => talk-plugin-rich-text}/client/index.js (100%) rename plugins/{talk-plugin-rich-text-coral => talk-plugin-rich-text}/client/utils.js (100%) diff --git a/plugins/talk-plugin-rich-text-coral/index.js b/plugins/talk-plugin-rich-text-coral/index.js deleted file mode 100644 index f053ebf79..000000000 --- a/plugins/talk-plugin-rich-text-coral/index.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = {}; diff --git a/plugins/talk-plugin-rich-text-coral/package.json b/plugins/talk-plugin-rich-text-coral/package.json deleted file mode 100644 index f15b565e7..000000000 --- a/plugins/talk-plugin-rich-text-coral/package.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "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-coral/README.md b/plugins/talk-plugin-rich-text/README.client.md similarity index 100% rename from plugins/talk-plugin-rich-text-coral/README.md rename to plugins/talk-plugin-rich-text/README.client.md diff --git a/plugins/talk-plugin-rich-text-coral/client/.eslintrc.json b/plugins/talk-plugin-rich-text/client/.eslintrc.json similarity index 100% rename from plugins/talk-plugin-rich-text-coral/client/.eslintrc.json rename to plugins/talk-plugin-rich-text/client/.eslintrc.json diff --git a/plugins/talk-plugin-rich-text-coral/client/components/Button.css b/plugins/talk-plugin-rich-text/client/components/Button.css similarity index 100% rename from plugins/talk-plugin-rich-text-coral/client/components/Button.css rename to plugins/talk-plugin-rich-text/client/components/Button.css diff --git a/plugins/talk-plugin-rich-text-coral/client/components/Button.js b/plugins/talk-plugin-rich-text/client/components/Button.js similarity index 100% rename from plugins/talk-plugin-rich-text-coral/client/components/Button.js rename to plugins/talk-plugin-rich-text/client/components/Button.js diff --git a/plugins/talk-plugin-rich-text-coral/client/components/CommentContent.js b/plugins/talk-plugin-rich-text/client/components/CommentContent.js similarity index 100% rename from plugins/talk-plugin-rich-text-coral/client/components/CommentContent.js rename to plugins/talk-plugin-rich-text/client/components/CommentContent.js diff --git a/plugins/talk-plugin-rich-text-coral/client/components/Editor.css b/plugins/talk-plugin-rich-text/client/components/Editor.css similarity index 100% rename from plugins/talk-plugin-rich-text-coral/client/components/Editor.css rename to plugins/talk-plugin-rich-text/client/components/Editor.css diff --git a/plugins/talk-plugin-rich-text-coral/client/components/Editor.js b/plugins/talk-plugin-rich-text/client/components/Editor.js similarity index 100% rename from plugins/talk-plugin-rich-text-coral/client/components/Editor.js rename to plugins/talk-plugin-rich-text/client/components/Editor.js diff --git a/plugins/talk-plugin-rich-text-coral/client/components/Toolbar.css b/plugins/talk-plugin-rich-text/client/components/Toolbar.css similarity index 100% rename from plugins/talk-plugin-rich-text-coral/client/components/Toolbar.css rename to plugins/talk-plugin-rich-text/client/components/Toolbar.css diff --git a/plugins/talk-plugin-rich-text-coral/client/components/Toolbar.js b/plugins/talk-plugin-rich-text/client/components/Toolbar.js similarity index 100% rename from plugins/talk-plugin-rich-text-coral/client/components/Toolbar.js rename to plugins/talk-plugin-rich-text/client/components/Toolbar.js diff --git a/plugins/talk-plugin-rich-text-coral/client/constants.js b/plugins/talk-plugin-rich-text/client/constants.js similarity index 100% rename from plugins/talk-plugin-rich-text-coral/client/constants.js rename to plugins/talk-plugin-rich-text/client/constants.js diff --git a/plugins/talk-plugin-rich-text-coral/client/containers/CommentContent.js b/plugins/talk-plugin-rich-text/client/containers/CommentContent.js similarity index 100% rename from plugins/talk-plugin-rich-text-coral/client/containers/CommentContent.js rename to plugins/talk-plugin-rich-text/client/containers/CommentContent.js diff --git a/plugins/talk-plugin-rich-text-coral/client/containers/Editor.js b/plugins/talk-plugin-rich-text/client/containers/Editor.js similarity index 100% rename from plugins/talk-plugin-rich-text-coral/client/containers/Editor.js rename to plugins/talk-plugin-rich-text/client/containers/Editor.js diff --git a/plugins/talk-plugin-rich-text-coral/client/index.js b/plugins/talk-plugin-rich-text/client/index.js similarity index 100% rename from plugins/talk-plugin-rich-text-coral/client/index.js rename to plugins/talk-plugin-rich-text/client/index.js diff --git a/plugins/talk-plugin-rich-text-coral/client/utils.js b/plugins/talk-plugin-rich-text/client/utils.js similarity index 100% rename from plugins/talk-plugin-rich-text-coral/client/utils.js rename to plugins/talk-plugin-rich-text/client/utils.js diff --git a/plugins/talk-plugin-rich-text/package.json b/plugins/talk-plugin-rich-text/package.json index e933626f6..31388e584 100644 --- a/plugins/talk-plugin-rich-text/package.json +++ b/plugins/talk-plugin-rich-text/package.json @@ -9,6 +9,7 @@ "dependencies": { "dompurify": "^1.0.3", "jsdom": "^11.6.2", - "linkifyjs": "^2.1.5" + "linkifyjs": "^2.1.5", + "react-contenteditable": "^2.0.7" } } From f6e661090c236484ddee58fed9c2e6d4c4c67c4d Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Wed, 21 Mar 2018 13:54:10 +0100 Subject: [PATCH 04/18] Support preSubmit return input --- .../src/tabs/stream/containers/CommentBox.js | 9 ++++++--- .../client/components/CheckSpamHook.js | 5 ++++- .../client/components/CheckToxicityHook.js | 5 ++++- 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/client/coral-embed-stream/src/tabs/stream/containers/CommentBox.js b/client/coral-embed-stream/src/tabs/stream/containers/CommentBox.js index de0551141..c02bc63d4 100644 --- a/client/coral-embed-stream/src/tabs/stream/containers/CommentBox.js +++ b/client/coral-embed-stream/src/tabs/stream/containers/CommentBox.js @@ -65,9 +65,12 @@ class CommentBox extends React.Component { }; // Execute preSubmit Hooks - this.state.hooks.preSubmit.forEach(hook => - hook(input, this.handleBodyChange) - ); + this.state.hooks.preSubmit.forEach(hook => { + const result = hook(input); + if (result) { + input = result; + } + }); this.setState({ loadingState: 'loading' }); postComment(input, 'comments') diff --git a/plugins/talk-plugin-akismet/client/components/CheckSpamHook.js b/plugins/talk-plugin-akismet/client/components/CheckSpamHook.js index 056f28eef..83378d248 100644 --- a/plugins/talk-plugin-akismet/client/components/CheckSpamHook.js +++ b/plugins/talk-plugin-akismet/client/components/CheckSpamHook.js @@ -15,8 +15,11 @@ export default class CheckSpamHook extends React.Component { // If we haven't check the spam yet, make sure to include `checkSpam=true` in the mutation. // Otherwise post comment without checking the spam. if (!this.checked) { - input.checkSpam = true; this.checked = true; + return { + ...input, + checkSpam: true, + }; } }); diff --git a/plugins/talk-plugin-toxic-comments/client/components/CheckToxicityHook.js b/plugins/talk-plugin-toxic-comments/client/components/CheckToxicityHook.js index 4331e006b..814c107cc 100644 --- a/plugins/talk-plugin-toxic-comments/client/components/CheckToxicityHook.js +++ b/plugins/talk-plugin-toxic-comments/client/components/CheckToxicityHook.js @@ -15,8 +15,11 @@ export default class CheckToxicityHook extends React.Component { // If we haven't check the toxicity yet, make sure to include `checkToxicity=true` in the mutation. // Otherwise post comment without checking the toxicity. if (!this.checked) { - input.checkToxicity = true; this.checked = true; + return { + ...input, + checkToxicity: true, + }; } }); From 65778c31ff57d7304c4b73ec478594c1f7db18f8 Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Wed, 21 Mar 2018 15:41:27 +0100 Subject: [PATCH 05/18] Refactor draft area api step 1 --- .../src/tabs/stream/components/Comment.js | 2 +- .../src/tabs/stream/components/CommentForm.js | 20 +- .../src/tabs/stream/components/DraftArea.js | 23 +- .../stream/components/DraftAreaContent.js | 12 +- .../components/EditableCommentContent.js | 221 +++++------------- .../src/tabs/stream/containers/CommentBox.js | 122 ++++------ .../src/tabs/stream/containers/DraftArea.js | 37 +-- .../containers/EditableCommentContent.js | 147 +++++++++++- .../src/tabs/stream/hocs/withHooks.js | 57 +++++ .../src/tabs/stream/{components => }/util.js | 0 .../talk-plugin-profile-settings/README.md | 0 .../client/components/Editor.js | 55 ++--- 12 files changed, 378 insertions(+), 318 deletions(-) create mode 100644 client/coral-embed-stream/src/tabs/stream/hocs/withHooks.js rename client/coral-embed-stream/src/tabs/stream/{components => }/util.js (100%) delete mode 100644 plugins/talk-plugin-profile-settings/README.md diff --git a/client/coral-embed-stream/src/tabs/stream/components/Comment.js b/client/coral-embed-stream/src/tabs/stream/components/Comment.js index 6165d126b..5df119fbc 100644 --- a/client/coral-embed-stream/src/tabs/stream/components/Comment.js +++ b/client/coral-embed-stream/src/tabs/stream/components/Comment.js @@ -16,7 +16,7 @@ import mapValues from 'lodash/mapValues'; import get from 'lodash/get'; import LoadMore from './LoadMore'; -import { getEditableUntilDate } from './util'; +import { getEditableUntilDate } from '../util'; import { findCommentWithId } from '../../../graphql/utils'; import CommentContent from 'coral-framework/components/CommentContent'; import Slot from 'coral-framework/components/Slot'; diff --git a/client/coral-embed-stream/src/tabs/stream/components/CommentForm.js b/client/coral-embed-stream/src/tabs/stream/components/CommentForm.js index 65937b3ce..64e0fbf84 100644 --- a/client/coral-embed-stream/src/tabs/stream/components/CommentForm.js +++ b/client/coral-embed-stream/src/tabs/stream/components/CommentForm.js @@ -37,15 +37,15 @@ class CommentForm extends React.Component { submitButtonCStyle: PropTypes.string, // return whether the submit button should be enabled for the provided - // comment ({ body }) (for reasons other than charCount) + // input (for reasons other than charCount) submitEnabled: PropTypes.func, // className to add to buttons submitButtonClassName: PropTypes.string, cancelButtonClassName: PropTypes.string, - body: PropTypes.string.isRequired, - onBodyChange: PropTypes.func.isRequired, + input: PropTypes.object.isRequired, + onInputChange: PropTypes.func.isRequired, onSubmit: PropTypes.func.isRequired, onCancel: PropTypes.func, state: PropTypes.string, @@ -53,6 +53,7 @@ class CommentForm extends React.Component { registerHook: PropTypes.func, unregisterHook: PropTypes.func, isReply: PropTypes.bool, + isEdit: PropTypes.bool, root: PropTypes.object.isRequired, comment: PropTypes.object, }; @@ -90,20 +91,20 @@ class CommentForm extends React.Component { cancelButtonClassName, submitButtonClassName, charCountEnable, - body, + input, loadingState, comment, root, } = this.props; - const length = body.length; + const length = input.body.length; const isRespectingMaxCount = length => charCountEnable && maxCharCount && length > maxCharCount; const disableSubmitButton = !length || - body.trim().length === 0 || + input.body.trim().length === 0 || isRespectingMaxCount(length) || - !submitEnabled({ body }) || + !submitEnabled(input) || loadingState === 'loading'; const disableCancelButton = loadingState === 'loading'; const disableTextArea = loadingState === 'loading'; @@ -115,15 +116,16 @@ class CommentForm extends React.Component { comment={comment} id={this.props.bodyInputId} label={this.props.bodyLabel} - value={body} + input={input} placeholder={this.props.bodyPlaceholder} - onChange={this.props.onBodyChange} + onInputChange={this.props.onInputChange} disabled={disableTextArea} charCountEnable={this.props.charCountEnable} maxCharCount={this.props.maxCharCount} registerHook={this.props.registerHook} unregisterHook={this.props.unregisterHook} isReply={this.props.isReply} + isEdit={this.props.isEdit} />
{this.props.buttonContainerStart} diff --git a/client/coral-embed-stream/src/tabs/stream/components/DraftArea.js b/client/coral-embed-stream/src/tabs/stream/components/DraftArea.js index 7150a124d..ce469f201 100644 --- a/client/coral-embed-stream/src/tabs/stream/components/DraftArea.js +++ b/client/coral-embed-stream/src/tabs/stream/components/DraftArea.js @@ -13,17 +13,17 @@ import styles from './DraftArea.css'; */ export default class DraftArea extends React.Component { renderCharCount() { - const { value, maxCharCount } = this.props; + const { input, maxCharCount } = this.props; const className = cn( styles.charCount, 'talk-plugin-commentbox-char-count', { [`${styles.charMax} talk-plugin-commentbox-char-max`]: - value.length > maxCharCount, + input.body.length > maxCharCount, } ); - const remaining = maxCharCount - value.length; + const remaining = maxCharCount - input.body.length; return (
@@ -34,7 +34,7 @@ export default class DraftArea extends React.Component { render() { const { - value, + input, placeholder, id, disabled, @@ -42,8 +42,9 @@ export default class DraftArea extends React.Component { label, charCountEnable, maxCharCount, - onChange, + onInputChange, isReply, + isEdit, registerHook, unregisterHook, root, @@ -51,7 +52,7 @@ export default class DraftArea extends React.Component { } = this.props; return ( -
+
@@ -67,13 +68,14 @@ export default class DraftArea extends React.Component { comment, registerHook, unregisterHook, - value, + input, placeholder, id, - onChange, + onInputChange, rows, disabled, isReply, + isEdit, }} /> @@ -92,10 +94,10 @@ DraftArea.propTypes = { charCountEnable: PropTypes.bool, maxCharCount: PropTypes.number, id: PropTypes.string, - value: PropTypes.string, + input: PropTypes.object, placeholder: PropTypes.string, label: PropTypes.string, - onChange: PropTypes.func, + onInputChange: PropTypes.func, disabled: PropTypes.bool, rows: PropTypes.number, root: PropTypes.object.isRequired, @@ -103,4 +105,5 @@ DraftArea.propTypes = { registerHook: PropTypes.func, unregisterHook: PropTypes.func, isReply: PropTypes.bool, + isEdit: PropTypes.bool, }; diff --git a/client/coral-embed-stream/src/tabs/stream/components/DraftAreaContent.js b/client/coral-embed-stream/src/tabs/stream/components/DraftAreaContent.js index 50a481f6f..5c77b20aa 100644 --- a/client/coral-embed-stream/src/tabs/stream/components/DraftAreaContent.js +++ b/client/coral-embed-stream/src/tabs/stream/components/DraftAreaContent.js @@ -4,19 +4,19 @@ import cn from 'classnames'; import styles from './DraftAreaContent.css'; const DraftAreaContent = ({ - value, + input, placeholder, id, - onChange, + onInputChange, rows, disabled, }) => (