mirror of
https://github.com/wassname/talk.git
synced 2026-08-16 11:29:31 +08:00
merge with master after huge coral-embed-stream refactor
This commit is contained in:
@@ -0,0 +1,204 @@
|
||||
import React, {PropTypes} from 'react';
|
||||
import {notifyForNewCommentStatus, CommentForm} from 'coral-plugin-commentbox/CommentBox';
|
||||
import styles from './Comment.css';
|
||||
|
||||
import {Icon} from 'coral-ui';
|
||||
import I18n from 'coral-framework/modules/i18n/i18n';
|
||||
import translations from 'coral-framework/translations';
|
||||
const lang = new I18n(translations);
|
||||
|
||||
const getEditableUntilDate = (comment) => {
|
||||
const editing = comment && comment.editing;
|
||||
const editableUntil = editing && editing.editableUntil && new Date(Date.parse(editing.editableUntil));
|
||||
return editableUntil;
|
||||
};
|
||||
|
||||
/**
|
||||
* 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 (
|
||||
<div className={styles.editCommentForm}>
|
||||
<CommentForm
|
||||
defaultValue={this.props.comment.body}
|
||||
charCountEnable={this.props.asset.settings.charCountEnable}
|
||||
maxCharCount={this.props.maxCharCount}
|
||||
saveCommentEnabled={(comment) => {
|
||||
|
||||
// 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={<span>{lang.t('editComment.saveButton')}</span>}
|
||||
saveButtonCStyle="green"
|
||||
cancelButtonClicked={this.props.stopEditing}
|
||||
buttonClass={styles.button}
|
||||
buttonContainerStart={
|
||||
<div className={styles.buttonContainerLeft}>
|
||||
<span className={styles.editWindowRemaining}>
|
||||
{
|
||||
editWindowExpired
|
||||
? <span>
|
||||
{lang.t('editComment.editWindowExpired')}
|
||||
{
|
||||
typeof this.props.stopEditing === 'function'
|
||||
? <span> <a className={styles.link} onClick={this.props.stopEditing}>{lang.t('editComment.editWindowExpiredClose')}</a></span>
|
||||
: null
|
||||
}
|
||||
</span>
|
||||
: <span>
|
||||
<Icon name="timer"/> {lang.t('editComment.editWindowTimerPrefix')}
|
||||
<CountdownSeconds
|
||||
until={editableUntil}
|
||||
classNameForMsRemaining={(remainingMs) => (remainingMs <= 10 * 1000) ? styles.editWindowAlmostOver : '' }
|
||||
/>
|
||||
</span>
|
||||
}
|
||||
</span>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Countdown the number of seconds until a given Date
|
||||
*/
|
||||
class CountdownSeconds extends React.Component {
|
||||
static propTypes = {
|
||||
until: PropTypes.instanceOf(Date).isRequired,
|
||||
classNameForMsRemaining: PropTypes.func,
|
||||
}
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.countdownInterval = null;
|
||||
}
|
||||
componentDidMount() {
|
||||
const {until} = this.props;
|
||||
const now = new Date();
|
||||
if (until - now > 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 (
|
||||
<span className={classFromProp}>
|
||||
{`${wholeSecRemaining} ${units}`}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user