mirror of
https://github.com/wassname/talk.git
synced 2026-06-29 15:28:40 +08:00
Merge branch 'master' into story/150848457
This commit is contained in:
@@ -5,7 +5,7 @@ import {t, timeago} from 'plugin-api/beta/client/services';
|
||||
import {Slot, CommentAuthorName} from 'plugin-api/beta/client/components';
|
||||
import {Icon} from 'plugin-api/beta/client/components/ui';
|
||||
import {pluginName} from '../../package.json';
|
||||
import Button from './Button';
|
||||
import FeaturedButton from '../containers/FeaturedButton';
|
||||
|
||||
class Comment extends React.Component {
|
||||
|
||||
@@ -50,7 +50,7 @@ class Comment extends React.Component {
|
||||
inline
|
||||
/>
|
||||
|
||||
<Button
|
||||
<FeaturedButton
|
||||
root={root}
|
||||
data={data}
|
||||
comment={comment}
|
||||
|
||||
+3
-4
@@ -1,12 +1,11 @@
|
||||
import React from 'react';
|
||||
import cn from 'classnames';
|
||||
import styles from './Button.css';
|
||||
import styles from './FeaturedButton.css';
|
||||
import {pluginName} from '../../package.json';
|
||||
import {can} from 'plugin-api/beta/client/services';
|
||||
import {withTags} from 'plugin-api/beta/client/hocs';
|
||||
import {Icon} from 'plugin-api/beta/client/components/ui';
|
||||
|
||||
const Button = (props) => {
|
||||
const FeaturedButton = (props) => {
|
||||
const {alreadyTagged, deleteTag, postTag, user} = props;
|
||||
|
||||
return can(user, 'MODERATE_COMMENTS') ? (
|
||||
@@ -23,4 +22,4 @@ const Button = (props) => {
|
||||
) : null ;
|
||||
};
|
||||
|
||||
export default withTags('featured')(Button);
|
||||
export default FeaturedButton;
|
||||
@@ -3,10 +3,9 @@ import cn from 'classnames';
|
||||
import styles from './ModActionButton.css';
|
||||
import {pluginName} from '../../package.json';
|
||||
import {t} from 'plugin-api/beta/client/services';
|
||||
import {withTags} from 'plugin-api/beta/client/hocs';
|
||||
import {Icon} from 'plugin-api/beta/client/components/ui';
|
||||
|
||||
export class Button extends React.Component {
|
||||
export class ModActionButton extends React.Component {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
@@ -30,12 +29,23 @@ export class Button extends React.Component {
|
||||
});
|
||||
}
|
||||
|
||||
handleDeleteTag = () => {
|
||||
this.props.deleteTag();
|
||||
this.props.closeMenu();
|
||||
}
|
||||
|
||||
handlePostTag = () => {
|
||||
this.props.postTag();
|
||||
this.props.closeMenu();
|
||||
}
|
||||
|
||||
render() {
|
||||
const {alreadyTagged, deleteTag, postTag} = this.props;
|
||||
const {alreadyTagged} = this.props;
|
||||
const {handleDeleteTag, handlePostTag} = this;
|
||||
|
||||
return (
|
||||
<button className={cn(`${pluginName}-tag-button`, styles.button, {[styles.featured] : alreadyTagged})}
|
||||
onClick={alreadyTagged ? deleteTag : postTag}
|
||||
onClick={alreadyTagged ? handleDeleteTag : handlePostTag}
|
||||
onMouseEnter={this.handleMouseEnter}
|
||||
onMouseLeave={this.handleMouseLeave} >
|
||||
|
||||
@@ -56,5 +66,5 @@ export class Button extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
export default withTags('featured')(Button);
|
||||
export default ModActionButton;
|
||||
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
import FeaturedButton from '../components/FeaturedButton';
|
||||
import {withTags} from 'plugin-api/beta/client/hocs';
|
||||
|
||||
export default withTags('featured')(FeaturedButton);
|
||||
@@ -1,10 +1,17 @@
|
||||
import {compose} from 'react-apollo';
|
||||
import {excludeIf} from 'plugin-api/beta/client/hocs';
|
||||
import {can} from 'plugin-api/beta/client/services';
|
||||
import Button from '../components/Button';
|
||||
import {bindActionCreators} from 'redux';
|
||||
import ModActionButton from '../components/ModActionButton';
|
||||
import {withTags, connect} from 'plugin-api/beta/client/hocs';
|
||||
import {closeMenu} from 'plugins/talk-plugin-moderation-actions/client/actions';
|
||||
|
||||
const mapDispatchToProps = (dispatch) =>
|
||||
bindActionCreators({
|
||||
closeMenu,
|
||||
}, dispatch);
|
||||
|
||||
const enhance = compose(
|
||||
excludeIf((props) => !can(props.user, 'MODERATE_COMMENTS')),
|
||||
withTags('featured'),
|
||||
connect(null, mapDispatchToProps),
|
||||
);
|
||||
|
||||
export default enhance(Button);
|
||||
export default enhance(ModActionButton);
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import Tab from './containers/Tab';
|
||||
import Tag from './containers/Tag';
|
||||
import ModActionButton from './components/ModActionButton';
|
||||
import TabPane from './containers/TabPane';
|
||||
import translations from './translations.yml';
|
||||
import update from 'immutability-helper';
|
||||
import reducer from './reducer';
|
||||
import ModTag from './containers/ModTag';
|
||||
import ModActionButton from './containers/ModActionButton';
|
||||
import ModSubscription from './containers/ModSubscription';
|
||||
import {gql} from 'react-apollo';
|
||||
|
||||
import {findCommentInEmbedQuery} from 'coral-embed-stream/src/graphql/utils';
|
||||
import {prependNewNodes} from 'plugin-api/beta/client/utils';
|
||||
@@ -60,17 +61,6 @@ export default {
|
||||
if (previous.asset.comments) {
|
||||
updated = update(previous, {
|
||||
asset: {
|
||||
comments: {
|
||||
nodes: {
|
||||
$apply: (nodes) => nodes.map((node) => {
|
||||
if (node.id === variables.id) {
|
||||
node.status = 'ACCEPTED';
|
||||
}
|
||||
|
||||
return node;
|
||||
})
|
||||
}
|
||||
},
|
||||
featuredComments: {
|
||||
nodes: {
|
||||
$apply: (nodes) => prependNewNodes(nodes, [comment]),
|
||||
@@ -84,8 +74,28 @@ export default {
|
||||
}
|
||||
|
||||
return updated;
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
update: (proxy) => {
|
||||
|
||||
if (variables.name !== 'FEATURED') {
|
||||
return;
|
||||
}
|
||||
|
||||
const fragmentId = `Comment_${variables.id}`;
|
||||
|
||||
const fragment = gql`
|
||||
fragment Talk_FeaturedComments_addTag on Comment {
|
||||
status
|
||||
}
|
||||
`;
|
||||
|
||||
const data = proxy.readFragment({fragment, id: fragmentId});
|
||||
|
||||
data.status = 'ACCEPTED';
|
||||
|
||||
proxy.writeFragment({fragment, id: fragmentId, data});
|
||||
},
|
||||
}),
|
||||
RemoveTag: ({variables}) => ({
|
||||
updateQueries: {
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
import {OPEN_MENU, CLOSE_MENU} from './constants';
|
||||
|
||||
export const openMenu = (id) => ({
|
||||
type: OPEN_MENU,
|
||||
id,
|
||||
});
|
||||
|
||||
export const closeMenu = () => ({
|
||||
type: CLOSE_MENU,
|
||||
});
|
||||
@@ -7,17 +7,15 @@ import cn from 'classnames';
|
||||
const isApproved = (status) => (status === 'ACCEPTED');
|
||||
|
||||
export default ({approveComment, comment: {status}}) => (
|
||||
<button className={cn(styles.button, 'talk-plugin-moderation-actions-reject')} onClick={approveComment}>
|
||||
{isApproved(status) ? (
|
||||
<span className={styles.approved}>
|
||||
<Icon name="check_circle" className={styles.icon} />
|
||||
{t('talk-plugin-moderation-actions.approved_comment')}
|
||||
</span>
|
||||
) : (
|
||||
<span>
|
||||
<Icon name="done" className={styles.icon} />
|
||||
{t('talk-plugin-moderation-actions.approve_comment')}
|
||||
</span>
|
||||
)}
|
||||
</button>
|
||||
isApproved(status) ? (
|
||||
<span className={styles.approved}>
|
||||
<Icon name="check_circle" className={styles.icon} />
|
||||
{t('talk-plugin-moderation-actions.approved_comment')}
|
||||
</span>
|
||||
) : (
|
||||
<button className={cn(styles.button, 'talk-plugin-moderation-actions-reject')} onClick={approveComment}>
|
||||
<Icon name="done" className={styles.icon} />
|
||||
{t('talk-plugin-moderation-actions.approve_comment')}
|
||||
</button>
|
||||
)
|
||||
);
|
||||
|
||||
+3
-3
@@ -1,4 +1,4 @@
|
||||
.tooltip {
|
||||
.menu {
|
||||
background-color: white;
|
||||
border: solid 1px #999;
|
||||
border-radius: 3px;
|
||||
@@ -14,7 +14,7 @@
|
||||
color: #616161;
|
||||
}
|
||||
|
||||
.tooltip::before{
|
||||
.menu::before{
|
||||
content: '';
|
||||
border: 10px solid transparent;
|
||||
border-top-color: #999;
|
||||
@@ -24,7 +24,7 @@
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
.tooltip::after{
|
||||
.menu::after{
|
||||
content: '';
|
||||
border: 10px solid transparent;
|
||||
border-top-color: white;
|
||||
+2
-2
@@ -1,10 +1,10 @@
|
||||
import React from 'react';
|
||||
import cn from 'classnames';
|
||||
import styles from './Tooltip.css';
|
||||
import styles from './Menu.css';
|
||||
import {t} from 'plugin-api/beta/client/services';
|
||||
|
||||
export default ({className = '', children}) => (
|
||||
<div className={cn(styles.tooltip, className)}>
|
||||
<div className={cn(styles.menu, className)}>
|
||||
<h3 className={styles.headline}>
|
||||
{t('talk-plugin-moderation-actions.moderation_actions')}
|
||||
</h3>
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import cn from 'classnames';
|
||||
import Tooltip from './Tooltip';
|
||||
import Menu from './Menu';
|
||||
import styles from './ModerationActions.css';
|
||||
import {Icon} from 'plugin-api/beta/client/components/ui';
|
||||
import ClickOutside from 'coral-framework/components/ClickOutside';
|
||||
@@ -9,51 +9,27 @@ import ApproveCommentAction from '../containers/ApproveCommentAction';
|
||||
import {Slot} from 'plugin-api/beta/client/components';
|
||||
|
||||
export default class ModerationActions extends React.Component {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.state = {
|
||||
tooltip: false
|
||||
};
|
||||
}
|
||||
|
||||
toogleTooltip = () => {
|
||||
const {tooltip} = this.state;
|
||||
this.setState({
|
||||
tooltip: !tooltip
|
||||
});
|
||||
}
|
||||
|
||||
hideTooltip = () => {
|
||||
this.setState({
|
||||
tooltip: false
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
const {tooltip} = this.state;
|
||||
const {comment, asset, data} = this.props;
|
||||
const {comment, asset, data, menuVisible, toogleMenu, hideMenu} = this.props;
|
||||
|
||||
return(
|
||||
<ClickOutside onClickOutside={this.hideTooltip}>
|
||||
<ClickOutside onClickOutside={hideMenu}>
|
||||
<div className={cn(styles.moderationActions, 'talk-plugin-moderation-actions')}>
|
||||
<span onClick={this.toogleTooltip} className={cn(styles.arrow, 'talk-plugin-moderation-actions-arrow')}>
|
||||
{tooltip ? <Icon name="keyboard_arrow_up" className={styles.icon} /> :
|
||||
<span onClick={toogleMenu} className={cn(styles.arrow, 'talk-plugin-moderation-actions-arrow')}>
|
||||
{menuVisible ? <Icon name="keyboard_arrow_up" className={styles.icon} /> :
|
||||
<Icon name="keyboard_arrow_down" className={styles.icon} />}
|
||||
</span>
|
||||
{tooltip && (
|
||||
<Tooltip>
|
||||
|
||||
{menuVisible && (
|
||||
<Menu>
|
||||
<Slot
|
||||
className="talk-plugin-modetarion-actions-slot"
|
||||
fill="moderationActions"
|
||||
queryData={{comment, asset}}
|
||||
data={data}
|
||||
/>
|
||||
|
||||
<ApproveCommentAction comment={comment} />
|
||||
<RejectCommentAction comment={comment} />
|
||||
</Tooltip>
|
||||
<ApproveCommentAction comment={comment} hideMenu={hideMenu} />
|
||||
<RejectCommentAction comment={comment} hideMenu={hideMenu} />
|
||||
</Menu>
|
||||
)}
|
||||
</div>
|
||||
</ClickOutside>
|
||||
|
||||
@@ -12,10 +12,10 @@
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
letter-spacing: 0.3px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: #D8D8D8;
|
||||
}
|
||||
.button:not(.approved):hover {
|
||||
background-color: #D8D8D8;
|
||||
}
|
||||
|
||||
.icon {
|
||||
@@ -24,6 +24,8 @@
|
||||
}
|
||||
|
||||
.approved {
|
||||
display: inline-block;
|
||||
color: #519954;
|
||||
font-weight: bold;
|
||||
padding: 6px;
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
const prefix = 'TALK_MODERATION_ACTIONS';
|
||||
|
||||
export const OPEN_MENU = `${prefix}_OPEN_MENU`;
|
||||
export const CLOSE_MENU = `${prefix}_CLOSE_MENU`;
|
||||
@@ -1,28 +1,27 @@
|
||||
import React from 'react';
|
||||
import {compose} from 'react-apollo';
|
||||
import {bindActionCreators} from 'redux';
|
||||
import {getErrorMessages} from 'plugin-api/beta/client/utils';
|
||||
import {withSetCommentStatus} from 'plugin-api/beta/client/hocs';
|
||||
import {notify} from 'plugin-api/beta/client/actions/notification';
|
||||
import ApproveCommentAction from '../components/ApproveCommentAction';
|
||||
import isNil from 'lodash/isNil';
|
||||
import {connect, withSetCommentStatus} from 'plugin-api/beta/client/hocs';
|
||||
|
||||
class ApproveCommentActionContainer extends React.Component {
|
||||
|
||||
approveComment = async () => {
|
||||
const {setCommentStatus, comment} = this.props;
|
||||
const {setCommentStatus, comment, hideMenu, notify} = this.props;
|
||||
|
||||
try {
|
||||
const result = await setCommentStatus({
|
||||
await setCommentStatus({
|
||||
commentId: comment.id,
|
||||
status: 'ACCEPTED'
|
||||
});
|
||||
|
||||
if (!isNil(result.data.setCommentStatus)) {
|
||||
throw result.data.setCommentStatus.errors;
|
||||
}
|
||||
|
||||
} catch (err) {
|
||||
}
|
||||
catch(err) {
|
||||
notify('error', getErrorMessages(err));
|
||||
}
|
||||
|
||||
hideMenu();
|
||||
}
|
||||
|
||||
render() {
|
||||
@@ -30,4 +29,14 @@ class ApproveCommentActionContainer extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
export default withSetCommentStatus(ApproveCommentActionContainer);
|
||||
const mapDispatchToProps = (dispatch) =>
|
||||
bindActionCreators({
|
||||
notify
|
||||
}, dispatch);
|
||||
|
||||
const enhance = compose(
|
||||
connect(null, mapDispatchToProps),
|
||||
withSetCommentStatus
|
||||
);
|
||||
|
||||
export default enhance(ApproveCommentActionContainer);
|
||||
|
||||
@@ -1,14 +1,72 @@
|
||||
import React from 'react';
|
||||
import {bindActionCreators} from 'redux';
|
||||
import {gql, compose} from 'react-apollo';
|
||||
import {openMenu, closeMenu} from '../actions';
|
||||
import {can} from 'plugin-api/beta/client/services';
|
||||
import {getShallowChanges} from 'plugin-api/beta/client/utils';
|
||||
import ModerationActions from '../components/ModerationActions';
|
||||
import {connect, excludeIf, withFragments} from 'plugin-api/beta/client/hocs';
|
||||
|
||||
const mapStateToProps = ({auth}) => ({
|
||||
user: auth.user
|
||||
class ModerationActionsContainer extends React.Component {
|
||||
|
||||
shouldComponentUpdate(nextProps) {
|
||||
|
||||
// Specifically handle `showMenuForComment` if it is the only change.
|
||||
const changes = getShallowChanges(this.props, nextProps);
|
||||
if (changes.length === 1 && changes[0] === 'showMenuForComment') {
|
||||
const commentId = this.props.comment.id;
|
||||
if (
|
||||
commentId !== this.props.showMenuForComment &&
|
||||
commentId !== nextProps.showMenuForComment
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Prevent Slot from rerendering when no props has shallowly changed.
|
||||
return changes.length !== 0;
|
||||
}
|
||||
|
||||
toogleMenu = () => {
|
||||
if (this.props.showMenuForComment === this.props.comment.id) {
|
||||
this.props.closeMenu();
|
||||
} else {
|
||||
this.props.openMenu(this.props.comment.id);
|
||||
}
|
||||
}
|
||||
|
||||
hideMenu = () => {
|
||||
if (this.props.showMenuForComment === this.props.comment.id) {
|
||||
this.props.closeMenu();
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return <ModerationActions
|
||||
data={this.props.data}
|
||||
root={this.props.root}
|
||||
asset={this.props.asset}
|
||||
comment={this.props.comment}
|
||||
menuVisible={this.props.showMenuForComment === this.props.comment.id}
|
||||
toogleMenu={this.toogleMenu}
|
||||
hideMenu={this.hideMenu}
|
||||
/>;
|
||||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = ({auth, talkPluginModerationActions: state}) => ({
|
||||
user: auth.user,
|
||||
showMenuForComment: state.showMenuForComment,
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch) =>
|
||||
bindActionCreators({
|
||||
openMenu,
|
||||
closeMenu,
|
||||
}, dispatch);
|
||||
|
||||
const enhance = compose(
|
||||
connect(mapStateToProps),
|
||||
connect(mapStateToProps, mapDispatchToProps),
|
||||
withFragments({
|
||||
asset: gql`
|
||||
fragment TalkModerationActions_asset on Asset {
|
||||
@@ -29,4 +87,4 @@ const enhance = compose(
|
||||
excludeIf((props) => !can(props.user, 'MODERATE_COMMENTS')),
|
||||
);
|
||||
|
||||
export default enhance(ModerationActions);
|
||||
export default enhance(ModerationActionsContainer);
|
||||
|
||||
@@ -1,28 +1,27 @@
|
||||
import React from 'react';
|
||||
import {compose} from 'react-apollo';
|
||||
import {bindActionCreators} from 'redux';
|
||||
import {getErrorMessages} from 'plugin-api/beta/client/utils';
|
||||
import {withSetCommentStatus} from 'plugin-api/beta/client/hocs';
|
||||
import {notify} from 'plugin-api/beta/client/actions/notification';
|
||||
import RejectCommentAction from '../components/RejectCommentAction';
|
||||
import isNil from 'lodash/isNil';
|
||||
import {connect, withSetCommentStatus} from 'plugin-api/beta/client/hocs';
|
||||
|
||||
class RejectCommentActionContainer extends React.Component {
|
||||
|
||||
rejectComment = async () => {
|
||||
const {setCommentStatus, comment} = this.props;
|
||||
const {setCommentStatus, comment, hideMenu, notify} = this.props;
|
||||
|
||||
try {
|
||||
const result = await setCommentStatus({
|
||||
await setCommentStatus({
|
||||
commentId: comment.id,
|
||||
status: 'REJECTED'
|
||||
});
|
||||
|
||||
if (!isNil(result.data.setCommentStatus)) {
|
||||
throw result.data.setCommentStatus.errors;
|
||||
}
|
||||
|
||||
} catch (err) {
|
||||
}
|
||||
catch(err) {
|
||||
notify('error', getErrorMessages(err));
|
||||
}
|
||||
|
||||
hideMenu();
|
||||
}
|
||||
|
||||
render() {
|
||||
@@ -30,4 +29,14 @@ class RejectCommentActionContainer extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
export default withSetCommentStatus(RejectCommentActionContainer);
|
||||
const mapDispatchToProps = (dispatch) =>
|
||||
bindActionCreators({
|
||||
notify
|
||||
}, dispatch);
|
||||
|
||||
const enhance = compose(
|
||||
connect(null, mapDispatchToProps),
|
||||
withSetCommentStatus
|
||||
);
|
||||
|
||||
export default enhance(RejectCommentActionContainer);
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import ModerationActions from './containers/ModerationActions';
|
||||
import translations from './translations.yml';
|
||||
import reducer from './reducer';
|
||||
|
||||
export default {
|
||||
slots: {
|
||||
commentInfoBar: [ModerationActions],
|
||||
},
|
||||
reducer,
|
||||
translations
|
||||
};
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
import {OPEN_MENU, CLOSE_MENU} from './constants';
|
||||
|
||||
const initialState = {
|
||||
showMenuForComment: null,
|
||||
};
|
||||
|
||||
export default function reducer(state = initialState, action) {
|
||||
switch (action.type) {
|
||||
case OPEN_MENU:
|
||||
return {
|
||||
...state,
|
||||
showMenuForComment: action.id
|
||||
};
|
||||
case CLOSE_MENU:
|
||||
return {
|
||||
...state,
|
||||
showMenuForComment: null
|
||||
};
|
||||
default :
|
||||
return state;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user