Merge pull request #1437 from oxen-io/clearnet

Fix Moderators dialogs
This commit is contained in:
Audric Ackermann 2021-01-19 09:16:46 +11:00 committed by GitHub
commit 334f371367
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
20 changed files with 157 additions and 72 deletions

View file

@ -1752,6 +1752,18 @@
"message": "You cannot remove this user as they are the creator of the group.",
"description": "Toast description when the user tries to remove the creator from a closed group v2 member list."
},
"userNeedsToHaveJoined": {
"message": "User needs to have joined",
"description": "Toast title when the user tries to remove the creator from a closed group v2 member list."
},
"userNeedsToHaveJoinedDesc": {
"message": "Remember that this user needs to have already joined the server for this ADD to work.",
"description": "Toast description when the user adds a moderator for an open group."
},
"addToTheListBelow": {
"message": "Add to the list below",
"description": "Button action to add a moderator by pubkey"
},
"noContactsForGroup": {
"message": "You don't have any contacts yet",
"androidKey": "activity_create_closed_group_empty_state_message"

View file

@ -65,7 +65,7 @@ export interface ConversationModel
isRss: () => boolean;
isBlocked: () => boolean;
isClosable: () => boolean;
isModerator: (id?: string) => boolean;
isModerator: (id: string) => boolean;
throttledBumpTyping: () => void;
messageCollection: Backbone.Collection<MessageModel>;

View file

@ -1827,6 +1827,9 @@
if (!this.isPublic()) {
return false;
}
if (!pubKey) {
throw new Error('isModerator() pubKey is falsy');
}
const moderators = this.get('moderators');
return Array.isArray(moderators) && moderators.includes(pubKey);
},

View file

@ -23,7 +23,7 @@
openInbox: 'openInbox',
},
applyRtl() {
const rtlLocales = ['fa'];
const rtlLocales = ['fa', 'ar', 'he'];
const loc = window.i18n.getLocale();
if (rtlLocales.includes(loc)) {

View file

@ -21,17 +21,20 @@
const modPubKeys = await this.channelAPI.getModerators();
// private contacts (not you) that aren't already moderators
const contacts = convo.filter(
d =>
!!d &&
d.isPrivate() &&
!d.isBlocked() &&
!d.isMe() &&
!modPubKeys.includes(d.id)
);
const contacts = window
.getConversationController()
.getConversations()
.filter(
d =>
!!d &&
d.isPrivate() &&
!d.isBlocked() &&
!d.isMe() &&
!modPubKeys.includes(d.id)
);
this.contacts = contacts;
this.this.theme = convo.theme;
this.theme = convo.theme;
this.$el.focus();
this.render();
@ -56,11 +59,16 @@
this.remove();
},
async onSubmit(pubKeys) {
log.info(`asked to add ${pubKeys}`);
log.info(`asked to add moderators: ${pubKeys}`);
window.libsession.Utils.ToastUtils.pushUserNeedsToHaveJoined();
const res = await this.channelAPI.serverAPI.addModerators(pubKeys);
if (res !== true) {
// we have errors, deal with them...
// how?
window.log.warn('failed to add moderators:', res);
} else {
window.log.info(`${pubKeys} added as moderators...`);
}
},
});

View file

@ -56,10 +56,15 @@
this.remove();
},
async onSubmit(pubKeys) {
window.log.info(`asked to remove moderators ${pubKeys}`);
const res = await this.channelAPI.serverAPI.removeModerators(pubKeys);
if (res !== true) {
// we have errors, deal with them...
// how?
window.log.warn('failed to remove moderators:', res);
} else {
window.log.info(`${pubKeys} removed from moderators...`);
}
},
});

View file

