Plugins renaming

This commit is contained in:
Belen Curcio
2017-07-20 11:52:36 -03:00
parent eb3a9431f9
commit 6ec33a0225
115 changed files with 0 additions and 411 deletions
+194
View File
@@ -0,0 +1,194 @@
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 = 'talk-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', t('comment_box.comment_post_banned_word'));
} else if (status === 'PREMOD') {
addNotification('success', t('comment_box.comment_post_notif_premod'));
}
};
/**
* Container for posting a new Comment
*/
class CommentBox extends React.Component {
constructor(props) {
super(props);
this.state = {
username: '',
body: '',
loadingState: '',
hooks: {
preSubmit: [],
postSubmit: []
}
};
}
handleSubmit = () => {
const {
commentPostedHandler,
postComment,
assetId,
parentId,
addNotification,
currentUser,
} = this.props;
if (!can(currentUser, 'INTERACT_WITH_COMMUNITY')) {
addNotification('error', t('error.NOT_AUTHORIZED'));
return;
}
let comment = {
asset_id: assetId,
parent_id: parentId,
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', body: ''});
const postedComment = data.createComment.comment;
// Execute postSubmit Hooks
this.state.hooks.postSubmit.forEach((hook) => hook(data));
notifyForNewCommentStatus(addNotification, postedComment.status);
if (commentPostedHandler) {
commentPostedHandler();
}
})
.catch((err) => {
this.setState({loadingState: 'error'});
forEachError(err, ({msg}) => addNotification('error', msg));
});
}
handleBodyChange = (body) => {
this.setState({body});
}
registerHook = (hookType = '', hook = () => {}) => {
if (typeof hook !== 'function') {
return console.warn(`Hooks must be functions. Please check your ${hookType} hooks`);
} else if (typeof hookType === 'string') {
this.setState((state) => ({
hooks: {
...state.hooks,
[hookType]: [
...state.hooks[hookType],
hook
]
}
}));
return {
hookType,
hook
};
} else {
return console.warn('hookTypes must be a string. Please check your hooks');
}
}
unregisterHook = (hookData) => {
const {hookType, hook} = hookData;
this.setState((state) => {
let newHooks = state.hooks[newHooks];
const idx = state.hooks[hookType].indexOf(hook);
if (idx !== -1) {
newHooks = [
...state.hooks[hookType].slice(0, idx),
...state.hooks[hookType].slice(idx + 1)
];
}
return {
hooks: {
...state.hooks,
[hookType]: newHooks
}
};
});
}
render () {
const {isReply, maxCharCount} = this.props;
let {onCancel} = this.props;
if (isReply && typeof onCancel !== 'function') {
console.warn('the CommentBox component should have a onCancel callback defined if it lives in a Reply');
onCancel = () => {};
}
return <div>
<CommentForm
defaultValue={this.props.defaultValue}
bodyInputId={isReply ? 'replyText' : 'commentText'}
bodyLabel={isReply ? t('comment_box.reply') : t('comment.comment')}
maxCharCount={maxCharCount}
charCountEnable={this.props.charCountEnable}
bodyPlaceholder={t('comment.comment')}
bodyInputId={isReply ? 'replyText' : 'commentText'}
body={this.state.body}
buttonContainerStart={<Slot
fill="commentInputDetailArea"
registerHook={this.registerHook}
unregisterHook={this.unregisterHook}
isReply={isReply}
inline
/>}
onBodyChange={this.handleBodyChange}
loadingState={this.state.loadingState}
onCancel={onCancel}
onSubmit={this.handleSubmit}
/>
</div>;
}
}
CommentBox.propTypes = {
// Initial value for underlying comment body textarea
defaultValue: PropTypes.string,
charCountEnable: PropTypes.bool.isRequired,
maxCharCount: PropTypes.number,
commentPostedHandler: PropTypes.func,
postComment: PropTypes.func.isRequired,
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});
export default connect(mapStateToProps, null)(CommentBox);
@@ -0,0 +1,142 @@
import React, {PropTypes} from 'react';
import {Button} from 'coral-ui';
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';
/**
* Common UI for Creating or Editing a Comment
*/
export class CommentForm extends React.Component {
static propTypes = {
charCountEnable: PropTypes.bool.isRequired,
maxCharCount: PropTypes.number,
// 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,
// cStyle for enabled submit <coral-ui/Button>
submitButtonCStyle: PropTypes.string,
// return whether the submit button should be enabled for the provided
// comment ({ body }) (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,
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'),
submitButtonCStyle: 'darkGrey',
submitEnabled: () => true,
};
}
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 disableSubmitButton = !length || isRespectingMaxCount(length) || !submitEnabled({body}) || loadingState === 'loading';
const disableCancelButton = loadingState === 'loading';
const disableTextArea = loadingState === 'loading';
return <div>
<div className={`${name}-container`}>
<label
htmlFor={this.props.bodyInputId}
className="screen-reader-text"
aria-hidden={true}>
{this.props.bodyLabel}
</label>
<textarea
className={`${name}-textarea`}
value={body}
placeholder={this.props.bodyPlaceholder}
id={this.props.bodyInputId}
onChange={this.onBodyChange}
rows={3}
disabled={disableTextArea}
/>
<Slot fill='commentInputArea' />
</div>
{
this.props.charCountEnable &&
<div className={`${name}-char-count ${length > maxCharCount ? `${name}-char-max` : ''}`}>
{maxCharCount && `${maxCharCount - length} ${t('comment_box.characters_remaining')}`}
</div>
}
<div className={`${name}-button-container`}>
{ this.props.buttonContainerStart }
{
typeof this.props.onCancel === 'function' && (
<Button
cStyle='darkGrey'
className={cn(`${name}-cancel-button`, cancelButtonClassName)}
onClick={this.props.onCancel}
disabled={disableCancelButton}
>
{t('comment_box.cancel')}
</Button>
)
}
<Button
cStyle={disableSubmitButton ? 'lightGrey' : this.props.submitButtonCStyle}
className={cn(`${name}-button`, submitButtonClassName, this.getButtonClassName())}
onClick={this.onClickSubmit}
disabled={disableSubmitButton ? 'disabled' : ''}>
{this.props.submitText}
</Button>
</div>
</div>;
}
}
@@ -0,0 +1,26 @@
import React from 'react';
import {shallow} from 'enzyme';
import {expect} from 'chai';
import CommentBox from '../CommentBox';
describe('CommentBox', () => {
let comment;
let render;
beforeEach(() => {
comment = {};
const postItem = (item) => {
comment.posted = item;
return Promise.resolve(4);
};
render = shallow(<CommentBox
postItem={postItem}
updateItem={(e) => comment.text = e.target.value}
item_id={'1'}
comments={['1', '2', '3']}/>);
});
it('should render the CommentBox appropriately', () => {
expect(render.contains('<div class="CommentBox"')).to.be.true;
expect(render.contains('<button class="postCommentButton"')).to.be.true;
});
});
+13
View File
@@ -0,0 +1,13 @@
export const addTag = (tag) => ({
type: 'ADD_TAG',
tag
});
export const removeTag = (idx) => ({
type: 'REMOVE_TAG',
idx
});
export const clearTags = () => ({
type: 'CLEAR_TAGS',
});
@@ -0,0 +1,3 @@
export const ADD_TAG = 'ADD_TAG';
export const REMOVE_TAG = 'REMOVE_TAG';
export const CLEAR_TAGS = 'CLEAR_TAGS';
+5
View File
@@ -0,0 +1,5 @@
import reducer from './reducer';
export default {
reducer
};
+27
View File
@@ -0,0 +1,27 @@
import {ADD_TAG, REMOVE_TAG, CLEAR_TAGS} from './constants';
const initialState = {
tags: []
};
export default function commentBox (state = initialState, action) {
switch (action.type) {
case ADD_TAG :
return {
...state,
tags: [...state.tags, action.tag]
};
case REMOVE_TAG :
return {
...state,
tags: [
...state.tags.slice(0, action.idx),
...state.tags.slice(action.idx + 1)
]
};
case CLEAR_TAGS :
return initialState;
default :
return state;
}
}
+90
View File
@@ -0,0 +1,90 @@
/**
* Example loading state animations on the button.
*/
/*
@-webkit-keyframes sk-scaleout {
0% { -webkit-transform: scale(0) }
100% {
-webkit-transform: scale(1.0);
opacity: 0;
}
}
@keyframes sk-scaleout {
0% {
-webkit-transform: scale(0);
transform: scale(0);
} 100% {
-webkit-transform: scale(1.0);
transform: scale(1.0);
opacity: 0;
}
}
@keyframes comeAndGo {
0% {
opacity: 0;
}
50% {
opacity: 1.0;
}
100% {
opacity: 0;
}
}
.buttonLoading, .buttonLoading:disabled, .buttonLoading:hover {
transition: none;
font-weight: normal;
}
.buttonSuccess, .buttonSuccess:disabled, .buttonSuccess:hover {
}
.buttonSuccess::before {
content: '✓';
display: block;
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
padding: 3px 0px;
animation: comeAndGo 2s forwards;
background: rgb(51, 204, 51);
color: white;
}
.buttonLoading::before {
content: '';
display: inline-block;
width: 40px;
height: 40px;
position: absolute;
top: 0px;
background-color: #333;
border-radius: 100%;
-webkit-animation: sk-scaleout 1.0s infinite ease-in-out;
animation: sk-scaleout 1.0s infinite ease-in-out;
}
.buttonError, .buttonError:disabled, .buttonError:hover {
}
.buttonError::before {
content: '×';
display: block;
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
padding: 3px 0px;
animation: comeAndGo 2s forwards;
background: rgb(250, 100, 100);
color: white;
}
*/