mirror of
https://github.com/oxen-io/session-desktop.git
synced 2023-12-14 02:12:57 +01:00
commit
334f371367
20 changed files with 157 additions and 72 deletions
|
@ -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"
|
||||
|
|
2
js/models/conversations.d.ts
vendored
2
js/models/conversations.d.ts
vendored
|
@ -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>;
|
||||
|
|
|
@ -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);
|
||||
},
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
openInbox: 'openInbox',
|
||||
},
|
||||
applyRtl() {
|
||||
const rtlLocales = ['fa'];
|
||||
const rtlLocales = ['fa', 'ar', 'he'];
|
||||
|
||||
const loc = window.i18n.getLocale();
|
||||
if (rtlLocales.includes(loc)) {
|
||||
|
|
|
@ -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...`);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
|
|
@ -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...`);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
|
|
@ -33,6 +33,9 @@
|
|||
.content {
|
||||
max-width: 100% !important;
|
||||
}
|
||||
.contact-selection-list {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.buttons {
|
||||
margin: 8px;
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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 => {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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 });
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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 });
|
||||
},
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -259,3 +259,11 @@ export function pushCannotRemoveCreatorFromGroup() {
|
|||
window.i18n('cannotRemoveCreatorFromGroupDesc')
|
||||
);
|
||||
}
|
||||
|
||||
export function pushUserNeedsToHaveJoined() {
|
||||
pushToastInfo(
|
||||
'userNeedsToHaveJoined',
|
||||
window.i18n('userNeedsToHaveJoined'),
|
||||
window.i18n('userNeedsToHaveJoinedDesc')
|
||||
);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue