From 87f8ed401f169167e86d0db73ce80a53ed138193 Mon Sep 17 00:00:00 2001 From: Belen Curcio Date: Thu, 29 Jun 2017 10:18:40 -0300 Subject: [PATCH 01/13] Using CommentId by queryParam --- client/coral-embed/src/index.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/client/coral-embed/src/index.js b/client/coral-embed/src/index.js index 82eea8ef7..3f3c63ba3 100644 --- a/client/coral-embed/src/index.js +++ b/client/coral-embed/src/index.js @@ -190,7 +190,10 @@ Talk.render = function(el, opts) { // Compose the query to send down to the Talk API so it knows what to load. let query = {}; - query.comment_id = window.location.hash.slice(1); + let urlParams = new URLSearchParams(window.location.search); + + query.comment_id = urlParams.get('commentId'); + query.asset_id = opts.asset_id; query.asset_url = opts.asset_url; From 0312530b86e25699c1388454e3c77a8a761824ba Mon Sep 17 00:00:00 2001 From: Belen Curcio Date: Thu, 29 Jun 2017 10:20:33 -0300 Subject: [PATCH 02/13] Modifying permalink plugin --- .../talk-plugin-permalink/client/components/PermalinkButton.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/talk-plugin-permalink/client/components/PermalinkButton.js b/plugins/talk-plugin-permalink/client/components/PermalinkButton.js index 68406472e..fc8561bdc 100644 --- a/plugins/talk-plugin-permalink/client/components/PermalinkButton.js +++ b/plugins/talk-plugin-permalink/client/components/PermalinkButton.js @@ -83,7 +83,7 @@ export default class PermalinkButton extends React.Component { className={cn(styles.input, `${name}-copy-field`)} type='text' ref={(input) => this.permalinkInput = input} - defaultValue={`${asset.url}#${this.props.commentId}`} + defaultValue={`${asset.url}?commentId=${this.props.commentId}`} /> @@ -44,7 +47,9 @@ class LoadMore extends React.Component { LoadMore.propTypes = { replyCount: PropTypes.number, topLevel: PropTypes.bool.isRequired, - loadMore: PropTypes.func.isRequired + loadMore: PropTypes.func.isRequired, + moreComments: PropTypes.bool, + loadingState: PropTypes.oneOf(['', 'loading', 'success', 'error']), }; export default LoadMore; diff --git a/client/coral-embed-stream/src/components/Stream.js b/client/coral-embed-stream/src/components/Stream.js index 165b3ce4c..0245466a5 100644 --- a/client/coral-embed-stream/src/components/Stream.js +++ b/client/coral-embed-stream/src/components/Stream.js @@ -14,6 +14,7 @@ import QuestionBox from 'coral-plugin-questionbox/QuestionBox'; import IgnoredCommentTombstone from './IgnoredCommentTombstone'; import NewCount from './NewCount'; import {TransitionGroup} from 'react-transition-group'; +import {forEachError} from 'coral-framework/utils'; const hasComment = (nodes, id) => nodes.some((node) => node.id === id); @@ -53,13 +54,12 @@ function invalidateCursor(invalidated, state, props) { class Stream extends React.Component { - isLoadingMore = false; - constructor(props) { super(props); this.state = { ...resetCursors(this.state, props), keepCommentBox: false, + loadingState: '', }; } @@ -107,15 +107,15 @@ class Stream extends React.Component { }; loadMoreComments = () => { - if (!this.isLoadingMore) { - this.isLoadingMore = true; - this.props.loadMoreComments() - .then(() => this.isLoadingMore = false) - .catch((e) => { - this.isLoadingMore = false; - throw e; - }); - } + this.setState({loadingState: 'loading'}); + this.props.loadMoreComments() + .then(() => { + this.setState({loadingState: 'success'}); + }) + .catch((error) => { + this.setState({loadingState: 'error'}); + forEachError(error, ({msg}) => {this.props.addNotification('error', msg);}); + }); } // getVisibileComments returns a list containing comments @@ -163,7 +163,7 @@ class Stream extends React.Component { pluginProps, editName } = this.props; - const {keepCommentBox} = this.state; + const {keepCommentBox, loadingState} = this.state; const view = this.getVisibleComments(); const open = asset.closedAt === null; @@ -317,6 +317,7 @@ class Stream extends React.Component { topLevel={true} moreComments={asset.comments.hasNextPage} loadMore={this.loadMoreComments} + loadingState={loadingState} /> } diff --git a/client/coral-embed-stream/style/default.css b/client/coral-embed-stream/style/default.css index a752493a3..9b1232e3b 100644 --- a/client/coral-embed-stream/style/default.css +++ b/client/coral-embed-stream/style/default.css @@ -197,12 +197,12 @@ hr { } /* Comment Box Styles */ -.coral-plugin-commentbox-container { +.talk-plugin-commentbox-container { display: flex; width: 100%; } -.coral-plugin-commentbox-textarea { +.talk-plugin-commentbox-textarea { color: #262626; flex: 1; padding: 1em; @@ -212,13 +212,13 @@ hr { border: 1px solid #9E9E9E; } -.coral-plugin-commentbox-button-container { +.talk-plugin-commentbox-button-container { display: flex; justify-content: flex-end; margin-top: 10px; } -.coral-plugin-commentbox-button { +.talk-plugin-commentbox-button { float: right; margin-top: 10px; padding: 5px 10px; @@ -228,18 +228,18 @@ hr { border-radius: 2px; } -.coral-plugin-commentbox-username { +.talk-plugin-commentbox-username { width: 50%; padding-left: 5px; margin-bottom: 5px; } -.coral-plugin-commentbox-char-count { +.talk-plugin-commentbox-char-count { color: #ccc; text-align: right; } -.coral-plugin-commentbox-char-max { +.talk-plugin-commentbox-char-max { color: #d50000; } @@ -417,11 +417,11 @@ button.comment__action-button[disabled], /* Load More */ -.coral-load-more { +.talk-load-more { text-align: center; } -.coral-load-more button { +.talk-load-more button { text-align: center; color: #FFF; background-color: #2376D8; @@ -433,11 +433,11 @@ button.comment__action-button[disabled], display: inline-block; } -.coral-load-more:hover button { +.talk-load-more:hover button { background-color: #4399FF; } -.coral-load-more-replies, .coral-new-comments { +.talk-load-more-replies, .coral-new-comments { width: 100%; display: flex; justify-content: center; @@ -450,7 +450,7 @@ button.comment__action-button[disabled], z-index: 100; } -.coral-load-more-replies button.coral-load-more, .coral-new-comments button.coral-load-more{ +.talk-load-more-replies button.talk-load-more, .coral-new-comments button.talk-load-more{ width: initial; } diff --git a/client/coral-framework/utils/index.js b/client/coral-framework/utils/index.js index 0b79b7b02..1866005be 100644 --- a/client/coral-framework/utils/index.js +++ b/client/coral-framework/utils/index.js @@ -1,4 +1,5 @@ import {gql} from 'react-apollo'; +import t from 'coral-framework/services/i18n'; export const getTotalActionCount = (type, comment) => { return comment.action_summaries @@ -126,3 +127,20 @@ export function createDefaultResponseFragments(...names) { }); return result; } + +export function forEachError(error, callback) { + const errors = error.errors || [error]; + errors.forEach((e) => { + console.error(e); + + let msg = ''; + if (e.translation_key) { + msg = t(`error.${e.translation_key}`); + } else if (error.networkError) { + msg = t('error.network_error'); + } else { + msg = t('error.unexpected'); + } + callback({error: e, msg}); + }); +} diff --git a/client/coral-plugin-commentbox/CommentBox.js b/client/coral-plugin-commentbox/CommentBox.js index efa1d27ed..6e17b119f 100644 --- a/client/coral-plugin-commentbox/CommentBox.js +++ b/client/coral-plugin-commentbox/CommentBox.js @@ -2,12 +2,13 @@ import React, {PropTypes} from 'react'; import t from 'coral-framework/services/i18n'; import {can} from 'coral-framework/services/perms'; +import {forEachError} from 'coral-framework/utils'; import Slot from 'coral-framework/components/Slot'; import {connect} from 'react-redux'; import {CommentForm} from './CommentForm'; -export const name = 'coral-plugin-commentbox'; +export const name = 'talk-plugin-commentbox'; // Given a newly posted comment's status, show a notification to the user // if needed @@ -28,16 +29,17 @@ class CommentBox extends React.Component { this.state = { username: '', + body: '', + loadingState: '', - // incremented on successful post to clear form - postedCount: 0, hooks: { preSubmit: [], postSubmit: [] } }; } - postComment = ({body}) => { + + handleSubmit = () => { const { commentPostedHandler, postComment, @@ -55,15 +57,17 @@ class CommentBox extends React.Component { let comment = { asset_id: assetId, parent_id: parentId, - body, + body: this.state.body, ...this.props.commentBox }; // Execute preSubmit Hooks this.state.hooks.preSubmit.forEach((hook) => hook()); + this.setState({loadingState: 'loading'}); postComment(comment, 'comments') .then(({data}) => { + this.setState({loadingState: 'success'}); const postedComment = data.createComment.comment; // Execute postSubmit Hooks @@ -74,12 +78,17 @@ class CommentBox extends React.Component { if (commentPostedHandler) { commentPostedHandler(); } + + this.setState({body: ''}); }) .catch((err) => { - console.error(err); + this.setState({loadingState: 'error'}); + forEachError(err, ({msg}) => addNotification('error', msg)); }); + } - this.setState({postedCount: this.state.postedCount + 1}); + handleBodyChange = (body) => { + this.setState({body}); } registerHook = (hookType = '', hook = () => {}) => { @@ -130,21 +139,17 @@ class CommentBox extends React.Component { }); } - handleChange = (e) => this.setState({body: e.target.value}); - render () { - const {styles, isReply, currentUser, maxCharCount} = this.props; - let {cancelButtonClicked} = this.props; + const {isReply, maxCharCount} = this.props; + let {onCancel} = this.props; - if (isReply && typeof cancelButtonClicked !== 'function') { - console.warn('the CommentBox component should have a cancelButtonClicked callback defined if it lives in a Reply'); - cancelButtonClicked = () => {}; + if (isReply && typeof onCancel !== 'function') { + console.warn('the CommentBox component should have a onCancel callback defined if it lives in a Reply'); + onCancel = () => {}; } return
} - cancelButtonClicked={cancelButtonClicked} + onBodyChange={this.handleBodyChange} + loadingState={this.state.loadingState} + onCancel={onCancel} + onSubmit={this.handleSubmit} />
; } @@ -174,12 +182,13 @@ CommentBox.propTypes = { maxCharCount: PropTypes.number, commentPostedHandler: PropTypes.func, postComment: PropTypes.func.isRequired, - cancelButtonClicked: PropTypes.func, + onCancel: PropTypes.func, assetId: PropTypes.string.isRequired, parentId: PropTypes.string, currentUser: PropTypes.object.isRequired, isReply: PropTypes.bool.isRequired, canPost: PropTypes.bool, + addNotification: PropTypes.func.isRequired, }; const mapStateToProps = ({commentBox}) => ({commentBox}); diff --git a/client/coral-plugin-commentbox/CommentForm.js b/client/coral-plugin-commentbox/CommentForm.js index 4ce32c11f..fe0f9f6d9 100644 --- a/client/coral-plugin-commentbox/CommentForm.js +++ b/client/coral-plugin-commentbox/CommentForm.js @@ -1,9 +1,10 @@ import React, {PropTypes} from 'react'; import {Button} from 'coral-ui'; -import classnames from 'classnames'; +import cn from 'classnames'; import Slot from 'coral-framework/components/Slot'; import {name} from './CommentBox'; +import styles from './styles.css'; import t from 'coral-framework/services/i18n'; @@ -13,15 +14,8 @@ import t from 'coral-framework/services/i18n'; export class CommentForm extends React.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, @@ -38,53 +32,63 @@ export class CommentForm extends React.Component { // render inside submit button submitText: PropTypes.node, - styles: PropTypes.shape({ - textarea: PropTypes.string - }), + // cStyle for enabled submit + submitButtonCStyle: PropTypes.string, - // cStyle for enabled save - saveButtonCStyle: PropTypes.string, - - // return whether the save button should be enabled for the provided + // return whether the submit button should be enabled for the provided // comment ({ body }) (for reasons other than charCount) - saveCommentEnabled: PropTypes.func, + submitEnabled: PropTypes.func, // className to add to buttons - buttonClass: PropTypes.string, + submitButtonClassName: PropTypes.string, + cancelButtonClassName: PropTypes.string, + + body: PropTypes.string.isRequired, + onBodyChange: PropTypes.func.isRequired, + onSubmit: PropTypes.func.isRequired, + onCancel: PropTypes.func, + state: PropTypes.string, + loadingState: PropTypes.oneOf(['', 'loading', 'success', 'error']), } static get defaultProps() { return { bodyLabel: t('comment_box.comment'), bodyPlaceholder: t('comment_box.comment'), submitText: t('comment_box.post'), - saveButtonCStyle: 'darkGrey', - saveCommentEnabled: () => true, + submitButtonCStyle: 'darkGrey', + submitEnabled: () => 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, buttonClass, charCountEnable} = this.props; - const body = this.state.body; + onBodyChange = (e) => { + this.props.onBodyChange(e.target.value); + } + + onClickSubmit = () => { + this.props.onSubmit(); + } + + getButtonClassName = () => { + switch (this.props.loadingState) { + case 'loading': + return cn(`${name}-button-loading`, styles.buttonLoading); + case 'success': + return cn(`${name}-button-success`, styles.buttonSuccess); + case 'error': + return cn(`${name}-button-error`, styles.buttonError); + default: + return ''; + } + } + + render() { + const {maxCharCount, submitEnabled, cancelButtonClassName, submitButtonClassName, charCountEnable, body, loadingState} = this.props; + const length = body.length; const isRespectingMaxCount = (length) => charCountEnable && maxCharCount && length > maxCharCount; - const disablePostComment = !length || isRespectingMaxCount(length) || !saveCommentEnabled({body}); + const disableSubmitButton = !length || isRespectingMaxCount(length) || !submitEnabled({body}) || loadingState === 'loading'; + const disableCancelButton = loadingState === 'loading'; + const disableTextArea = loadingState === 'loading'; return
@@ -95,13 +99,14 @@ export class CommentForm extends React.Component { {this.props.bodyLabel}