);
}
@@ -14,3 +15,8 @@ export default function Slot ({fill, inline = false, ...rest}) {
Slot.propTypes = {
fill: React.PropTypes.string
};
+
+const mapStateToProps = ({config: {plugin_config = {}}}) => ({plugin_config});
+
+export default connect(mapStateToProps, null)(Slot);
+
diff --git a/client/coral-framework/constants/asset.js b/client/coral-framework/constants/asset.js
index c26dd099d..40f746706 100644
--- a/client/coral-framework/constants/asset.js
+++ b/client/coral-framework/constants/asset.js
@@ -8,4 +8,3 @@ export const UPDATE_ASSET_SETTINGS_FAILURE = 'UPDATE_ASSET_SETTINGS_FAILURE';
export const OPEN_COMMENTS = 'OPEN_COMMENTS';
export const CLOSE_COMMENTS = 'CLOSE_COMMENTS';
-
diff --git a/client/coral-framework/constants/config.js b/client/coral-framework/constants/config.js
deleted file mode 100644
index 5dca44ba9..000000000
--- a/client/coral-framework/constants/config.js
+++ /dev/null
@@ -1,9 +0,0 @@
-export const UPDATE_CONFIG_REQUEST = 'UPDATE_CONFIG_REQUEST';
-export const UPDATE_CONFIG_SUCCESS = 'UPDATE_CONFIG_SUCCESS';
-export const UPDATE_CONFIG_FAILURE = 'UPDATE_CONFIG_FAILURE';
-
-export const UPDATE_CONFIG = 'UPDATE_CONFIG';
-
-export const OPEN_COMMENTS = 'OPEN_COMMENTS';
-export const CLOSE_COMMENTS = 'CLOSE_COMMENTS';
-export const ADD_ITEM = 'ADD_ITEM';
diff --git a/client/coral-framework/graphql/fragments/commentView.graphql b/client/coral-framework/graphql/fragments/commentView.graphql
index 71a6ae2d4..1832cc77d 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
;
}
}
CommentBox.propTypes = {
+
+ // Initial value for underlying comment body textarea
+ defaultValue: PropTypes.string,
charCountEnable: PropTypes.bool.isRequired,
maxCharCount: PropTypes.number,
commentPostedHandler: PropTypes.func,
@@ -199,6 +188,7 @@ CommentBox.propTypes = {
authorId: PropTypes.string.isRequired,
isReply: PropTypes.bool.isRequired,
canPost: PropTypes.bool,
+ setCommentCountCache: PropTypes.func,
};
const mapStateToProps = ({commentBox}) => ({commentBox});
diff --git a/client/coral-plugin-commentbox/CommentForm.js b/client/coral-plugin-commentbox/CommentForm.js
new file mode 100644
index 000000000..dc27a1906
--- /dev/null
+++ b/client/coral-plugin-commentbox/CommentForm.js
@@ -0,0 +1,137 @@
+import React, {PropTypes} from 'react';
+import {Button} from 'coral-ui';
+import classnames from 'classnames';
+import {I18n} from '../coral-framework';
+import translations from './translations.json';
+import Slot from 'coral-framework/components/Slot';
+
+import {name} from './CommentBox';
+
+const lang = new I18n(translations);
+
+/**
+ * Common UI for Creating or Editing a Comment
+ */
+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,
+
+ // 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,
+
+ // className to add to buttons
+ buttonClass: PropTypes.string,
+ }
+ 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, buttonClass, charCountEnable} = this.props;
+
+ const body = this.state.body;
+ const length = body.length;
+ const isNotValidLength = (length) => !length || (maxCharCount && length > maxCharCount);
+ const disablePostComment = (charCountEnable && isNotValidLength(length)) || ! saveCommentEnabled({body});
+
+ return
+
+
+
+ {
+ this.props.charCountEnable &&
+
maxCharCount ? `${name}-char-max` : ''}`}>
+ {maxCharCount && `${maxCharCount - length} ${lang.t('characters-remaining')}`}
+
+ }
+
+ { this.props.buttonContainerStart }
+ {
+ typeof this.props.cancelButtonClicked === 'function' && (
+
+ )
+ }
+
+
+
;
+ }
+}
diff --git a/client/coral-plugin-flags/FlagButton.js b/client/coral-plugin-flags/FlagButton.js
index e5daf72c9..fc7c427b0 100644
--- a/client/coral-plugin-flags/FlagButton.js
+++ b/client/coral-plugin-flags/FlagButton.js
@@ -15,8 +15,7 @@ class FlagButton extends Component {
message: '',
step: 0,
posted: false,
- localPost: null,
- localDelete: false
+ localPost: null
}
componentDidUpdate () {
@@ -27,17 +26,12 @@ class FlagButton extends Component {
// When the "report" button is clicked expand the menu
onReportClick = () => {
- const {currentUser, deleteAction, flaggedByCurrentUser, flag} = this.props;
- const {localPost, localDelete} = this.state;
- const localFlagged = (flaggedByCurrentUser && !localDelete) || localPost;
+ const {currentUser} = this.props;
if (!currentUser) {
this.props.showSignInDialog();
return;
}
- if (localFlagged) {
- this.setState((prev) => prev.localPost ? {...prev, localPost: null, step: 0} : {...prev, localDelete: true});
- deleteAction(localPost || flag.current_user.id);
- } else if (this.state.showMenu){
+ if (this.state.showMenu) {
this.closeMenu();
} else {
this.setState({showMenu: true});
@@ -136,14 +130,14 @@ class FlagButton extends Component {
render () {
const {getPopupMenu, flaggedByCurrentUser} = this.props;
- const {localPost, localDelete} = this.state;
- const flagged = (flaggedByCurrentUser && !localDelete) || localPost;
+ const {localPost} = this.state;
+ const flagged = flaggedByCurrentUser || localPost;
const popupMenu = getPopupMenu[this.state.step](this.state.itemType);
return