From 30e7836a1961e8e70938b14232abd91238eb2be4 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Tue, 5 Jul 2022 02:41:40 +0200 Subject: [PATCH] [Glitch] Add customizable user roles Port front-end changes from 44b2ee3485ba0845e5910cefcb4b1e2f84f34470 to glitch-soc Signed-off-by: Claire --- .../glitch/components/status_action_bar.js | 6 ++++-- .../flavours/glitch/containers/mastodon.js | 1 + .../glitch/features/account/components/header.js | 9 +++++++-- .../notifications/components/column_settings.js | 10 +++++++--- .../features/status/components/action_bar.js | 6 ++++-- .../glitch/features/ui/components/link_footer.js | 9 +++++++-- app/javascript/flavours/glitch/permissions.js | 3 +++ app/javascript/flavours/glitch/reducers/meta.js | 3 ++- app/javascript/flavours/glitch/styles/admin.scss | 15 +++++++++++++++ app/javascript/flavours/glitch/styles/forms.scss | 4 ++++ .../flavours/glitch/util/initial_state.js | 2 -- 11 files changed, 54 insertions(+), 14 deletions(-) create mode 100644 app/javascript/flavours/glitch/permissions.js diff --git a/app/javascript/flavours/glitch/components/status_action_bar.js b/app/javascript/flavours/glitch/components/status_action_bar.js index 0a5c5b69d..667afac5a 100644 --- a/app/javascript/flavours/glitch/components/status_action_bar.js +++ b/app/javascript/flavours/glitch/components/status_action_bar.js @@ -5,10 +5,11 @@ import IconButton from './icon_button'; import DropdownMenuContainer from 'flavours/glitch/containers/dropdown_menu_container'; import { defineMessages, injectIntl } from 'react-intl'; import ImmutablePureComponent from 'react-immutable-pure-component'; -import { me, isStaff } from 'flavours/glitch/util/initial_state'; +import { me } from 'flavours/glitch/util/initial_state'; import RelativeTimestamp from './relative_timestamp'; import { accountAdminLink, statusAdminLink } from 'flavours/glitch/util/backend_links'; import classNames from 'classnames'; +import { PERMISSION_MANAGE_USERS } from 'flavours/glitch/permissions'; const messages = defineMessages({ delete: { id: 'status.delete', defaultMessage: 'Delete' }, @@ -47,6 +48,7 @@ class StatusActionBar extends ImmutablePureComponent { static contextTypes = { router: PropTypes.object, + identity: PropTypes.object, }; static propTypes = { @@ -240,7 +242,7 @@ class StatusActionBar extends ImmutablePureComponent { menu.push({ text: intl.formatMessage(messages.block, { name: status.getIn(['account', 'username']) }), action: this.handleBlockClick }); menu.push({ text: intl.formatMessage(messages.report, { name: status.getIn(['account', 'username']) }), action: this.handleReport }); - if (isStaff && (accountAdminLink || statusAdminLink)) { + if ((this.context.identity.permissions & PERMISSION_MANAGE_USERS) === PERMISSION_MANAGE_USERS && (accountAdminLink || statusAdminLink)) { menu.push(null); if (accountAdminLink !== undefined) { menu.push({ diff --git a/app/javascript/flavours/glitch/containers/mastodon.js b/app/javascript/flavours/glitch/containers/mastodon.js index 989e37024..d07b2b3d0 100644 --- a/app/javascript/flavours/glitch/containers/mastodon.js +++ b/app/javascript/flavours/glitch/containers/mastodon.js @@ -31,6 +31,7 @@ const createIdentityContext = state => ({ signedIn: !!state.meta.me, accountId: state.meta.me, accessToken: state.meta.access_token, + permissions: state.role.permissions, }); export default class Mastodon extends React.PureComponent { diff --git a/app/javascript/flavours/glitch/features/account/components/header.js b/app/javascript/flavours/glitch/features/account/components/header.js index 45aba53f7..53170b7a6 100644 --- a/app/javascript/flavours/glitch/features/account/components/header.js +++ b/app/javascript/flavours/glitch/features/account/components/header.js @@ -3,7 +3,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes'; import PropTypes from 'prop-types'; import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import ImmutablePureComponent from 'react-immutable-pure-component'; -import { autoPlayGif, me, isStaff } from 'flavours/glitch/util/initial_state'; +import { autoPlayGif, me } from 'flavours/glitch/util/initial_state'; import { preferencesLink, profileLink, accountAdminLink } from 'flavours/glitch/util/backend_links'; import classNames from 'classnames'; import Icon from 'flavours/glitch/components/icon'; @@ -13,6 +13,7 @@ import Button from 'flavours/glitch/components/button'; import { NavLink } from 'react-router-dom'; import DropdownMenuContainer from 'flavours/glitch/containers/dropdown_menu_container'; import AccountNoteContainer from '../containers/account_note_container'; +import { PERMISSION_MANAGE_USERS } from 'flavours/glitch/permissions'; const messages = defineMessages({ unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' }, @@ -64,6 +65,10 @@ const dateFormatOptions = { export default @injectIntl class Header extends ImmutablePureComponent { + static contextTypes = { + identity: PropTypes.object, + }; + static propTypes = { account: ImmutablePropTypes.map, identity_props: ImmutablePropTypes.list, @@ -244,7 +249,7 @@ class Header extends ImmutablePureComponent { } } - if (account.get('id') !== me && isStaff && accountAdminLink) { + if (account.get('id') !== me && (this.context.identity.permissions & PERMISSION_MANAGE_USERS) === PERMISSION_MANAGE_USERS && accountAdminLink) { menu.push(null); menu.push({ text: intl.formatMessage(messages.admin_account, { name: account.get('username') }), href: accountAdminLink(account.get('id')) }); } 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 a8502f563..42ab9de35 100644 --- a/app/javascript/flavours/glitch/features/notifications/components/column_settings.js +++ b/app/javascript/flavours/glitch/features/notifications/components/column_settings.js @@ -6,10 +6,14 @@ import ClearColumnButton from './clear_column_button'; import GrantPermissionButton from './grant_permission_button'; import SettingToggle from './setting_toggle'; import PillBarButton from './pill_bar_button'; -import { isStaff } from 'flavours/glitch/util/initial_state'; +import { PERMISSION_MANAGE_USERS, PERMISSION_MANAGE_REPORTS } from 'flavours/glitch/permissions'; export default class ColumnSettings extends React.PureComponent { + static contextTypes = { + identity: PropTypes.object, + }; + static propTypes = { settings: ImmutablePropTypes.map.isRequired, pushSettings: ImmutablePropTypes.map.isRequired, @@ -167,7 +171,7 @@ export default class ColumnSettings extends React.PureComponent { - {isStaff && ( + {(this.context.identity.permissions & PERMISSION_MANAGE_USERS === PERMISSION_MANAGE_USERS) && (
@@ -180,7 +184,7 @@ export default class ColumnSettings extends React.PureComponent {
)} - {isStaff && ( + {(this.context.identity.permissions & PERMISSION_MANAGE_REPORTS === PERMISSION_MANAGE_REPORTS) && (
diff --git a/app/javascript/flavours/glitch/features/status/components/action_bar.js b/app/javascript/flavours/glitch/features/status/components/action_bar.js index a67a045da..ef0f0f2b7 100644 --- a/app/javascript/flavours/glitch/features/status/components/action_bar.js +++ b/app/javascript/flavours/glitch/features/status/components/action_bar.js @@ -4,9 +4,10 @@ import IconButton from 'flavours/glitch/components/icon_button'; import ImmutablePropTypes from 'react-immutable-proptypes'; import DropdownMenuContainer from 'flavours/glitch/containers/dropdown_menu_container'; import { defineMessages, injectIntl } from 'react-intl'; -import { me, isStaff } from 'flavours/glitch/util/initial_state'; +import { me } from 'flavours/glitch/util/initial_state'; import { accountAdminLink, statusAdminLink } from 'flavours/glitch/util/backend_links'; import classNames from 'classnames'; +import { PERMISSION_MANAGE_USERS } from 'flavours/glitch/permissions'; const messages = defineMessages({ delete: { id: 'status.delete', defaultMessage: 'Delete' }, @@ -41,6 +42,7 @@ class ActionBar extends React.PureComponent { static contextTypes = { router: PropTypes.object, + identity: PropTypes.object, }; static propTypes = { @@ -182,7 +184,7 @@ class ActionBar extends React.PureComponent { menu.push({ text: intl.formatMessage(messages.mute, { name: status.getIn(['account', 'username']) }), action: this.handleMuteClick }); menu.push({ text: intl.formatMessage(messages.block, { name: status.getIn(['account', 'username']) }), action: this.handleBlockClick }); menu.push({ text: intl.formatMessage(messages.report, { name: status.getIn(['account', 'username']) }), action: this.handleReport }); - if (isStaff && (accountAdminLink || statusAdminLink)) { + if ((this.context.identity.permissions & PERMISSION_MANAGE_USERS) === PERMISSION_MANAGE_USERS && (accountAdminLink || statusAdminLink)) { menu.push(null); if (accountAdminLink !== undefined) { menu.push({ diff --git a/app/javascript/flavours/glitch/features/ui/components/link_footer.js b/app/javascript/flavours/glitch/features/ui/components/link_footer.js index d9579e9c9..3abdaad4b 100644 --- a/app/javascript/flavours/glitch/features/ui/components/link_footer.js +++ b/app/javascript/flavours/glitch/features/ui/components/link_footer.js @@ -3,10 +3,11 @@ import React from 'react'; import PropTypes from 'prop-types'; import { FormattedMessage, defineMessages, injectIntl } from 'react-intl'; import { Link } from 'react-router-dom'; -import { invitesEnabled, limitedFederationMode, version, repository, source_url } from 'flavours/glitch/util/initial_state'; +import { limitedFederationMode, version, repository, source_url } from 'flavours/glitch/util/initial_state'; import { signOutLink, securityLink } from 'flavours/glitch/util/backend_links'; import { logOut } from 'flavours/glitch/util/log_out'; import { openModal } from 'flavours/glitch/actions/modal'; +import { PERMISSION_INVITE_USERS } from 'flavours/glitch/permissions'; const messages = defineMessages({ logoutMessage: { id: 'confirmations.logout.message', defaultMessage: 'Are you sure you want to log out?' }, @@ -28,6 +29,10 @@ export default @injectIntl @connect(null, mapDispatchToProps) class LinkFooter extends React.PureComponent { + static contextTypes = { + identity: PropTypes.object, + }; + static propTypes = { onLogout: PropTypes.func.isRequired, intl: PropTypes.object.isRequired, @@ -46,7 +51,7 @@ class LinkFooter extends React.PureComponent { return (
    - {invitesEnabled &&
  • ·
  • } + {((this.context.identity.permissions & PERMISSION_INVITE_USERS) === PERMISSION_INVITE_USERS) &&
  • ·
  • } {!!securityLink &&
  • ·
  • } {!limitedFederationMode &&
  • ·
  • }
  • ·
  • diff --git a/app/javascript/flavours/glitch/permissions.js b/app/javascript/flavours/glitch/permissions.js new file mode 100644 index 000000000..752ddd6c5 --- /dev/null +++ b/app/javascript/flavours/glitch/permissions.js @@ -0,0 +1,3 @@ +export const PERMISSION_INVITE_USERS = 0x0000000000010000; +export const PERMISSION_MANAGE_USERS = 0x0000000000000400; +export const PERMISSION_MANAGE_REPORTS = 0x0000000000000010; diff --git a/app/javascript/flavours/glitch/reducers/meta.js b/app/javascript/flavours/glitch/reducers/meta.js index a98dc436a..0f3ab3b84 100644 --- a/app/javascript/flavours/glitch/reducers/meta.js +++ b/app/javascript/flavours/glitch/reducers/meta.js @@ -4,12 +4,13 @@ import { Map as ImmutableMap } from 'immutable'; const initialState = ImmutableMap({ streaming_api_base_url: null, access_token: null, + permissions: '0', }); export default function meta(state = initialState, action) { switch(action.type) { case STORE_HYDRATE: - return state.merge(action.state.get('meta')); + return state.merge(action.state.get('meta')).set('permissions', action.state.getIn(['role', 'permissions'])); default: return state; } diff --git a/app/javascript/flavours/glitch/styles/admin.scss b/app/javascript/flavours/glitch/styles/admin.scss index 6ed67edc4..77890c467 100644 --- a/app/javascript/flavours/glitch/styles/admin.scss +++ b/app/javascript/flavours/glitch/styles/admin.scss @@ -943,6 +943,10 @@ a.name-tag, margin-top: 15px; } +.user-role { + color: var(--user-role-accent); +} + .announcements-list, .filters-list { border: 1px solid lighten($ui-base-color, 4%); @@ -979,6 +983,17 @@ a.name-tag, &__meta { padding: 0 15px; color: $dark-text-color; + + a { + color: inherit; + text-decoration: underline; + + &:hover, + &:focus, + &:active { + text-decoration: none; + } + } } &__action-bar { diff --git a/app/javascript/flavours/glitch/styles/forms.scss b/app/javascript/flavours/glitch/styles/forms.scss index 1ce13b874..8ae2b5bd8 100644 --- a/app/javascript/flavours/glitch/styles/forms.scss +++ b/app/javascript/flavours/glitch/styles/forms.scss @@ -247,6 +247,10 @@ code { } } + .input.with_block_label.user_role_permissions_as_keys ul { + columns: unset; + } + .input.datetime .label_input select { display: inline-block; width: auto; diff --git a/app/javascript/flavours/glitch/util/initial_state.js b/app/javascript/flavours/glitch/util/initial_state.js index b6eab0c87..90dada4b3 100644 --- a/app/javascript/flavours/glitch/util/initial_state.js +++ b/app/javascript/flavours/glitch/util/initial_state.js @@ -23,14 +23,12 @@ export const me = getMeta('me'); export const searchEnabled = getMeta('search_enabled'); export const maxChars = (initialState && initialState.max_toot_chars) || 500; export const pollLimits = (initialState && initialState.poll_limits); -export const invitesEnabled = getMeta('invites_enabled'); export const limitedFederationMode = getMeta('limited_federation_mode'); export const repository = getMeta('repository'); export const source_url = getMeta('source_url'); export const version = getMeta('version'); export const mascot = getMeta('mascot'); export const profile_directory = getMeta('profile_directory'); -export const isStaff = getMeta('is_staff'); export const defaultContentType = getMeta('default_content_type'); export const forceSingleColumn = getMeta('advanced_layout') === false; export const useBlurhash = getMeta('use_blurhash');