diff options
Diffstat (limited to 'app/javascript/flavours/glitch')
15 files changed, 349 insertions, 13 deletions
diff --git a/app/javascript/flavours/glitch/actions/notifications.js b/app/javascript/flavours/glitch/actions/notifications.js index 85938867b..3993b1ea5 100644 --- a/app/javascript/flavours/glitch/actions/notifications.js +++ b/app/javascript/flavours/glitch/actions/notifications.js @@ -103,6 +103,10 @@ export function updateNotifications(notification, intlMessages, intlLocale) { dispatch(importFetchedStatus(notification.status)); } + if (notification.report) { + dispatch(importFetchedAccount(notification.report.target_account)); + } + dispatch({ type: NOTIFICATIONS_UPDATE, notification, @@ -146,6 +150,7 @@ const excludeTypesFromFilter = filter => { 'status', 'update', 'admin.sign_up', + 'admin.report', ]); return allTypes.filterNot(item => item === filter).toJS(); @@ -191,6 +196,7 @@ export function expandNotifications({ maxId } = {}, done = noOp) { dispatch(importFetchedAccounts(response.data.map(item => item.account))); dispatch(importFetchedStatuses(response.data.map(item => item.status).filter(status => !!status))); + dispatch(importFetchedAccounts(response.data.filter(item => item.report).map(item => item.report.target_account))); dispatch(expandNotificationsSuccess(response.data, next ? next.uri : null, isLoadingMore, isLoadingRecent, isLoadingRecent && preferPendingItems)); fetchRelatedRelationships(dispatch, response.data); diff --git a/app/javascript/flavours/glitch/components/icon_button.js b/app/javascript/flavours/glitch/components/icon_button.js index 9a05badd0..be2468d68 100644 --- a/app/javascript/flavours/glitch/components/icon_button.js +++ b/app/javascript/flavours/glitch/components/icon_button.js @@ -140,8 +140,16 @@ export default class IconButton extends React.PureComponent { ); if (href) { - contents = ( - <a href={href} target='_blank' rel='noopener noreferrer'> + return ( + <a + href={href} + aria-label={title} + title={title} + target='_blank' + rel='noopener noreferrer' + className={classes} + style={style} + > {contents} </a> ); diff --git a/app/javascript/flavours/glitch/features/notifications/components/admin_report.js b/app/javascript/flavours/glitch/features/notifications/components/admin_report.js new file mode 100644 index 000000000..80beeb9da --- /dev/null +++ b/app/javascript/flavours/glitch/features/notifications/components/admin_report.js @@ -0,0 +1,108 @@ +// Package imports. +import React from 'react'; +import ImmutablePropTypes from 'react-immutable-proptypes'; +import PropTypes from 'prop-types'; +import { FormattedMessage, defineMessages, injectIntl } from 'react-intl'; +import ImmutablePureComponent from 'react-immutable-pure-component'; +import { HotKeys } from 'react-hotkeys'; +import classNames from 'classnames'; + +// Our imports. +import Permalink from 'flavours/glitch/components/permalink'; +import AccountContainer from 'flavours/glitch/containers/account_container'; +import NotificationOverlayContainer from '../containers/overlay_container'; +import Icon from 'flavours/glitch/components/icon'; +import Report from './report'; + +const messages = defineMessages({ + adminReport: { id: 'notification.admin.report', defaultMessage: '{name} reported {target}' }, +}); + +export default class AdminReport extends ImmutablePureComponent { + + static propTypes = { + hidden: PropTypes.bool, + id: PropTypes.string.isRequired, + account: ImmutablePropTypes.map.isRequired, + notification: ImmutablePropTypes.map.isRequired, + unread: PropTypes.bool, + report: ImmutablePropTypes.map.isRequired, + }; + + handleMoveUp = () => { + const { notification, onMoveUp } = this.props; + onMoveUp(notification.get('id')); + } + + handleMoveDown = () => { + const { notification, onMoveDown } = this.props; + onMoveDown(notification.get('id')); + } + + handleOpen = () => { + this.handleOpenProfile(); + } + + handleOpenProfile = () => { + const { notification } = this.props; + this.context.router.history.push(`/@${notification.getIn(['account', 'acct'])}`); + } + + handleMention = e => { + e.preventDefault(); + + const { notification, onMention } = this.props; + onMention(notification.get('account'), this.context.router.history); + } + + getHandlers () { + return { + moveUp: this.handleMoveUp, + moveDown: this.handleMoveDown, + open: this.handleOpen, + openProfile: this.handleOpenProfile, + mention: this.handleMention, + reply: this.handleMention, + }; + } + + render () { + const { intl, account, notification, unread, report } = this.props; + + // Links to the display name. + const displayName = account.get('display_name_html') || account.get('username'); + const link = ( + <bdi><Permalink + className='notification__display-name' + href={account.get('url')} + title={account.get('acct')} + to={`/@${account.get('acct')}`} + dangerouslySetInnerHTML={{ __html: displayName }} + /></bdi> + ); + + const targetAccount = report.get('target_account'); + const targetDisplayNameHtml = { __html: targetAccount.get('display_name_html') }; + const targetLink = <bdi><Permalink className='notification__display-name' href={targetAccount.get('url')} title={targetAccount.get('acct')} to={`/@${targetAccount.get('acct')}`} dangerouslySetInnerHTML={targetDisplayNameHtml} /></bdi>; + + return ( + <HotKeys handlers={this.getHandlers()}> + <div className={classNames('notification notification-admin-report focusable', { unread })} tabIndex='0'> + <div className='notification__message'> + <div className='notification__favourite-icon-wrapper'> + <Icon id='flag' fixedWidth /> + </div> + + <span title={notification.get('created_at')}> + <FormattedMessage id='notification.admin.report' defaultMessage='{name} reported {target}' values={{ name: link, target: targetLink }} /> + </span> + </div> + + <Report account={account} report={notification.get('report')} hidden={this.props.hidden} /> + <NotificationOverlayContainer notification={notification} /> + </div> + </HotKeys> + ); + } + +} diff --git a/app/javascript/flavours/glitch/features/notifications/components/column_settings.js b/app/javascript/flavours/glitch/features/notifications/components/column_settings.js index 0be2a7e13..a8502f563 100644 --- a/app/javascript/flavours/glitch/features/notifications/components/column_settings.js +++ b/app/javascript/flavours/glitch/features/notifications/components/column_settings.js @@ -179,6 +179,19 @@ export default class ColumnSettings extends React.PureComponent { </div> </div> )} + + {isStaff && ( + <div role='group' aria-labelledby='notifications-admin-report'> + <span id='notifications-status' className='column-settings__section'><FormattedMessage id='notifications.column_settings.admin.report' defaultMessage='New reports:' /></span> + + <div className='column-settings__pillbar'> + <PillBarButton disabled={browserPermission === 'denied'} prefix='notifications_desktop' settings={settings} settingPath={['alerts', 'admin.report']} onChange={onChange} label={alertStr} /> + {showPushSettings && <PillBarButton prefix='notifications_push' settings={pushSettings} settingPath={['alerts', 'admin.report']} onChange={this.onPushChange} label={pushStr} />} + <PillBarButton prefix='notifications' settings={settings} settingPath={['shows', 'admin.report']} onChange={onChange} label={showStr} /> + <PillBarButton prefix='notifications' settings={settings} settingPath={['sounds', 'admin.report']} onChange={onChange} label={soundStr} /> + </div> + </div> + )} </div> ); } diff --git a/app/javascript/flavours/glitch/features/notifications/components/notification.js b/app/javascript/flavours/glitch/features/notifications/components/notification.js index e0cd3c7a6..d676a4207 100644 --- a/app/javascript/flavours/glitch/features/notifications/components/notification.js +++ b/app/javascript/flavours/glitch/features/notifications/components/notification.js @@ -9,6 +9,7 @@ import StatusContainer from 'flavours/glitch/containers/status_container'; import NotificationFollow from './follow'; import NotificationFollowRequestContainer from '../containers/follow_request_container'; import NotificationAdminSignup from './admin_signup'; +import NotificationAdminReportContainer from '../containers/admin_report_container'; export default class Notification extends ImmutablePureComponent { @@ -77,6 +78,19 @@ export default class Notification extends ImmutablePureComponent { unread={this.props.unread} /> ); + case 'admin.report': + return ( + <NotificationAdminReportContainer + hidden={hidden} + id={notification.get('id')} + account={notification.get('account')} + notification={notification} + onMoveDown={onMoveDown} + onMoveUp={onMoveUp} + onMention={onMention} + unread={this.props.unread} + /> + ); case 'mention': return ( <StatusContainer diff --git a/app/javascript/flavours/glitch/features/notifications/components/report.js b/app/javascript/flavours/glitch/features/notifications/components/report.js new file mode 100644 index 000000000..46a307250 --- /dev/null +++ b/app/javascript/flavours/glitch/features/notifications/components/report.js @@ -0,0 +1,62 @@ +import React, { Fragment } from 'react'; +import ImmutablePropTypes from 'react-immutable-proptypes'; +import PropTypes from 'prop-types'; +import { defineMessages, FormattedMessage, injectIntl } from 'react-intl'; +import ImmutablePureComponent from 'react-immutable-pure-component'; +import AvatarOverlay from 'flavours/glitch/components/avatar_overlay'; +import RelativeTimestamp from 'flavours/glitch/components/relative_timestamp'; + +const messages = defineMessages({ + openReport: { id: 'report_notification.open', defaultMessage: 'Open report' }, + other: { id: 'report_notification.categories.other', defaultMessage: 'Other' }, + spam: { id: 'report_notification.categories.spam', defaultMessage: 'Spam' }, + violation: { id: 'report_notification.categories.violation', defaultMessage: 'Rule violation' }, +}); + +export default @injectIntl +class Report extends ImmutablePureComponent { + + static propTypes = { + account: ImmutablePropTypes.map.isRequired, + report: ImmutablePropTypes.map.isRequired, + hidden: PropTypes.bool, + intl: PropTypes.object.isRequired, + }; + + render () { + const { intl, hidden, report, account } = this.props; + + if (!report) { + return null; + } + + if (hidden) { + return ( + <Fragment> + {report.get('id')} + </Fragment> + ); + } + + return ( + <div className='notification__report'> + <div className='notification__report__avatar'> + <AvatarOverlay account={report.get('target_account')} friend={account} /> + </div> + + <div className='notification__report__details'> + <div> + <RelativeTimestamp timestamp={report.get('created_at')} short={false} /> · <FormattedMessage id='report_notification.attached_statuses' defaultMessage='{count, plural, one {{count} post} other {{count} posts}} attached' values={{ count: report.get('status_ids').size }} /> + <br /> + <strong>{intl.formatMessage(messages[report.get('category')])}</strong> + </div> + + <div className='notification__report__actions'> + <a href={`/admin/reports/${report.get('id')}`} className='button' target='_blank' rel='noopener noreferrer'>{intl.formatMessage(messages.openReport)}</a> + </div> + </div> + </div> + ); + } + +} diff --git a/app/javascript/flavours/glitch/features/notifications/containers/admin_report_container.js b/app/javascript/flavours/glitch/features/notifications/containers/admin_report_container.js new file mode 100644 index 000000000..4198afce8 --- /dev/null +++ b/app/javascript/flavours/glitch/features/notifications/containers/admin_report_container.js @@ -0,0 +1,13 @@ +import { connect } from 'react-redux'; +import { makeGetReport } from 'flavours/glitch/selectors'; +import AdminReport from '../components/admin_report'; + +const mapStateToProps = (state, { notification }) => { + const getReport = makeGetReport(); + + return { + report: notification.get('report') ? getReport(state, notification.get('report'), notification.getIn(['report', 'target_account', 'id'])) : null, + }; +}; + +export default connect(mapStateToProps)(AdminReport); diff --git a/app/javascript/flavours/glitch/packs/settings.js b/app/javascript/flavours/glitch/packs/settings.js index 0a53e1c25..de88d4f52 100644 --- a/app/javascript/flavours/glitch/packs/settings.js +++ b/app/javascript/flavours/glitch/packs/settings.js @@ -2,6 +2,7 @@ import 'packs/public-path'; import loadPolyfills from 'flavours/glitch/util/load_polyfills'; import ready from 'flavours/glitch/util/ready'; import loadKeyboardExtensions from 'flavours/glitch/util/load_keyboard_extensions'; +import 'cocoon-js-vanilla'; function main() { const { delegate } = require('@rails/ujs'); diff --git a/app/javascript/flavours/glitch/reducers/notifications.js b/app/javascript/flavours/glitch/reducers/notifications.js index 859a8b3a1..f538af7fa 100644 --- a/app/javascript/flavours/glitch/reducers/notifications.js +++ b/app/javascript/flavours/glitch/reducers/notifications.js @@ -31,7 +31,7 @@ import { } from 'flavours/glitch/actions/markers'; import { DOMAIN_BLOCK_SUCCESS } from 'flavours/glitch/actions/domain_blocks'; import { TIMELINE_DELETE, TIMELINE_DISCONNECT } from 'flavours/glitch/actions/timelines'; -import { Map as ImmutableMap, List as ImmutableList } from 'immutable'; +import { fromJS, Map as ImmutableMap, List as ImmutableList } from 'immutable'; import compareId from 'flavours/glitch/util/compare_id'; const initialState = ImmutableMap({ @@ -58,6 +58,7 @@ const notificationToMap = (state, notification) => ImmutableMap({ account: notification.account.id, markedForDelete: state.get('markNewForDelete'), status: notification.status ? notification.status.id : null, + report: notification.report ? fromJS(notification.report) : null, }); const normalizeNotification = (state, notification, usePendingItems) => { diff --git a/app/javascript/flavours/glitch/reducers/settings.js b/app/javascript/flavours/glitch/reducers/settings.js index 0c28b2959..1d99441a1 100644 --- a/app/javascript/flavours/glitch/reducers/settings.js +++ b/app/javascript/flavours/glitch/reducers/settings.js @@ -43,6 +43,7 @@ const initialState = ImmutableMap({ status: false, update: false, 'admin.sign_up': false, + 'admin.report': false, }), quickFilter: ImmutableMap({ @@ -64,6 +65,7 @@ const initialState = ImmutableMap({ status: true, update: true, 'admin.sign_up': true, + 'admin.report': true, }), sounds: ImmutableMap({ @@ -76,6 +78,7 @@ const initialState = ImmutableMap({ status: true, update: true, 'admin.sign_up': true, + 'admin.report': true, }), }), diff --git a/app/javascript/flavours/glitch/selectors/index.js b/app/javascript/flavours/glitch/selectors/index.js index 99afe5355..d9aa8f140 100644 --- a/app/javascript/flavours/glitch/selectors/index.js +++ b/app/javascript/flavours/glitch/selectors/index.js @@ -171,14 +171,15 @@ export const getAlerts = createSelector([getAlertsBase], (base) => { return arr; }); -export const makeGetNotification = () => { - return createSelector([ - (_, base) => base, - (state, _, accountId) => state.getIn(['accounts', accountId]), - ], (base, account) => { - return base.set('account', account); - }); -}; +export const makeGetNotification = () => createSelector([ + (_, base) => base, + (state, _, accountId) => state.getIn(['accounts', accountId]), +], (base, account) => base.set('account', account)); + +export const makeGetReport = () => createSelector([ + (_, base) => base, + (state, _, targetAccountId) => state.getIn(['accounts', targetAccountId]), +], (base, targetAccount) => base.set('target_account', targetAccount)); export const getAccountGallery = createSelector([ (state, id) => state.getIn(['timelines', `account:${id}:media`, 'items'], ImmutableList()), diff --git a/app/javascript/flavours/glitch/styles/admin.scss b/app/javascript/flavours/glitch/styles/admin.scss index 9553aa4ae..6ed67edc4 100644 --- a/app/javascript/flavours/glitch/styles/admin.scss +++ b/app/javascript/flavours/glitch/styles/admin.scss @@ -75,6 +75,13 @@ $content-width: 840px; height: 100px; } + .logo--wordmark { + display: inherit; + margin: inherit; + width: inherit; + height: 20px; + } + @media screen and (max-width: $no-columns-breakpoint) { & > a:first-child { display: none; @@ -927,7 +934,8 @@ a.name-tag, text-align: center; } -.applications-list__item { +.applications-list__item, +.filters-list__item { padding: 15px 0; background: $ui-base-color; border: 1px solid lighten($ui-base-color, 4%); @@ -935,7 +943,8 @@ a.name-tag, margin-top: 15px; } -.announcements-list { +.announcements-list, +.filters-list { border: 1px solid lighten($ui-base-color, 4%); border-radius: 4px; @@ -988,6 +997,33 @@ a.name-tag, } } +.filters-list__item { + &__title { + display: flex; + justify-content: space-between; + margin-bottom: 0; + } + + &__permissions { + margin-top: 0; + margin-bottom: 10px; + } + + .expiration { + font-size: 13px; + } + + &.expired { + .expiration { + color: lighten($error-red, 12%); + } + + .permissions-list__item__icon { + color: $dark-text-color; + } + } +} + .dashboard__counters.admin-account-counters { margin-top: 10px; } diff --git a/app/javascript/flavours/glitch/styles/components/accounts.scss b/app/javascript/flavours/glitch/styles/components/accounts.scss index 377cdd91f..4e912b18b 100644 --- a/app/javascript/flavours/glitch/styles/components/accounts.scss +++ b/app/javascript/flavours/glitch/styles/components/accounts.scss @@ -105,6 +105,8 @@ position: relative; @include avatar-size(48px); + position: relative; + &-base { @include avatar-radius(); @include avatar-size(36px); @@ -243,6 +245,33 @@ margin-right: 10px; } +.notification__report { + padding: 8px 10px; + padding-left: 68px; + position: relative; + border-bottom: 1px solid lighten($ui-base-color, 8%); + min-height: 54px; + + &__details { + display: flex; + justify-content: space-between; + align-items: center; + color: $darker-text-color; + font-size: 15px; + line-height: 22px; + + strong { + font-weight: 500; + } + } + + &__avatar { + position: absolute; + left: 10px; + top: 10px; + } +} + .notification__message { margin-left: 42px; padding: 8px 0 0 26px; diff --git a/app/javascript/flavours/glitch/styles/components/single_column.scss b/app/javascript/flavours/glitch/styles/components/single_column.scss index d10fc1d3e..3843bcd68 100644 --- a/app/javascript/flavours/glitch/styles/components/single_column.scss +++ b/app/javascript/flavours/glitch/styles/components/single_column.scss @@ -154,6 +154,16 @@ padding-top: 15px; } + .notification__report { + padding: 15px 15px 15px (48px + 15px * 2); + min-height: 48px + 2px; + + &__avatar { + left: 15px; + top: 17px; + } + } + .status { padding: 15px; min-height: 48px + 2px; diff --git a/app/javascript/flavours/glitch/styles/forms.scss b/app/javascript/flavours/glitch/styles/forms.scss index 5aa75b41e..1ce13b874 100644 --- a/app/javascript/flavours/glitch/styles/forms.scss +++ b/app/javascript/flavours/glitch/styles/forms.scss @@ -1065,3 +1065,34 @@ code { } } } + +.keywords-table { + thead { + th { + white-space: nowrap; + } + + th:first-child { + width: 100%; + } + } + + tfoot { + td { + border: 0; + } + } + + .input.string { + margin-bottom: 0; + } + + .label_input__wrapper { + margin-top: 10px; + } + + .table-action-link { + margin-top: 10px; + white-space: nowrap; + } +} |