[Glitch] Add customizable user roles
Port front-end changes from 44b2ee3485
to glitch-soc
Signed-off-by: Claire <claire.github-309c@sitedethib.com>
remotes/1703361221475462875/rebase/4.0.0rc1
parent
92c06a1113
commit
30e7836a19
|
@ -5,10 +5,11 @@ import IconButton from './icon_button';
|
||||||
import DropdownMenuContainer from 'flavours/glitch/containers/dropdown_menu_container';
|
import DropdownMenuContainer from 'flavours/glitch/containers/dropdown_menu_container';
|
||||||
import { defineMessages, injectIntl } from 'react-intl';
|
import { defineMessages, injectIntl } from 'react-intl';
|
||||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
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 RelativeTimestamp from './relative_timestamp';
|
||||||
import { accountAdminLink, statusAdminLink } from 'flavours/glitch/util/backend_links';
|
import { accountAdminLink, statusAdminLink } from 'flavours/glitch/util/backend_links';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
import { PERMISSION_MANAGE_USERS } from 'flavours/glitch/permissions';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
delete: { id: 'status.delete', defaultMessage: 'Delete' },
|
delete: { id: 'status.delete', defaultMessage: 'Delete' },
|
||||||
|
@ -47,6 +48,7 @@ class StatusActionBar extends ImmutablePureComponent {
|
||||||
|
|
||||||
static contextTypes = {
|
static contextTypes = {
|
||||||
router: PropTypes.object,
|
router: PropTypes.object,
|
||||||
|
identity: PropTypes.object,
|
||||||
};
|
};
|
||||||
|
|
||||||
static propTypes = {
|
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.block, { name: status.getIn(['account', 'username']) }), action: this.handleBlockClick });
|
||||||
menu.push({ text: intl.formatMessage(messages.report, { name: status.getIn(['account', 'username']) }), action: this.handleReport });
|
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);
|
menu.push(null);
|
||||||
if (accountAdminLink !== undefined) {
|
if (accountAdminLink !== undefined) {
|
||||||
menu.push({
|
menu.push({
|
||||||
|
|
|
@ -31,6 +31,7 @@ const createIdentityContext = state => ({
|
||||||
signedIn: !!state.meta.me,
|
signedIn: !!state.meta.me,
|
||||||
accountId: state.meta.me,
|
accountId: state.meta.me,
|
||||||
accessToken: state.meta.access_token,
|
accessToken: state.meta.access_token,
|
||||||
|
permissions: state.role.permissions,
|
||||||
});
|
});
|
||||||
|
|
||||||
export default class Mastodon extends React.PureComponent {
|
export default class Mastodon extends React.PureComponent {
|
||||||
|
|
|
@ -3,7 +3,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
||||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
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 { preferencesLink, profileLink, accountAdminLink } from 'flavours/glitch/util/backend_links';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import Icon from 'flavours/glitch/components/icon';
|
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 { NavLink } from 'react-router-dom';
|
||||||
import DropdownMenuContainer from 'flavours/glitch/containers/dropdown_menu_container';
|
import DropdownMenuContainer from 'flavours/glitch/containers/dropdown_menu_container';
|
||||||
import AccountNoteContainer from '../containers/account_note_container';
|
import AccountNoteContainer from '../containers/account_note_container';
|
||||||
|
import { PERMISSION_MANAGE_USERS } from 'flavours/glitch/permissions';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' },
|
unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' },
|
||||||
|
@ -64,6 +65,10 @@ const dateFormatOptions = {
|
||||||
export default @injectIntl
|
export default @injectIntl
|
||||||
class Header extends ImmutablePureComponent {
|
class Header extends ImmutablePureComponent {
|
||||||
|
|
||||||
|
static contextTypes = {
|
||||||
|
identity: PropTypes.object,
|
||||||
|
};
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
account: ImmutablePropTypes.map,
|
account: ImmutablePropTypes.map,
|
||||||
identity_props: ImmutablePropTypes.list,
|
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(null);
|
||||||
menu.push({ text: intl.formatMessage(messages.admin_account, { name: account.get('username') }), href: accountAdminLink(account.get('id')) });
|
menu.push({ text: intl.formatMessage(messages.admin_account, { name: account.get('username') }), href: accountAdminLink(account.get('id')) });
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,10 +6,14 @@ import ClearColumnButton from './clear_column_button';
|
||||||
import GrantPermissionButton from './grant_permission_button';
|
import GrantPermissionButton from './grant_permission_button';
|
||||||
import SettingToggle from './setting_toggle';
|
import SettingToggle from './setting_toggle';
|
||||||
import PillBarButton from './pill_bar_button';
|
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 {
|
export default class ColumnSettings extends React.PureComponent {
|
||||||
|
|
||||||
|
static contextTypes = {
|
||||||
|
identity: PropTypes.object,
|
||||||
|
};
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
settings: ImmutablePropTypes.map.isRequired,
|
settings: ImmutablePropTypes.map.isRequired,
|
||||||
pushSettings: ImmutablePropTypes.map.isRequired,
|
pushSettings: ImmutablePropTypes.map.isRequired,
|
||||||
|
@ -167,7 +171,7 @@ export default class ColumnSettings extends React.PureComponent {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{isStaff && (
|
{(this.context.identity.permissions & PERMISSION_MANAGE_USERS === PERMISSION_MANAGE_USERS) && (
|
||||||
<div role='group' aria-labelledby='notifications-admin-sign-up'>
|
<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>
|
<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>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{isStaff && (
|
{(this.context.identity.permissions & PERMISSION_MANAGE_REPORTS === PERMISSION_MANAGE_REPORTS) && (
|
||||||
<div role='group' aria-labelledby='notifications-admin-report'>
|
<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>
|
<span id='notifications-status' className='column-settings__section'><FormattedMessage id='notifications.column_settings.admin.report' defaultMessage='New reports:' /></span>
|
||||||
|
|
||||||
|
|
|
@ -4,9 +4,10 @@ import IconButton from 'flavours/glitch/components/icon_button';
|
||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
import DropdownMenuContainer from 'flavours/glitch/containers/dropdown_menu_container';
|
import DropdownMenuContainer from 'flavours/glitch/containers/dropdown_menu_container';
|
||||||
import { defineMessages, injectIntl } from 'react-intl';
|
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 { accountAdminLink, statusAdminLink } from 'flavours/glitch/util/backend_links';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
import { PERMISSION_MANAGE_USERS } from 'flavours/glitch/permissions';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
delete: { id: 'status.delete', defaultMessage: 'Delete' },
|
delete: { id: 'status.delete', defaultMessage: 'Delete' },
|
||||||
|
@ -41,6 +42,7 @@ class ActionBar extends React.PureComponent {
|
||||||
|
|
||||||
static contextTypes = {
|
static contextTypes = {
|
||||||
router: PropTypes.object,
|
router: PropTypes.object,
|
||||||
|
identity: PropTypes.object,
|
||||||
};
|
};
|
||||||
|
|
||||||
static propTypes = {
|
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.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.block, { name: status.getIn(['account', 'username']) }), action: this.handleBlockClick });
|
||||||
menu.push({ text: intl.formatMessage(messages.report, { name: status.getIn(['account', 'username']) }), action: this.handleReport });
|
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);
|
menu.push(null);
|
||||||
if (accountAdminLink !== undefined) {
|
if (accountAdminLink !== undefined) {
|
||||||
menu.push({
|
menu.push({
|
||||||
|
|
|
@ -3,10 +3,11 @@ import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { FormattedMessage, defineMessages, injectIntl } from 'react-intl';
|
import { FormattedMessage, defineMessages, injectIntl } from 'react-intl';
|
||||||
import { Link } from 'react-router-dom';
|
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 { signOutLink, securityLink } from 'flavours/glitch/util/backend_links';
|
||||||
import { logOut } from 'flavours/glitch/util/log_out';
|
import { logOut } from 'flavours/glitch/util/log_out';
|
||||||
import { openModal } from 'flavours/glitch/actions/modal';
|
import { openModal } from 'flavours/glitch/actions/modal';
|
||||||
|
import { PERMISSION_INVITE_USERS } from 'flavours/glitch/permissions';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
logoutMessage: { id: 'confirmations.logout.message', defaultMessage: 'Are you sure you want to log out?' },
|
logoutMessage: { id: 'confirmations.logout.message', defaultMessage: 'Are you sure you want to log out?' },
|
||||||
|
@ -28,6 +29,10 @@ export default @injectIntl
|
||||||
@connect(null, mapDispatchToProps)
|
@connect(null, mapDispatchToProps)
|
||||||
class LinkFooter extends React.PureComponent {
|
class LinkFooter extends React.PureComponent {
|
||||||
|
|
||||||
|
static contextTypes = {
|
||||||
|
identity: PropTypes.object,
|
||||||
|
};
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
onLogout: PropTypes.func.isRequired,
|
onLogout: PropTypes.func.isRequired,
|
||||||
intl: PropTypes.object.isRequired,
|
intl: PropTypes.object.isRequired,
|
||||||
|
@ -46,7 +51,7 @@ class LinkFooter extends React.PureComponent {
|
||||||
return (
|
return (
|
||||||
<div className='getting-started__footer'>
|
<div className='getting-started__footer'>
|
||||||
<ul>
|
<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>}
|
{!!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>}
|
{!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>
|
<li><a href='https://joinmastodon.org/apps' target='_blank'><FormattedMessage id='navigation_bar.apps' defaultMessage='Mobile apps' /></a> · </li>
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
export const PERMISSION_INVITE_USERS = 0x0000000000010000;
|
||||||
|
export const PERMISSION_MANAGE_USERS = 0x0000000000000400;
|
||||||
|
export const PERMISSION_MANAGE_REPORTS = 0x0000000000000010;
|
|
@ -4,12 +4,13 @@ import { Map as ImmutableMap } from 'immutable';
|
||||||
const initialState = ImmutableMap({
|
const initialState = ImmutableMap({
|
||||||
streaming_api_base_url: null,
|
streaming_api_base_url: null,
|
||||||
access_token: null,
|
access_token: null,
|
||||||
|
permissions: '0',
|
||||||
});
|
});
|
||||||
|
|
||||||
export default function meta(state = initialState, action) {
|
export default function meta(state = initialState, action) {
|
||||||
switch(action.type) {
|
switch(action.type) {
|
||||||
case STORE_HYDRATE:
|
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:
|
default:
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
|
@ -943,6 +943,10 @@ a.name-tag,
|
||||||
margin-top: 15px;
|
margin-top: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.user-role {
|
||||||
|
color: var(--user-role-accent);
|
||||||
|
}
|
||||||
|
|
||||||
.announcements-list,
|
.announcements-list,
|
||||||
.filters-list {
|
.filters-list {
|
||||||
border: 1px solid lighten($ui-base-color, 4%);
|
border: 1px solid lighten($ui-base-color, 4%);
|
||||||
|
@ -979,6 +983,17 @@ a.name-tag,
|
||||||
&__meta {
|
&__meta {
|
||||||
padding: 0 15px;
|
padding: 0 15px;
|
||||||
color: $dark-text-color;
|
color: $dark-text-color;
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: inherit;
|
||||||
|
text-decoration: underline;
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&:focus,
|
||||||
|
&:active {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&__action-bar {
|
&__action-bar {
|
||||||
|
|
|
@ -247,6 +247,10 @@ code {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.input.with_block_label.user_role_permissions_as_keys ul {
|
||||||
|
columns: unset;
|
||||||
|
}
|
||||||
|
|
||||||
.input.datetime .label_input select {
|
.input.datetime .label_input select {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
width: auto;
|
width: auto;
|
||||||
|
|
|
@ -23,14 +23,12 @@ export const me = getMeta('me');
|
||||||
export const searchEnabled = getMeta('search_enabled');
|
export const searchEnabled = getMeta('search_enabled');
|
||||||
export const maxChars = (initialState && initialState.max_toot_chars) || 500;
|
export const maxChars = (initialState && initialState.max_toot_chars) || 500;
|
||||||
export const pollLimits = (initialState && initialState.poll_limits);
|
export const pollLimits = (initialState && initialState.poll_limits);
|
||||||
export const invitesEnabled = getMeta('invites_enabled');
|
|
||||||
export const limitedFederationMode = getMeta('limited_federation_mode');
|
export const limitedFederationMode = getMeta('limited_federation_mode');
|
||||||
export const repository = getMeta('repository');
|
export const repository = getMeta('repository');
|
||||||
export const source_url = getMeta('source_url');
|
export const source_url = getMeta('source_url');
|
||||||
export const version = getMeta('version');
|
export const version = getMeta('version');
|
||||||
export const mascot = getMeta('mascot');
|
export const mascot = getMeta('mascot');
|
||||||
export const profile_directory = getMeta('profile_directory');
|
export const profile_directory = getMeta('profile_directory');
|
||||||
export const isStaff = getMeta('is_staff');
|
|
||||||
export const defaultContentType = getMeta('default_content_type');
|
export const defaultContentType = getMeta('default_content_type');
|
||||||
export const forceSingleColumn = getMeta('advanced_layout') === false;
|
export const forceSingleColumn = getMeta('advanced_layout') === false;
|
||||||
export const useBlurhash = getMeta('use_blurhash');
|
export const useBlurhash = getMeta('use_blurhash');
|
||||||
|
|
Loading…
Reference in New Issue