mirror of
https://github.com/oxen-io/session-desktop.git
synced 2023-12-14 02:12:57 +01:00
make moderators and groupAdmins a single name
This commit is contained in:
parent
6a776b56f6
commit
dc0733968d
22 changed files with 218 additions and 182 deletions
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;
|
||||
isAdmin: (id: string) => boolean;
|
||||
throttledBumpTyping: () => void;
|
||||
|
||||
messageCollection: Backbone.Collection<MessageModel>;
|
||||
|
|
|
@ -457,14 +457,18 @@
|
|||
format() {
|
||||
return this.cachedProps;
|
||||
},
|
||||
getGroupAdmins() {
|
||||
return this.get('groupAdmins') || this.get('moderators');
|
||||
},
|
||||
getProps() {
|
||||
const { format } = PhoneNumber;
|
||||
const regionCode = storage.get('regionCode');
|
||||
const typingKeys = Object.keys(this.contactTypingTimers || {});
|
||||
|
||||
const groupAdmins = this.isPublic()
|
||||
? this.get('moderators')
|
||||
: this.get('groupAdmins');
|
||||
const groupAdmins = this.getGroupAdmins();
|
||||
|
||||
const members =
|
||||
this.isGroup() && !this.isPublic() ? this.get('members') : undefined;
|
||||
|
||||
const result = {
|
||||
id: this.id,
|
||||
|
@ -499,7 +503,7 @@
|
|||
isKickedFromGroup: !!this.get('isKickedFromGroup'),
|
||||
left: !!this.get('left'),
|
||||
groupAdmins,
|
||||
|
||||
members,
|
||||
onClick: () => this.trigger('select', this),
|
||||
onBlockContact: () => this.block(),
|
||||
onUnblockContact: () => this.unblock(),
|
||||
|
@ -676,6 +680,15 @@
|
|||
}
|
||||
},
|
||||
async updateGroupAdmins(groupAdmins) {
|
||||
const existingAdmins = _.sortBy(this.getGroupAdmins());
|
||||
const newAdmins = _.sortBy(groupAdmins);
|
||||
|
||||
if (_.isEqual(existingAdmins, newAdmins)) {
|
||||
window.log.info(
|
||||
'Skipping updates of groupAdmins/moderators. No change detected.'
|
||||
);
|
||||
return;
|
||||
}
|
||||
this.set({ groupAdmins });
|
||||
await this.commit();
|
||||
},
|
||||
|
@ -1828,27 +1841,16 @@
|
|||
await this.commit();
|
||||
}
|
||||
},
|
||||
isModerator(pubKey) {
|
||||
isAdmin(pubKey) {
|
||||
if (!this.isPublic()) {
|
||||
return false;
|
||||
}
|
||||
if (!pubKey) {
|
||||
throw new Error('isModerator() pubKey is falsy');
|
||||
throw new Error('isAdmin() pubKey is falsy');
|
||||
}
|
||||
const moderators = this.get('moderators');
|
||||
return Array.isArray(moderators) && moderators.includes(pubKey);
|
||||
const groupAdmins = this.getGroupAdmins();
|
||||
return Array.isArray(groupAdmins) && groupAdmins.includes(pubKey);
|
||||
},
|
||||
async setModerators(moderators) {
|
||||
if (!this.isPublic()) {
|
||||
return;
|
||||
}
|
||||
// TODO: compare array properly
|
||||
if (!_.isEqual(this.get('moderators'), moderators)) {
|
||||
this.set({ moderators });
|
||||
await this.commit();
|
||||
}
|
||||
},
|
||||
|
||||
// SIGNAL PROFILES
|
||||
|
||||
onChangeProfileKey() {
|
||||
|
|
|
@ -568,8 +568,7 @@
|
|||
// for the public group chat
|
||||
const conversation = this.getConversation();
|
||||
|
||||
const isModerator =
|
||||
conversation && !!conversation.isModerator(phoneNumber);
|
||||
const isAdmin = conversation && !!conversation.isAdmin(phoneNumber);
|
||||
|
||||
const convoId = conversation ? conversation.id : undefined;
|
||||
const isGroup = !!conversation && !conversation.isPrivate();
|
||||
|
@ -606,9 +605,9 @@
|
|||
conversation && conversation.get('isKickedFromGroup'),
|
||||
isDeletable:
|
||||
!this.get('isPublic') ||
|
||||
isModerator ||
|
||||
isAdmin ||
|
||||
phoneNumber === textsecure.storage.user.getNumber(),
|
||||
isModerator,
|
||||
isAdmin,
|
||||
|
||||
onCopyText: () => this.copyText(),
|
||||
onCopyPubKey: () => this.copyPubKey(),
|
||||
|
|
|
@ -1204,7 +1204,7 @@ class LokiPublicChannelAPI {
|
|||
}
|
||||
|
||||
if (this.running) {
|
||||
await this.conversation.setModerators(moderators || []);
|
||||
await this.conversation.updateGroupAdmins(moderators || []);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -31,7 +31,7 @@
|
|||
this.titleText = i18n('updateGroupDialogTitle', this.groupName);
|
||||
// I'd much prefer to integrate mods with groupAdmins
|
||||
// but lets discuss first...
|
||||
this.isAdmin = groupConvo.isModerator(
|
||||
this.isAdmin = groupConvo.isAdmin(
|
||||
window.storage.get('primaryDevicePubKey')
|
||||
);
|
||||
}
|
||||
|
@ -92,7 +92,7 @@
|
|||
this.titleText = i18n('updateGroupDialogTitle', this.groupName);
|
||||
// I'd much prefer to integrate mods with groupAdmins
|
||||
// but lets discuss first...
|
||||
this.isAdmin = groupConvo.isModerator(
|
||||
this.isAdmin = groupConvo.isAdmin(
|
||||
window.storage.get('primaryDevicePubKey')
|
||||
);
|
||||
// zero out contactList for now
|
||||
|
|
|
@ -69,7 +69,6 @@ export class LeftPane extends React.Component<Props> {
|
|||
}
|
||||
|
||||
public render(): JSX.Element {
|
||||
const ourPrimaryConversation = this.props.ourPrimaryConversation;
|
||||
return (
|
||||
<SessionTheme theme={this.props.theme}>
|
||||
<div className="module-left-pane-session">
|
||||
|
@ -77,8 +76,6 @@ export class LeftPane extends React.Component<Props> {
|
|||
{...this.props}
|
||||
selectedSection={this.props.focusedSection}
|
||||
onSectionSelected={this.handleSectionSelected}
|
||||
unreadMessageCount={this.props.unreadMessageCount}
|
||||
ourPrimaryConversation={ourPrimaryConversation}
|
||||
/>
|
||||
<div className="module-left-pane">{this.renderSection()}</div>
|
||||
</div>
|
||||
|
@ -162,22 +159,13 @@ export class LeftPane extends React.Component<Props> {
|
|||
}
|
||||
|
||||
private renderSettingSection() {
|
||||
const {
|
||||
isSecondaryDevice,
|
||||
showSessionSettingsCategory,
|
||||
settingsCategory,
|
||||
} = this.props;
|
||||
const { settingsCategory } = this.props;
|
||||
|
||||
const category = settingsCategory || SessionSettingCategory.Appearance;
|
||||
|
||||
return (
|
||||
<>
|
||||
<LeftPaneSettingSection
|
||||
{...this.props}
|
||||
isSecondaryDevice={isSecondaryDevice}
|
||||
showSessionSettingsCategory={showSessionSettingsCategory}
|
||||
settingsCategory={category}
|
||||
/>
|
||||
<LeftPaneSettingSection {...this.props} settingsCategory={category} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -52,8 +52,8 @@ interface LinkPreviewType {
|
|||
export interface Props {
|
||||
disableMenu?: boolean;
|
||||
isDeletable: boolean;
|
||||
isModerator?: boolean;
|
||||
weAreModerator?: boolean;
|
||||
isAdmin?: boolean;
|
||||
weAreAdmin?: boolean;
|
||||
text?: string;
|
||||
bodyPending?: boolean;
|
||||
id: string;
|
||||
|
@ -574,7 +574,7 @@ class MessageInner extends React.PureComponent<Props, State> {
|
|||
authorPhoneNumber,
|
||||
authorProfileName,
|
||||
collapseMetadata,
|
||||
isModerator,
|
||||
isAdmin,
|
||||
conversationType,
|
||||
direction,
|
||||
onShowUserDetails,
|
||||
|
@ -605,7 +605,7 @@ class MessageInner extends React.PureComponent<Props, State> {
|
|||
}}
|
||||
pubkey={authorPhoneNumber}
|
||||
/>
|
||||
{isModerator && (
|
||||
{isAdmin && (
|
||||
<div className="module-avatar__icon--crown-wrapper">
|
||||
<div className="module-avatar__icon--crown" />
|
||||
</div>
|
||||
|
@ -692,7 +692,7 @@ class MessageInner extends React.PureComponent<Props, State> {
|
|||
onRetrySend,
|
||||
onShowDetail,
|
||||
isPublic,
|
||||
weAreModerator,
|
||||
weAreAdmin,
|
||||
onBanUser,
|
||||
} = this.props;
|
||||
|
||||
|
@ -760,7 +760,7 @@ class MessageInner extends React.PureComponent<Props, State> {
|
|||
</Item>
|
||||
</>
|
||||
) : null}
|
||||
{weAreModerator && isPublic ? (
|
||||
{weAreAdmin && isPublic ? (
|
||||
<Item onClick={onBanUser}>{window.i18n('banUser')}</Item>
|
||||
) : null}
|
||||
</Menu>
|
||||
|
|
|
@ -14,7 +14,7 @@ import styled, { DefaultTheme } from 'styled-components';
|
|||
|
||||
type Props = {
|
||||
disableMenu?: boolean;
|
||||
isModerator?: boolean;
|
||||
isAdmin?: boolean;
|
||||
isDeletable: boolean;
|
||||
text?: string;
|
||||
bodyPending?: boolean;
|
||||
|
@ -74,7 +74,7 @@ export const MessageMetadata = (props: Props) => {
|
|||
serverTimestamp,
|
||||
isShowingImage,
|
||||
isPublic,
|
||||
isModerator,
|
||||
isAdmin,
|
||||
theme,
|
||||
} = props;
|
||||
|
||||
|
@ -109,7 +109,7 @@ export const MessageMetadata = (props: Props) => {
|
|||
<MetadataBadges
|
||||
direction={direction}
|
||||
isPublic={isPublic}
|
||||
isModerator={isModerator}
|
||||
isAdmin={isAdmin}
|
||||
id={id}
|
||||
withImageNoCaption={withImageNoCaption}
|
||||
/>
|
||||
|
|
|
@ -45,15 +45,15 @@ type BadgesProps = {
|
|||
id: string;
|
||||
direction: string;
|
||||
isPublic?: boolean;
|
||||
isModerator?: boolean;
|
||||
isAdmin?: boolean;
|
||||
withImageNoCaption: boolean;
|
||||
};
|
||||
|
||||
export const MetadataBadges = (props: BadgesProps): JSX.Element => {
|
||||
const { id, direction, isPublic, isModerator, withImageNoCaption } = props;
|
||||
const { id, direction, isPublic, isAdmin, withImageNoCaption } = props;
|
||||
const badges = [
|
||||
(isPublic && 'Public') || null,
|
||||
(isModerator && 'Mod') || null,
|
||||
(isAdmin && 'Mod') || null,
|
||||
].filter(nonNullish);
|
||||
|
||||
if (!badges || badges.length === 0) {
|
||||
|
|
|
@ -10,10 +10,11 @@ import { ConversationType } from '../../state/ducks/conversations';
|
|||
import { noop } from 'lodash';
|
||||
import { DefaultTheme } from 'styled-components';
|
||||
import { StateType } from '../../state/reducer';
|
||||
import { MessageEncrypter } from '../../session/crypto';
|
||||
import { PubKey } from '../../session/types';
|
||||
import { UserUtil } from '../../util';
|
||||
import { ConversationController } from '../../session/conversations';
|
||||
import { getFocusedSection } from '../../state/selectors/section';
|
||||
import { getTheme } from '../../state/selectors/theme';
|
||||
import { getPrimaryPubkey } from '../../state/selectors/user';
|
||||
// tslint:disable-next-line: no-import-side-effect no-submodule-imports
|
||||
|
||||
export enum SectionType {
|
||||
|
@ -30,6 +31,7 @@ interface Props {
|
|||
selectedSection: SectionType;
|
||||
unreadMessageCount: number;
|
||||
ourPrimaryConversation: ConversationType;
|
||||
ourPrimary: string;
|
||||
applyTheme?: any;
|
||||
theme: DefaultTheme;
|
||||
}
|
||||
|
@ -68,6 +70,7 @@ class ActionsPanelPrivate extends React.Component<Props> {
|
|||
avatarPath?: string;
|
||||
notificationCount?: number;
|
||||
}) => {
|
||||
const { ourPrimary } = this.props;
|
||||
const handleClick = onSelect
|
||||
? () => {
|
||||
/* tslint:disable:no-void-expression */
|
||||
|
@ -89,7 +92,6 @@ class ActionsPanelPrivate extends React.Component<Props> {
|
|||
: undefined;
|
||||
|
||||
if (type === SectionType.Profile) {
|
||||
const ourPrimary = window.storage.get('primaryDevicePubKey');
|
||||
const conversation = ConversationController.getInstance().get(ourPrimary);
|
||||
|
||||
const profile = conversation?.getLokiProfile();
|
||||
|
@ -202,11 +204,10 @@ class ActionsPanelPrivate extends React.Component<Props> {
|
|||
}
|
||||
|
||||
const mapStateToProps = (state: StateType) => {
|
||||
const { section, theme } = state;
|
||||
|
||||
return {
|
||||
section: section.focusedSection,
|
||||
theme,
|
||||
section: getFocusedSection(state),
|
||||
theme: getTheme(state),
|
||||
ourPrimary: getPrimaryPubkey(state),
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
@ -29,6 +29,7 @@ import { MemberItem } from '../../conversation/MemberList';
|
|||
import { CaptionEditor } from '../../CaptionEditor';
|
||||
import { DefaultTheme } from 'styled-components';
|
||||
import { ConversationController } from '../../../session/conversations/ConversationController';
|
||||
import { ConversationType } from '../../../state/ducks/conversations';
|
||||
|
||||
export interface ReplyingToMessageProps {
|
||||
convoId: string;
|
||||
|
@ -64,7 +65,8 @@ interface Props {
|
|||
isPrivate: boolean;
|
||||
isKickedFromGroup: boolean;
|
||||
left: boolean;
|
||||
conversationKey: string;
|
||||
selectedConversationKey: string;
|
||||
selectedConversation: ConversationType | undefined;
|
||||
isPublic: boolean;
|
||||
|
||||
quotedMessageProps?: ReplyingToMessageProps;
|
||||
|
@ -186,7 +188,9 @@ export class SessionCompositionBox extends React.Component<Props, State> {
|
|||
}
|
||||
public componentDidUpdate(prevProps: Props, _prevState: State) {
|
||||
// reset the state on new conversation key
|
||||
if (prevProps.conversationKey !== this.props.conversationKey) {
|
||||
if (
|
||||
prevProps.selectedConversationKey !== this.props.selectedConversationKey
|
||||
) {
|
||||
this.setState(getDefaultState(), this.focusCompositionBox);
|
||||
this.lastBumpTypingMessageLength = 0;
|
||||
} else if (
|
||||
|
@ -452,13 +456,14 @@ export class SessionCompositionBox extends React.Component<Props, State> {
|
|||
}
|
||||
|
||||
private fetchUsersForClosedGroup(query: any, callback: any) {
|
||||
const conversationModel = ConversationController.getInstance().get(
|
||||
this.props.conversationKey
|
||||
);
|
||||
if (!conversationModel) {
|
||||
const { selectedConversation } = this.props;
|
||||
if (!selectedConversation) {
|
||||
return;
|
||||
}
|
||||
const allPubKeys = selectedConversation.members;
|
||||
if (!allPubKeys || allPubKeys.length === 0) {
|
||||
return;
|
||||
}
|
||||
const allPubKeys = conversationModel.get('members');
|
||||
|
||||
const allMembers = allPubKeys.map(pubKey => {
|
||||
const conv = ConversationController.getInstance().get(pubKey);
|
||||
|
@ -724,7 +729,7 @@ export class SessionCompositionBox extends React.Component<Props, State> {
|
|||
// catching ESC, tab, or whatever which is not typing
|
||||
if (message.length && message.length !== this.lastBumpTypingMessageLength) {
|
||||
const conversationModel = ConversationController.getInstance().get(
|
||||
this.props.conversationKey
|
||||
this.props.selectedConversationKey
|
||||
);
|
||||
if (!conversationModel) {
|
||||
return;
|
||||
|
|
|
@ -72,8 +72,8 @@ interface State {
|
|||
|
||||
interface Props {
|
||||
ourPrimary: string;
|
||||
conversationKey: string;
|
||||
conversation: ConversationType;
|
||||
selectedConversationKey: string;
|
||||
selectedConversation?: ConversationType;
|
||||
theme: DefaultTheme;
|
||||
messages: Array<any>;
|
||||
actions: any;
|
||||
|
@ -88,13 +88,7 @@ export class SessionConversation extends React.Component<Props, State> {
|
|||
constructor(props: any) {
|
||||
super(props);
|
||||
|
||||
const { conversationKey } = this.props;
|
||||
|
||||
const conversationModel = ConversationController.getInstance().get(
|
||||
conversationKey
|
||||
);
|
||||
|
||||
const unreadCount = conversationModel?.get('unreadCount') || 0;
|
||||
const unreadCount = this.props.selectedConversation?.unreadCount || 0;
|
||||
this.state = {
|
||||
messageProgressVisible: false,
|
||||
sendingProgress: 0,
|
||||
|
@ -163,10 +157,10 @@ export class SessionConversation extends React.Component<Props, State> {
|
|||
|
||||
public componentDidUpdate(prevProps: Props, prevState: State) {
|
||||
const {
|
||||
conversationKey: newConversationKey,
|
||||
conversation: newConversation,
|
||||
selectedConversationKey: newConversationKey,
|
||||
selectedConversation: newConversation,
|
||||
} = this.props;
|
||||
const { conversationKey: oldConversationKey } = prevProps;
|
||||
const { selectedConversationKey: oldConversationKey } = prevProps;
|
||||
|
||||
// if the convo is valid, and it changed, register for drag events
|
||||
if (
|
||||
|
@ -255,18 +249,19 @@ export class SessionConversation extends React.Component<Props, State> {
|
|||
} = this.state;
|
||||
const selectionMode = !!selectedMessages.length;
|
||||
|
||||
const { conversation, conversationKey, messages } = this.props;
|
||||
const conversationModel = ConversationController.getInstance().get(
|
||||
conversationKey
|
||||
);
|
||||
const {
|
||||
selectedConversation,
|
||||
selectedConversationKey,
|
||||
messages,
|
||||
} = this.props;
|
||||
|
||||
if (!conversationModel || !messages) {
|
||||
if (!selectedConversation || !messages) {
|
||||
// return an empty message view
|
||||
return <MessageView />;
|
||||
}
|
||||
|
||||
const { isRss } = conversation;
|
||||
|
||||
const conversationModel = ConversationController.getInstance().get(
|
||||
selectedConversationKey
|
||||
);
|
||||
// TODO VINCE: OPTIMISE FOR NEW SENDING???
|
||||
const sendMessageFn = (
|
||||
body: any,
|
||||
|
@ -276,6 +271,9 @@ export class SessionConversation extends React.Component<Props, State> {
|
|||
groupInvitation: any,
|
||||
otherOptions: any
|
||||
) => {
|
||||
if (!conversationModel) {
|
||||
return;
|
||||
}
|
||||
void conversationModel.sendMessage(
|
||||
body,
|
||||
attachments,
|
||||
|
@ -292,11 +290,12 @@ export class SessionConversation extends React.Component<Props, State> {
|
|||
}
|
||||
};
|
||||
|
||||
const shouldRenderRightPanel = !conversationModel.isRss();
|
||||
|
||||
const showSafetyNumber = infoViewState === 'safetyNumber';
|
||||
const showMessageDetails = !!messageDetailShowProps;
|
||||
|
||||
const isPublic = selectedConversation.isPublic || false;
|
||||
const isPrivate = selectedConversation.type === 'direct';
|
||||
|
||||
return (
|
||||
<SessionTheme theme={this.props.theme}>
|
||||
<div className="conversation-header">{this.renderHeader()}</div>
|
||||
|
@ -344,45 +343,41 @@ export class SessionConversation extends React.Component<Props, State> {
|
|||
{isDraggingFile && <SessionFileDropzone />}
|
||||
</div>
|
||||
|
||||
{!isRss && (
|
||||
// tslint:disable-next-line: use-simple-attributes
|
||||
<SessionCompositionBox
|
||||
isBlocked={conversation.isBlocked}
|
||||
left={conversation.left}
|
||||
isKickedFromGroup={conversation.isKickedFromGroup}
|
||||
isPrivate={conversation.type === 'direct'}
|
||||
isPublic={conversation.isPublic || false}
|
||||
conversationKey={conversationKey}
|
||||
sendMessage={sendMessageFn}
|
||||
stagedAttachments={stagedAttachments}
|
||||
onMessageSending={this.onMessageSending}
|
||||
onMessageSuccess={this.onMessageSuccess}
|
||||
onMessageFailure={this.onMessageFailure}
|
||||
onLoadVoiceNoteView={this.onLoadVoiceNoteView}
|
||||
onExitVoiceNoteView={this.onExitVoiceNoteView}
|
||||
quotedMessageProps={quotedMessageProps}
|
||||
removeQuotedMessage={() => {
|
||||
void this.replyToMessage(undefined);
|
||||
}}
|
||||
textarea={this.compositionBoxRef}
|
||||
clearAttachments={this.clearAttachments}
|
||||
removeAttachment={this.removeAttachment}
|
||||
onChoseAttachments={this.onChoseAttachments}
|
||||
theme={this.props.theme}
|
||||
/>
|
||||
)}
|
||||
<SessionCompositionBox
|
||||
isBlocked={selectedConversation.isBlocked}
|
||||
left={selectedConversation.left}
|
||||
isKickedFromGroup={selectedConversation.isKickedFromGroup}
|
||||
isPrivate={isPrivate}
|
||||
isPublic={isPublic}
|
||||
selectedConversationKey={selectedConversationKey}
|
||||
selectedConversation={selectedConversation}
|
||||
sendMessage={sendMessageFn}
|
||||
stagedAttachments={stagedAttachments}
|
||||
onMessageSending={this.onMessageSending}
|
||||
onMessageSuccess={this.onMessageSuccess}
|
||||
onMessageFailure={this.onMessageFailure}
|
||||
onLoadVoiceNoteView={this.onLoadVoiceNoteView}
|
||||
onExitVoiceNoteView={this.onExitVoiceNoteView}
|
||||
quotedMessageProps={quotedMessageProps}
|
||||
removeQuotedMessage={() => {
|
||||
void this.replyToMessage(undefined);
|
||||
}}
|
||||
textarea={this.compositionBoxRef}
|
||||
clearAttachments={this.clearAttachments}
|
||||
removeAttachment={this.removeAttachment}
|
||||
onChoseAttachments={this.onChoseAttachments}
|
||||
theme={this.props.theme}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{shouldRenderRightPanel && (
|
||||
<div
|
||||
className={classNames(
|
||||
'conversation-item__options-pane',
|
||||
showOptionsPane && 'show'
|
||||
)}
|
||||
>
|
||||
<SessionRightPanelWithDetails {...this.getRightPanelProps()} />
|
||||
</div>
|
||||
)}
|
||||
<div
|
||||
className={classNames(
|
||||
'conversation-item__options-pane',
|
||||
showOptionsPane && 'show'
|
||||
)}
|
||||
>
|
||||
<SessionRightPanelWithDetails {...this.getRightPanelProps()} />
|
||||
</div>
|
||||
</SessionTheme>
|
||||
);
|
||||
}
|
||||
|
@ -397,33 +392,34 @@ export class SessionConversation extends React.Component<Props, State> {
|
|||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
public async loadInitialMessages() {
|
||||
const { conversationKey } = this.props;
|
||||
const conversationModel = ConversationController.getInstance().get(
|
||||
conversationKey
|
||||
);
|
||||
if (!conversationModel) {
|
||||
const { selectedConversation, selectedConversationKey } = this.props;
|
||||
|
||||
if (!selectedConversation) {
|
||||
return;
|
||||
}
|
||||
const conversationModel = ConversationController.getInstance().get(
|
||||
selectedConversationKey
|
||||
);
|
||||
const unreadCount = await conversationModel.getUnreadCount();
|
||||
const messagesToFetch = Math.max(
|
||||
Constants.CONVERSATION.DEFAULT_MESSAGE_FETCH_COUNT,
|
||||
unreadCount
|
||||
);
|
||||
this.props.actions.fetchMessagesForConversation({
|
||||
conversationKey,
|
||||
conversationKey: selectedConversationKey,
|
||||
count: messagesToFetch,
|
||||
});
|
||||
}
|
||||
|
||||
public getHeaderProps() {
|
||||
const { conversationKey } = this.props;
|
||||
const { selectedConversationKey, ourPrimary } = this.props;
|
||||
const {
|
||||
selectedMessages,
|
||||
infoViewState,
|
||||
messageDetailShowProps,
|
||||
} = this.state;
|
||||
const conversation = ConversationController.getInstance().getOrThrow(
|
||||
conversationKey
|
||||
selectedConversationKey
|
||||
);
|
||||
const expireTimer = conversation.get('expireTimer');
|
||||
const expirationSettingName = expireTimer
|
||||
|
@ -446,9 +442,7 @@ export class SessionConversation extends React.Component<Props, State> {
|
|||
isPrivate: conversation.isPrivate(),
|
||||
isPublic: conversation.isPublic(),
|
||||
isRss: conversation.isRss(),
|
||||
isAdmin: conversation.isModerator(
|
||||
window.storage.get('primaryDevicePubKey')
|
||||
),
|
||||
isAdmin: conversation.isAdmin(ourPrimary),
|
||||
members,
|
||||
subscriberCount: conversation.get('subscriberCount'),
|
||||
isKickedFromGroup: conversation.get('isKickedFromGroup'),
|
||||
|
@ -524,17 +518,23 @@ export class SessionConversation extends React.Component<Props, State> {
|
|||
}
|
||||
|
||||
public getMessagesListProps() {
|
||||
const { conversation, ourPrimary, messages, actions } = this.props;
|
||||
const {
|
||||
selectedConversation,
|
||||
selectedConversationKey,
|
||||
ourPrimary,
|
||||
messages,
|
||||
actions,
|
||||
} = this.props;
|
||||
const { quotedMessageTimestamp, selectedMessages } = this.state;
|
||||
|
||||
return {
|
||||
selectedMessages,
|
||||
ourPrimary,
|
||||
conversationKey: conversation.id,
|
||||
conversationKey: selectedConversationKey,
|
||||
messages,
|
||||
resetSelection: this.resetSelection,
|
||||
quotedMessageTimestamp,
|
||||
conversation,
|
||||
conversation: selectedConversation as ConversationType,
|
||||
selectMessage: this.selectMessage,
|
||||
deleteMessage: this.deleteMessage,
|
||||
fetchMessagesForConversation: actions.fetchMessagesForConversation,
|
||||
|
@ -548,9 +548,9 @@ export class SessionConversation extends React.Component<Props, State> {
|
|||
}
|
||||
|
||||
public getRightPanelProps() {
|
||||
const { conversationKey } = this.props;
|
||||
const { selectedConversationKey } = this.props;
|
||||
const conversation = ConversationController.getInstance().getOrThrow(
|
||||
conversationKey
|
||||
selectedConversationKey
|
||||
);
|
||||
const ourPrimary = window.storage.get('primaryDevicePubKey');
|
||||
|
||||
|
@ -558,7 +558,7 @@ export class SessionConversation extends React.Component<Props, State> {
|
|||
const isAdmin = conversation.isMediumGroup()
|
||||
? true
|
||||
: conversation.isPublic()
|
||||
? conversation.isModerator(ourPrimary)
|
||||
? conversation.isAdmin(ourPrimary)
|
||||
: false;
|
||||
|
||||
return {
|
||||
|
@ -670,26 +670,32 @@ export class SessionConversation extends React.Component<Props, State> {
|
|||
askUserForConfirmation: boolean
|
||||
) {
|
||||
// Get message objects
|
||||
const { conversationKey, messages } = this.props;
|
||||
const {
|
||||
selectedConversationKey,
|
||||
selectedConversation,
|
||||
messages,
|
||||
} = this.props;
|
||||
|
||||
const conversationModel = ConversationController.getInstance().getOrThrow(
|
||||
conversationKey
|
||||
selectedConversationKey
|
||||
);
|
||||
if (!selectedConversation) {
|
||||
window.log.info('No valid selected conversation.');
|
||||
return;
|
||||
}
|
||||
const selectedMessages = messages.filter(message =>
|
||||
messageIds.find(selectedMessage => selectedMessage === message.id)
|
||||
);
|
||||
|
||||
const multiple = selectedMessages.length > 1;
|
||||
|
||||
const isPublic = conversationModel.isPublic();
|
||||
|
||||
// In future, we may be able to unsend private messages also
|
||||
// isServerDeletable also defined in ConversationHeader.tsx for
|
||||
// future reference
|
||||
const isServerDeletable = isPublic;
|
||||
const isServerDeletable = selectedConversation.isPublic;
|
||||
|
||||
const warningMessage = (() => {
|
||||
if (isPublic) {
|
||||
if (selectedConversation.isPublic) {
|
||||
return multiple
|
||||
? window.i18n('deleteMultiplePublicWarning')
|
||||
: window.i18n('deletePublicWarning');
|
||||
|
@ -704,7 +710,7 @@ export class SessionConversation extends React.Component<Props, State> {
|
|||
|
||||
// VINCE TODO: MARK TO-DELETE MESSAGES AS READ
|
||||
|
||||
if (isPublic) {
|
||||
if (selectedConversation.isPublic) {
|
||||
// Get our Moderator status
|
||||
const ourDevicePubkey = await UserUtil.getCurrentDevicePubKey();
|
||||
if (!ourDevicePubkey) {
|
||||
|
@ -713,7 +719,7 @@ export class SessionConversation extends React.Component<Props, State> {
|
|||
const ourPrimaryPubkey = (
|
||||
await MultiDeviceProtocol.getPrimaryDevice(ourDevicePubkey)
|
||||
).key;
|
||||
const isModerator = conversationModel.isModerator(ourPrimaryPubkey);
|
||||
const isAdmin = conversationModel.isAdmin(ourPrimaryPubkey);
|
||||
const ourNumbers = (await MultiDeviceProtocol.getOurDevices()).map(
|
||||
m => m.key
|
||||
);
|
||||
|
@ -721,7 +727,7 @@ export class SessionConversation extends React.Component<Props, State> {
|
|||
ourNumbers.includes(message.attributes.source)
|
||||
);
|
||||
|
||||
if (!isAllOurs && !isModerator) {
|
||||
if (!isAllOurs && !isAdmin) {
|
||||
ToastUtils.pushMessageDeleteForbidden();
|
||||
|
||||
this.setState({ selectedMessages: [] });
|
||||
|
@ -820,14 +826,14 @@ export class SessionConversation extends React.Component<Props, State> {
|
|||
// ~~~~~~~~~~~~~~ MESSAGE QUOTE ~~~~~~~~~~~~~~~
|
||||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
private async replyToMessage(quotedMessageTimestamp?: number) {
|
||||
if (this.props.conversation.isBlocked) {
|
||||
if (this.props.selectedConversation?.isBlocked) {
|
||||
pushUnblockToSend();
|
||||
return;
|
||||
}
|
||||
if (!_.isEqual(this.state.quotedMessageTimestamp, quotedMessageTimestamp)) {
|
||||
const { messages, conversationKey } = this.props;
|
||||
const { messages, selectedConversationKey } = this.props;
|
||||
const conversationModel = ConversationController.getInstance().getOrThrow(
|
||||
conversationKey
|
||||
selectedConversationKey
|
||||
);
|
||||
|
||||
let quotedMessageProps = null;
|
||||
|
@ -1229,7 +1235,7 @@ export class SessionConversation extends React.Component<Props, State> {
|
|||
|
||||
private async updateMemberList() {
|
||||
const allPubKeys = await window.Signal.Data.getPubkeysInPublicConversation(
|
||||
this.props.conversationKey
|
||||
this.props.selectedConversationKey
|
||||
);
|
||||
|
||||
const allMembers = allPubKeys.map((pubKey: string) => {
|
||||
|
|
|
@ -297,9 +297,8 @@ export class SessionMessagesList extends React.Component<Props, State> {
|
|||
);
|
||||
}
|
||||
|
||||
// allow moderators feature on messages (like banning a user)
|
||||
if (messageProps.isPublic) {
|
||||
messageProps.weAreModerator = conversation.groupAdmins?.includes(
|
||||
if (messageProps.conversationType === 'group') {
|
||||
messageProps.weAreAdmin = conversation.groupAdmins?.includes(
|
||||
ourPrimary
|
||||
);
|
||||
}
|
||||
|
|
|
@ -19,6 +19,10 @@ import { mapDispatchToProps } from '../../../state/actions';
|
|||
import { connect } from 'react-redux';
|
||||
import { StateType } from '../../../state/reducer';
|
||||
import { ConversationController } from '../../../session/conversations';
|
||||
import {
|
||||
getConversationLookup,
|
||||
getConversations,
|
||||
} from '../../../state/selectors/conversations';
|
||||
|
||||
export enum SessionSettingCategory {
|
||||
Appearance = 'appearance',
|
||||
|
@ -746,10 +750,8 @@ class SettingsViewInner extends React.Component<SettingsViewProps, State> {
|
|||
}
|
||||
|
||||
const mapStateToProps = (state: StateType) => {
|
||||
const { conversations } = state;
|
||||
|
||||
return {
|
||||
conversations: conversations.conversationLookup,
|
||||
conversations: getConversationLookup(state),
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
@ -35,7 +35,7 @@ export type MessageType = {
|
|||
isSelected?: boolean;
|
||||
};
|
||||
|
||||
type MessageTypeInConvo = {
|
||||
export type MessageTypeInConvo = {
|
||||
id: string;
|
||||
conversationId: string;
|
||||
attributes: any;
|
||||
|
@ -80,6 +80,7 @@ export type ConversationType = {
|
|||
left: boolean;
|
||||
avatarPath?: string; // absolute filepath to the avatar
|
||||
groupAdmins?: Array<string>; // admins for closed groups and moderators for open groups
|
||||
members?: Array<string>; // members for closed groups only
|
||||
};
|
||||
export type ConversationLookupType = {
|
||||
[key: string]: ConversationType;
|
||||
|
|
|
@ -8,7 +8,6 @@ import {
|
|||
import { reducer as user, UserStateType } from './ducks/user';
|
||||
import { reducer as theme, ThemeStateType } from './ducks/theme';
|
||||
import { reducer as section, SectionStateType } from './ducks/section';
|
||||
import { PubKey } from '../session/types';
|
||||
|
||||
export type StateType = {
|
||||
search: SearchStateType;
|
||||
|
|
|
@ -7,6 +7,7 @@ import {
|
|||
ConversationLookupType,
|
||||
ConversationsStateType,
|
||||
ConversationType,
|
||||
MessageTypeInConvo,
|
||||
} from '../ducks/conversations';
|
||||
|
||||
import { getIntl, getRegionCode, getUserNumber } from './user';
|
||||
|
@ -23,19 +24,33 @@ export const getConversationLookup = createSelector(
|
|||
}
|
||||
);
|
||||
|
||||
export const getSelectedConversation = createSelector(
|
||||
export const getSelectedConversationKey = createSelector(
|
||||
getConversations,
|
||||
(state: ConversationsStateType): string | undefined => {
|
||||
return state.selectedConversation;
|
||||
}
|
||||
);
|
||||
|
||||
export const getSelectedConversation = createSelector(
|
||||
getConversations,
|
||||
(state: ConversationsStateType): ConversationType | undefined => {
|
||||
return state.selectedConversation
|
||||
? state.conversationLookup[state.selectedConversation]
|
||||
: undefined;
|
||||
}
|
||||
);
|
||||
|
||||
export const getOurPrimaryConversation = createSelector(
|
||||
getConversations,
|
||||
(state: ConversationsStateType): ConversationType =>
|
||||
state.conversationLookup[window.storage.get('primaryDevicePubKey')]
|
||||
);
|
||||
|
||||
export const getMessagesOfSelectedConversation = createSelector(
|
||||
getConversations,
|
||||
(state: ConversationsStateType): Array<MessageTypeInConvo> => state.messages
|
||||
);
|
||||
|
||||
function getConversationTitle(
|
||||
conversation: ConversationType,
|
||||
options: { i18n: LocalizerType; ourRegionCode: string }
|
||||
|
@ -229,14 +244,14 @@ export const _getSessionConversationInfo = (
|
|||
export const getLeftPaneLists = createSelector(
|
||||
getConversationLookup,
|
||||
getConversationComparator,
|
||||
getSelectedConversation,
|
||||
getSelectedConversationKey,
|
||||
_getLeftPaneLists
|
||||
);
|
||||
|
||||
export const getSessionConversationInfo = createSelector(
|
||||
getConversationLookup,
|
||||
getConversationComparator,
|
||||
getSelectedConversation,
|
||||
getSelectedConversationKey,
|
||||
_getSessionConversationInfo
|
||||
);
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@ import { SearchStateType } from '../ducks/search';
|
|||
import {
|
||||
getConversationLookup,
|
||||
getSelectedConversation,
|
||||
getSelectedConversationKey,
|
||||
} from './conversations';
|
||||
import { ConversationLookupType } from '../ducks/conversations';
|
||||
|
||||
|
@ -38,7 +39,7 @@ export const getSearchResults = createSelector(
|
|||
getSearch,
|
||||
getRegionCode,
|
||||
getConversationLookup,
|
||||
getSelectedConversation,
|
||||
getSelectedConversationKey,
|
||||
getSelectedMessage,
|
||||
],
|
||||
(
|
||||
|
|
12
ts/state/selectors/section.ts
Normal file
12
ts/state/selectors/section.ts
Normal file
|
@ -0,0 +1,12 @@
|
|||
import { createSelector } from 'reselect';
|
||||
|
||||
import { StateType } from '../reducer';
|
||||
import { SectionStateType } from '../ducks/section';
|
||||
import { SectionType } from '../../components/session/ActionsPanel';
|
||||
|
||||
export const getSection = (state: StateType): SectionStateType => state.section;
|
||||
|
||||
export const getFocusedSection = createSelector(
|
||||
getSection,
|
||||
(state: SectionStateType): SectionType => state.focusedSection
|
||||
);
|
4
ts/state/selectors/theme.ts
Normal file
4
ts/state/selectors/theme.ts
Normal file
|
@ -0,0 +1,4 @@
|
|||
import { StateType } from '../reducer';
|
||||
import { ThemeStateType } from '../ducks/theme';
|
||||
|
||||
export const getTheme = (state: StateType): ThemeStateType => state.theme;
|
|
@ -14,6 +14,8 @@ import {
|
|||
getOurPrimaryConversation,
|
||||
} from '../selectors/conversations';
|
||||
import { mapDispatchToProps } from '../actions';
|
||||
import { getFocusedSection } from '../selectors/section';
|
||||
import { getTheme } from '../selectors/theme';
|
||||
|
||||
// Workaround: A react component's required properties are filtering up through connect()
|
||||
// https://github.com/DefinitelyTyped/DefinitelyTyped/issues/31363
|
||||
|
@ -34,8 +36,8 @@ const mapStateToProps = (state: StateType) => {
|
|||
searchResults,
|
||||
i18n: getIntl(state),
|
||||
unreadMessageCount: leftPaneList.unreadCount,
|
||||
theme: state.theme,
|
||||
focusedSection: state.section.focusedSection,
|
||||
theme: getTheme(state),
|
||||
focusedSection: getFocusedSection(state),
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
@ -3,20 +3,20 @@ import { mapDispatchToProps } from '../actions';
|
|||
import { SessionConversation } from '../../components/session/conversation/SessionConversation';
|
||||
import { StateType } from '../reducer';
|
||||
import { getPrimaryPubkey } from '../selectors/user';
|
||||
import { getTheme } from '../selectors/theme';
|
||||
import {
|
||||
getMessagesOfSelectedConversation,
|
||||
getSelectedConversation,
|
||||
getSelectedConversationKey,
|
||||
} from '../selectors/conversations';
|
||||
|
||||
const mapStateToProps = (state: StateType) => {
|
||||
const conversationKey = state.conversations.selectedConversation;
|
||||
const ourPrimary = getPrimaryPubkey(state);
|
||||
const conversation =
|
||||
(conversationKey &&
|
||||
state.conversations.conversationLookup[conversationKey]) ||
|
||||
null;
|
||||
return {
|
||||
conversation,
|
||||
conversationKey,
|
||||
theme: state.theme,
|
||||
messages: state.conversations.messages,
|
||||
ourPrimary,
|
||||
selectedConversation: getSelectedConversation(state),
|
||||
selectedConversationKey: getSelectedConversationKey(state),
|
||||
theme: getTheme(state),
|
||||
messages: getMessagesOfSelectedConversation(state),
|
||||
ourPrimary: getPrimaryPubkey(state),
|
||||
};
|
||||
};
|
||||
|
||||
|
|
Loading…
Reference in a new issue