diff --git a/client/coral-embed-stream/src/components/Comment.css b/client/coral-embed-stream/src/components/Comment.css index 8d7c7ebf9..5fab57d95 100644 --- a/client/coral-embed-stream/src/components/Comment.css +++ b/client/coral-embed-stream/src/components/Comment.css @@ -13,13 +13,87 @@ pointer-events: none; } -.topRightMenu { +.bylineSecondary { + color: #696969; + font-size: 12px; +} + +.editedMarker { + font-style: italic; +} + +/* element in the top right of the Comment */ +.topRight { float: right; + margin-top: 10px; text-align: right; +} + +.topRight > * { + text-align: initial; +} + +.topRight .popover { + margin-top: 1em; + right: 0px; +} + +.topRight .link.active, +.topRight .active .link { + padding-bottom: 0.125em; + border-bottom: 2px solid currentColor; +} + +.topRightMenu { cursor: pointer; margin-top: 5px; } -.topRightMenu > * { - text-align: initial; +.editCommentForm { + margin-bottom: 10px; +} + +.editCommentForm .buttonContainerLeft { + margin-right: auto; + display: flex; + justify-content: center; + flex-direction: column; +} + +.editCommentForm .buttonContainerLeft .editWindowRemaining { + margin-right: 1em; +} + +.editCommentForm .button { + flex-shrink: 0; +} + +.editWindowAlmostOver { + font-weight: bold; +} + +.link { + color: #2376D8; + cursor: pointer; +} + +.popover { + position: absolute; + z-index: 1; +} + +/* Wizard used for Ignore User, Delete Comment confirmations */ +.Wizard { + background-color: #2E343B; + color: white; + padding: 1em; + max-width: 220px; /* consider moving to better class */ +} + +.Wizard header { + font-weight: bold; +} + +.Wizard .textAlignRight { + text-align: right; } diff --git a/client/coral-embed-stream/src/components/Comment.js b/client/coral-embed-stream/src/components/Comment.js index 4349fe38d..13d286296 100644 --- a/client/coral-embed-stream/src/components/Comment.js +++ b/client/coral-embed-stream/src/components/Comment.js @@ -19,8 +19,10 @@ import Slot from 'coral-framework/components/Slot'; import LoadMore from './LoadMore'; import IgnoredCommentTombstone from './IgnoredCommentTombstone'; import {TopRightMenu} from './TopRightMenu'; +import classnames from 'classnames'; +import {EditableCommentContent} from './EditableCommentContent'; import {getActionSummary, iPerformedThisAction} from 'coral-framework/utils'; - +import {getEditableUntilDate} from './util'; import styles from './Comment.css'; const isStaff = tags => !tags.every(t => t.name !== 'STAFF'); @@ -37,7 +39,17 @@ const ActionButton = ({children}) => { class Comment extends React.Component { constructor(props) { super(props); - this.state = {replyBoxVisible: false}; + + // timeout to keep track of Comment edit window expiration + this.editWindowExpiryTimeout = null; + this.onClickEdit = this.onClickEdit.bind(this); + this.stopEditing = this.stopEditing.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 = { @@ -84,7 +96,13 @@ class Comment extends React.Component { user: PropTypes.shape({ id: PropTypes.string.isRequired, name: PropTypes.string.isRequired - }).isRequired + }).isRequired, + editing: PropTypes.shape({ + edited: PropTypes.bool, + + // ISO8601 + editableUntil: PropTypes.string, + }) }).isRequired, // given a comment, return whether it should be rendered as ignored @@ -97,10 +115,46 @@ class Comment extends React.Component { removeCommentTag: React.PropTypes.func, // dispatch action to ignore another user - ignoreUser: React.PropTypes.func - }; + ignoreUser: React.PropTypes.func, - render() { + // edit a comment, passed (id, asset_id, { body }) + editComment: React.PropTypes.func, + } + + onClickEdit (e) { + e.preventDefault(); + this.setState({isEditing: true}); + } + + stopEditing () { + if (this._isMounted) { + this.setState({isEditing: false}); + } + } + + componentDidMount() { + this._isMounted = true; + if (this.editWindowExpiryTimeout) { + this.editWindowExpiryTimeout = clearTimeout(this.editWindowExpiryTimeout); + } + + // if still in the edit window, set a timeout to re-render once it expires + const msLeftToEdit = editWindowRemainingMs(this.props.comment); + if (msLeftToEdit > 0) { + this.editWindowExpiryTimeout = setTimeout(() => { + + // re-render + this.setState(this.state); + }, msLeftToEdit); + } + } + componentWillUnmount() { + if (this.editWindowExpiryTimeout) { + this.editWindowExpiryTimeout = clearTimeout(this.editWindowExpiryTimeout); + } + this._isMounted = false; + } + render () { const { comment, parentId, @@ -190,8 +244,17 @@ class Comment extends React.Component { {commentIsBest(comment) ? - : null} - + : null } + + + + { + (comment.editing && comment.editing.edited) + ?  (Edited) + : null + } + + - {currentUser && comment.user.id !== currentUser.id - ? - - - : null} - - + { (currentUser && + (comment.user.id === currentUser.id)) + + /* User can edit/delete their own comment for a short window after posting */ + ? + { + commentIsStillEditable(comment) && + Edit + } + + + /* TopRightMenu allows currentUser to ignore other users' comments */ + : + + + } + + { + this.state.isEditing + ? + :
+ + +
+ } +
0) { + this.countdownInterval = setInterval(() => { + + // re-render + this.forceUpdate(); + }, 1000); + } + } + componentWillUnmount() { + if (this.countdownInterval) { + this.countdownInterval = clearInterval(this.countdownInterval); + } + } + render() { + const now = new Date(); + const {until, classNameForMsRemaining} = this.props; + const msRemaining = until - now; + const secRemaining = msRemaining / 1000; + const wholeSecRemaining = Math.floor(secRemaining); + const plural = secRemaining !== 1; + const units = lang.t(plural ? 'editComment.secondsPlural' : 'editComment.second'); + let classFromProp; + if (typeof classNameForMsRemaining === 'function') { + classFromProp = classNameForMsRemaining(msRemaining); + } + return ( + + {`${wholeSecRemaining} ${units}`} + + ); + } +} diff --git a/client/coral-embed-stream/src/components/EditableCommentContent.js b/client/coral-embed-stream/src/components/EditableCommentContent.js new file mode 100644 index 000000000..03b61edce --- /dev/null +++ b/client/coral-embed-stream/src/components/EditableCommentContent.js @@ -0,0 +1,153 @@ +import React, {PropTypes} from 'react'; +import {notifyForNewCommentStatus} from 'coral-plugin-commentbox/CommentBox'; +import {CommentForm} from 'coral-plugin-commentbox/CommentForm'; +import styles from './Comment.css'; +import {CountdownSeconds} from './CountdownSeconds'; +import {getEditableUntilDate} from './util'; + +import {Icon} from 'coral-ui'; +import I18n from 'coral-framework/modules/i18n/i18n'; +import translations from 'coral-framework/translations'; +const lang = new I18n(translations); + +/** + * 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 { + static propTypes = { + + // show notification to the user (e.g. for errors) + addNotification: PropTypes.func.isRequired, + asset: PropTypes.shape({ + settings: PropTypes.shape({ + charCountEnable: PropTypes.bool, + }), + }).isRequired, + + // comment that is being edited + comment: PropTypes.shape({ + body: PropTypes.string, + editing: PropTypes.shape({ + edited: PropTypes.bool, + + // ISO8601 + editableUntil: PropTypes.string, + }) + }).isRequired, + + // logged in user + currentUser: PropTypes.shape({ + id: PropTypes.string.isRequired + }), + maxCharCount: PropTypes.number, + + // edit a comment, passed {{ body }} + editComment: React.PropTypes.func, + + // called when editing should be stopped + stopEditing: React.PropTypes.func, + } + constructor(props) { + super(props); + this.editComment = this.editComment.bind(this); + this.editWindowExpiryTimeout = null; + } + componentDidMount() { + const editableUntil = getEditableUntilDate(this.props.comment); + const now = new Date(); + const editWindowRemainingMs = editableUntil && (editableUntil - now); + if (editWindowRemainingMs > 0) { + this.editWindowExpiryTimeout = setTimeout(() => { + this.forceUpdate(); + }, editWindowRemainingMs); + } + } + componentWillUnmount() { + if (this.editWindowExpiryTimeout) { + this.editWindowExpiryTimeout = clearTimeout(this.editWindowExpiryTimeout); + } + } + async editComment(edit) { + const {editComment, addNotification, stopEditing} = this.props; + if (typeof editComment !== 'function') {return;} + let response; + let successfullyEdited = false; + try { + response = await editComment(edit); + const errors = (response && response.data && response.data.editComment) + ? response.data.editComment.errors + : null; + if (errors && (errors.length === 1)) { + throw errors[0]; + } + successfullyEdited = true; + } catch (error) { + if (error.translation_key) { + addNotification('error', lang.t(error.translation_key) || error.translation_key); + } else if (error.networkError) { + addNotification('error', lang.t('error.networkError')); + } else { + addNotification('error', lang.t('editComment.unexpectedError')); + throw error; + } + } + if (successfullyEdited) { + const status = response.data.editComment.comment.status; + notifyForNewCommentStatus(this.props.addNotification, status); + } + if (successfullyEdited && typeof stopEditing === 'function') { + stopEditing(); + } + } + render() { + const originalBody = this.props.comment.body; + const editableUntil = getEditableUntilDate(this.props.comment); + const editWindowExpired = (editableUntil - new Date()) < 0; + return ( +
+ { + + // should be disabled if user hasn't actually changed their + // original comment + return (comment.body !== originalBody) && ! editWindowExpired; + }} + saveComment={this.editComment} + bodyLabel={lang.t('editComment.bodyInputLabel')} + bodyPlaceholder="" + submitText={{lang.t('editComment.saveButton')}} + saveButtonCStyle="green" + cancelButtonClicked={this.props.stopEditing} + buttonClass={styles.button} + buttonContainerStart={ +
+ + { + editWindowExpired + ? + {lang.t('editComment.editWindowExpired')} + { + typeof this.props.stopEditing === 'function' + ?  {lang.t('editComment.editWindowExpiredClose')} + : null + } + + : + {lang.t('editComment.editWindowTimerPrefix')} + (remainingMs <= 10 * 1000) ? styles.editWindowAlmostOver : '' } + /> + + } + +
+ } + /> +
+ ); + } +} diff --git a/client/coral-embed-stream/src/components/IgnoreUserWizard.css b/client/coral-embed-stream/src/components/IgnoreUserWizard.css deleted file mode 100644 index 838f2f76a..000000000 --- a/client/coral-embed-stream/src/components/IgnoreUserWizard.css +++ /dev/null @@ -1,14 +0,0 @@ -.IgnoreUserWizard { - background-color: #2E343B; - color: white; - padding: 1em; - max-width: 220px; -} - -.IgnoreUserWizard header { - font-weight: bold; -} - -.IgnoreUserWizard .textAlignRight { - text-align: right; -} diff --git a/client/coral-embed-stream/src/components/IgnoreUserWizard.js b/client/coral-embed-stream/src/components/IgnoreUserWizard.js index 371e6d48b..7e72bb924 100644 --- a/client/coral-embed-stream/src/components/IgnoreUserWizard.js +++ b/client/coral-embed-stream/src/components/IgnoreUserWizard.js @@ -1,5 +1,5 @@ import React, {PropTypes} from 'react'; -import styles from './IgnoreUserWizard.css'; +import styles from './Comment.css'; import {Button} from 'coral-ui'; // Guides the user through ignoring another user, including confirming their decision @@ -58,7 +58,7 @@ export class IgnoreUserWizard extends React.Component { const {step} = this.state; const elForThisStep = elsForStep[step - 1]; return ( -
+
{ elForThisStep }
); diff --git a/client/coral-embed-stream/src/components/Stream.js b/client/coral-embed-stream/src/components/Stream.js index 46ac75dca..121aa7993 100644 --- a/client/coral-embed-stream/src/components/Stream.js +++ b/client/coral-embed-stream/src/components/Stream.js @@ -137,6 +137,7 @@ class Stream extends React.Component { comment={highlightedComment} charCountEnable={asset.settings.charCountEnable} maxCharCount={asset.settings.charCount} + editComment={this.props.editComment} /> :
) )}
@@ -205,7 +207,10 @@ Stream.propTypes = { removeCommentTag: PropTypes.func, // dispatch action to ignore another user - ignoreUser: React.PropTypes.func + ignoreUser: React.PropTypes.func, + + // edit a comment, passed (id, asset_id, { body }) + editComment: React.PropTypes.func, }; export default Stream; diff --git a/client/coral-embed-stream/src/components/TopRightMenu.css b/client/coral-embed-stream/src/components/TopRightMenu.css index 5cddae125..0ce8a119b 100644 --- a/client/coral-embed-stream/src/components/TopRightMenu.css +++ b/client/coral-embed-stream/src/components/TopRightMenu.css @@ -20,5 +20,4 @@ position: relative; transform: rotate(180deg); top: 0; - /*top: -0.25em;*/ } diff --git a/client/coral-embed-stream/src/components/util.js b/client/coral-embed-stream/src/components/util.js new file mode 100644 index 000000000..fe5258853 --- /dev/null +++ b/client/coral-embed-stream/src/components/util.js @@ -0,0 +1,10 @@ +/** + * Given a comment, return when the comment can no longer be edited + * @param {Object} comment + * @returns {Date} when the comment can no longer be edited. + */ +export const getEditableUntilDate = (comment) => { + const editing = comment && comment.editing; + const editableUntil = editing && editing.editableUntil && new Date(Date.parse(editing.editableUntil)); + return editableUntil; +}; diff --git a/client/coral-embed-stream/src/containers/Comment.js b/client/coral-embed-stream/src/containers/Comment.js index 9b48e0809..41fcbeafa 100644 --- a/client/coral-embed-stream/src/containers/Comment.js +++ b/client/coral-embed-stream/src/containers/Comment.js @@ -41,6 +41,10 @@ export default withFragments({ id } } + editing { + edited + editableUntil + } ${pluginFragments.spreads('comment')} } ${pluginFragments.definitions('comment')} diff --git a/client/coral-embed-stream/src/containers/Embed.js b/client/coral-embed-stream/src/containers/Embed.js index 3572877ce..0d21aac0a 100644 --- a/client/coral-embed-stream/src/containers/Embed.js +++ b/client/coral-embed-stream/src/containers/Embed.js @@ -5,6 +5,7 @@ import {bindActionCreators} from 'redux'; import isEqual from 'lodash/isEqual'; import branch from 'recompose/branch'; import renderComponent from 'recompose/renderComponent'; +import update from 'immutability-helper'; import {Spinner} from 'coral-ui'; import {authActions, assetActions, pym} from 'coral-framework'; @@ -80,8 +81,11 @@ export const withQuery = graphql(EMBED_QUERY, { assetUrl, commentId, hasComment: commentId !== '', - excludeIgnored: Boolean(auth && auth.user && auth.user.id) - } + excludeIgnored: Boolean(auth && auth.user && auth.user.id), + }, + reducer: (previousResult, action, variables) => { + return reduceEditCommentActionsToUpdateStreamQuery(previousResult, action, variables); + }, }), props: ({data}) => separateDataAndRoot(data) }); @@ -114,3 +118,86 @@ export default compose( branch(props => !props.auth.checkedInitialLogin && props.config, renderComponent(Spinner)), withQuery )(EmbedContainer); + +/** + * Reduce editComment mutation actions + * producing a new queryStream result where asset.comments reflects the edit + */ +function reduceEditCommentActionsToUpdateStreamQuery(previousResult, action) { + if ( ! (action.type === 'APOLLO_MUTATION_RESULT' && action.operationName === 'editComment')) { + return previousResult; + } + const resultHasErrors = (result) => { + try { + return result.data.editComment.errors.length > 0; + } catch (error) { + + // expected if no errors; + return false; + } + }; + if (resultHasErrors(action.result)) { + return previousResult; + } + const {variables: {id, edit}, result: {data: {editComment: {comment: {status}}}}} = action; + const updateCommentWithEdit = (comment, edit) => { + const {body} = edit; + const editedComment = update(comment, { + $merge: { + body + }, + editing: {$merge:{edited:true}} + }); + return editedComment; + }; + const commentIsStillVisible = (comment) => { + return ! ((id === comment.id) && (['PREMOD', 'REJECTED'].includes(status))); + }; + const resultReflectingEdit = update(previousResult, { + asset: { + comments: { + $apply: comments => { + return comments.filter(commentIsStillVisible).map(comment => { + let replyWasEditedToBeHidden = false; + if (comment.id === id) { + return updateCommentWithEdit(comment, edit); + } + const commentWithUpdatedReplies = update(comment, { + replies: { + $apply: (comments) => { + return comments + .filter(c => { + if (commentIsStillVisible(c)) { + return true; + } + replyWasEditedToBeHidden = true; + return false; + }) + .map(comment => { + if (comment.id === id) { + return updateCommentWithEdit(comment, edit); + } + return comment; + }); + } + }, + }); + + // If a reply was edited to be hdiden, then this parent needs its replyCount to be decremented. + if (replyWasEditedToBeHidden) { + return update(commentWithUpdatedReplies, { + replyCount: { + $apply: (replyCount) => { + return replyCount - 1; + } + } + }); + } + return commentWithUpdatedReplies; + }); + } + } + } + }); + return resultReflectingEdit; +} diff --git a/client/coral-embed-stream/src/containers/Stream.js b/client/coral-embed-stream/src/containers/Stream.js index 16714df06..443008913 100644 --- a/client/coral-embed-stream/src/containers/Stream.js +++ b/client/coral-embed-stream/src/containers/Stream.js @@ -6,7 +6,7 @@ import uniqBy from 'lodash/uniqBy'; import sortBy from 'lodash/sortBy'; import isNil from 'lodash/isNil'; import {NEW_COMMENT_COUNT_POLL_INTERVAL} from '../constants/stream'; -import {postComment, postFlag, postDontAgree, deleteAction, addCommentTag, removeCommentTag, ignoreUser} from 'coral-framework/graphql/mutations'; +import {postComment, postFlag, postDontAgree, deleteAction, addCommentTag, removeCommentTag, ignoreUser, editComment} from 'coral-framework/graphql/mutations'; import {notificationActions, authActions} from 'coral-framework'; import {editName} from 'coral-framework/actions/user'; import {setCommentCountCache, setActiveReplyBox} from '../actions/stream'; @@ -243,5 +243,6 @@ export default compose( removeCommentTag, ignoreUser, deleteAction, + editComment, )(StreamContainer); diff --git a/client/coral-embed-stream/style/default.css b/client/coral-embed-stream/style/default.css index d1afed204..42a0d7c85 100644 --- a/client/coral-embed-stream/style/default.css +++ b/client/coral-embed-stream/style/default.css @@ -191,12 +191,13 @@ hr { } .coral-plugin-commentbox-textarea { + color: #262626; flex: 1; - padding: 5px; + padding: 1em; min-height: 100px; margin-top: 10px; font-size: 16px; - border: 1px solid #ccc; + border: 1px solid #9E9E9E; } .coral-plugin-commentbox-button-container { @@ -317,9 +318,7 @@ button.comment__action-button[disabled], } .coral-plugin-pubdate-text { - color: #696969; display: inline-block; - font-size: .75rem; margin-left: 5px; } diff --git a/client/coral-framework/graphql/fragments/commentView.graphql b/client/coral-framework/graphql/fragments/commentView.graphql index 0ed5e00b8..76f4c8caa 100644 --- a/client/coral-framework/graphql/fragments/commentView.graphql +++ b/client/coral-framework/graphql/fragments/commentView.graphql @@ -15,4 +15,8 @@ fragment commentView on Comment { action_summaries { ...actionSummaryView } + editing { + edited + editableUntil + } } diff --git a/client/coral-framework/graphql/mutations/editComment.graphql b/client/coral-framework/graphql/mutations/editComment.graphql new file mode 100644 index 000000000..3cd1ec323 --- /dev/null +++ b/client/coral-framework/graphql/mutations/editComment.graphql @@ -0,0 +1,10 @@ +mutation editComment ($id: ID!, $asset_id: ID!, $edit: EditCommentInput) { + editComment(id:$id, asset_id:$asset_id, edit:$edit) { + comment { + status + } + errors { + translation_key + } + } +} diff --git a/client/coral-framework/graphql/mutations/index.js b/client/coral-framework/graphql/mutations/index.js index bdd7fd1a1..4fb87c42c 100644 --- a/client/coral-framework/graphql/mutations/index.js +++ b/client/coral-framework/graphql/mutations/index.js @@ -7,6 +7,7 @@ import ADD_COMMENT_TAG from './addCommentTag.graphql'; import REMOVE_COMMENT_TAG from './removeCommentTag.graphql'; import IGNORE_USER from './ignoreUser.graphql'; import STOP_IGNORING_USER from './stopIgnoringUser.graphql'; +import EDIT_COMMENT from './editComment.graphql'; import commentView from '../fragments/commentView.graphql'; @@ -171,3 +172,20 @@ export const stopIgnoringUser = graphql(STOP_IGNORING_USER, { }; } }); + +export const editComment = graphql(EDIT_COMMENT, { + props: ({mutate}) => { + return { + editComment: (id, asset_id, edit) => { + return mutate({ + variables: { + id, + asset_id, + edit, + }, + refetchQueries: [] + }); + } + }; + } +}); diff --git a/client/coral-framework/translations.json b/client/coral-framework/translations.json index 88ead57af..40860fc3a 100644 --- a/client/coral-framework/translations.json +++ b/client/coral-framework/translations.json @@ -22,9 +22,21 @@ "comment": "comment", "comments": "comments", "commentIsIgnored": "This comment is hidden because you ignored this user.", + "editComment": { + "bodyInputLabel": "Edit this comment", + "saveButton": "Save changes", + "editWindowExpired": "You can no longer edit this comment. The time window to do so has expired. Why not post another one?", + "editWindowExpiredClose": "Close", + "editWindowTimerPrefix": "Edit Window: ", + "second": "second", + "secondsPlural": "seconds", + "unexpectedError": "Unexpected error while saving changes. Sorry!" + }, "error": { + "editWindowExpired": "You can no longer edit this comment. The time window to do so has expired.", "emailNotVerified": "Email address {0} not verified.", "email": "Not a valid E-Mail", + "networkError": "Failed to connect to server. Check your internet connection and try again.", "password": "Password must be at least 8 characters", "username": "Usernames can contain letters, numbers and _ only", "confirmPassword": "Passwords don't match. Please, check again", @@ -61,9 +73,21 @@ "comments": "commentarios", "commentIsIgnored": "Este comentario está escondido porque has ignorado al usuario.", "showAllComments": "Mostrar todos los comentarios", + "editComment": { + "bodyInputLabel": "Editar este comentario", + "saveButton": "Guardar cambios", + "editWindowExpired": "Ya no puedes editar este comentario. La ventana de tiempo para hacerlo ha caducado. ¿Por qué no publicar otro?", + "editWindowExpiredClose": "Cerca", + "editWindowTimerPrefix": "Ventana de edición: ", + "second": "segundo", + "secondsPlural": "segundos", + "unexpectedError": "Unexpected error while saving changes. Sorry!" + }, "error": { + "editWindowExpired": "Ya no puedes editar este comentario. La ventana de tiempo para hacerlo ha caducado.", "emailNotVerified": "E-mail {0} no verificado.", "email": "No es un e-mail válido", + "networkError": "Error al conectar con el servidor. Compruebe su conexión a Internet y vuelva a intentarlo.", "password": "La contraseña debe tener por lo menos 8 caracteres", "username": "Los nombres pueden contener letras, números y _", "organizationName": "El nombre de la organización debe contener letras y/o números.", diff --git a/client/coral-plugin-commentbox/CommentBox.js b/client/coral-plugin-commentbox/CommentBox.js index 5d0906b6e..e703a3058 100644 --- a/client/coral-plugin-commentbox/CommentBox.js +++ b/client/coral-plugin-commentbox/CommentBox.js @@ -1,28 +1,46 @@ import React, {PropTypes} from 'react'; -import {Button} from 'coral-ui'; -import {connect} from 'react-redux'; import {I18n} from '../coral-framework'; import translations from './translations.json'; import Slot from 'coral-framework/components/Slot'; +import {connect} from 'react-redux'; +import {CommentForm} from './CommentForm'; -const name = 'coral-plugin-commentbox'; +export const name = 'coral-plugin-commentbox'; +// Given a newly posted comment's status, show a notification to the user +// if needed +export const notifyForNewCommentStatus = (addNotification, status) => { + if (status === 'REJECTED') { + addNotification('error', lang.t('comment-post-banned-word')); + } else if (status === 'PREMOD') { + addNotification('success', lang.t('comment-post-notif-premod')); + } +}; + +/** + * Container for posting a new Comment + */ class CommentBox extends React.Component { - constructor(props) { super(props); this.state = { username: '', - body: '', + + // incremented on successful post to clear form + postedCount: 0, hooks: { preSubmit: [], postSubmit: [] } }; } - - postComment = () => { + static get defaultProps() { + return { + setCommentCountCache: () => {} + }; + } + postComment = ({body}) => { const { commentPostedHandler, postItem, @@ -37,7 +55,7 @@ class CommentBox extends React.Component { let comment = { asset_id: assetId, parent_id: parentId, - body: this.state.body, + body, ...this.props.commentBox }; @@ -53,11 +71,11 @@ class CommentBox extends React.Component { // Execute postSubmit Hooks this.state.hooks.postSubmit.forEach(hook => hook(data)); + notifyForNewCommentStatus(addNotification, postedComment.status); + if (postedComment.status === 'REJECTED') { - addNotification('error', lang.t('comment-post-banned-word')); !isReply && setCommentCountCache(commentCountCache); } else if (postedComment.status === 'PREMOD') { - addNotification('success', lang.t('comment-post-notif-premod')); !isReply && setCommentCountCache(commentCountCache); } @@ -69,7 +87,8 @@ class CommentBox extends React.Component { console.error(err); !isReply && setCommentCountCache(commentCountCache); }); - this.setState({body: ''}); + + this.setState({postedCount: this.state.postedCount + 1}); } registerHook = (hookType = '', hook = () => {}) => { @@ -126,69 +145,39 @@ class CommentBox extends React.Component { const {styles, isReply, authorId, maxCharCount} = this.props; let {cancelButtonClicked} = this.props; - const length = this.state.body.length; - const enablePostComment = !length || (maxCharCount && length > maxCharCount); - if (isReply && typeof cancelButtonClicked !== 'function') { console.warn('the CommentBox component should have a cancelButtonClicked callback defined if it lives in a Reply'); cancelButtonClicked = () => {}; } return
-
- -