From 00a2a65d3160a1656e9a96a14f006c65d9a7254d Mon Sep 17 00:00:00 2001 From: Benjamin Goering Date: Mon, 1 May 2017 16:58:24 -0500 Subject: [PATCH] Edit Comment UI --- client/coral-embed-stream/src/Comment.css | 3 +- client/coral-embed-stream/src/Comment.js | 91 +++----- .../src/EditableCommentContent.js | 62 +++++ client/coral-plugin-commentbox/CommentBox.js | 218 +++++++++++++----- 4 files changed, 254 insertions(+), 120 deletions(-) create mode 100644 client/coral-embed-stream/src/EditableCommentContent.js diff --git a/client/coral-embed-stream/src/Comment.css b/client/coral-embed-stream/src/Comment.css index 5fe5401fe..24124f9ae 100644 --- a/client/coral-embed-stream/src/Comment.css +++ b/client/coral-embed-stream/src/Comment.css @@ -29,7 +29,8 @@ right: 0px; } -.topRight .popoverMenuOpen .link { +.topRight .link.active, +.topRight .active .link { padding-bottom: 0.125em; border-bottom: 2px solid currentColor; } diff --git a/client/coral-embed-stream/src/Comment.js b/client/coral-embed-stream/src/Comment.js index b5a7dfeb3..442c79bc1 100644 --- a/client/coral-embed-stream/src/Comment.js +++ b/client/coral-embed-stream/src/Comment.js @@ -23,8 +23,8 @@ import Slot from 'coral-framework/components/Slot'; import IgnoredCommentTombstone from './IgnoredCommentTombstone'; import {TopRightMenu} from './TopRightMenu'; import {getActionSummary, getTotalActionCount, iPerformedThisAction} from 'coral-framework/utils'; -import {Button} from 'coral-ui'; import classnames from 'classnames'; +import {EditableCommentContent} from './EditableCommentContent'; import styles from './Comment.css'; @@ -39,7 +39,13 @@ class Comment extends React.Component { constructor(props) { super(props); - this.state = {replyBoxVisible: false}; + this.onClickEdit = this.onClickEdit.bind(this); + this.state = { + + // Whether the comment should be editable (e.g. after a commenter clicking the 'Edit' button on their own comment) + isEditing: false, + replyBoxVisible: false, + }; } static propTypes = { @@ -102,6 +108,11 @@ class Comment extends React.Component { ignoreUser: React.PropTypes.func, } + onClickEdit (e) { + e.preventDefault(); + this.setState({isEditing: true}); + } + render () { const { comment, @@ -163,55 +174,6 @@ class Comment extends React.Component { tag: BEST_TAG, }), () => 'Failed to remove best comment tag'); - class PopoverMenu extends React.Component { - static propTypes = { - children: PropTypes.node, - Popover: PropTypes.oneOfType([PropTypes.element, PropTypes.func]), - openClassName: PropTypes.string, - } - constructor(props) { - super(props); - this.toggle = this.toggle.bind(this); - this.close = this.close.bind(this); - this.state = { - isOpen: false - }; - } - toggle() { - this.setState({isOpen: ! this.state.isOpen}); - } - close() { - this.setState({isOpen: false}); - } - render() { - const {isOpen} = this.state; - const {children, Popover, openClassName} = this.props; - return ( - - - { children } - - - { isOpen ? : null } - - - ); - } - } - - const DeleteCommentConfirmation = ({cancel, deleteComment}) => { - return ( -
-
Delete a comment
-

Are you sure you want to delete that comment

-
- - -
-
- ); - }; - return (
- - { /*console.log('delete comment', comment)*/ }} - /> }> - Delete - + Edit /* TopRightMenu allows currentUser to ignore other users' comments */ @@ -257,7 +212,19 @@ class Comment extends React.Component { } - + { + this.state.isEditing + ? + : + } +
{/* TODO implmement iPerformedThisAction for the like */} diff --git a/client/coral-embed-stream/src/EditableCommentContent.js b/client/coral-embed-stream/src/EditableCommentContent.js new file mode 100644 index 000000000..09c5fae9b --- /dev/null +++ b/client/coral-embed-stream/src/EditableCommentContent.js @@ -0,0 +1,62 @@ +import React, {PropTypes} from 'react'; +import {CommentForm} from 'coral-plugin-commentbox/CommentBox'; + +/** + * Renders a Comment's body in such a way that the end-user can edit it and save changes + */ +export class EditableCommentContent extends React.Component { + + // @TODO (bengo) make sure these are accurate wrt isRequired + static propTypes = { + + // show notification to the user (e.g. for errors) + addNotification: PropTypes.func.isRequired, + asset: PropTypes.shape({ + id: PropTypes.string.isRequired, + settings: PropTypes.shape({ + charCountEnable: PropTypes.bool, + }), + }).isRequired, + + // comment that is being edited + comment: PropTypes.shape({ + body: PropTypes.string + }).isRequired, + + // logged in user + currentUser: PropTypes.shape({ + id: PropTypes.string.isRequired + }), + maxCharCount: PropTypes.number, + + parentId: PropTypes.string, + } + constructor(props) { + super(props); + } + render() { + const saveComment = function () { + }; + const originalBody = this.props.comment.body; + return ( +
+ { + + // should be disabled if user hasn't actually changed their + // original comment + return comment.body !== originalBody; + }} + saveComment={saveComment} + bodyLabel={'Edit this comment' /* @TODO (bengo) i18n */} + bodyPlaceholder="" + submitText={'Save changes' /* @TODO (bengo) i18n */} + saveButtonCStyle="green" + /> +
+ ); + } +} diff --git a/client/coral-plugin-commentbox/CommentBox.js b/client/coral-plugin-commentbox/CommentBox.js index 261c95414..ee3b9722c 100644 --- a/client/coral-plugin-commentbox/CommentBox.js +++ b/client/coral-plugin-commentbox/CommentBox.js @@ -7,6 +7,132 @@ import {connect} from 'react-redux'; const name = 'coral-plugin-commentbox'; +/** + * Common UI for Creating or Editing a Comment + */ +export class CommentForm extends Component { + static propTypes = { + + // Initial value for underlying comment body textarea + defaultValue: PropTypes.string, + charCountEnable: PropTypes.bool.isRequired, + maxCharCount: PropTypes.number, + cancelButtonClicked: PropTypes.func, + + // Save the comment in the form. + // Will be passed { body: String } + saveComment: PropTypes.func.isRequired, + + // DOM ID for form input that edits comment body + bodyInputId: PropTypes.string, + + // screen reader label for input that edits comment body + bodyLabel: PropTypes.string, + + // Placeholder for input that edits comment body + bodyPlaceholder: PropTypes.string, + + // render at start of button container (useful for extra buttons) + buttonContainerStart: PropTypes.node, + + // render inside submit button + submitText: PropTypes.node, + + styles: PropTypes.shape({ + textarea: PropTypes.string + }), + + // cStyle for enabled save + saveButtonCStyle: PropTypes.string, + + // return whether the save button should be enabled for the provided + // comment ({ body }) (for reasons other than charCount) + saveCommentEnabled: PropTypes.func, + } + static get defaultProps() { + return { + bodyLabel: lang.t('comment'), + bodyPlaceholder: lang.t('comment'), + submitText: lang.t('post'), + saveButtonCStyle: 'darkGrey', + saveCommentEnabled: () => true, + }; + } + constructor(props) { + super(props); + this.onBodyChange = this.onBodyChange.bind(this); + this.onClickSubmit = this.onClickSubmit.bind(this); + this.state = { + body: props.defaultValue || '' + }; + } + onBodyChange(e) { + this.setState({body: e.target.value}); + } + onClickSubmit(e) { + e.preventDefault(); + const {saveComment} = this.props; + const {body} = this.state; + saveComment({body}); + } + render() { + const {maxCharCount, styles, saveCommentEnabled} = this.props; + + const body = this.state.body; + const length = body.length; + const isNotValidLength = (length) => !length || (maxCharCount && length > maxCharCount); + const disablePostComment = isNotValidLength(length) || ! saveCommentEnabled({body}); + + return
+
+ +