]> cat aescling's git repositories - mastodon.git/commitdiff
[Glitch] Add customizable user roles
authorEugen Rochko <eugen@zeonfederated.com>
Tue, 5 Jul 2022 00:41:40 +0000 (02:41 +0200)
committeraescling <aescling+gitlab@cat.family>
Mon, 5 Sep 2022 04:27:53 +0000 (00:27 -0400)
Port front-end changes from 44b2ee3485ba0845e5910cefcb4b1e2f84f34470 to glitch-soc

Signed-off-by: Claire <claire.github-309c@sitedethib.com>
app/javascript/flavours/glitch/components/status_action_bar.js
app/javascript/flavours/glitch/containers/mastodon.js
app/javascript/flavours/glitch/features/account/components/header.js
app/javascript/flavours/glitch/features/notifications/components/column_settings.js
app/javascript/flavours/glitch/features/status/components/action_bar.js
app/javascript/flavours/glitch/features/ui/components/link_footer.js
app/javascript/flavours/glitch/permissions.js [new file with mode: 0644]
app/javascript/flavours/glitch/reducers/meta.js
app/javascript/flavours/glitch/styles/admin.scss
app/javascript/flavours/glitch/styles/forms.scss
app/javascript/flavours/glitch/util/initial_state.js

index 0a5c5b69d4ca99d83e18e374eab7949967b83579..667afac5a88bb9275f35f29bbadb22ee7b71441b 100644 (file)
@@ -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({
index 989e37024a97fcf5dc819b4477059fdeb8647e41..d07b2b3d0eb00e8bf487c556249aca63ffff20a7 100644 (file)
@@ -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 {
index 45aba53f74ae7916e528810cef733eaf6a584df2..53170b7a6e0d0d6b993e94f68b4e188abefff469 100644 (file)
@@ -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')) });
     }
index a8502f5637e5f6d15b27415c7dc4ef5175b0176d..42ab9de35b4796aa46e3a1fc6aea69e4334d6ea9 100644 (file)
@@ -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 {
           </div>
         </div>
 
-        {isStaff && (
+        {(this.context.identity.permissions & PERMISSION_MANAGE_USERS === PERMISSION_MANAGE_USERS) && (
           <div role='group' aria-labelledby='notifications-admin-sign-up'>
             <span id='notifications-status' className='column-settings__section'><FormattedMessage id='notifications.column_settings.admin.sign_up' defaultMessage='New sign-ups:' /></span>
 
@@ -180,7 +184,7 @@ export default class ColumnSettings extends React.PureComponent {
           </div>
         )}
 
-        {isStaff && (
+        {(this.context.identity.permissions & PERMISSION_MANAGE_REPORTS === PERMISSION_MANAGE_REPORTS) && (
           <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>
 
index a67a045da98d2eaf5090459ebe35e8399442a79f..ef0f0f2b7e800ac0f160e32fba429a21fb4e2b5e 100644 (file)
@@ -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({
index d9579e9c9ac6f25f28978b8e8ffa7a14644038a3..3abdaad4bdd3cccb63cb5e5700b678ba657e5b44 100644 (file)
@@ -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 (
       <div className='getting-started__footer'>
         <ul>
-          {invitesEnabled && <li><a href='/invites' target='_blank'><FormattedMessage id='getting_started.invite' defaultMessage='Invite people' /></a> · </li>}
+          {((this.context.identity.permissions & PERMISSION_INVITE_USERS) === PERMISSION_INVITE_USERS) && <li><a href='/invites' target='_blank'><FormattedMessage id='getting_started.invite' defaultMessage='Invite people' /></a> · </li>}
           {!!securityLink && <li><a href='/auth/edit'><FormattedMessage id='getting_started.security' defaultMessage='Security' /></a> · </li>}
           {!limitedFederationMode && <li><a href='/about/more' target='_blank'><FormattedMessage id='navigation_bar.info' defaultMessage='About this server' /></a> · </li>}
           <li><a href='https://joinmastodon.org/apps' target='_blank'><FormattedMessage id='navigation_bar.apps' defaultMessage='Mobile apps' /></a> · </li>
diff --git a/app/javascript/flavours/glitch/permissions.js b/app/javascript/flavours/glitch/permissions.js
new file mode 100644 (file)
index 0000000..752ddd6
--- /dev/null
@@ -0,0 +1,3 @@
+export const PERMISSION_INVITE_USERS   = 0x0000000000010000;
+export const PERMISSION_MANAGE_USERS   = 0x0000000000000400;
+export const PERMISSION_MANAGE_REPORTS = 0x0000000000000010;
index a98dc436a01f4650be0be3b8fc9196392191e5e4..0f3ab3b848e5de00573a53e98cdf38c78820c028 100644 (file)
@@ -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;
   }
index 6ed67edc49b0a7735e3dd76e073211d6d0fc1c45..77890c467c0fbfb48548ae4710a1dc5dd31eafa0 100644 (file)
@@ -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 {
index 1ce13b874d8c39a10f326050be52d675db0817a9..8ae2b5bd845bca032edd0adcd9ac845343a2f36c 100644 (file)
@@ -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;
index b6eab0c871dc02b9caa369991b6022110a88d2aa..90dada4b31d8c4bccf6785b932391d0c994065d6 100644 (file)
@@ -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');