Merge branch 'master' into hackday-upvote-downvote-ama

This commit is contained in:
Belén Curcio
2018-03-20 15:53:15 -03:00
committed by GitHub
118 changed files with 1686 additions and 1282 deletions
@@ -1,14 +1,13 @@
import React from 'react';
import PropTypes from 'prop-types';
import Menu from './Menu';
import styles from './AuthorName.css';
import { ClickOutside } from 'plugin-api/beta/client/components';
import cn from 'classnames';
export default ({
data,
root,
asset,
comment,
const AuthorName = ({
slotPassthrough,
username,
contentSlot,
menuVisible,
toggleMenu,
@@ -21,18 +20,23 @@ export default ({
className={cn(styles.button, 'talk-plugin-author-menu-button')}
onClick={toggleMenu}
>
<span className={styles.name}>{comment.user.username}</span>
<span className={styles.name}>{username}</span>
</button>
{menuVisible && (
<Menu
data={data}
root={root}
asset={asset}
comment={comment}
contentSlot={contentSlot}
/>
<Menu slotPassthrough={slotPassthrough} contentSlot={contentSlot} />
)}
</div>
</ClickOutside>
);
};
AuthorName.propTypes = {
slotPassthrough: PropTypes.object.isRequired,
username: PropTypes.string.isRequired,
menuVisible: PropTypes.bool.isRequired,
toggleMenu: PropTypes.func.isRequired,
hideMenu: PropTypes.func.isRequired,
contentSlot: PropTypes.string,
};
export default AuthorName;
@@ -1,17 +1,14 @@
import React from 'react';
import PropTypes from 'prop-types';
import styles from './Menu.css';
import { Slot } from 'plugin-api/beta/client/components';
import cn from 'classnames';
export default ({ data, root, asset, comment, contentSlot }) => {
const Menu = ({ slotPassthrough, contentSlot }) => {
if (contentSlot) {
return (
<div className={cn(styles.menu, 'talk-plugin-author-menu-popup')}>
<Slot
fill={contentSlot}
data={data}
queryData={{ asset, root, comment }}
/>
<Slot fill={contentSlot} passthrough={slotPassthrough} />
</div>
);
}
@@ -21,15 +18,20 @@ export default ({ data, root, asset, comment, contentSlot }) => {
<Slot
className={cn('talk-plugin-author-menu-infos')}
fill={'authorMenuInfos'}
data={data}
queryData={{ asset, root, comment }}
passthrough={slotPassthrough}
/>
<Slot
className={cn(styles.actions, 'talk-plugin-author-menu-actions')}
fill={'authorMenuActions'}
data={data}
queryData={{ asset, root, comment }}
passthrough={slotPassthrough}
/>
</div>
);
};
Menu.propTypes = {
slotPassthrough: PropTypes.object.isRequired,
contentSlot: PropTypes.string,
};
export default Menu;
@@ -1,4 +1,5 @@
import React from 'react';
import PropTypes from 'prop-types';
import { connect, withFragments } from 'plugin-api/beta/client/hocs';
import { bindActionCreators } from 'redux';
import AuthorName from '../components/AuthorName';
@@ -47,21 +48,39 @@ class AuthorNameContainer extends React.Component {
};
render() {
const {
root,
asset,
comment,
contentSlot,
showMenuForComment,
} = this.props;
const slotPassthrough = { root, asset, comment };
return (
<AuthorName
data={this.props.data}
root={this.props.root}
asset={this.props.asset}
comment={this.props.comment}
contentSlot={this.props.contentSlot}
menuVisible={this.props.showMenuForComment === this.props.comment.id}
username={comment.user.username}
contentSlot={contentSlot}
menuVisible={showMenuForComment === comment.id}
toggleMenu={this.toggleMenu}
hideMenu={this.hideMenu}
slotPassthrough={slotPassthrough}
/>
);
}
}
AuthorNameContainer.propTypes = {
root: PropTypes.object.isRequired,
asset: PropTypes.object.isRequired,
comment: PropTypes.object.isRequired,
contentSlot: PropTypes.string,
showMenuForComment: PropTypes.string,
openMenu: PropTypes.func.isRequired,
closeMenu: PropTypes.func.isRequired,
};
const slots = ['authorMenuInfos', 'authorMenuActions'];
const mapStateToProps = ({ talkPluginAuthorMenu: state }) => ({
@@ -18,8 +18,8 @@ class Comment extends React.Component {
};
render() {
const { comment, asset, root, data } = this.props;
const queryData = { comment, asset, root };
const { comment, asset, root } = this.props;
const slotPassthrough = { comment, asset, root };
return (
<div className={cn(styles.root, `${pluginName}-comment`)}>
<Slot
@@ -27,8 +27,7 @@ class Comment extends React.Component {
className={cn(styles.quote, `${pluginName}-comment-body`)}
fill="commentContent"
defaultComponent={CommentContent}
data={data}
queryData={queryData}
passthrough={slotPassthrough}
/>
<div className={cn(`${pluginName}-comment-username-box`)}>
@@ -36,8 +35,7 @@ class Comment extends React.Component {
className={cn(styles.username, `${pluginName}-comment-username`)}
fill="commentAuthorName"
defaultComponent={CommentAuthorName}
queryData={queryData}
data={data}
passthrough={slotPassthrough}
inline
/>
@@ -45,9 +43,7 @@ class Comment extends React.Component {
fill="commentTimestamp"
defaultComponent={CommentTimestamp}
className={cn(styles.timestamp, `${pluginName}-comment-timestamp`)}
created_at={comment.created_at}
data={data}
queryData={queryData}
passthrough={{ created_at: comment.created_at, ...slotPassthrough }}
inline
/>
</div>
@@ -62,19 +58,11 @@ class Comment extends React.Component {
>
<Slot
fill="commentReactions"
root={root}
data={data}
comment={comment}
asset={asset}
passthrough={slotPassthrough}
inline
/>
<FeaturedButton
root={root}
data={data}
comment={comment}
asset={asset}
/>
<FeaturedButton root={root} comment={comment} asset={asset} />
</div>
<div
className={cn(
@@ -22,7 +22,6 @@ class TabPane extends React.Component {
render() {
const {
root,
data,
asset: { featuredComments, ...asset },
viewComment,
} = this.props;
@@ -32,7 +31,6 @@ class TabPane extends React.Component {
<Comment
key={comment.id}
root={root}
data={data}
comment={comment}
asset={asset}
viewComment={viewComment}
@@ -1,6 +1,7 @@
import React from 'react';
import { gql } from 'react-apollo';
import { subscriptionFields } from 'coral-admin/src/routes/Moderation/graphql';
import { compose, withSubscribeToMore } from 'plugin-api/beta/client/hocs';
class ModIndicatorSubscription extends React.Component {
subscriptions = null;
@@ -27,7 +28,7 @@ class ModIndicatorSubscription extends React.Component {
},
];
this.subscriptions = configs.map(config =>
this.props.data.subscribeToMore(config)
this.props.subscribeToMore(config)
);
}
@@ -60,4 +61,4 @@ const COMMENT_UNFEATURED_SUBSCRIPTION = gql`
}
`;
export default ModIndicatorSubscription;
export default compose(withSubscribeToMore)(ModIndicatorSubscription);
@@ -6,6 +6,11 @@ import { getDefinitionName } from 'coral-framework/utils';
import truncate from 'lodash/truncate';
import t from 'coral-framework/services/i18n';
import { subscriptionFields } from 'coral-admin/src/routes/Moderation/graphql';
import {
compose,
withSubscribeToMore,
withVariables,
} from 'plugin-api/beta/client/hocs';
function prepareNotificationText(text) {
return truncate(text, { length: 50 }).replace('\n', ' ');
@@ -19,7 +24,7 @@ class ModSubscription extends React.Component {
{
document: COMMENT_FEATURED_SUBSCRIPTION,
variables: {
assetId: this.props.data.variables.asset_id,
assetId: this.props.variables.asset_id,
},
updateQuery: (
prev,
@@ -39,7 +44,7 @@ class ModSubscription extends React.Component {
{
document: COMMENT_UNFEATURED_SUBSCRIPTION,
variables: {
assetId: this.props.data.variables.asset_id,
assetId: this.props.variables.asset_id,
},
updateQuery: (
prev,
@@ -62,7 +67,7 @@ class ModSubscription extends React.Component {
},
];
this.subscriptions = configs.map(config =>
this.props.data.subscribeToMore(config)
this.props.subscribeToMore(config)
);
}
@@ -111,4 +116,8 @@ const mapStateToProps = state => ({
user: state.auth.user,
});
export default connect(mapStateToProps, null)(ModSubscription);
export default compose(
connect(mapStateToProps, null),
withVariables,
withSubscribeToMore
)(ModSubscription);
@@ -2,7 +2,12 @@ import React from 'react';
import { bindActionCreators } from 'redux';
import { compose, gql } from 'react-apollo';
import TabPane from '../components/TabPane';
import { withFragments, connect } from 'plugin-api/beta/client/hocs';
import {
withFragments,
connect,
withFetchMore,
withVariables,
} from 'plugin-api/beta/client/hocs';
import Comment from '../containers/Comment';
import { viewComment } from 'coral-embed-stream/src/actions/stream';
import {
@@ -13,15 +18,15 @@ import update from 'immutability-helper';
class TabPaneContainer extends React.Component {
loadMore = () => {
return this.props.data.fetchMore({
return this.props.fetchMore({
query: LOAD_MORE_QUERY,
variables: {
limit: 5,
cursor: this.props.asset.featuredComments.endCursor,
asset_id: this.props.asset.id,
sortOrder: this.props.data.variables.sortOrder,
sortBy: this.props.data.variables.sortBy,
excludeIgnored: this.props.data.variables.excludeIgnored,
sortOrder: this.props.variables.sortOrder,
sortBy: this.props.variables.sortBy,
excludeIgnored: this.props.variables.excludeIgnored,
},
updateQuery: (previous, { fetchMoreResult: { comments } }) => {
const updated = update(previous, {
@@ -86,6 +91,8 @@ const mapDispatchToProps = dispatch =>
const enhance = compose(
connect(null, mapDispatchToProps),
withFetchMore,
withVariables,
withFragments({
root: gql`
fragment TalkFeaturedComments_TabPane_root on RootQuery {
@@ -10,7 +10,7 @@ import {
class FlagDetails extends Component {
render() {
const { comment: { actions }, more, data, root, comment } = this.props;
const { comment: { actions }, more, root, comment } = this.props;
const flagActions =
actions && actions.filter(a => a.__typename === 'FlagAction');
@@ -26,7 +26,7 @@ class FlagDetails extends Component {
}, {});
const reasons = Object.keys(summaries);
const queryData = {
const slotPassthrough = {
root,
comment,
};
@@ -52,12 +52,11 @@ class FlagDetails extends Component {
{more && (
<IfSlotIsNotEmpty
slot="adminCommentMoreFlagDetails"
queryData={queryData}
passthrough={slotPassthrough}
>
<Slot
fill="adminCommentMoreFlagDetails"
data={data}
queryData={queryData}
passthrough={slotPassthrough}
/>
</IfSlotIsNotEmpty>
)}
@@ -68,7 +67,6 @@ class FlagDetails extends Component {
FlagDetails.propTypes = {
more: PropTypes.bool,
data: PropTypes.object,
root: PropTypes.object,
comment: PropTypes.shape({
actions: PropTypes.arrayOf(
@@ -24,7 +24,7 @@ export default {
createComment: {
comment: {
user: {
created_at: new Date(),
created_at: new Date().toISOString(),
__typename: 'User',
},
__typename: 'Comment',
@@ -16,12 +16,16 @@ export default class ModerationActions extends React.Component {
comment,
root,
asset,
data,
menuVisible,
toogleMenu,
hideMenu,
} = this.props;
const slotPassthrough = {
comment,
asset,
};
return (
<ClickOutside onClickOutside={hideMenu}>
<div
@@ -45,8 +49,7 @@ export default class ModerationActions extends React.Component {
<Slot
className="talk-plugin-modetarion-actions-slot"
fill="moderationActions"
queryData={{ comment, asset }}
data={data}
passthrough={slotPassthrough}
/>
<ApproveCommentAction comment={comment} hideMenu={hideMenu} />
<RejectCommentAction comment={comment} hideMenu={hideMenu} />
@@ -67,7 +70,6 @@ ModerationActions.propTypes = {
comment: PropTypes.object,
root: PropTypes.object,
asset: PropTypes.object,
data: PropTypes.object,
menuVisible: PropTypes.bool,
toogleMenu: PropTypes.func,
hideMenu: PropTypes.func,
@@ -42,7 +42,6 @@ class ModerationActionsContainer extends React.Component {
render() {
return (
<ModerationActions
data={this.props.data}
root={this.props.root}
asset={this.props.asset}
comment={this.props.comment}
@@ -1,74 +0,0 @@
import React from 'react';
import PropTypes from 'prop-types';
import { compose, gql } from 'react-apollo';
import Toggle from 'talk-plugin-notifications/client/components/Toggle';
import { t } from 'plugin-api/beta/client/services';
import { withFragments } from 'plugin-api/beta/client/hocs';
class ToggleContainer extends React.Component {
constructor(props) {
super(props);
props.setTurnOffInputFragment({ onFeatured: false });
if (this.getOnFeaturedSetting()) {
props.indicateOn();
}
}
componentWillReceiveProps(nextProps) {
const prevSetting = this.getOnFeaturedSetting(this.props);
const nextSetting = this.getOnFeaturedSetting(nextProps);
if (prevSetting && !nextSetting) {
nextProps.indicateOff();
} else if (!prevSetting && nextSetting) {
nextProps.indicateOn();
}
}
getOnFeaturedSetting = (props = this.props) =>
props.root.me.notificationSettings.onFeatured;
toggle = () => {
this.props.updateNotificationSettings({
onFeatured: !this.getOnFeaturedSetting(),
});
};
render() {
return (
<Toggle
checked={this.getOnFeaturedSetting()}
onChange={this.toggle}
disabled={this.props.disabled}
>
{t('talk-plugin-notifications-category-featured.toggle_description')}
</Toggle>
);
}
}
ToggleContainer.propTypes = {
data: PropTypes.object,
root: PropTypes.object,
indicateOn: PropTypes.func.isRequired,
indicateOff: PropTypes.func.isRequired,
setTurnOffInputFragment: PropTypes.func.isRequired,
updateNotificationSettings: PropTypes.func.isRequired,
disabled: PropTypes.bool.isRequired,
};
const enhance = compose(
withFragments({
root: gql`
fragment TalkNotificationsCategoryFeatured_Toggle_root on RootQuery {
me {
notificationSettings {
onFeatured
}
}
}
`,
})
);
export default enhance(ToggleContainer);
@@ -1,33 +0,0 @@
import { gql } from 'react-apollo';
export default {
mutations: {
UpdateNotificationSettings: ({
variables: { input },
state: { auth: { user: { id } } },
}) => ({
update: proxy => {
if (input.onFeatured === undefined) {
return;
}
const fragment = gql`
fragment TalkNotificationsCategoryFeatured_User_Fragment on User {
notificationSettings {
onFeatured
}
}
`;
const fragmentId = `User_${id}`;
const data = {
__typename: 'User',
notificationSettings: {
__typename: 'NotificationSettings',
onFeatured: input.onFeatured,
},
};
proxy.writeFragment({ fragment, id: fragmentId, data });
},
}),
},
};
@@ -1,11 +1,14 @@
import Toggle from './containers/Toggle';
import translations from './translations.yml';
import graphql from './graphql';
import { t } from 'plugin-api/beta/client/services';
import { createSettingsToggle } from 'talk-plugin-notifications/client/api/factories';
const SettingsToggle = createSettingsToggle('onFeatured', () =>
t('talk-plugin-notifications-category-featured.toggle_description')
);
export default {
slots: {
notificationSettings: [Toggle],
notificationSettings: [SettingsToggle],
},
translations,
...graphql,
};
@@ -1,74 +0,0 @@
import React from 'react';
import PropTypes from 'prop-types';
import { compose, gql } from 'react-apollo';
import Toggle from 'talk-plugin-notifications/client/components/Toggle';
import { t } from 'plugin-api/beta/client/services';
import { withFragments } from 'plugin-api/beta/client/hocs';
class ToggleContainer extends React.Component {
constructor(props) {
super(props);
props.setTurnOffInputFragment({ onReply: false });
if (this.getOnReplySetting()) {
props.indicateOn();
}
}
componentWillReceiveProps(nextProps) {
const prevSetting = this.getOnReplySetting(this.props);
const nextSetting = this.getOnReplySetting(nextProps);
if (prevSetting && !nextSetting) {
nextProps.indicateOff();
} else if (!prevSetting && nextSetting) {
nextProps.indicateOn();
}
}
getOnReplySetting = (props = this.props) =>
props.root.me.notificationSettings.onReply;
toggle = () => {
this.props.updateNotificationSettings({
onReply: !this.getOnReplySetting(),
});
};
render() {
return (
<Toggle
checked={this.getOnReplySetting()}
onChange={this.toggle}
disabled={this.props.disabled}
>
{t('talk-plugin-notifications-category-reply.toggle_description')}
</Toggle>
);
}
}
ToggleContainer.propTypes = {
data: PropTypes.object,
root: PropTypes.object,
indicateOn: PropTypes.func.isRequired,
indicateOff: PropTypes.func.isRequired,
setTurnOffInputFragment: PropTypes.func.isRequired,
updateNotificationSettings: PropTypes.func.isRequired,
disabled: PropTypes.bool.isRequired,
};
const enhance = compose(
withFragments({
root: gql`
fragment TalkNotificationsCategoryReply_Toggle_root on RootQuery {
me {
notificationSettings {
onReply
}
}
}
`,
})
);
export default enhance(ToggleContainer);
@@ -1,33 +0,0 @@
import { gql } from 'react-apollo';
export default {
mutations: {
UpdateNotificationSettings: ({
variables: { input },
state: { auth: { user: { id } } },
}) => ({
update: proxy => {
if (input.onReply === undefined) {
return;
}
const fragment = gql`
fragment TalkNotificationsCategoryReply_User_Fragment on User {
notificationSettings {
onReply
}
}
`;
const fragmentId = `User_${id}`;
const data = {
__typename: 'User',
notificationSettings: {
__typename: 'NotificationSettings',
onReply: input.onReply,
},
};
proxy.writeFragment({ fragment, id: fragmentId, data });
},
}),
},
};
@@ -1,11 +1,14 @@
import Toggle from './containers/Toggle';
import translations from './translations.yml';
import graphql from './graphql';
import { t } from 'plugin-api/beta/client/services';
import { createSettingsToggle } from 'talk-plugin-notifications/client/api/factories';
const SettingsToggle = createSettingsToggle('onReply', () =>
t('talk-plugin-notifications-category-reply.toggle_description')
);
export default {
slots: {
notificationSettings: [Toggle],
notificationSettings: [SettingsToggle],
},
translations,
...graphql,
};
@@ -1,74 +0,0 @@
import React from 'react';
import PropTypes from 'prop-types';
import { compose, gql } from 'react-apollo';
import Toggle from 'talk-plugin-notifications/client/components/Toggle';
import { t } from 'plugin-api/beta/client/services';
import { withFragments } from 'plugin-api/beta/client/hocs';
class ToggleContainer extends React.Component {
constructor(props) {
super(props);
props.setTurnOffInputFragment({ onStaffReply: false });
if (this.getOnReplySetting()) {
props.indicateOn();
}
}
componentWillReceiveProps(nextProps) {
const prevSetting = this.getOnReplySetting(this.props);
const nextSetting = this.getOnReplySetting(nextProps);
if (prevSetting && !nextSetting) {
nextProps.indicateOff();
} else if (!prevSetting && nextSetting) {
nextProps.indicateOn();
}
}
getOnReplySetting = (props = this.props) =>
props.root.me.notificationSettings.onStaffReply;
toggle = () => {
this.props.updateNotificationSettings({
onStaffReply: !this.getOnReplySetting(),
});
};
render() {
return (
<Toggle
checked={this.getOnReplySetting()}
onChange={this.toggle}
disabled={this.props.disabled}
>
{t('talk-plugin-notifications-category-staff.toggle_description')}
</Toggle>
);
}
}
ToggleContainer.propTypes = {
data: PropTypes.object,
root: PropTypes.object,
indicateOn: PropTypes.func.isRequired,
indicateOff: PropTypes.func.isRequired,
setTurnOffInputFragment: PropTypes.func.isRequired,
updateNotificationSettings: PropTypes.func.isRequired,
disabled: PropTypes.bool.isRequired,
};
const enhance = compose(
withFragments({
root: gql`
fragment TalkNotificationsCategoryStaffReply_User_Fragment on RootQuery {
me {
notificationSettings {
onStaffReply
}
}
}
`,
})
);
export default enhance(ToggleContainer);
@@ -1,33 +0,0 @@
import { gql } from 'react-apollo';
export default {
mutations: {
UpdateNotificationSettings: ({
variables: { input },
state: { auth: { user: { id } } },
}) => ({
update: proxy => {
if (input.onStaffReply === undefined) {
return;
}
const fragment = gql`
fragment TalkNotificationsCategoryStaffReply_User_Fragment on User {
notificationSettings {
onStaffReply
}
}
`;
const fragmentId = `User_${id}`;
const data = {
__typename: 'User',
notificationSettings: {
__typename: 'NotificationSettings',
onStaffReply: input.onStaffReply,
},
};
proxy.writeFragment({ fragment, id: fragmentId, data });
},
}),
},
};
@@ -1,11 +1,14 @@
import Toggle from './containers/Toggle';
import translations from './translations.yml';
import graphql from './graphql';
import { t } from 'plugin-api/beta/client/services';
import { createSettingsToggle } from 'talk-plugin-notifications/client/api/factories';
const SettingsToggle = createSettingsToggle('onStaffReply', () =>
t('talk-plugin-notifications-category-staff.toggle_description')
);
export default {
slots: {
notificationSettings: [Toggle],
notificationSettings: [SettingsToggle],
},
translations,
...graphql,
};
@@ -0,0 +1,20 @@
.title {
display: inline-block;
width: 100%;
cursor: pointer;
user-select: none;
&.disabled {
color: #e5e5e5;
cursor: default;
}
}
.toggle {
display: flex;
align-items: center;
}
.checkBox {
text-align: right;
}
@@ -0,0 +1,41 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Checkbox } from 'plugin-api/beta/client/components/ui';
import styles from './Toggle.css';
import uuid from 'uuid/v4';
import cn from 'classnames';
class Toggle extends React.Component {
id = uuid();
render() {
const { checked, onChange, children, disabled } = this.props;
return (
<div className={styles.toggle}>
<label
htmlFor={this.id}
className={cn(styles.title, { [styles.disabled]: disabled })}
>
{children}
</label>
<div className={styles.checkBox}>
<Checkbox
checked={checked}
onChange={onChange}
id={this.id}
disabled={disabled}
/>
</div>
</div>
);
}
}
Toggle.propTypes = {
disabled: PropTypes.bool,
checked: PropTypes.bool,
onChange: PropTypes.func,
children: PropTypes.node,
};
export default Toggle;
@@ -0,0 +1 @@
export { default as Toggle } from './Toggle';
@@ -0,0 +1,22 @@
import React from 'react';
import Toggle from '../components/Toggle';
import withSettingsToggle from '../hocs/withSettingsToggle';
/**
* createSettingsToggle will add a boolean setting with the
* name `settingsName` to notification settings and return
* a full Toggle Component.
*
* You must provide a `label` either as a string or as a callback.
* E.g. to provide translations you could do:
*
* `const SettingsToggle = createSettingsToggle('onReply', () => t('translate'));`
*/
const createSettingsToggle = (settingsName, label) => {
const SettingsToggle = props => (
<Toggle {...props}>{typeof label === 'function' ? label() : label}</Toggle>
);
return withSettingsToggle(settingsName)(SettingsToggle);
};
export default createSettingsToggle;
@@ -0,0 +1 @@
export { default as createSettingsToggle } from './createSettingsToggle';
@@ -0,0 +1 @@
export { default as withSettingsToggle } from './withSettingsToggle';
@@ -0,0 +1,121 @@
import React from 'react';
import PropTypes from 'prop-types';
import { compose, gql } from 'react-apollo';
import {
withFragments,
withGraphQLExtension,
} from 'plugin-api/beta/client/hocs';
const createHOC = settingsName => WrappedComponent => {
class WithSettingsToggle extends React.Component {
constructor(props) {
super(props);
props.setTurnOffInputFragment({ [settingsName]: false });
if (this.isChecked()) {
props.indicateOn();
}
}
componentWillReceiveProps(nextProps) {
const prevSetting = this.isChecked(this.props);
const nextSetting = this.isChecked(nextProps);
if (prevSetting && !nextSetting) {
nextProps.indicateOff();
} else if (!prevSetting && nextSetting) {
nextProps.indicateOn();
}
}
isChecked = (props = this.props) =>
props.root.me.notificationSettings[settingsName];
toggle = () => {
this.props.updateNotificationSettings({
[settingsName]: !this.isChecked(),
});
};
render() {
return (
<WrappedComponent
checked={this.isChecked()}
onChange={this.toggle}
disabled={this.props.disabled}
/>
);
}
}
WithSettingsToggle.propTypes = {
root: PropTypes.object,
indicateOn: PropTypes.func.isRequired,
indicateOff: PropTypes.func.isRequired,
setTurnOffInputFragment: PropTypes.func.isRequired,
updateNotificationSettings: PropTypes.func.isRequired,
disabled: PropTypes.bool.isRequired,
};
return WithSettingsToggle;
};
/**
* withSettingsToggle will add a boolean setting with the
* name `settingsName` to notification settings and provide
* the folliwng props:
*
* `checked: boolean` Whether setting is on or off
* `onChange: () => void` Calling this will toggle the setting
* `disabled: boolean` Whether setting is disabled
*/
const withSettingsToggle = settingsName => {
const extension = {
mutations: {
UpdateNotificationSettings: ({
variables: { input },
state: { auth: { user: { id } } },
}) => ({
update: proxy => {
if (input[settingsName] === undefined) {
return;
}
const fragment = gql`
fragment TalkNotifications_Toggle_${settingsName}_Fragment on User {
notificationSettings {
${settingsName}
}
}
`;
const fragmentId = `User_${id}`;
const data = {
__typename: 'User',
notificationSettings: {
__typename: 'NotificationSettings',
[settingsName]: input[settingsName],
},
};
proxy.writeFragment({ fragment, id: fragmentId, data });
},
}),
},
};
return compose(
withFragments({
root: gql`
fragment TalkNotifications_Toggle_${settingsName}_root on RootQuery {
me {
notificationSettings {
${settingsName}
}
}
}
`,
}),
withGraphQLExtension(extension),
createHOC(settingsName)
);
};
export default withSettingsToggle;
@@ -37,15 +37,18 @@ class Settings extends React.Component {
onChangeDigestFrequency,
} = this.props;
const slotProps = {
queryData: { root },
setTurnOffInputFragment: setTurnOffInputFragment,
updateNotificationSettings: updateNotificationSettings,
const slotPassthrough = {
root,
setTurnOffInputFragment,
updateNotificationSettings,
disabled: needEmailVerification,
};
return (
<IfSlotIsNotEmpty slot="notificationSettings" {...slotProps}>
<IfSlotIsNotEmpty
slot="notificationSettings"
passthrough={slotPassthrough}
>
<div className={styles.root}>
<h3>{t('talk-plugin-notifications.settings_title')}</h3>
<div className={styles.bannerContainer}>
@@ -63,7 +66,7 @@ class Settings extends React.Component {
className={styles.notifcationSettingsSlot}
fill="notificationSettings"
childFactory={this.childFactory}
{...slotProps}
passthrough={slotPassthrough}
/>
</div>
{digestFrequencyValues.length > 1 && (
@@ -50,7 +50,6 @@ class SettingsContainer extends React.Component {
render() {
return (
<Settings
data={this.props.data}
root={this.props.root}
indicateOn={this.indicateOn}
indicateOff={this.indicateOff}
@@ -72,7 +71,6 @@ class SettingsContainer extends React.Component {
}
SettingsContainer.propTypes = {
data: PropTypes.object,
root: PropTypes.object,
updateNotificationSettings: PropTypes.func.isRequired,
digestFrequencyValues: PropTypes.array.isRequired,
@@ -1,3 +0,0 @@
{
"extends": "@coralproject/eslint-config-talk/client"
}
@@ -1,8 +0,0 @@
import React from 'react';
import { t } from 'plugin-api/beta/client/services';
const Tab = () => {
return <span>{t('talk-plugin-profile-settings.tab')}</span>;
};
export default Tab;
@@ -1,21 +0,0 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Slot } from 'plugin-api/beta/client/components';
class TabPane extends React.Component {
render() {
const { data, root } = this.props;
return (
<div>
<Slot fill="profileSettings" data={data} queryData={{ root }} />
</div>
);
}
}
TabPane.propTypes = {
data: PropTypes.object,
root: PropTypes.object,
};
export default TabPane;
@@ -1,26 +0,0 @@
import React from 'react';
import { compose, gql } from 'react-apollo';
import TabPane from '../components/TabPane';
import { withFragments } from 'plugin-api/beta/client/hocs';
import { getSlotFragmentSpreads } from 'plugin-api/beta/client/utils';
const slots = ['profileSettings'];
class TabPaneContainer extends React.Component {
render() {
return <TabPane {...this.props} />;
}
}
const enhance = compose(
withFragments({
root: gql`
fragment TalkProfileSettings_TabPane_root on RootQuery {
__typename
${getSlotFragmentSpreads(slots, 'root')}
}
`,
})
);
export default enhance(TabPaneContainer);
@@ -1,11 +0,0 @@
import Tab from './components/Tab';
import TabPane from './containers/TabPane';
import translations from './translations.yml';
export default {
slots: {
profileTabs: [Tab],
profileTabPanes: [TabPane],
},
translations,
};
@@ -1,30 +0,0 @@
ar:
talk-plugin-profile-settings:
tab: إعدادات
en:
talk-plugin-profile-settings:
tab: Settings
de:
talk-plugin-profile-settings:
tab: Einstellungen
es:
talk-plugin-profile-settings:
tab: Configuración
fr:
talk-plugin-profile-settings:
tab: Paramètres
nl_NL:
talk-plugin-profile-settings:
tab: Instellingen
da:
talk-plugin-profile-settings:
tab: Indstillinger
pt_PR:
talk-plugin-profile-settings:
tab: Configurações
zh_TW:
talk-plugin-profile-settings:
tab: 設置
zh_CN:
talk-plugin-profile-settings:
tab: 设置
@@ -1 +0,0 @@
module.exports = {};
@@ -1,4 +1,5 @@
import React from 'react';
import PropTypes from 'prop-types';
import styles from './Category.css';
import { Slot } from 'plugin-api/beta/client/components';
@@ -8,7 +9,7 @@ const childFactory = child => (
</li>
);
const ViewingOptions = ({ slot, title, data, asset, root }) => {
const Category = ({ slot, title, slotPassthrough }) => {
return (
<div className={styles.root}>
<div className={styles.title}>{title}</div>
@@ -17,11 +18,16 @@ const ViewingOptions = ({ slot, title, data, asset, root }) => {
childFactory={childFactory}
className={styles.list}
component={'ul'}
data={data}
queryData={{ asset, root }}
passthrough={slotPassthrough}
/>
</div>
);
};
export default ViewingOptions;
Category.propTypes = {
slot: PropTypes.string.isRequired,
title: PropTypes.string.isRequired,
slotPassthrough: PropTypes.object.isRequired,
};
export default Category;
@@ -1,4 +1,5 @@
import React from 'react';
import PropTypes from 'prop-types';
import cn from 'classnames';
import styles from './Menu.css';
import { capitalize } from 'plugin-api/beta/client/utils';
@@ -13,16 +14,19 @@ class Menu extends React.Component {
};
render() {
const { slotPassthrough } = this.props;
return (
<div className={cn([styles.menu, 'talk-plugin-viewing-options-menu'])}>
{Object.keys(this.categories).map(category => (
<IfSlotIsNotEmpty
slot={`viewingOptions${capitalize(category)}`}
key={category}
passthrough={slotPassthrough}
>
<Category
slot={`viewingOptions${capitalize(category)}`}
title={this.categories[category]}
slotPassthrough={slotPassthrough}
/>
</IfSlotIsNotEmpty>
))}
@@ -31,4 +35,8 @@ class Menu extends React.Component {
}
}
Menu.propTypes = {
slotPassthrough: PropTypes.object.isRequired,
};
export default Menu;
@@ -1,4 +1,5 @@
import React from 'react';
import PropTypes from 'prop-types';
import cn from 'classnames';
import styles from './ViewingOptions.css';
import { t } from 'plugin-api/beta/client/services';
@@ -24,7 +25,7 @@ class ViewingOptions extends React.Component {
};
render() {
const { open, data, root, asset } = this.props;
const { open, slotPassthrough } = this.props;
return (
<ClickOutside onClickOutside={this.handleClickOutside}>
<div className={cn([styles.root, 'talk-plugin-viewing-options'])}>
@@ -41,11 +42,18 @@ class ViewingOptions extends React.Component {
)}
</button>
</div>
{open && <Menu data={data} root={root} asset={asset} />}
{open && <Menu slotPassthrough={slotPassthrough} />}
</div>
</ClickOutside>
);
}
}
ViewingOptions.propTypes = {
slotPassthrough: PropTypes.object.isRequired,
open: PropTypes.bool.isRequired,
openMenu: PropTypes.func.isRequired,
closeMenu: PropTypes.func.isRequired,
};
export default ViewingOptions;
@@ -4,6 +4,7 @@ import ViewingOptions from '../components/ViewingOptions';
import { openMenu, closeMenu } from '../actions';
import { compose, gql } from 'react-apollo';
import { getSlotFragmentSpreads } from 'plugin-api/beta/client/utils';
import { mapProps } from 'recompose';
const slots = ['viewingOptionsSort', 'viewingOptionsFilter'];
@@ -29,7 +30,14 @@ const withViewingOptionsFragments = withFragments({
const enhance = compose(
connect(mapStateToProps, mapDispatchToProps),
withViewingOptionsFragments
withViewingOptionsFragments,
mapProps(({ root, asset, ...rest }) => ({
slotPassthrough: {
root,
asset,
},
...rest,
}))
);
export default enhance(ViewingOptions);