@ -33,6 +33,9 @@
.content {
max-width: 100% !important;
}
.contact-selection-list {
width: 100%;
}
.buttons {
margin: 8px;

View file

@ -4,8 +4,6 @@ import { QRCode } from 'react-qr-svg';
import { SessionModal } from './session/SessionModal';
import { SessionButton, SessionButtonColor } from './session/SessionButton';
import { SessionSpinner } from './session/SessionSpinner';
import { toast } from 'react-toastify';
import { SessionToast, SessionToastType } from './session/SessionToast';
import { ToastUtils } from '../session/utils';
import { DefaultTheme } from 'styled-components';
import { ConversationController } from '../session/conversations';

View file

@ -256,7 +256,7 @@ export class EditProfileDialog extends React.Component<Props, State> {
private renderAvatar() {
const { avatar, profileName } = this.state;
const { pubkey } = this.props;
const userName = name || profileName || pubkey;
const userName = profileName || pubkey;
return (
<Avatar avatarPath={avatar} name={userName} size={80} pubkey={pubkey} />
@ -264,8 +264,6 @@ export class EditProfileDialog extends React.Component<Props, State> {
}
private onNameEdited(event: any) {
event.persist();
const newName = event.target.value.replace(window.displayNameRegex, '');
this.setState(state => {

View file

@ -44,7 +44,7 @@ interface Props {
isPrivate: boolean;
isPublic: boolean;
isRss: boolean;
amMod: boolean;
isAdmin: boolean;
// We might not always have the full list of members,
// e.g. for open groups where we could have thousands

View file

@ -1,14 +1,17 @@
import React from 'react';
import { Contact, MemberList } from './MemberList';
import { cleanSearchTerm } from '../../util/cleanSearchTerm';
import { DefaultTheme } from 'styled-components';
import {
SessionButton,
SessionButtonColor,
SessionButtonType,
} from '../session/SessionButton';
interface Props {
contactList: Array<any>;
chatName: string;
onSubmit: any;
onClose: any;
// theme: DefaultTheme;
}
interface State {
@ -117,7 +120,7 @@ export class AddModeratorsDialog extends React.Component<Props, State> {
}
public render() {
const i18n = window.i18n;
const { i18n } = window;
const hasContacts = this.state.contactList.length !== 0;
@ -136,9 +139,12 @@ export class AddModeratorsDialog extends React.Component<Props, State> {
dir="auto"
onChange={this.updateSearchBound}
/>
<button className="add" tabIndex={0} onClick={this.add}>
{i18n('add')}
</button>
<SessionButton
buttonType={SessionButtonType.Brand}
buttonColor={SessionButtonColor.Primary}
onClick={this.add}
text={i18n('addToTheListBelow')}
/>
</div>
<div className="moderatorList">
<p>From friends:</p>
@ -152,13 +158,19 @@ export class AddModeratorsDialog extends React.Component<Props, State> {
</div>
{hasContacts ? null : <p>{i18n('noContactsToAdd')}</p>}
</div>
<div className="buttons">
<button className="cancel" tabIndex={0} onClick={this.closeDialog}>
{i18n('cancel')}
</button>
<button className="ok" tabIndex={0} onClick={this.onClickOK}>
{i18n('ok')}
</button>
<div className="session-modal__button-group">
<SessionButton
buttonType={SessionButtonType.Brand}
buttonColor={SessionButtonColor.Secondary}
onClick={this.closeDialog}
text={i18n('cancel')}
/>
<SessionButton
buttonType={SessionButtonType.BrandOutline}
buttonColor={SessionButtonColor.Green}
onClick={this.onClickOK}
text={i18n('ok')}
/>
</div>
</div>
);

View file

@ -1,4 +1,9 @@
import React from 'react';
import {
SessionButton,
SessionButtonColor,
SessionButtonType,
} from '../session/SessionButton';
import { Contact, MemberList } from './MemberList';
interface Props {
@ -69,13 +74,19 @@ export class RemoveModeratorsDialog extends React.Component<Props, State> {
</div>
{hasMods ? null : <p>{i18n('noModeratorsToRemove')}</p>}
</div>
<div className="buttons">
<button className="cancel" tabIndex={0} onClick={this.closeDialog}>
{i18n('cancel')}
</button>
<button className="ok" tabIndex={0} onClick={this.onClickOK}>
{i18n('ok')}
</button>
<div className="session-modal__button-group">
<SessionButton
buttonType={SessionButtonType.Brand}
buttonColor={SessionButtonColor.Primary}
onClick={this.closeDialog}
text={i18n('cancel')}
/>
<SessionButton
buttonType={SessionButtonType.BrandOutline}
buttonColor={SessionButtonColor.Green}
onClick={this.onClickOK}
text={i18n('ok')}
/>
</div>
</div>
);

View file

@ -157,12 +157,11 @@ class UpdateGroupNameDialogInner extends React.Component<Props, State> {
}
private onGroupNameChanged(event: any) {
event.persist();
const groupName = event.target.value;
this.setState(state => {
return {
...state,
groupName: event.target.value,
groupName,
};
});
}

View file

@ -72,7 +72,6 @@ export class SessionInput extends React.PureComponent<Props, State> {
this.updateInputValue(e);
}}
onKeyPress={event => {
event.persist();
if (event.key === 'Enter' && this.props.onEnterPressed) {
this.props.onEnterPressed();
}

View file

@ -285,14 +285,18 @@ class SessionPasswordModalInner extends React.Component<Props, State> {
if (event.key === 'Enter') {
return this.setPassword(this.props.onOk);
}
this.setState({ currentPasswordEntered: event.target.value });
const currentPasswordEntered = event.target.value;
this.setState({ currentPasswordEntered });
}
private async onPasswordConfirmInput(event: any) {
if (event.key === 'Enter') {
return this.setPassword(this.props.onOk);
}
this.setState({ currentPasswordConfirmEntered: event.target.value });
const currentPasswordConfirmEntered = event.target.value;
this.setState({ currentPasswordConfirmEntered });
}
}

View file

@ -444,7 +444,7 @@ export class SessionConversation extends React.Component<Props, State> {
isPrivate: conversation.isPrivate(),
isPublic: conversation.isPublic(),
isRss: conversation.isRss(),
amMod: conversation.isModerator(
isAdmin: conversation.isModerator(
window.storage.get('primaryDevicePubKey')
),
members,
@ -484,7 +484,7 @@ export class SessionConversation extends React.Component<Props, State> {
},
onUpdateGroupName: () => {
conversation.onUpdateGroupName();
window.Whisper.events.trigger('updateGroupName', conversation);
},
onBlockUser: () => {
@ -549,12 +549,14 @@ export class SessionConversation extends React.Component<Props, State> {
const conversation = ConversationController.getInstance().getOrThrow(
conversationKey
);
const ourPrimary = window.storage.get('primaryDevicePubKey');
const ourPK = window.textsecure.storage.user.getNumber();
const members = conversation.get('members') || [];
const isAdmin = conversation.isMediumGroup()
? true
: conversation.get('groupAdmins')?.includes(ourPK);
: conversation.isPublic()
? conversation.isModerator(ourPrimary)
: false;
return {
id: conversation.id,
@ -563,7 +565,6 @@ export class SessionConversation extends React.Component<Props, State> {
phoneNumber: conversation.getNumber(),
profileName: conversation.getProfileName(),
avatarPath: conversation.getAvatarPath(),
amMod: conversation.isModerator(),
isKickedFromGroup: conversation.get('isKickedFromGroup'),
left: conversation.get('left'),
isGroup: !conversation.isPrivate(),
@ -601,7 +602,13 @@ export class SessionConversation extends React.Component<Props, State> {
onLeaveGroup: () => {
window.Whisper.events.trigger('leaveGroup', conversation);
},
onAddModerators: () => {
window.Whisper.events.trigger('addModerators', conversation);
},
onRemoveModerators: () => {
window.Whisper.events.trigger('removeModerators', conversation);
},
onShowLightBox: (lightBoxOptions = {}) => {
this.setState({ lightBoxOptions });
},

View file

@ -29,7 +29,6 @@ interface Props {
timerOptions: Array<TimerOption>;
isPublic: boolean;
isAdmin: boolean;
amMod: boolean;
isKickedFromGroup: boolean;
left: boolean;
isBlocked: boolean;
@ -40,6 +39,8 @@ interface Props {
onInviteContacts: () => void;
onLeaveGroup: () => void;
onUpdateGroupName: () => void;
onAddModerators: () => void;
onRemoveModerators: () => void;
onUpdateGroupMembers: () => void;
onShowLightBox: (options: any) => void;
onSetDisappearingMessages: (seconds: number) => void;
@ -247,7 +248,6 @@ class SessionRightPanel extends React.Component<Props, State> {
left,
isPublic,
isAdmin,
amMod,
isBlocked,
isGroup,
} = this.props;
@ -273,10 +273,9 @@ class SessionRightPanel extends React.Component<Props, State> {
};
});
const showUpdateGroupNameButton =
isPublic && !commonNoShow
? amMod && !commonNoShow
: isAdmin && !commonNoShow;
const showUpdateGroupNameButton = isAdmin && !commonNoShow;
const showAddRemoveModeratorsButton = isAdmin && !commonNoShow && isPublic;
const showUpdateGroupMembersButton = !isPublic && !commonNoShow && isAdmin;
return (
@ -305,6 +304,25 @@ class SessionRightPanel extends React.Component<Props, State> {
{isPublic ? window.i18n('editGroup') : window.i18n('editGroupName')}
</div>
)}
{showAddRemoveModeratorsButton && (
<>
<div
className="group-settings-item"
role="button"
onClick={this.props.onAddModerators}
>
{window.i18n('addModerators')}
</div>
<div
className="group-settings-item"
role="button"
onClick={this.props.onRemoveModerators}
>
{window.i18n('removeModerators')}
</div>
</>
)}
{showUpdateGroupMembersButton && (
<div
className="group-settings-item"

View file

@ -25,7 +25,7 @@ export type PropsConversationHeaderMenu = {
isKickedFromGroup?: boolean;
left?: boolean;
isGroup: boolean;
amMod: boolean;
isAdmin: boolean;
timerOptions: Array<TimerOption>;
isPrivate: boolean;
isBlocked: boolean;
@ -54,7 +54,7 @@ export const ConversationHeaderMenu = (props: PropsConversationHeaderMenu) => {
isRss,
isGroup,
isKickedFromGroup,
amMod,
isAdmin,
timerOptions,
isBlocked,
isPrivate,
@ -115,19 +115,19 @@ export const ConversationHeaderMenu = (props: PropsConversationHeaderMenu) => {
{getCopyMenuItem(isPublic, isRss, isGroup, onCopyPublicKey, window.i18n)}
{getDeleteMessagesMenuItem(isPublic, onDeleteMessages, window.i18n)}
{getAddModeratorsMenuItem(
amMod,
isAdmin,
isKickedFromGroup,
onAddModerators,
window.i18n
)}
{getRemoveModeratorsMenuItem(
amMod,
isAdmin,
isKickedFromGroup,
onRemoveModerators,
window.i18n
)}
{getUpdateGroupNameMenuItem(
amMod,
isAdmin,
isKickedFromGroup,
left,
onUpdateGroupName,

View file

@ -75,25 +75,25 @@ function showDeleteContact(
}
function showAddModerators(
amMod: boolean,
isAdmin: boolean,
isKickedFromGroup: boolean
): boolean {
return !isKickedFromGroup && amMod;
return !isKickedFromGroup && isAdmin;
}
function showRemoveModerators(
amMod: boolean,
isAdmin: boolean,
isKickedFromGroup: boolean
): boolean {
return !isKickedFromGroup && amMod;
return !isKickedFromGroup && isAdmin;
}
function showUpdateGroupName(
amMod: boolean,
isAdmin: boolean,
isKickedFromGroup: boolean,
left: boolean
): boolean {
return !isKickedFromGroup && !left && amMod;
return !isKickedFromGroup && !left && isAdmin;
}
function showLeaveGroup(
@ -174,7 +174,7 @@ export function getLeaveGroupMenuItem(
}
export function getUpdateGroupNameMenuItem(
amMod: boolean | undefined,
isAdmin: boolean | undefined,
isKickedFromGroup: boolean | undefined,
left: boolean | undefined,
action: any,
@ -182,7 +182,7 @@ export function getUpdateGroupNameMenuItem(
): JSX.Element | null {
if (
showUpdateGroupName(
Boolean(amMod),
Boolean(isAdmin),
Boolean(isKickedFromGroup),
Boolean(left)
)
@ -193,24 +193,24 @@ export function getUpdateGroupNameMenuItem(
}
export function getRemoveModeratorsMenuItem(
amMod: boolean | undefined,
isAdmin: boolean | undefined,
isKickedFromGroup: boolean | undefined,
action: any,
i18n: LocalizerType
): JSX.Element | null {
if (showRemoveModerators(Boolean(amMod), Boolean(isKickedFromGroup))) {
if (showRemoveModerators(Boolean(isAdmin), Boolean(isKickedFromGroup))) {
return <Item onClick={action}>{i18n('removeModerators')}</Item>;
}
return null;
}
export function getAddModeratorsMenuItem(
amMod: boolean | undefined,
isAdmin: boolean | undefined,
isKickedFromGroup: boolean | undefined,
action: any,
i18n: LocalizerType
): JSX.Element | null {
if (showAddModerators(Boolean(amMod), Boolean(isKickedFromGroup))) {
if (showAddModerators(Boolean(isAdmin), Boolean(isKickedFromGroup))) {
return <Item onClick={action}>{i18n('addModerators')}</Item>;
}
return null;

View file

@ -259,3 +259,11 @@ export function pushCannotRemoveCreatorFromGroup() {
window.i18n('cannotRemoveCreatorFromGroupDesc')
);
}
export function pushUserNeedsToHaveJoined() {
pushToastInfo(
'userNeedsToHaveJoined',
window.i18n('userNeedsToHaveJoined'),
window.i18n('userNeedsToHaveJoinedDesc')
);
}