diff --git a/app/javascript/flavours/glitch/components/admin/ReportReasonSelector.js b/app/javascript/flavours/glitch/components/admin/ReportReasonSelector.js new file mode 100644 index 000000000..0f2a4fe36 --- /dev/null +++ b/app/javascript/flavours/glitch/components/admin/ReportReasonSelector.js @@ -0,0 +1,159 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import api from 'flavours/glitch/util/api'; +import { injectIntl, defineMessages } from 'react-intl'; +import classNames from 'classnames'; + +const messages = defineMessages({ + other: { id: 'report.categories.other', defaultMessage: 'Other' }, + spam: { id: 'report.categories.spam', defaultMessage: 'Spam' }, + violation: { id: 'report.categories.violation', defaultMessage: 'Content violates one or more server rules' }, +}); + +class Category extends React.PureComponent { + + static propTypes = { + id: PropTypes.string.isRequired, + text: PropTypes.string.isRequired, + selected: PropTypes.bool, + disabled: PropTypes.bool, + onSelect: PropTypes.func, + children: PropTypes.node, + }; + + handleClick = () => { + const { id, disabled, onSelect } = this.props; + + if (!disabled) { + onSelect(id); + } + }; + + render () { + const { id, text, disabled, selected, children } = this.props; + + return ( +
+ {selected && } + +
+ + {text} +
+ + {(selected && children) && ( +
+ {children} +
+ )} +
+ ); + } + +} + +class Rule extends React.PureComponent { + + static propTypes = { + id: PropTypes.string.isRequired, + text: PropTypes.string.isRequired, + selected: PropTypes.bool, + disabled: PropTypes.bool, + onToggle: PropTypes.func, + }; + + handleClick = () => { + const { id, disabled, onToggle } = this.props; + + if (!disabled) { + onToggle(id); + } + }; + + render () { + const { id, text, disabled, selected } = this.props; + + return ( +
+ + {selected && } + {text} +
+ ); + } + +} + +export default @injectIntl +class ReportReasonSelector extends React.PureComponent { + + static propTypes = { + id: PropTypes.string.isRequired, + category: PropTypes.string.isRequired, + rule_ids: PropTypes.arrayOf(PropTypes.string), + disabled: PropTypes.bool, + intl: PropTypes.object.isRequired, + }; + + state = { + category: this.props.category, + rule_ids: this.props.rule_ids || [], + rules: [], + }; + + componentDidMount() { + api().get('/api/v1/instance').then(res => { + this.setState({ + rules: res.data.rules, + }); + }).catch(err => { + console.error(err); + }); + } + + _save = () => { + const { id, disabled } = this.props; + const { category, rule_ids } = this.state; + + if (disabled) { + return; + } + + api().put(`/api/v1/admin/reports/${id}`, { + category, + rule_ids, + }).catch(err => { + console.error(err); + }); + }; + + handleSelect = id => { + this.setState({ category: id }, () => this._save()); + }; + + handleToggle = id => { + const { rule_ids } = this.state; + + if (rule_ids.includes(id)) { + this.setState({ rule_ids: rule_ids.filter(x => x !== id ) }, () => this._save()); + } else { + this.setState({ rule_ids: [...rule_ids, id] }, () => this._save()); + } + }; + + render () { + const { disabled, intl } = this.props; + const { rules, category, rule_ids } = this.state; + + return ( +
+ + + + {rules.map(rule => )} + +
+ ); + } + +} diff --git a/app/javascript/flavours/glitch/styles/admin.scss b/app/javascript/flavours/glitch/styles/admin.scss index 8fd556c73..92061585a 100644 --- a/app/javascript/flavours/glitch/styles/admin.scss +++ b/app/javascript/flavours/glitch/styles/admin.scss @@ -595,39 +595,44 @@ body, .log-entry { line-height: 20px; - padding: 15px 0; + padding: 15px; + padding-left: 15px * 2 + 40px; background: $ui-base-color; - border-bottom: 1px solid lighten($ui-base-color, 4%); + border-bottom: 1px solid darken($ui-base-color, 8%); + position: relative; + + &:first-child { + border-top-left-radius: 4px; + border-top-right-radius: 4px; + } &:last-child { + border-bottom-left-radius: 4px; + border-bottom-right-radius: 4px; border-bottom: 0; } + &:hover { + background: lighten($ui-base-color, 4%); + } + &__header { - display: flex; - justify-content: flex-start; - align-items: center; color: $darker-text-color; font-size: 14px; - padding: 0 10px; } &__avatar { - margin-right: 10px; + position: absolute; + left: 15px; + top: 15px; .avatar { - display: block; - margin: 0; - border-radius: 50%; + border-radius: 4px; width: 40px; height: 40px; } } - &__content { - max-width: calc(100% - 90px); - } - &__title { word-wrap: break-word; } @@ -643,6 +648,14 @@ body, text-decoration: none; font-weight: 500; } + + a { + &:hover, + &:focus, + &:active { + text-decoration: underline; + } + } } a.name-tag, @@ -671,8 +684,9 @@ a.inline-name-tag, a.name-tag, .name-tag { - display: flex; + display: inline-flex; align-items: center; + vertical-align: top; .avatar { display: block; @@ -1130,3 +1144,287 @@ a.sparkline { } } } + +.report-reason-selector { + border-radius: 4px; + background: $ui-base-color; + margin-bottom: 20px; + + &__category { + cursor: pointer; + border-bottom: 1px solid darken($ui-base-color, 8%); + + &:last-child { + border-bottom: 0; + } + + &__label { + padding: 15px; + } + + &__rules { + margin-left: 30px; + } + } + + &__rule { + cursor: pointer; + padding: 15px; + } +} + +.report-header { + display: grid; + grid-gap: 15px; + grid-template-columns: minmax(0, 1fr) 300px; + + &__details { + &__item { + border-bottom: 1px solid lighten($ui-base-color, 8%); + padding: 15px 0; + + &:last-child { + border-bottom: 0; + } + + &__header { + font-weight: 600; + padding: 4px 0; + } + } + + &--horizontal { + display: grid; + grid-auto-columns: minmax(0, 1fr); + grid-auto-flow: column; + + .report-header__details__item { + border-bottom: 0; + } + } + } +} + +.account-card { + background: $ui-base-color; + border-radius: 4px; + + &__header { + padding: 4px; + border-radius: 4px; + height: 128px; + + img { + display: block; + margin: 0; + width: 100%; + height: 100%; + object-fit: cover; + background: darken($ui-base-color, 8%); + } + } + + &__title { + margin-top: -25px; + display: flex; + align-items: flex-end; + + &__avatar { + padding: 15px; + + img { + display: block; + margin: 0; + width: 56px; + height: 56px; + background: darken($ui-base-color, 8%); + border-radius: 8px; + } + } + + .display-name { + color: $darker-text-color; + padding-bottom: 15px; + font-size: 15px; + + bdi { + display: block; + color: $primary-text-color; + font-weight: 500; + } + } + } + + &__bio { + padding: 0 15px; + overflow: hidden; + text-overflow: ellipsis; + word-wrap: break-word; + max-height: 18px * 2; + position: relative; + + &::after { + display: block; + content: ""; + width: 50px; + height: 18px; + position: absolute; + bottom: 0; + right: 15px; + background: linear-gradient(to left, $ui-base-color, transparent); + pointer-events: none; + } + } + + &__actions { + display: flex; + align-items: center; + padding-top: 10px; + + &__button { + flex: 0 0 auto; + padding: 0 15px; + } + } + + &__counters { + flex: 1 1 auto; + display: grid; + grid-auto-columns: minmax(0, 1fr); + grid-auto-flow: column; + + &__item { + padding: 15px; + text-align: center; + color: $primary-text-color; + font-weight: 600; + font-size: 15px; + + small { + display: block; + color: $darker-text-color; + font-weight: 400; + font-size: 13px; + } + } + } +} + +.report-notes { + margin-bottom: 20px; + + &__item { + background: $ui-base-color; + position: relative; + padding: 15px; + padding-left: 15px * 2 + 40px; + border-bottom: 1px solid darken($ui-base-color, 8%); + + &:first-child { + border-top-left-radius: 4px; + border-top-right-radius: 4px; + } + + &:last-child { + border-bottom-left-radius: 4px; + border-bottom-right-radius: 4px; + border-bottom: 0; + } + + &:hover { + background-color: lighten($ui-base-color, 4%); + } + + &__avatar { + position: absolute; + left: 15px; + top: 15px; + border-radius: 4px; + width: 40px; + height: 40px; + } + + &__header { + color: $darker-text-color; + font-size: 15px; + line-height: 20px; + margin-bottom: 4px; + + .username a { + color: $primary-text-color; + font-weight: 500; + text-decoration: none; + margin-right: 5px; + + &:hover, + &:focus, + &:active { + text-decoration: underline; + } + } + + time { + margin-left: 5px; + vertical-align: baseline; + } + } + + &__content { + font-size: 15px; + line-height: 20px; + word-wrap: break-word; + font-weight: 400; + color: $primary-text-color; + + p { + margin-bottom: 20px; + white-space: pre-wrap; + unicode-bidi: plaintext; + + &:last-child { + margin-bottom: 0; + } + } + } + + &__actions { + position: absolute; + top: 15px; + right: 15px; + text-align: right; + } + } +} + +.report-actions { + border: 1px solid darken($ui-base-color, 8%); + + &__item { + display: flex; + align-items: center; + line-height: 18px; + border-bottom: 1px solid darken($ui-base-color, 8%); + + &:last-child { + border-bottom: 0; + } + + &__button { + flex: 0 0 auto; + width: 100px; + padding: 15px; + padding-right: 0; + + .button { + display: block; + width: 100%; + } + } + + &__description { + padding: 15px; + font-size: 14px; + color: $dark-text-color; + } + } +} diff --git a/app/javascript/flavours/glitch/styles/polls.scss b/app/javascript/flavours/glitch/styles/polls.scss index 5fc41ed9e..a2cdecf06 100644 --- a/app/javascript/flavours/glitch/styles/polls.scss +++ b/app/javascript/flavours/glitch/styles/polls.scss @@ -150,6 +150,21 @@ &:active { outline: 0 !important; } + + &.disabled { + border-color: $dark-text-color; + + &.active { + background: $dark-text-color; + } + + &:active, + &:focus, + &:hover { + border-color: $dark-text-color; + border-width: 1px; + } + } } &__number { diff --git a/app/javascript/flavours/glitch/util/backend_links.js b/app/javascript/flavours/glitch/util/backend_links.js index 0fb378cc1..2e5111a7f 100644 --- a/app/javascript/flavours/glitch/util/backend_links.js +++ b/app/javascript/flavours/glitch/util/backend_links.js @@ -3,7 +3,7 @@ export const profileLink = '/settings/profile'; export const signOutLink = '/auth/sign_out'; export const termsLink = '/terms'; export const accountAdminLink = (id) => `/admin/accounts/${id}`; -export const statusAdminLink = (account_id, status_id) => `/admin/accounts/${account_id}/statuses/${status_id}`; +export const statusAdminLink = (account_id, status_id) => `/admin/accounts/${account_id}/statuses?id=${status_id}`; export const filterEditLink = (id) => `/filters/${id}/edit`; export const relationshipsLink = '/relationships'; export const securityLink = '/auth/edit';