mirror of
https://github.com/wassname/talk.git
synced 2026-07-24 13:20:47 +08:00
Display digest settings
This commit is contained in:
@@ -130,3 +130,12 @@ th.header:nth-child(2), th.header:nth-child(3) {
|
||||
.loadMore {
|
||||
margin-top: 24px;
|
||||
}
|
||||
|
||||
.roleDropdown {
|
||||
width: 150px;
|
||||
}
|
||||
|
||||
.roleOption {
|
||||
min-width: 100px;
|
||||
}
|
||||
|
||||
|
||||
@@ -200,24 +200,31 @@ class People extends React.Component {
|
||||
</td>
|
||||
<td className="mdl-data-table__cell--non-numeric">
|
||||
<Dropdown
|
||||
containerClassName="talk-admin-community-people-dd-role"
|
||||
className={cn(
|
||||
'talk-admin-community-people-dd-role',
|
||||
styles.roleDropdown
|
||||
)}
|
||||
value={user.role}
|
||||
placeholder={t('community.role')}
|
||||
onChange={role => setUserRole(user.id, role)}
|
||||
>
|
||||
<Option
|
||||
className={styles.roleOption}
|
||||
value={COMMENTER}
|
||||
label={t('community.commenter')}
|
||||
/>
|
||||
<Option
|
||||
className={styles.roleOption}
|
||||
value={STAFF}
|
||||
label={t('community.staff')}
|
||||
/>
|
||||
<Option
|
||||
className={styles.roleOption}
|
||||
value={MODERATOR}
|
||||
label={t('community.moderator')}
|
||||
/>
|
||||
<Option
|
||||
className={styles.roleOption}
|
||||
value={ADMIN}
|
||||
label={t('community.admin')}
|
||||
/>
|
||||
|
||||
@@ -89,3 +89,12 @@
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
|
||||
.statusDropdown {
|
||||
width: 150px;
|
||||
}
|
||||
|
||||
.statusDropdownOption {
|
||||
min-width: 100px;
|
||||
}
|
||||
|
||||
|
||||
@@ -22,11 +22,20 @@ class Stories extends Component {
|
||||
const closed = !!(closedAt && new Date(closedAt).getTime() < Date.now());
|
||||
return (
|
||||
<Dropdown
|
||||
className={styles.statusDropdown}
|
||||
value={closed}
|
||||
onChange={value => this.props.onStatusChange(value, id)}
|
||||
>
|
||||
<Option value={false} label={t('streams.open')} />
|
||||
<Option value={true} label={t('streams.closed')} />
|
||||
<Option
|
||||
value={false}
|
||||
label={t('streams.open')}
|
||||
className={styles.statusDropdownOption}
|
||||
/>
|
||||
<Option
|
||||
value={true}
|
||||
label={t('streams.closed')}
|
||||
className={styles.statusDropdownOption}
|
||||
/>
|
||||
</Dropdown>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -10,6 +10,7 @@ class TalkProvider extends React.Component {
|
||||
plugins: this.props.plugins,
|
||||
rest: this.props.rest,
|
||||
graphql: this.props.graphql,
|
||||
introspection: this.props.introspection,
|
||||
notification: this.props.notification,
|
||||
localStorage: this.props.localStorage,
|
||||
sessionStorage: this.props.sessionStorage,
|
||||
@@ -29,6 +30,7 @@ class TalkProvider extends React.Component {
|
||||
|
||||
TalkProvider.childContextTypes = {
|
||||
pym: PropTypes.object,
|
||||
introspection: PropTypes.object,
|
||||
eventEmitter: PropTypes.object,
|
||||
plugins: PropTypes.object,
|
||||
rest: PropTypes.func,
|
||||
|
||||
@@ -11,6 +11,7 @@ export { default as withSignUp } from './withSignUp';
|
||||
export { default as withForgotPassword } from './withForgotPassword';
|
||||
export { default as withSetUsername } from './withSetUsername';
|
||||
export { default as withPopupAuthHandler } from './withPopupAuthHandler';
|
||||
export { default as withEnumValues } from './withEnumValues';
|
||||
export {
|
||||
default as withResendEmailConfirmation,
|
||||
} from './withResendEmailConfirmation';
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
import React from 'react';
|
||||
import hoistStatics from 'recompose/hoistStatics';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
/**
|
||||
* WithEnumValues inject given property name for given enum.
|
||||
*/
|
||||
export default (enumName, propName) =>
|
||||
hoistStatics(WrappedComponent => {
|
||||
class WithEnumValues extends React.Component {
|
||||
static contextTypes = {
|
||||
introspection: PropTypes.object,
|
||||
};
|
||||
|
||||
render() {
|
||||
const inject = {
|
||||
[propName]: this.context.introspection.getEnumValues(enumName),
|
||||
};
|
||||
return <WrappedComponent {...this.props} {...inject} />;
|
||||
}
|
||||
}
|
||||
|
||||
return WithEnumValues;
|
||||
});
|
||||
@@ -19,6 +19,16 @@ class Introspection {
|
||||
isValidEnumValue(name, value) {
|
||||
return this._enums[name] && this._enums[name].indexOf(value) >= 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* getEnumValues returns array of possible values.
|
||||
* @param {string} name
|
||||
* @param {string} value
|
||||
* @return {array}
|
||||
*/
|
||||
getEnumValues(name) {
|
||||
return this._enums[name];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,20 +1,27 @@
|
||||
.dropdown {
|
||||
position: relative;
|
||||
width: 150px;
|
||||
height: 34px;
|
||||
background: #2c2c2c;
|
||||
box-sizing: border-box;
|
||||
color: white;
|
||||
border-radius: 3px;
|
||||
box-shadow: 0 2px 2px 0 rgba(0,0,0,.14), 0 3px 1px -2px rgba(0,0,0,.2), 0 1px 5px 0 rgba(0,0,0,.12);
|
||||
line-height: 20px;
|
||||
|
||||
font-size: 0.98em;
|
||||
border-radius: 3px;
|
||||
cursor: pointer;
|
||||
|
||||
&.disabled {
|
||||
color: #e5e5e5;
|
||||
background: #888;
|
||||
cursor: default;
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
|
||||
.toggle {
|
||||
padding: 8px 15px;
|
||||
cursor: pointer;
|
||||
padding: 8px 45px 8px 15px;
|
||||
outline: none;
|
||||
|
||||
&:focus {
|
||||
@@ -38,7 +45,6 @@
|
||||
border-radius: 2px;
|
||||
background: white;
|
||||
box-shadow: 0 2px 2px 0 rgba(0,0,0,.14), 0 3px 1px -2px rgba(0,0,0,.2), 0 1px 5px 0 rgba(0,0,0,.12);
|
||||
width: 100%;
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
|
||||
@@ -13,6 +13,12 @@ class Dropdown extends React.Component {
|
||||
isOpen: false,
|
||||
};
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (this.state.isOpen && nextProps.disabled) {
|
||||
this.toggle();
|
||||
}
|
||||
}
|
||||
|
||||
componentDidUpdate(_, prevState) {
|
||||
if (!this.state.isOpen && prevState.isOpen) {
|
||||
// Refocus on the toggle element when menu closes.
|
||||
@@ -69,6 +75,10 @@ class Dropdown extends React.Component {
|
||||
};
|
||||
|
||||
toggle = () => {
|
||||
if (this.props.disabled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this.setState({
|
||||
isOpen: !this.state.isOpen,
|
||||
});
|
||||
@@ -135,11 +145,21 @@ class Dropdown extends React.Component {
|
||||
containerClassName,
|
||||
toggleClassName,
|
||||
toggleOpenClassName,
|
||||
disabled,
|
||||
className,
|
||||
} = this.props;
|
||||
return (
|
||||
<ClickOutside onClickOutside={this.hideMenu}>
|
||||
<div
|
||||
className={cn(styles.dropdown, containerClassName, 'dd dd-container')}
|
||||
className={cn(
|
||||
styles.dropdown,
|
||||
className,
|
||||
containerClassName,
|
||||
'dd dd-container',
|
||||
{
|
||||
[styles.disabled]: disabled,
|
||||
}
|
||||
)}
|
||||
>
|
||||
<div
|
||||
className={cn(styles.toggle, toggleClassName, {
|
||||
@@ -150,7 +170,7 @@ class Dropdown extends React.Component {
|
||||
role="button"
|
||||
aria-pressed={this.state.isOpen}
|
||||
aria-haspopup="true"
|
||||
tabIndex="0"
|
||||
tabIndex={disabled ? '-1' : '0'}
|
||||
ref={this.handleToggleRef}
|
||||
>
|
||||
{this.props.icon && (
|
||||
@@ -206,6 +226,7 @@ class Dropdown extends React.Component {
|
||||
}
|
||||
|
||||
Dropdown.propTypes = {
|
||||
className: PropTypes.string,
|
||||
containerClassName: PropTypes.string,
|
||||
toggleClassName: PropTypes.string,
|
||||
toggleOpenClassName: PropTypes.string,
|
||||
@@ -213,6 +234,7 @@ Dropdown.propTypes = {
|
||||
icon: PropTypes.string,
|
||||
onChange: PropTypes.func.isRequired,
|
||||
children: PropTypes.node.isRequired,
|
||||
disabled: PropTypes.bool,
|
||||
value: PropTypes.oneOfType([
|
||||
PropTypes.number,
|
||||
PropTypes.string,
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
padding: 10px;
|
||||
text-transform: capitalize;
|
||||
outline: none;
|
||||
white-space: nowrap;
|
||||
|
||||
&:focus, &:hover {
|
||||
background-color: #ccc;
|
||||
|
||||
@@ -12,6 +12,7 @@ export {
|
||||
withSignUp,
|
||||
withResendEmailConfirmation,
|
||||
withSetUsername,
|
||||
withEnumValues,
|
||||
} from 'coral-framework/hocs';
|
||||
export {
|
||||
withIgnoreUser,
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"extends": "@coralproject/eslint-config-talk/client"
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import translations from './translations.yml';
|
||||
|
||||
export default {
|
||||
translations,
|
||||
};
|
||||
@@ -0,0 +1,4 @@
|
||||
en:
|
||||
talk-plugin-notifications:
|
||||
digest_enum:
|
||||
DAILY: In a daily digest
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"extends": "@coralproject/eslint-config-talk/client"
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import translations from './translations.yml';
|
||||
|
||||
export default {
|
||||
translations,
|
||||
};
|
||||
@@ -0,0 +1,4 @@
|
||||
en:
|
||||
talk-plugin-notifications:
|
||||
digest_enum:
|
||||
HOURLY: In an hourly digest
|
||||
@@ -30,3 +30,16 @@
|
||||
.disabled {
|
||||
color: #e5e5e5;
|
||||
}
|
||||
|
||||
.digest {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.titleDigest {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.digestDropDown {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
@@ -4,7 +4,11 @@ import { IfSlotIsNotEmpty } from 'plugin-api/beta/client/components';
|
||||
import { Slot } from 'plugin-api/beta/client/components';
|
||||
import { t } from 'plugin-api/beta/client/services';
|
||||
import styles from './Settings.css';
|
||||
import { BareButton } from 'plugin-api/beta/client/components/ui';
|
||||
import {
|
||||
BareButton,
|
||||
Dropdown,
|
||||
Option,
|
||||
} from 'plugin-api/beta/client/components/ui';
|
||||
import EmailVerificationBanner from '../containers/EmailVerificationBanner';
|
||||
import cn from 'classnames';
|
||||
|
||||
@@ -24,9 +28,13 @@ class Settings extends React.Component {
|
||||
setTurnOffInputFragment,
|
||||
updateNotificationSettings,
|
||||
turnOffAll,
|
||||
turnOffButtonDisabled,
|
||||
needEmailVerification,
|
||||
email,
|
||||
digestFrequencyValues,
|
||||
digestFrequency,
|
||||
disableDigest,
|
||||
disableTurnoffButton,
|
||||
onChangeDigestFrequency,
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
@@ -51,14 +59,37 @@ class Settings extends React.Component {
|
||||
updateNotificationSettings={updateNotificationSettings}
|
||||
disabled={needEmailVerification}
|
||||
/>
|
||||
<BareButton
|
||||
className={styles.turnOffButton}
|
||||
onClick={turnOffAll}
|
||||
disabled={turnOffButtonDisabled}
|
||||
>
|
||||
{t('talk-plugin-notifications.turn_off_all')}
|
||||
</BareButton>
|
||||
</div>
|
||||
<div className={styles.digest}>
|
||||
<h4
|
||||
className={cn(styles.titleDigest, {
|
||||
[styles.disabled]: disableDigest,
|
||||
})}
|
||||
>
|
||||
{t('talk-plugin-notifications.digest_option')}
|
||||
</h4>
|
||||
<Dropdown
|
||||
className={styles.digestDropDown}
|
||||
value={digestFrequency}
|
||||
onChange={onChangeDigestFrequency}
|
||||
disabled={disableDigest}
|
||||
>
|
||||
{digestFrequencyValues.map(v => (
|
||||
<Option
|
||||
value={v}
|
||||
key={v}
|
||||
label={t(`talk-plugin-notifications.digest_enum.${v}`)}
|
||||
/>
|
||||
))}
|
||||
</Dropdown>
|
||||
</div>
|
||||
<BareButton
|
||||
className={styles.turnOffButton}
|
||||
onClick={turnOffAll}
|
||||
disabled={disableTurnoffButton}
|
||||
>
|
||||
{t('talk-plugin-notifications.turn_off_all')}
|
||||
</BareButton>
|
||||
</div>
|
||||
</IfSlotIsNotEmpty>
|
||||
);
|
||||
@@ -72,9 +103,13 @@ Settings.propTypes = {
|
||||
setTurnOffInputFragment: PropTypes.func.isRequired,
|
||||
updateNotificationSettings: PropTypes.func.isRequired,
|
||||
turnOffAll: PropTypes.func.isRequired,
|
||||
turnOffButtonDisabled: PropTypes.bool.isRequired,
|
||||
disableTurnoffButton: PropTypes.bool.isRequired,
|
||||
disableDigest: PropTypes.bool.isRequired,
|
||||
needEmailVerification: PropTypes.bool.isRequired,
|
||||
email: PropTypes.string,
|
||||
digestFrequencyValues: PropTypes.array.isRequired,
|
||||
digestFrequency: PropTypes.string.isRequired,
|
||||
onChangeDigestFrequency: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default Settings;
|
||||
|
||||
@@ -2,7 +2,11 @@ import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { compose, gql } from 'react-apollo';
|
||||
import Settings from '../components/Settings';
|
||||
import { withFragments, excludeIf } from 'plugin-api/beta/client/hocs';
|
||||
import {
|
||||
withFragments,
|
||||
excludeIf,
|
||||
withEnumValues,
|
||||
} from 'plugin-api/beta/client/hocs';
|
||||
import { getSlotFragmentSpreads } from 'plugin-api/beta/client/utils';
|
||||
import { withUpdateNotificationSettings } from '../mutations';
|
||||
|
||||
@@ -39,6 +43,11 @@ class SettingsContainer extends React.Component {
|
||||
);
|
||||
}
|
||||
|
||||
setDigestFrequency(val) {
|
||||
// TODO: implement mutation.
|
||||
console.log(val);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Settings
|
||||
@@ -49,9 +58,15 @@ class SettingsContainer extends React.Component {
|
||||
setTurnOffInputFragment={this.setTurnOffInputFragment}
|
||||
updateNotificationSettings={this.props.updateNotificationSettings}
|
||||
turnOffAll={this.turnOffAll}
|
||||
turnOffButtonDisabled={this.state.hasNotifications.length === 0}
|
||||
needEmailVerification={this.getNeedEmailVerification()}
|
||||
email={this.props.root.me.email}
|
||||
digestFrequencyValues={this.props.digestFrequencyValues}
|
||||
digestFrequency={
|
||||
this.props.root.me.notificationSettings.digestFrequency
|
||||
}
|
||||
disableDigest={this.state.hasNotifications.length === 0}
|
||||
disableTurnoffButton={this.state.hasNotifications.length === 0}
|
||||
onChangeDigestFrequency={this.setDigestFrequency}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -61,6 +76,7 @@ SettingsContainer.propTypes = {
|
||||
data: PropTypes.object,
|
||||
root: PropTypes.object,
|
||||
updateNotificationSettings: PropTypes.func.isRequired,
|
||||
digestFrequencyValues: PropTypes.array.isRequired,
|
||||
};
|
||||
|
||||
const enhance = compose(
|
||||
@@ -70,6 +86,9 @@ const enhance = compose(
|
||||
__typename
|
||||
${getSlotFragmentSpreads(slots, 'root')}
|
||||
me {
|
||||
notificationSettings {
|
||||
digestFrequency
|
||||
}
|
||||
email
|
||||
profiles {
|
||||
provider
|
||||
@@ -85,7 +104,8 @@ const enhance = compose(
|
||||
props =>
|
||||
!props.root.me.profiles.some(profile => profile.provider === 'local')
|
||||
),
|
||||
withUpdateNotificationSettings
|
||||
withUpdateNotificationSettings,
|
||||
withEnumValues('DIGEST_FREQUENCY', 'digestFrequencyValues')
|
||||
);
|
||||
|
||||
export default enhance(SettingsContainer);
|
||||
|
||||
@@ -13,3 +13,6 @@ en:
|
||||
banner_error:
|
||||
title: Error
|
||||
text: There has been an error sending your verification email. Please try again later.
|
||||
digest_option: I want to receive notifications
|
||||
digest_enum:
|
||||
NONE: Immediately
|
||||
|
||||
Reference in New Issue
Block a user