diff --git a/app/soapbox/actions/admin.js b/app/soapbox/actions/admin.js index 84857abba..04fdd1a31 100644 --- a/app/soapbox/actions/admin.js +++ b/app/soapbox/actions/admin.js @@ -37,6 +37,10 @@ export const ADMIN_STATUS_DELETE_REQUEST = 'ADMIN_STATUS_DELETE_REQUEST'; export const ADMIN_STATUS_DELETE_SUCCESS = 'ADMIN_STATUS_DELETE_SUCCESS'; export const ADMIN_STATUS_DELETE_FAIL = 'ADMIN_STATUS_DELETE_FAIL'; +export const ADMIN_LOG_FETCH_REQUEST = 'ADMIN_LOG_FETCH_REQUEST'; +export const ADMIN_LOG_FETCH_SUCCESS = 'ADMIN_LOG_FETCH_SUCCESS'; +export const ADMIN_LOG_FETCH_FAIL = 'ADMIN_LOG_FETCH_FAIL'; + export function fetchConfig() { return (dispatch, getState) => { dispatch({ type: ADMIN_CONFIG_FETCH_REQUEST }); @@ -158,3 +162,17 @@ export function deleteStatus(id) { }); }; } + +export function fetchModerationLog(params) { + return (dispatch, getState) => { + dispatch({ type: ADMIN_LOG_FETCH_REQUEST }); + return api(getState) + .get('/api/pleroma/admin/moderation_log', { params }) + .then(({ data }) => { + dispatch({ type: ADMIN_LOG_FETCH_SUCCESS, items: data.items, total: data.total }); + return data; + }).catch(error => { + dispatch({ type: ADMIN_LOG_FETCH_FAIL, error }); + }); + }; +} diff --git a/app/soapbox/features/admin/moderation_log.js b/app/soapbox/features/admin/moderation_log.js new file mode 100644 index 000000000..e84fd6ed6 --- /dev/null +++ b/app/soapbox/features/admin/moderation_log.js @@ -0,0 +1,59 @@ +import React from 'react'; +import { defineMessages, injectIntl } from 'react-intl'; +import { connect } from 'react-redux'; +import ImmutablePureComponent from 'react-immutable-pure-component'; +import PropTypes from 'prop-types'; +import Column from '../ui/components/column'; +import ScrollableList from 'soapbox/components/scrollable_list'; +import { fetchModerationLog } from 'soapbox/actions/admin'; +import { List as ImmutableList, fromJS } from 'immutable'; + +const messages = defineMessages({ + heading: { id: 'column.admin.moderation_log', defaultMessage: 'Moderation Log' }, + emptyMessage: { id: 'admin.moderation_log.empty_message', defaultMessage: 'You have not performed any moderation actions yet. When you do, a history will be shown here.' }, +}); + +export default @connect() +@injectIntl +class ModerationLog extends ImmutablePureComponent { + + static propTypes = { + intl: PropTypes.object.isRequired, + }; + + state = { + isLoading: true, + items: ImmutableList(), + } + + componentDidMount() { + const { dispatch } = this.props; + dispatch(fetchModerationLog()) + .then(data => this.setState({ isLoading: false, items: fromJS(data.items) })) + .catch(() => {}); + } + + render() { + const { intl } = this.props; + const { isLoading, items } = this.state; + const showLoading = isLoading && items.count() === 0; + + return ( + + + {items.map((item, i) => ( +
+ {item.get('message')} +
+ ))} +
+
+ ); + } + +} diff --git a/app/soapbox/features/admin/reports.js b/app/soapbox/features/admin/reports.js index 50c7c33a7..141db1d45 100644 --- a/app/soapbox/features/admin/reports.js +++ b/app/soapbox/features/admin/reports.js @@ -4,7 +4,7 @@ import { connect } from 'react-redux'; import ImmutablePureComponent from 'react-immutable-pure-component'; import PropTypes from 'prop-types'; import ImmutablePropTypes from 'react-immutable-proptypes'; -import Column from '../ui/components/column'; +import Column from '../ui/components/better_column'; import ScrollableList from 'soapbox/components/scrollable_list'; import { fetchReports } from 'soapbox/actions/admin'; import Report from './components/report'; @@ -12,6 +12,7 @@ import { makeGetReport } from 'soapbox/selectors'; const messages = defineMessages({ heading: { id: 'column.admin.reports', defaultMessage: 'Reports' }, + modlog: { id: 'column.admin.reports.menu.moderation_log', defaultMessage: 'Moderation Log' }, emptyMessage: { id: 'admin.reports.empty_message', defaultMessage: 'There are no open reports. If a user gets reported, they will show up here.' }, }); @@ -37,6 +38,15 @@ class Reports extends ImmutablePureComponent { isLoading: true, } + makeColumnMenu = () => { + const { intl } = this.props; + + return [{ + text: intl.formatMessage(messages.modlog), + to: '/admin/log', + }]; + } + componentDidMount() { const { dispatch } = this.props; dispatch(fetchReports()) @@ -50,7 +60,7 @@ class Reports extends ImmutablePureComponent { const showLoading = isLoading && reports.count() === 0; return ( - + +
+ {heading && } + {menu && ( +
+ +
+ )} + +
+ {children} + + ); + } + +} diff --git a/app/soapbox/features/ui/components/column.js b/app/soapbox/features/ui/components/column.js index 888b2c04e..5f31399e3 100644 --- a/app/soapbox/features/ui/components/column.js +++ b/app/soapbox/features/ui/components/column.js @@ -1,7 +1,6 @@ import React from 'react'; import ColumnHeader from './column_header'; import PropTypes from 'prop-types'; -import { isMobile } from '../../../is_mobile'; import ColumnBackButton from '../../../components/column_back_button'; import ColumnBackButtonSlim from '../../../components/column_back_button_slim'; @@ -12,25 +11,17 @@ export default class Column extends React.PureComponent { icon: PropTypes.string, children: PropTypes.node, active: PropTypes.bool, - hideHeadingOnMobile: PropTypes.bool, backBtnSlim: PropTypes.bool, }; render() { - const { heading, icon, children, active, hideHeadingOnMobile, backBtnSlim } = this.props; - - const showHeading = heading && (!hideHeadingOnMobile || (hideHeadingOnMobile && !isMobile(window.innerWidth))); - - const columnHeaderId = showHeading && heading.replace(/ /g, '-'); - const header = showHeading && ( - - ); - + const { heading, icon, children, active, backBtnSlim } = this.props; + const columnHeaderId = heading && heading.replace(/ /g, '-'); const backBtn = backBtnSlim ? () : (); return (
- {header} + {heading && } {backBtn} {children}
diff --git a/app/soapbox/features/ui/components/column_header.js b/app/soapbox/features/ui/components/column_header.js index 6ff52e44f..bb027eb38 100644 --- a/app/soapbox/features/ui/components/column_header.js +++ b/app/soapbox/features/ui/components/column_header.js @@ -19,16 +19,11 @@ export default class ColumnHeader extends React.PureComponent { render() { const { icon, type, active, columnHeaderId } = this.props; - let iconElement = ''; - - if (icon) { - iconElement = ; - } return (

diff --git a/app/soapbox/features/ui/index.js b/app/soapbox/features/ui/index.js index 59c36c831..6a556510c 100644 --- a/app/soapbox/features/ui/index.js +++ b/app/soapbox/features/ui/index.js @@ -90,6 +90,7 @@ import { Dashboard, AwaitingApproval, Reports, + ModerationLog, } from './util/async-components'; // Dummy import, to make sure that ends up in the application bundle. @@ -282,6 +283,7 @@ class SwitchingColumnsArea extends React.PureComponent { + diff --git a/app/soapbox/features/ui/util/async-components.js b/app/soapbox/features/ui/util/async-components.js index 1e9cc7550..bc0c98ca8 100644 --- a/app/soapbox/features/ui/util/async-components.js +++ b/app/soapbox/features/ui/util/async-components.js @@ -229,3 +229,7 @@ export function AwaitingApproval() { export function Reports() { return import(/* webpackChunkName: "features/admin/reports" */'../../admin/reports'); } + +export function ModerationLog() { + return import(/* webpackChunkName: "features/admin/moderation_log" */'../../admin/moderation_log'); +} diff --git a/app/styles/components/admin.scss b/app/styles/components/admin.scss index 8943e5555..caf9ddbcb 100644 --- a/app/styles/components/admin.scss +++ b/app/styles/components/admin.scss @@ -89,8 +89,11 @@ } } -.slist .item-list article:nth-child(2n-1) .unapproved-account { - background-color: hsla(var(--accent-color_hsl), 0.07); +.page--admin .slist .item-list article:nth-child(2n-1) { + .unapproved-account, + .logentry { + background-color: hsla(var(--accent-color_hsl), 0.07); + } } .page--admin { @@ -197,3 +200,7 @@ } } } + +.logentry { + padding: 15px; +} diff --git a/app/styles/components/columns.scss b/app/styles/components/columns.scss index a2f430538..a48fb87d5 100644 --- a/app/styles/components/columns.scss +++ b/app/styles/components/columns.scss @@ -726,3 +726,44 @@ margin-right: 8px; } } + +.column--better { + .column__top { + display: flex; + align-items: center; + } + + .column-header { + margin-right: auto; + } + + .column__menu { + display: flex; + align-items: center; + justify-content: center; + + &, + > div, + button { + height: 100%; + } + + button { + padding: 0 15px; + + > div { + display: flex; + align-items: center; + justify-content: center; + } + } + } + + .column-back-button--slim { + &-button { + position: relative; + top: auto; + right: auto; + } + } +}