mirror of
https://github.com/wassname/talk.git
synced 2026-07-05 10:00:52 +08:00
Merge branch 'master' into talk-product-guide-features
This commit is contained in:
@@ -23,6 +23,14 @@ export default class UserDetail extends React.Component {
|
||||
toggleSelect: PropTypes.func.isRequired,
|
||||
bulkAccept: PropTypes.func.isRequired,
|
||||
bulkReject: PropTypes.func.isRequired,
|
||||
loading: PropTypes.bool.isRequired,
|
||||
data: PropTypes.shape({
|
||||
refetch: PropTypes.func.isRequired,
|
||||
}),
|
||||
activeTab: PropTypes.string.isRequired,
|
||||
selectedCommentIds: PropTypes.array.isRequired,
|
||||
viewUserDetail: PropTypes.any.isRequired,
|
||||
loadMore: PropTypes.any.isRequired
|
||||
}
|
||||
|
||||
rejectThenReload = async (info) => {
|
||||
@@ -116,24 +124,22 @@ export default class UserDetail extends React.Component {
|
||||
|
||||
<ul className={styles.stats}>
|
||||
<li className={styles.stat}>
|
||||
<span className={styles.statItem}> Total Comments </span>
|
||||
<spam className={styles.statResult}> {totalComments} </spam>
|
||||
<span className={styles.statItem}>Total Comments</span>
|
||||
<span className={styles.statResult}>{totalComments}</span>
|
||||
</li>
|
||||
<li className={styles.stat}>
|
||||
<spam className={styles.statItem}> Reject Rate </spam>
|
||||
<spam className={styles.statResult}> {`${(rejectedPercent).toFixed(1)}%`} </spam>
|
||||
<span className={styles.statItem}>Reject Rate</span>
|
||||
<span className={styles.statResult}>
|
||||
{rejectedPercent.toFixed(1)}%
|
||||
</span>
|
||||
</li>
|
||||
<li className={styles.stat}>
|
||||
<spam className={styles.statItem}> Reports </spam>
|
||||
<spam className={cn(styles.statReportResult, styles[getReliability(user.reliable.flagger)])}>
|
||||
<span className={styles.statItem}>Reports</span>
|
||||
<span className={cn(styles.statReportResult, styles[getReliability(user.reliable.flagger)])}>
|
||||
{capitalize(getReliability(user.reliable.flagger))}
|
||||
</spam>
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<p className={styles.small}>
|
||||
Data represents the last six months of activity
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<Slot
|
||||
@@ -165,7 +171,7 @@ export default class UserDetail extends React.Component {
|
||||
cStyle='reject'
|
||||
icon='close'>
|
||||
</Button>
|
||||
{`${selectedCommentIds.length} comments selected`}
|
||||
{selectedCommentIds.length} comments selected
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -152,7 +152,7 @@ export const withUserDetailQuery = withQuery(gql`
|
||||
}
|
||||
${getSlotFragmentSpreads(slots, 'user')}
|
||||
}
|
||||
totalComments: commentCount(query: {author_id: $author_id})
|
||||
totalComments: commentCount(query: {author_id: $author_id, statuses: []})
|
||||
rejectedComments: commentCount(query: {author_id: $author_id, statuses: [REJECTED]})
|
||||
comments: comments(query: {
|
||||
author_id: $author_id,
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import React from 'react';
|
||||
import {SelectField, Option} from 'react-mdl-selectfield';
|
||||
import styles from '../components/Table.css';
|
||||
import t from 'coral-framework/services/i18n';
|
||||
import PropTypes from 'prop-types';
|
||||
import {Dropdown, Option} from 'coral-ui';
|
||||
import cn from 'classnames';
|
||||
|
||||
const Table = ({headers, commenters, onHeaderClickHandler, onRoleChange, onCommenterStatusChange, viewUserDetail}) => (
|
||||
@@ -13,6 +13,7 @@ const Table = ({headers, commenters, onHeaderClickHandler, onRoleChange, onComme
|
||||
<th
|
||||
key={i}
|
||||
className="mdl-data-table__cell--non-numeric"
|
||||
scope="col"
|
||||
onClick={() => onHeaderClickHandler({field: header.field})}>
|
||||
{header.title}
|
||||
</th>
|
||||
@@ -30,26 +31,24 @@ const Table = ({headers, commenters, onHeaderClickHandler, onRoleChange, onComme
|
||||
{row.created_at}
|
||||
</td>
|
||||
<td className="mdl-data-table__cell--non-numeric">
|
||||
<SelectField
|
||||
value={row.status || ''}
|
||||
className={styles.selectField}
|
||||
label={t('community.status')}
|
||||
onChange={(status) => onCommenterStatusChange(row.id, status)}>
|
||||
<Option value={'ACTIVE'}>{t('community.active')}</Option>
|
||||
<Option value={'BANNED'}>{t('community.banned')}</Option>
|
||||
</SelectField>
|
||||
<Dropdown
|
||||
value={row.status}
|
||||
placeholder={t('community.status')}
|
||||
onChange={(status) => onCommenterStatusChange(row.id, status)}>
|
||||
<Option value={'ACTIVE'} label={t('community.active')} />
|
||||
<Option value={'BANNED'} label={t('community.banned')} />
|
||||
</Dropdown>
|
||||
</td>
|
||||
<td className="mdl-data-table__cell--non-numeric">
|
||||
<SelectField
|
||||
<Dropdown
|
||||
value={row.roles[0] || ''}
|
||||
className={styles.selectField}
|
||||
label={t('community.role')}
|
||||
placeholder={t('community.role')}
|
||||
onChange={(role) => onRoleChange(row.id, role)}>
|
||||
<Option value={''}>.</Option>
|
||||
<Option value={'STAFF'}>{t('community.staff')}</Option>
|
||||
<Option value={'MODERATOR'}>{t('community.moderator')}</Option>
|
||||
<Option value={'ADMIN'}>{t('community.admin')}</Option>
|
||||
</SelectField>
|
||||
<Option value={''} label={t('community.none')} />
|
||||
<Option value={'STAFF'} label={t('community.staff')} />
|
||||
<Option value={'MODERATOR'} label={t('community.moderator')} />
|
||||
<Option value={'ADMIN'} label={t('community.admin')} />
|
||||
</Dropdown>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
|
||||
@@ -53,45 +53,26 @@
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.selectField {
|
||||
.dropdow {
|
||||
position: relative;
|
||||
width: 140px;
|
||||
height: 36px;
|
||||
top: 5px;
|
||||
margin-right: 10px;
|
||||
background: #7B7B7B;
|
||||
color: white;
|
||||
padding: 6px 15px;
|
||||
box-sizing: border-box;
|
||||
border-radius: 2px;
|
||||
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);
|
||||
margin-top: 5px;
|
||||
|
||||
@media (--tablet) {
|
||||
display: inline-block;
|
||||
margin-top: 0px;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
> div {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
i {
|
||||
position: absolute;
|
||||
top: 7px;
|
||||
right: 7px;
|
||||
}
|
||||
|
||||
input {
|
||||
font-size: 1rem;
|
||||
border-bottom: 0px;
|
||||
font-family: inherit;
|
||||
}
|
||||
|
||||
label {
|
||||
top: -4px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.dropdownToggle {
|
||||
background: #7B7B7B;
|
||||
color: white;
|
||||
|
||||
&:focus {
|
||||
background: #aaa;
|
||||
}
|
||||
}
|
||||
|
||||
.dropdownToggleOpen {
|
||||
background: #aaa;
|
||||
}
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import styles from './ViewOptions.css';
|
||||
import {Card} from 'coral-ui';
|
||||
import cn from 'classnames';
|
||||
import {SelectField, Option} from 'react-mdl-selectfield';
|
||||
import t from 'coral-framework/services/i18n';
|
||||
import {Card, Dropdown, Option} from 'coral-ui';
|
||||
|
||||
class ViewOptions extends React.Component {
|
||||
render() {
|
||||
@@ -19,18 +18,19 @@ class ViewOptions extends React.Component {
|
||||
<h2 className={cn(styles.headline, 'talk-admin-moderation-view-options-headline')}>
|
||||
View Options
|
||||
</h2>
|
||||
<div className={styles.viewOptionsContent}>
|
||||
<div className={styles.viewOptionsContent}>
|
||||
<ul className={styles.viewOptionsList}>
|
||||
<li className={styles.viewOptionsItem}>
|
||||
Sort Comments
|
||||
<SelectField
|
||||
className={styles.selectField}
|
||||
label="Sort"
|
||||
<Dropdown
|
||||
toggleClassName={styles.dropdownToggle}
|
||||
toggleOpenClassName={styles.dropdownToggleOpen}
|
||||
placeholder={t('modqueue.sort')}
|
||||
value={sort}
|
||||
onChange={(sort) => selectSort(sort)}>
|
||||
<Option value={'DESC'}>{t('modqueue.newest_first')}</Option>
|
||||
<Option value={'ASC'}>{t('modqueue.oldest_first')}</Option>
|
||||
</SelectField>
|
||||
<Option value={'DESC'} label={t('modqueue.newest_first')} />
|
||||
<Option value={'ASC'} label={t('modqueue.oldest_first')} />
|
||||
</Dropdown>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
@@ -76,31 +76,6 @@
|
||||
display: block;
|
||||
}
|
||||
|
||||
.statusMenu {
|
||||
border-radius: 2px;
|
||||
width: 10em;
|
||||
text-align: center;
|
||||
float: right;
|
||||
color: #fff;
|
||||
cursor: pointer;
|
||||
letter-spacing: 0.7px;
|
||||
font-weight: 400;
|
||||
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);
|
||||
}
|
||||
|
||||
.statusMenuOpen {
|
||||
padding: 10px;
|
||||
background-color: #268D81;
|
||||
}
|
||||
|
||||
.statusMenuIcon {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.statusMenuClosed {
|
||||
padding: 10px;
|
||||
background-color: #262626;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
import React, {Component} from 'react';
|
||||
import styles from './Stories.css';
|
||||
import t from 'coral-framework/services/i18n';
|
||||
import {Link} from 'react-router';
|
||||
|
||||
import {Pager, Icon} from 'coral-ui';
|
||||
import {DataTable, TableHeader, RadioGroup, Radio} from 'react-mdl';
|
||||
import EmptyCard from 'coral-admin/src/components/EmptyCard';
|
||||
import PropTypes from 'prop-types';
|
||||
import sortBy from 'lodash/sortBy';
|
||||
import {Dropdown, Option, Pager, Icon} from 'coral-ui';
|
||||
import {DataTable, TableHeader, RadioGroup, Radio} from 'react-mdl';
|
||||
import t from 'coral-framework/services/i18n';
|
||||
import styles from './Stories.css';
|
||||
import EmptyCard from 'coral-admin/src/components/EmptyCard';
|
||||
|
||||
const limit = 25;
|
||||
|
||||
export default class Stories extends Component {
|
||||
class Stories extends Component {
|
||||
|
||||
state = {
|
||||
search: '',
|
||||
@@ -50,51 +50,28 @@ export default class Stories extends Component {
|
||||
return `${d.getMonth() + 1}/${d.getDate()}/${d.getFullYear()}`;
|
||||
}
|
||||
|
||||
onStatusClick = (closeStream, id, statusMenuOpen) => async () => {
|
||||
if (statusMenuOpen) {
|
||||
this.setState((prev) => {
|
||||
prev.statusMenus[id] = false;
|
||||
return prev;
|
||||
});
|
||||
|
||||
try {
|
||||
await this.props.updateAssetState(id, closeStream ? Date.now() : null);
|
||||
const {search, sort, filter, page} = this.state;
|
||||
this.props.fetchAssets(page, limit, search, sort, filter);
|
||||
} catch (err) {
|
||||
|
||||
// TODO: handle error.
|
||||
console.error(err);
|
||||
}
|
||||
} else {
|
||||
this.setState((prev) => {
|
||||
prev.statusMenus[id] = true;
|
||||
return prev;
|
||||
});
|
||||
onStatusChange = async (closeStream, id) => {
|
||||
try {
|
||||
this.props.updateAssetState(id, closeStream ? Date.now() : null);
|
||||
const {search, sort, filter, page} = this.state;
|
||||
this.props.fetchAssets(page, limit, search, sort, filter);
|
||||
} catch(err) {
|
||||
console.error(err);
|
||||
}
|
||||
}
|
||||
|
||||
renderTitle = (title, {id}) => <Link to={`/admin/moderate/${id}`}>{title}</Link>
|
||||
|
||||
renderStatus = (closedAt, {id}) => {
|
||||
const closed = closedAt && new Date(closedAt).getTime() < Date.now();
|
||||
const statusMenuOpen = this.state.statusMenus[id];
|
||||
return <div className={styles.statusMenu}>
|
||||
<div
|
||||
className={closed ? styles.statusMenuClosed : styles.statusMenuOpen}
|
||||
onClick={this.onStatusClick(closed, id, statusMenuOpen)}>
|
||||
{!statusMenuOpen && <Icon className={styles.statusMenuIcon} name='keyboard_arrow_down'/>}
|
||||
{closed ? t('streams.closed') : t('streams.open')}
|
||||
</div>
|
||||
{
|
||||
statusMenuOpen &&
|
||||
<div
|
||||
className={!closed ? styles.statusMenuClosed : styles.statusMenuOpen}
|
||||
onClick={this.onStatusClick(!closed, id, statusMenuOpen)}>
|
||||
{!closed ? t('streams.closed') : t('streams.open')}
|
||||
</div>
|
||||
}
|
||||
</div>;
|
||||
const closed = !!(closedAt && new Date(closedAt).getTime() < Date.now());
|
||||
return (
|
||||
<Dropdown
|
||||
value={closed}
|
||||
onChange={(value) => this.onStatusChange(value, id)}>
|
||||
<Option value={false} label={t('streams.open')} />
|
||||
<Option value={true} label={t('streams.closed')} />
|
||||
</Dropdown>
|
||||
);
|
||||
}
|
||||
|
||||
onPageClick = (page) => {
|
||||
@@ -174,3 +151,11 @@ export default class Stories extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
Stories.propTypes = {
|
||||
assets: PropTypes.object,
|
||||
fetchAssets: PropTypes.func,
|
||||
updateAssetState: PropTypes.func,
|
||||
};
|
||||
|
||||
export default Stories;
|
||||
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
.dropdown {
|
||||
position: relative;
|
||||
width: 150px;
|
||||
height: 36px;
|
||||
background: #2c2c2c;
|
||||
box-sizing: border-box;
|
||||
color: white;
|
||||
border-radius: 2px;
|
||||
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: 1.4;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.toggle {
|
||||
padding: 10px 15px;
|
||||
cursor: pointer;
|
||||
outline: none;
|
||||
|
||||
&:focus {
|
||||
background: #888;
|
||||
}
|
||||
}
|
||||
|
||||
.toggleOpen {
|
||||
background: #888;
|
||||
}
|
||||
|
||||
.label {
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
.list {
|
||||
position: absolute;
|
||||
top: 30px;
|
||||
left: 10px;
|
||||
color: #2c2c2c;
|
||||
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;
|
||||
transform: scale(0);
|
||||
transition: transform .1s cubic-bezier(.4,0,.2,1),opacity .1s cubic-bezier(.4,0,.2,1),-webkit-transform .1s cubic-bezier(.4,0,.2,1);
|
||||
transform-origin: 0 0;
|
||||
}
|
||||
|
||||
.listActive {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
z-index: 999;
|
||||
}
|
||||
|
||||
.arrow {
|
||||
position: absolute;
|
||||
right: 15px;
|
||||
font-size: 1.2rem;
|
||||
vertical-align: middle;
|
||||
top: 13px;
|
||||
}
|
||||
@@ -0,0 +1,187 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import styles from './Dropdown.css';
|
||||
import Icon from './Icon';
|
||||
import cn from 'classnames';
|
||||
import ClickOutside from 'coral-framework/components/ClickOutside';
|
||||
|
||||
class Dropdown extends React.Component {
|
||||
|
||||
toggleRef = null;
|
||||
optionsRef = [];
|
||||
|
||||
state = {
|
||||
isOpen: false
|
||||
};
|
||||
|
||||
componentDidUpdate(_, prevState) {
|
||||
if (!this.state.isOpen && prevState.isOpen) {
|
||||
|
||||
// Refocus on the toggle element when menu closes.
|
||||
this.toggleRef.focus();
|
||||
}
|
||||
}
|
||||
|
||||
goUp = () => {
|
||||
const index = this.optionsRef.findIndex((ref) => ref.hasFocus());
|
||||
if (index > 0) {
|
||||
this.optionsRef[index - 1].focus();
|
||||
}
|
||||
}
|
||||
|
||||
goDown = () => {
|
||||
const index = this.optionsRef.findIndex((ref) => ref.hasFocus());
|
||||
if (index < this.optionsRef.length - 1) {
|
||||
this.optionsRef[index + 1].focus();
|
||||
}
|
||||
}
|
||||
|
||||
handleOptionKeyDown = (value, e) => {
|
||||
const code = e.which;
|
||||
|
||||
switch (code) {
|
||||
case 13: // 13 = Return
|
||||
case 32: // 32 = Space
|
||||
e.preventDefault();
|
||||
this.setValue(value);
|
||||
break;
|
||||
case 38: // 38 = Arrow Up
|
||||
e.preventDefault();
|
||||
this.goUp();
|
||||
break;
|
||||
case 40: // 40 = Arrow Down
|
||||
e.preventDefault();
|
||||
this.goDown();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
handleOptionClick = (value) => {
|
||||
this.setValue(value);
|
||||
}
|
||||
|
||||
setValue = (value) => {
|
||||
if (this.props.onChange) {
|
||||
this.props.onChange(value);
|
||||
}
|
||||
|
||||
this.setState({
|
||||
isOpen: false
|
||||
});
|
||||
}
|
||||
|
||||
toggle = () => {
|
||||
this.setState({
|
||||
isOpen: !this.state.isOpen
|
||||
});
|
||||
}
|
||||
|
||||
handleClick = () => {
|
||||
this.toggle();
|
||||
}
|
||||
|
||||
handleKeyDown = (e) => {
|
||||
const code = e.which;
|
||||
|
||||
// 13 = Return, 32 = Space
|
||||
if ((code === 13) || (code === 32)) {
|
||||
e.preventDefault();
|
||||
this.toggle();
|
||||
}
|
||||
}
|
||||
|
||||
hideMenu = () => {
|
||||
this.setState({
|
||||
isOpen: false
|
||||
});
|
||||
}
|
||||
|
||||
handleToggleRef = (ref) => this.toggleRef = ref;
|
||||
|
||||
handleOptionsRef = (ref, index) => {
|
||||
this.optionsRef[index] = ref;
|
||||
|
||||
// Focus on current value when menu opens.
|
||||
if (ref) {
|
||||
if (ref.props.value === this.props.value || index === 0 && !this.props.value) {
|
||||
ref.focus();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Trap keyboard focus inside the dropdown until a value has been chosen.
|
||||
trapFocusBegin = () => this.optionsRef[this.optionsRef.length - 1].focus();
|
||||
trapFocusEnd = () => this.optionsRef[0].focus();
|
||||
|
||||
renderLabel() {
|
||||
const options = React.Children.toArray(this.props.children);
|
||||
const option = options.find((option) => option.props.value === this.props.value);
|
||||
|
||||
if (option) {
|
||||
return option.props.label ? option.props.label : option.props.value;
|
||||
} else if (this.props.value) {
|
||||
return this.props.value;
|
||||
} else {
|
||||
return this.props.placeholder;
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const {className, toggleClassName, toggleOpenClassName} = this.props;
|
||||
return (
|
||||
<ClickOutside onClickOutside={this.hideMenu}>
|
||||
<div className={cn(styles.dropdown, className)}>
|
||||
<div
|
||||
className={cn(styles.toggle, toggleClassName, {[cn(this.state.isOpen, toggleOpenClassName)]: this.state.isOpen})}
|
||||
onClick={this.handleClick}
|
||||
onKeyDown={this.handleKeyDown}
|
||||
role="button"
|
||||
aria-pressed={this.state.isOpen}
|
||||
aria-haspopup="true"
|
||||
tabIndex="0"
|
||||
ref={this.handleToggleRef}
|
||||
>
|
||||
{this.props.icon && <Icon name={this.props.icon} className={styles.icon} aria-hidden="true" />}
|
||||
<span className={styles.label}>{this.renderLabel()}</span>
|
||||
{this.state.isOpen ? <Icon name="keyboard_arrow_up" className={styles.arrow} aria-hidden="true"/> : <Icon name="keyboard_arrow_down" className={styles.arrow} aria-hidden="true"/>}
|
||||
</div>
|
||||
{this.state.isOpen &&
|
||||
<div>
|
||||
<div tabIndex="0" onFocus={this.trapFocusBegin} />
|
||||
<ul className={cn(styles.list, {[styles.listActive] : this.state.isOpen})}>
|
||||
{React.Children.toArray(this.props.children)
|
||||
.map((child, i) =>
|
||||
React.cloneElement(child, {
|
||||
key: child.props.value,
|
||||
ref: (ref) => this.handleOptionsRef(ref, i),
|
||||
index: i,
|
||||
onClick: () => this.handleOptionClick(child.props.value),
|
||||
onKeyDown: (e) => this.handleOptionKeyDown(child.props.value, e)
|
||||
}))}
|
||||
</ul>
|
||||
<div tabIndex="0" onFocus={this.trapFocusEnd} />
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</ClickOutside>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Dropdown.propTypes = {
|
||||
className: PropTypes.string,
|
||||
toggleClassName: PropTypes.string,
|
||||
toggleOpenClassName: PropTypes.string,
|
||||
placeholder: PropTypes.string,
|
||||
icon: PropTypes.string,
|
||||
onChange: PropTypes.func.isRequired,
|
||||
children: PropTypes.node.isRequired,
|
||||
value: PropTypes.oneOfType([
|
||||
PropTypes.number,
|
||||
PropTypes.string,
|
||||
PropTypes.bool
|
||||
]),
|
||||
};
|
||||
|
||||
export default Dropdown;
|
||||
@@ -4,12 +4,13 @@ import {Icon as IconMDL} from 'react-mdl';
|
||||
import cn from 'classnames';
|
||||
import styles from './Icon.css';
|
||||
|
||||
const Icon = ({className = '', name}) => (
|
||||
<IconMDL className={cn(styles.root, className)} name={name} />
|
||||
const Icon = ({className = '', ...rest}) => (
|
||||
<IconMDL className={cn(styles.root, className)} {...rest} />
|
||||
);
|
||||
|
||||
Icon.propTypes = {
|
||||
name: PropTypes.string.isRequired
|
||||
name: PropTypes.string.isRequired,
|
||||
className: PropTypes.string,
|
||||
};
|
||||
|
||||
export default Icon;
|
||||
|
||||
@@ -1,3 +1,10 @@
|
||||
.Option {
|
||||
|
||||
.option {
|
||||
padding: 10px;
|
||||
text-transform: capitalize;
|
||||
outline: none;
|
||||
|
||||
&:focus, &:hover {
|
||||
background-color: #ccc;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,39 @@
|
||||
import React from 'react';
|
||||
import {Option as OptionMDL} from 'react-mdl-selectfield';
|
||||
import PropTypes from 'prop-types';
|
||||
import styles from './Option.css';
|
||||
import cn from 'classnames';
|
||||
|
||||
const Option = (props) => {
|
||||
const {children, ...attrs} = props;
|
||||
return (
|
||||
<OptionMDL className={styles.Option} {...attrs}>
|
||||
{children}
|
||||
</OptionMDL>
|
||||
);
|
||||
class Option extends React.Component {
|
||||
|
||||
ref = null;
|
||||
|
||||
handleRef = (ref) => {
|
||||
this.ref = ref;
|
||||
};
|
||||
|
||||
focus = () => {this.ref.focus();}
|
||||
hasFocus = () => document.activeElement === this.ref;
|
||||
|
||||
render() {
|
||||
const {className, label = '', onClick, onKeyDown} = this.props;
|
||||
return (
|
||||
<li className={cn(styles.option, className)} onClick={onClick} onKeyDown={onKeyDown} role="option" tabIndex="0" ref={this.handleRef}>
|
||||
{label}
|
||||
</li>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Option.propTypes = {
|
||||
className: PropTypes.string,
|
||||
label: PropTypes.string,
|
||||
onClick: PropTypes.func,
|
||||
onKeyDown: PropTypes.func,
|
||||
value: PropTypes.oneOfType([
|
||||
PropTypes.number,
|
||||
PropTypes.string,
|
||||
PropTypes.bool
|
||||
]),
|
||||
};
|
||||
|
||||
export default Option;
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
.Select {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 40px;
|
||||
background: #2c2c2c;
|
||||
padding: 10px 15px;
|
||||
box-sizing: border-box;
|
||||
color: white;
|
||||
border-radius: 2px;
|
||||
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);
|
||||
|
||||
> div {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
i {
|
||||
position: absolute;
|
||||
top: 7px;
|
||||
right: 7px;
|
||||
}
|
||||
|
||||
input {
|
||||
padding: 0;
|
||||
font-size: 13px;
|
||||
letter-spacing: 0.7px;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
import React from 'react';
|
||||
import {SelectField} from 'react-mdl-selectfield';
|
||||
import styles from './Select.css';
|
||||
|
||||
const Select = (props) => {
|
||||
const {children, ...attrs} = props;
|
||||
return (
|
||||
<SelectField className={styles.Select} {...attrs}>
|
||||
{children}
|
||||
</SelectField>
|
||||
);
|
||||
};
|
||||
|
||||
export default Select;
|
||||
@@ -21,10 +21,10 @@ export {default as Success} from './components/Success';
|
||||
export {default as Pager} from './components/Pager';
|
||||
export {default as Wizard} from './components/Wizard';
|
||||
export {default as WizardNav} from './components/WizardNav';
|
||||
export {default as Select} from './components/Select';
|
||||
export {default as Option} from './components/Option';
|
||||
export {default as SnackBar} from './components/SnackBar';
|
||||
export {default as TextArea} from './components/TextArea';
|
||||
export {default as Drawer} from './components/Drawer';
|
||||
export {default as Label} from './components/Label';
|
||||
export {default as FlagLabel} from './components/FlagLabel';
|
||||
export {default as Dropdown} from './components/Dropdown';
|
||||
export {default as Option} from './components/Option';
|
||||
|
||||
@@ -92,7 +92,7 @@ const getCommentCountByQuery = (context, {ids, statuses, asset_id, parent_id, au
|
||||
let query = CommentModel.find();
|
||||
|
||||
// If user queries for statuses other than NONE and/or ACCEPTED statuses, it needs
|
||||
// special priviledges.
|
||||
// special privileges.
|
||||
if (
|
||||
(!statuses || statuses.some((status) => !['NONE', 'ACCEPTED'].includes(status))) &&
|
||||
(context.user == null || !context.user.can(SEARCH_NON_NULL_OR_ACCEPTED_COMMENTS))
|
||||
@@ -100,7 +100,7 @@ const getCommentCountByQuery = (context, {ids, statuses, asset_id, parent_id, au
|
||||
return null;
|
||||
}
|
||||
|
||||
if (statuses) {
|
||||
if (statuses && statuses.length > 0) {
|
||||
query = query.where({status: {$in: statuses}});
|
||||
}
|
||||
|
||||
|
||||
@@ -75,6 +75,7 @@ en:
|
||||
status: Status
|
||||
username_and_email: "Username and Email"
|
||||
yes_ban_user: "Yes Ban User"
|
||||
none: "None"
|
||||
configure:
|
||||
apply: Apply
|
||||
banned_word_text: "Comments which contain these words or phrases (not case-sensitive) will be automatically removed from the comment stream. Type a word and press Enter or Tab to add. Optionally paste a comma-separated list."
|
||||
@@ -289,6 +290,7 @@ en:
|
||||
select_stream: "Select Stream"
|
||||
shift_key: "⇧"
|
||||
shortcuts: "Shortcuts"
|
||||
sort: "Sort"
|
||||
show_shortcuts: "Show Shortcuts"
|
||||
singleview: "Toggle single comment edit view"
|
||||
thismenu: "Open this menu"
|
||||
|
||||
@@ -74,6 +74,7 @@ es:
|
||||
status: Estado
|
||||
username_and_email: "Usuario y Correo"
|
||||
yes_ban_user: "Si, Suspendan el usuario"
|
||||
none: "Ninguno"
|
||||
configure:
|
||||
apply: Aplicar
|
||||
banned_word_text: "Comentarios que contengan estas palabras o frases, en mayusculas o minúsculas, serán automáticamente eliminados del hilo de comentario. Escribir una palabra y apretar Enter o Tabulador para agregarla. O pueden pegar una lista de palabras separadas por coma."
|
||||
@@ -280,6 +281,7 @@ es:
|
||||
select_stream: "Seleccionar hilo de comentarios"
|
||||
shift_key: ⇧
|
||||
shortcuts: Atajos
|
||||
sort: "Ordenar"
|
||||
show_shortcuts: "Mostrar Atajos"
|
||||
singleview: "Colocar vista de edición de comentario único"
|
||||
thismenu: "Abrir este menu"
|
||||
|
||||
Reference in New Issue
Block a user