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');