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 0000000000..0f2a4fe362
--- /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 8fd556c736..92061585a5 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 5fc41ed9e9..a2cdecf06c 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 0fb378cc10..2e5111a7f4 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';