Fix types so the store can return undefined channels (#26393)

* Fix types so the store can return undefined channels

* Address feedback

* Revert gm change

* Fix lint

---------

Co-authored-by: Mattermost Build <build@mattermost.com>
Этот коммит содержится в:
Daniel Espino García
2024-05-06 12:04:36 +02:00
коммит произвёл GitHub
родитель c22509eca2
Коммит 539daee634
52 изменённых файлов: 290 добавлений и 227 удалений

Просмотреть файл

@@ -86,7 +86,10 @@ export function executeCommand(message: string, args: CommandArgs): ActionFuncAs
dispatch(GlobalActions.sendEphemeralPost('/leave is not supported in reply threads. Use it in the center channel instead.', args.channel_id, args.root_id)); dispatch(GlobalActions.sendEphemeralPost('/leave is not supported in reply threads. Use it in the center channel instead.', args.channel_id, args.root_id));
return {data: true}; return {data: true};
} }
const channel = getCurrentChannel(state) || {}; const channel = getCurrentChannel(state);
if (!channel) {
return {data: false};
}
if (channel.type === Constants.PRIVATE_CHANNEL) { if (channel.type === Constants.PRIVATE_CHANNEL) {
dispatch(openModal({modalId: ModalIdentifiers.LEAVE_PRIVATE_CHANNEL_MODAL, dialogType: LeaveChannelModal, dialogProps: {channel}})); dispatch(openModal({modalId: ModalIdentifiers.LEAVE_PRIVATE_CHANNEL_MODAL, dialogType: LeaveChannelModal, dialogProps: {channel}}));
return {data: true}; return {data: true};

Просмотреть файл

@@ -290,7 +290,7 @@ export function setEditingPost(postId = '', refocusId = '', title = '', isRHS =
const license = state.entities.general.license; const license = state.entities.general.license;
const userId = getCurrentUserId(state); const userId = getCurrentUserId(state);
const channel = getChannel(state, post.channel_id); const channel = getChannel(state, post.channel_id);
const teamId = channel.team_id || ''; const teamId = channel?.team_id || '';
const canEditNow = canEditPost(state, config, license, teamId, post.channel_id, userId, post); const canEditNow = canEditPost(state, config, license, teamId, post.channel_id, userId, post);

Просмотреть файл

@@ -64,14 +64,14 @@ import type {GlobalState} from 'types/store';
export function goToLastViewedChannel(): ActionFuncAsync { export function goToLastViewedChannel(): ActionFuncAsync {
return async (dispatch, getState) => { return async (dispatch, getState) => {
const state = getState(); const state = getState();
const currentChannel = getCurrentChannel(state) || {}; const currentChannel = getCurrentChannel(state);
const channelsInTeam = getChannelsNameMapInCurrentTeam(state); const channelsInTeam = getChannelsNameMapInCurrentTeam(state);
const directChannel = getAllDirectChannelsNameMapInCurrentTeam(state); const directChannel = getAllDirectChannelsNameMapInCurrentTeam(state);
const channels = Object.assign({}, channelsInTeam, directChannel); const channels = Object.assign({}, channelsInTeam, directChannel);
let channelToSwitchTo = getChannelByName(channels, getLastViewedChannelName(state)); let channelToSwitchTo = getChannelByName(channels, getLastViewedChannelName(state));
if (currentChannel.id === channelToSwitchTo!.id) { if (currentChannel?.id === channelToSwitchTo!.id) {
channelToSwitchTo = getChannelByName(channels, getRedirectChannelNameForTeam(state, getCurrentTeamId(state))); channelToSwitchTo = getChannelByName(channels, getRedirectChannelNameForTeam(state, getCurrentTeamId(state)));
} }
@@ -83,7 +83,10 @@ export function switchToChannelById(channelId: string): ActionFuncAsync {
return async (dispatch, getState) => { return async (dispatch, getState) => {
const state = getState(); const state = getState();
const channel = getChannel(state, channelId); const channel = getChannel(state, channelId);
return dispatch(switchToChannel(channel)); if (channel) {
return dispatch(switchToChannel(channel));
}
return {data: true};
}; };
} }
@@ -119,6 +122,9 @@ export function switchToChannel(channel: Channel & {userId?: string}): ActionFun
getHistory().push(`${teamUrl}/messages/@${channel.name}`); getHistory().push(`${teamUrl}/messages/@${channel.name}`);
} else if (channel.type === Constants.GM_CHANNEL) { } else if (channel.type === Constants.GM_CHANNEL) {
const gmChannel = getChannel(state, channel.id); const gmChannel = getChannel(state, channel.id);
if (!gmChannel?.name) {
return {error: true};
}
getHistory().push(`${teamUrl}/channels/${gmChannel.name}`); getHistory().push(`${teamUrl}/channels/${gmChannel.name}`);
} else if (channel.type === Constants.THREADS) { } else if (channel.type === Constants.THREADS) {
getHistory().push(`${teamUrl}/${channel.name}`); getHistory().push(`${teamUrl}/${channel.name}`);

Просмотреть файл

@@ -40,7 +40,7 @@ import SaveChangesPanel from '../../save_changes_panel';
export interface ChannelDetailsProps { export interface ChannelDetailsProps {
channelID: string; channelID: string;
channel: Channel; channel?: Channel;
team?: Team; team?: Team;
groups: Group[]; groups: Group[];
totalGroups: number; totalGroups: number;
@@ -108,9 +108,9 @@ export default class ChannelDetails extends React.PureComponent<ChannelDetailsPr
constructor(props: ChannelDetailsProps) { constructor(props: ChannelDetailsProps) {
super(props); super(props);
this.state = { this.state = {
isSynced: Boolean(props.channel.group_constrained), isSynced: Boolean(props.channel?.group_constrained),
isPublic: props.channel.type === Constants.OPEN_CHANNEL, isPublic: props.channel?.type === Constants.OPEN_CHANNEL,
isDefault: props.channel.name === Constants.DEFAULT_CHANNEL, isDefault: props.channel?.name === Constants.DEFAULT_CHANNEL,
isPrivacyChanging: false, isPrivacyChanging: false,
saving: false, saving: false,
totalGroups: props.totalGroups, totalGroups: props.totalGroups,
@@ -127,25 +127,25 @@ export default class ChannelDetails extends React.PureComponent<ChannelDetailsPr
previousServerError: undefined, previousServerError: undefined,
channelPermissions: props.channelPermissions, channelPermissions: props.channelPermissions,
teamScheme: props.teamScheme, teamScheme: props.teamScheme,
isLocalArchived: props.channel.delete_at > 0, isLocalArchived: props.channel?.delete_at !== 0,
showArchiveConfirmModal: false, showArchiveConfirmModal: false,
}; };
} }
componentDidUpdate(prevProps: ChannelDetailsProps) { componentDidUpdate(prevProps: ChannelDetailsProps) {
const {channel, totalGroups, actions} = this.props; const {channel, totalGroups, actions} = this.props;
if (channel.id !== prevProps.channel.id || totalGroups !== prevProps.totalGroups) { if (channel?.id !== prevProps.channel?.id || totalGroups !== prevProps.totalGroups) {
this.setState({ this.setState({
totalGroups, totalGroups,
isSynced: Boolean(channel.group_constrained), isSynced: Boolean(channel?.group_constrained),
isPublic: channel.type === Constants.OPEN_CHANNEL, isPublic: channel?.type === Constants.OPEN_CHANNEL,
isDefault: channel.name === Constants.DEFAULT_CHANNEL, isDefault: channel?.name === Constants.DEFAULT_CHANNEL,
isLocalArchived: channel.delete_at > 0, isLocalArchived: channel?.delete_at !== 0,
}); });
} }
// If we don't have the team and channel on mount, we need to request the team after we load the channel // If we don't have the team and channel on mount, we need to request the team after we load the channel
if (!prevProps.team?.id && !prevProps.channel.team_id && channel.team_id) { if (!prevProps.team?.id && !prevProps.channel?.team_id && channel?.team_id) {
actions.getTeam(channel.team_id). actions.getTeam(channel.team_id).
then(async (data: any) => { then(async (data: any) => {
if (data.data && data.data.scheme_id) { if (data.data && data.data.scheme_id) {
@@ -167,7 +167,7 @@ export default class ChannelDetails extends React.PureComponent<ChannelDetailsPr
actions.getChannel(channelID); actions.getChannel(channelID);
} }
if (channel.team_id) { if (channel?.team_id) {
actions.getTeam(channel.team_id). actions.getTeam(channel.team_id).
then(async (data: any) => { then(async (data: any) => {
if (data.data && data.data.scheme_id) { if (data.data && data.data.scheme_id) {
@@ -206,7 +206,7 @@ export default class ChannelDetails extends React.PureComponent<ChannelDetailsPr
private setToggles = (isSynced: boolean, isPublic: boolean) => { private setToggles = (isSynced: boolean, isPublic: boolean) => {
const {channel} = this.props; const {channel} = this.props;
const isOriginallyPublic = channel.type === Constants.OPEN_CHANNEL; const isOriginallyPublic = channel?.type === Constants.OPEN_CHANNEL;
this.setState( this.setState(
{ {
saveNeeded: true, saveNeeded: true,
@@ -355,7 +355,7 @@ export default class ChannelDetails extends React.PureComponent<ChannelDetailsPr
this.setState({showArchiveConfirmModal: true}); this.setState({showArchiveConfirmModal: true});
return; return;
} }
const isOriginallyPublic = channel.type === Constants.OPEN_CHANNEL; const isOriginallyPublic = channel?.type === Constants.OPEN_CHANNEL;
if (isSynced) { if (isSynced) {
isPublic = false; isPublic = false;
isPrivacyChanging = isOriginallyPublic; isPrivacyChanging = isOriginallyPublic;
@@ -380,11 +380,16 @@ export default class ChannelDetails extends React.PureComponent<ChannelDetailsPr
}; };
private handleSubmit = async () => { private handleSubmit = async () => {
const {groups: origGroups, channelID, actions, channel} = this.props;
if (!channel) {
return;
}
this.setState({showConvertConfirmModal: false, showRemoveConfirmModal: false, showConvertAndRemoveConfirmModal: false, showArchiveConfirmModal: false, saving: true}); this.setState({showConvertConfirmModal: false, showRemoveConfirmModal: false, showConvertAndRemoveConfirmModal: false, showArchiveConfirmModal: false, saving: true});
const {groups, isSynced, isPublic, isPrivacyChanging, channelPermissions, usersToAdd, usersToRemove, rolesToUpdate} = this.state; const {groups, isSynced, isPublic, isPrivacyChanging, channelPermissions, usersToAdd, usersToRemove, rolesToUpdate} = this.state;
let serverError: JSX.Element | undefined; let serverError: JSX.Element | undefined;
let saveNeeded = false; let saveNeeded = false;
const {groups: origGroups, channelID, actions, channel} = this.props;
if (this.channelToBeArchived()) { if (this.channelToBeArchived()) {
const result = await actions.deleteChannel(channel.id); const result = await actions.deleteChannel(channel.id);
@@ -607,13 +612,13 @@ export default class ChannelDetails extends React.PureComponent<ChannelDetailsPr
private channelToBeArchived = (): boolean => { private channelToBeArchived = (): boolean => {
const {isLocalArchived} = this.state; const {isLocalArchived} = this.state;
const isServerArchived = this.props.channel.delete_at !== 0; const isServerArchived = this.props.channel?.delete_at !== 0;
return isLocalArchived && !isServerArchived; return isLocalArchived && !isServerArchived;
}; };
private channelToBeRestored = (): boolean => { private channelToBeRestored = (): boolean => {
const {isLocalArchived} = this.state; const {isLocalArchived} = this.state;
const isServerArchived = this.props.channel.delete_at !== 0; const isServerArchived = this.props.channel?.delete_at !== 0;
return !isLocalArchived && isServerArchived; return !isLocalArchived && isServerArchived;
}; };
@@ -681,7 +686,7 @@ export default class ChannelDetails extends React.PureComponent<ChannelDetailsPr
this.setState(newState); this.setState(newState);
}; };
public render = (): JSX.Element => { public render = () => {
const { const {
totalGroups, totalGroups,
saving, saving,
@@ -703,6 +708,11 @@ export default class ChannelDetails extends React.PureComponent<ChannelDetailsPr
showArchiveConfirmModal, showArchiveConfirmModal,
} = this.state; } = this.state;
const {channel, team} = this.props; const {channel, team} = this.props;
if (!channel) {
return null;
}
const missingGroup = (og: {id: string}) => !groups.find((g: Group) => g.id === og.id); const missingGroup = (og: {id: string}) => !groups.find((g: Group) => g.id === og.id);
const removedGroups = this.props.groups.filter(missingGroup); const removedGroups = this.props.groups.filter(missingGroup);
const nonArchivedContent = ( const nonArchivedContent = (
@@ -722,7 +732,7 @@ export default class ChannelDetails extends React.PureComponent<ChannelDetailsPr
teamSchemeID={teamScheme?.id} teamSchemeID={teamScheme?.id}
teamSchemeDisplayName={teamScheme?.display_name} teamSchemeDisplayName={teamScheme?.display_name}
guestAccountsEnabled={this.props.guestAccountsEnabled} guestAccountsEnabled={this.props.guestAccountsEnabled}
isPublic={this.props.channel.type === Constants.OPEN_CHANNEL} isPublic={channel.type === Constants.OPEN_CHANNEL}
readOnly={this.props.isDisabled} readOnly={this.props.isDisabled}
/> />
} }

Просмотреть файл

@@ -23,7 +23,7 @@ import Constants, {ModalIdentifiers} from 'utils/constants';
type Props = { type Props = {
channelId: string; channelId: string;
channel: Channel; channel?: Channel;
filters: GetFilteredUsersStatsOpts; filters: GetFilteredUsersStatsOpts;
users: UserProfile[]; users: UserProfile[];

Просмотреть файл

@@ -54,7 +54,7 @@ function makeMapStateToProps() {
const config = getConfig(state); const config = getConfig(state);
const channelMembers = getChannelMembersInChannels(state)[channelId] || {}; const channelMembers = getChannelMembersInChannels(state)[channelId] || {};
const channel = getChannel(state, channelId) || {channel_id: channelId}; const channel = getChannel(state, channelId);
const searchTerm = state.views.search.userGridSearch?.term || ''; const searchTerm = state.views.search.userGridSearch?.term || '';
const filters = getUserGridFilters(state); const filters = getUserGridFilters(state);

Просмотреть файл

@@ -62,8 +62,8 @@ function mapStateToProps(state: GlobalState, ownProps: OwnProps) {
const guestAccountsEnabled = config.EnableGuestAccounts === 'true'; const guestAccountsEnabled = config.EnableGuestAccounts === 'true';
const channelID = ownProps.match.params.channel_id; const channelID = ownProps.match.params.channel_id;
const channel = getChannel(state, channelID) || {}; const channel = getChannel(state, channelID);
const team = getTeam(state, channel.team_id); const team = channel ? getTeam(state, channel.team_id) : undefined;
const groups = getGroupsAssociatedToChannel(state, channelID); const groups = getGroupsAssociatedToChannel(state, channelID);
const totalGroups = groups.length; const totalGroups = groups.length;
const allGroups = getAllGroups(state); const allGroups = getAllGroups(state);

Просмотреть файл

@@ -84,7 +84,7 @@ export type Props = {
currentChannelMembersCount: number; currentChannelMembersCount: number;
// Data used in multiple places of the component // Data used in multiple places of the component
currentChannel: Channel; currentChannel?: Channel;
//Data used for DM prewritten messages //Data used for DM prewritten messages
currentChannelTeammateUsername?: string; currentChannelTeammateUsername?: string;
@@ -239,7 +239,7 @@ type State = {
uploadsProgressPercent: {[clientID: string]: FilePreviewInfo}; uploadsProgressPercent: {[clientID: string]: FilePreviewInfo};
renderScrollbar: boolean; renderScrollbar: boolean;
scrollbarWidth: number; scrollbarWidth: number;
currentChannel: Channel; currentChannel?: Channel;
errorClass: string | null; errorClass: string | null;
serverError: (ServerError & {submittedMessage?: string}) | null; serverError: (ServerError & {submittedMessage?: string}) | null;
postError?: React.ReactNode; postError?: React.ReactNode;
@@ -271,7 +271,7 @@ class AdvancedCreatePost extends React.PureComponent<Props, State> {
currentChannel: props.currentChannel, currentChannel: props.currentChannel,
}; };
if ( if (
props.currentChannel.id !== state.currentChannel.id || props.currentChannel?.id !== state.currentChannel?.id ||
(props.isRemoteDraft && props.draft.message !== state.message) (props.isRemoteDraft && props.draft.message !== state.message)
) { ) {
updatedState = { updatedState = {
@@ -321,14 +321,14 @@ class AdvancedCreatePost extends React.PureComponent<Props, State> {
componentDidUpdate(prevProps: Props, prevState: State) { componentDidUpdate(prevProps: Props, prevState: State) {
const {currentChannel, actions} = this.props; const {currentChannel, actions} = this.props;
if (prevProps.currentChannel.id !== currentChannel.id) { if (prevProps.currentChannel?.id !== currentChannel?.id) {
this.lastChannelSwitchAt = Date.now(); this.lastChannelSwitchAt = Date.now();
this.focusTextbox(); this.focusTextbox();
this.saveDraftWithShow(prevProps); this.saveDraftWithShow(prevProps);
this.getChannelMemberCountsByGroup(); this.getChannelMemberCountsByGroup();
} }
if (currentChannel.id !== prevProps.currentChannel.id) { if (currentChannel?.id !== prevProps.currentChannel?.id) {
actions.setShowPreview(false); actions.setShowPreview(false);
} }
@@ -356,7 +356,7 @@ class AdvancedCreatePost extends React.PureComponent<Props, State> {
getChannelMemberCountsByGroup = () => { getChannelMemberCountsByGroup = () => {
const {useLDAPGroupMentions, useCustomGroupMentions, currentChannel, actions, draft} = this.props; const {useLDAPGroupMentions, useCustomGroupMentions, currentChannel, actions, draft} = this.props;
if ((useLDAPGroupMentions || useCustomGroupMentions) && currentChannel.id) { if ((useLDAPGroupMentions || useCustomGroupMentions) && currentChannel?.id) {
const mentions = mentionsMinusSpecialMentionsInText(draft.message); const mentions = mentionsMinusSpecialMentionsInText(draft.message);
if (mentions.length === 1) { if (mentions.length === 1) {
@@ -458,7 +458,11 @@ class AdvancedCreatePost extends React.PureComponent<Props, State> {
}; };
doSubmit = async (e?: React.FormEvent) => { doSubmit = async (e?: React.FormEvent) => {
const channelId = this.props.currentChannel.id; const channelId = this.props.currentChannel?.id;
if (!channelId) {
return;
}
if (e) { if (e) {
e.preventDefault(); e.preventDefault();
} }
@@ -628,6 +632,10 @@ class AdvancedCreatePost extends React.PureComponent<Props, State> {
useCustomGroupMentions, useCustomGroupMentions,
} = this.props; } = this.props;
if (!updateChannel) {
return;
}
this.setShowPreview(false); this.setShowPreview(false);
this.isDraftSubmitting = true; this.isDraftSubmitting = true;
@@ -671,7 +679,7 @@ class AdvancedCreatePost extends React.PureComponent<Props, State> {
} }
} }
const {data} = await this.props.actions.getChannelTimezones(this.props.currentChannel.id); const {data} = await this.props.actions.getChannelTimezones(updateChannel.id);
channelTimezoneCount = data ? data.length : 0; channelTimezoneCount = data ? data.length : 0;
} }
@@ -748,6 +756,10 @@ class AdvancedCreatePost extends React.PureComponent<Props, State> {
useCustomGroupMentions, useCustomGroupMentions,
} = this.props; } = this.props;
if (!currentChannel) {
return {data: false};
}
let post = originalPost; let post = originalPost;
post.channel_id = currentChannel.id; post.channel_id = currentChannel.id;
@@ -864,8 +876,10 @@ class AdvancedCreatePost extends React.PureComponent<Props, State> {
}; };
emitTypingEvent = () => { emitTypingEvent = () => {
const channelId = this.props.currentChannel.id; const channelId = this.props.currentChannel?.id;
GlobalActions.emitLocalUserTypingEvent(channelId, ''); if (channelId) {
GlobalActions.emitLocalUserTypingEvent(channelId, '');
}
}; };
handleChange = (e: React.ChangeEvent<TextboxElement>) => { handleChange = (e: React.ChangeEvent<TextboxElement>) => {
@@ -889,11 +903,15 @@ class AdvancedCreatePost extends React.PureComponent<Props, State> {
this.handleDraftChange(draft); this.handleDraftChange(draft);
}; };
handleDraftChange = (draft: PostDraft, channelId = this.props.currentChannel.id, instant = false) => { handleDraftChange = (draft: PostDraft, channelId = this.props.currentChannel?.id, instant = false) => {
if (this.saveDraftFrame) { if (this.saveDraftFrame) {
clearTimeout(this.saveDraftFrame); clearTimeout(this.saveDraftFrame);
} }
if (!channelId) {
return;
}
if (instant) { if (instant) {
this.props.actions.setDraft(StoragePrefixes.DRAFT + channelId, draft, channelId); this.props.actions.setDraft(StoragePrefixes.DRAFT + channelId, draft, channelId);
} else { } else {
@@ -905,7 +923,10 @@ class AdvancedCreatePost extends React.PureComponent<Props, State> {
this.draftsForChannel[channelId] = draft; this.draftsForChannel[channelId] = draft;
}; };
removeDraft = (channelId = this.props.currentChannel.id) => { removeDraft = (channelId = this.props.currentChannel?.id) => {
if (!channelId) {
return;
}
this.props.actions.setDraft(StoragePrefixes.DRAFT + channelId, null, channelId); this.props.actions.setDraft(StoragePrefixes.DRAFT + channelId, null, channelId);
this.draftsForChannel[channelId] = null; this.draftsForChannel[channelId] = null;
}; };
@@ -986,6 +1007,9 @@ class AdvancedCreatePost extends React.PureComponent<Props, State> {
}; };
removePreview = (id: string) => { removePreview = (id: string) => {
if (!this.props.currentChannel) {
return;
}
let modifiedDraft = {} as PostDraft; let modifiedDraft = {} as PostDraft;
const draft = {...this.props.draft}; const draft = {...this.props.draft};
@@ -1264,6 +1288,9 @@ class AdvancedCreatePost extends React.PureComponent<Props, State> {
}; };
handlePostPriorityApply = (settings?: PostPriorityMetadata) => { handlePostPriorityApply = (settings?: PostPriorityMetadata) => {
if (!this.props.currentChannel) {
return;
}
const updatedDraft = { const updatedDraft = {
...this.props.draft, ...this.props.draft,
}; };
@@ -1309,7 +1336,7 @@ class AdvancedCreatePost extends React.PureComponent<Props, State> {
return true; return true;
} }
if (currentChannel.type === Constants.DM_CHANNEL) { if (currentChannel?.type === Constants.DM_CHANNEL) {
return true; return true;
} }

Просмотреть файл

@@ -65,12 +65,12 @@ function makeMapStateToProps() {
return (state: GlobalState) => { return (state: GlobalState) => {
const config = getConfig(state); const config = getConfig(state);
const license = getLicense(state); const license = getLicense(state);
const currentChannel = getCurrentChannel(state) || {}; const currentChannel = getCurrentChannel(state);
const currentChannelTeammateUsername = getUser(state, currentChannel.teammate_id || '')?.username; const currentChannelTeammateUsername = currentChannel ? getUser(state, currentChannel.teammate_id || '')?.username : undefined;
const draft = getChannelDraft(state, currentChannel.id); const draft = getChannelDraft(state, currentChannel?.id || '');
const isRemoteDraft = state.views.drafts.remotes[`${StoragePrefixes.DRAFT}${currentChannel.id}`] || false; const isRemoteDraft = (currentChannel && state.views.drafts.remotes[`${StoragePrefixes.DRAFT}${currentChannel.id}`]) || false;
const latestReplyablePostId = getLatestReplyablePostId(state); const latestReplyablePostId = getLatestReplyablePostId(state);
const currentChannelMembersCount = getCurrentChannelStats(state) ? getCurrentChannelStats(state).member_count : 1; const currentChannelMembersCount = getCurrentChannelStats(state)?.member_count ?? 1;
const enableEmojiPicker = config.EnableEmojiPicker === 'true'; const enableEmojiPicker = config.EnableEmojiPicker === 'true';
const enableGifPicker = config.EnableGifPicker === 'true'; const enableGifPicker = config.EnableGifPicker === 'true';
const enableConfirmNotificationsToChannel = config.EnableConfirmNotificationsToChannel === 'true'; const enableConfirmNotificationsToChannel = config.EnableConfirmNotificationsToChannel === 'true';
@@ -82,9 +82,11 @@ function makeMapStateToProps() {
const isLDAPEnabled = license?.IsLicensed === 'true' && license?.LDAPGroups === 'true'; const isLDAPEnabled = license?.IsLicensed === 'true' && license?.LDAPGroups === 'true';
const useCustomGroupMentions = isCustomGroupsEnabled(state) && haveICurrentChannelPermission(state, Permissions.USE_GROUP_MENTIONS); const useCustomGroupMentions = isCustomGroupsEnabled(state) && haveICurrentChannelPermission(state, Permissions.USE_GROUP_MENTIONS);
const useLDAPGroupMentions = isLDAPEnabled && haveICurrentChannelPermission(state, Permissions.USE_GROUP_MENTIONS); const useLDAPGroupMentions = isLDAPEnabled && haveICurrentChannelPermission(state, Permissions.USE_GROUP_MENTIONS);
const channelMemberCountsByGroup = selectChannelMemberCountsByGroup(state, currentChannel.id); const channelMemberCountsByGroup = currentChannel ? selectChannelMemberCountsByGroup(state, currentChannel.id) : {};
const currentTeamId = getCurrentTeamId(state); const currentTeamId = getCurrentTeamId(state);
const groupsWithAllowReference = useLDAPGroupMentions || useCustomGroupMentions ? getAssociatedGroupsForReferenceByMention(state, currentTeamId, currentChannel.id) : null; const groupsWithAllowReference = (currentChannel && (useLDAPGroupMentions || useCustomGroupMentions)) ?
getAssociatedGroupsForReferenceByMention(state, currentTeamId, currentChannel.id) :
null;
const enableTutorial = config.EnableTutorial === 'true'; const enableTutorial = config.EnableTutorial === 'true';
const tutorialStep = getInt(state, TutorialTourName.ONBOARDING_TUTORIAL_STEP, currentUserId, 0); const tutorialStep = getInt(state, TutorialTourName.ONBOARDING_TUTORIAL_STEP, currentUserId, 0);

Просмотреть файл

@@ -113,6 +113,10 @@ function NotificationFromMembersModal(props: Props) {
return null; return null;
} }
if (!channel) {
return null;
}
const modalTitle = formatMessage({id: 'postypes.custom_open_pricing_modal_post_renderer.membersThatRequested', defaultMessage: 'Members that requested '}); const modalTitle = formatMessage({id: 'postypes.custom_open_pricing_modal_post_renderer.membersThatRequested', defaultMessage: 'Members that requested '});
const modalHeaderText = ( const modalHeaderText = (

Просмотреть файл

@@ -48,7 +48,7 @@ const popoverMarkdownOptions = {singleline: false, mentionHighlight: false, atMe
export type Props = { export type Props = {
teamId: string; teamId: string;
currentUser: UserProfile; currentUser: UserProfile;
channel: Channel; channel?: Channel;
memberCount?: number; memberCount?: number;
channelMember?: ChannelMembership; channelMember?: ChannelMembership;
dmUser?: UserProfile; dmUser?: UserProfile;
@@ -169,7 +169,7 @@ class ChannelHeader extends React.PureComponent<Props, State> {
showChannelFiles = () => { showChannelFiles = () => {
if (this.props.rhsState === RHSStates.CHANNEL_FILES) { if (this.props.rhsState === RHSStates.CHANNEL_FILES) {
this.props.actions.closeRightHandSide(); this.props.actions.closeRightHandSide();
} else { } else if (this.props.channel) {
this.props.actions.showChannelFiles(this.props.channel.id); this.props.actions.showChannelFiles(this.props.channel.id);
} }
}; };
@@ -180,6 +180,10 @@ class ChannelHeader extends React.PureComponent<Props, State> {
} }
const {actions, channel} = this.props; const {actions, channel} = this.props;
if (!channel) {
return;
}
const modalData = { const modalData = {
modalId: ModalIdentifiers.EDIT_CHANNEL_HEADER, modalId: ModalIdentifiers.EDIT_CHANNEL_HEADER,
dialogType: EditChannelHeaderModal, dialogType: EditChannelHeaderModal,
@@ -212,7 +216,7 @@ class ChannelHeader extends React.PureComponent<Props, State> {
toggleChannelMembersRHS = () => { toggleChannelMembersRHS = () => {
if (this.props.rhsState === RHSStates.CHANNEL_MEMBERS) { if (this.props.rhsState === RHSStates.CHANNEL_MEMBERS) {
this.props.actions.closeRightHandSide(); this.props.actions.closeRightHandSide();
} else { } else if (this.props.channel) {
this.props.actions.showChannelMembers(this.props.channel.id); this.props.actions.showChannelMembers(this.props.channel.id);
} }
}; };
@@ -259,6 +263,10 @@ class ChannelHeader extends React.PureComponent<Props, State> {
hasGuests, hasGuests,
hideGuestTags, hideGuestTags,
} = this.props; } = this.props;
if (!channel) {
return null;
}
const {formatMessage} = this.props.intl; const {formatMessage} = this.props.intl;
const ariaLabelChannelHeader = this.props.intl.formatMessage({id: 'accessibility.sections.channelHeader', defaultMessage: 'channel header region'}); const ariaLabelChannelHeader = this.props.intl.formatMessage({id: 'accessibility.sections.channelHeader', defaultMessage: 'channel header region'});

Просмотреть файл

@@ -19,17 +19,20 @@ const ChannelHeaderTitleFavorite = () => {
const dispatch = useDispatch(); const dispatch = useDispatch();
const isFavorite = useSelector(isCurrentChannelFavorite); const isFavorite = useSelector(isCurrentChannelFavorite);
const channel = useSelector(getCurrentChannel); const channel = useSelector(getCurrentChannel);
const channelIsArchived = channel.delete_at !== 0; const channelIsArchived = (channel?.delete_at ?? 0) > 0;
const toggleFavoriteRef = useRef<HTMLButtonElement>(null); const toggleFavoriteRef = useRef<HTMLButtonElement>(null);
const toggleFavoriteCallback = useCallback((e: React.MouseEvent<HTMLButtonElement>) => { const toggleFavoriteCallback = useCallback((e: React.MouseEvent<HTMLButtonElement>) => {
e.stopPropagation(); e.stopPropagation();
if (!channel) {
return;
}
if (isFavorite) { if (isFavorite) {
dispatch(unfavoriteChannel(channel.id)); dispatch(unfavoriteChannel(channel.id));
} else { } else {
dispatch(favoriteChannel(channel.id)); dispatch(favoriteChannel(channel.id));
} }
}, [isFavorite, channel.id]); }, [isFavorite, channel?.id]);
const removeTooltipLink = useCallback(() => { const removeTooltipLink = useCallback(() => {
// Bootstrap adds the attr dynamically, removing it to prevent a11y readout // Bootstrap adds the attr dynamically, removing it to prevent a11y readout

Просмотреть файл

@@ -49,16 +49,13 @@ import type {GlobalState} from 'types/store';
import ChannelHeader from './channel_header'; import ChannelHeader from './channel_header';
const EMPTY_CHANNEL = {};
const EMPTY_CHANNEL_STATS = {member_count: 0, guest_count: 0, pinnedpost_count: 0, files_count: 0};
function makeMapStateToProps() { function makeMapStateToProps() {
const doGetProfilesInChannel = makeGetProfilesInChannel(); const doGetProfilesInChannel = makeGetProfilesInChannel();
const getCustomStatus = makeGetCustomStatus(); const getCustomStatus = makeGetCustomStatus();
let timestampUnits: string[] = []; let timestampUnits: string[] = [];
return function mapStateToProps(state: GlobalState) { return function mapStateToProps(state: GlobalState) {
const channel = getCurrentChannel(state) || EMPTY_CHANNEL; const channel = getCurrentChannel(state);
const user = getCurrentUser(state); const user = getCurrentUser(state);
const teams = getMyTeams(state); const teams = getMyTeams(state);
const hasMoreThanOneTeam = teams.length > 1; const hasMoreThanOneTeam = teams.length > 1;
@@ -77,7 +74,7 @@ function makeMapStateToProps() {
} else if (channel && channel.type === General.GM_CHANNEL) { } else if (channel && channel.type === General.GM_CHANNEL) {
gmMembers = doGetProfilesInChannel(state, channel.id); gmMembers = doGetProfilesInChannel(state, channel.id);
} }
const stats = getCurrentChannelStats(state) || EMPTY_CHANNEL_STATS; const stats = getCurrentChannelStats(state);
let isLastActiveEnabled = false; let isLastActiveEnabled = false;
if (dmUser) { if (dmUser) {
@@ -89,7 +86,7 @@ function makeMapStateToProps() {
teamId: getCurrentTeamId(state), teamId: getCurrentTeamId(state),
channel, channel,
channelMember: getMyCurrentChannelMembership(state), channelMember: getMyCurrentChannelMembership(state),
memberCount: stats.member_count, memberCount: stats?.member_count || 0,
currentUser: user, currentUser: user,
dmUser, dmUser,
gmMembers, gmMembers,
@@ -98,8 +95,8 @@ function makeMapStateToProps() {
isReadOnly: false, isReadOnly: false,
isMuted: isCurrentChannelMuted(state), isMuted: isCurrentChannelMuted(state),
isQuickSwitcherOpen: isModalOpen(state, ModalIdentifiers.QUICK_SWITCH), isQuickSwitcherOpen: isModalOpen(state, ModalIdentifiers.QUICK_SWITCH),
hasGuests: stats.guest_count > 0, hasGuests: stats ? stats.guest_count > 0 : false,
pinnedPostsCount: stats.pinnedpost_count, pinnedPostsCount: stats?.pinnedpost_count || 0,
hasMoreThanOneTeam, hasMoreThanOneTeam,
currentRelativeTeamUrl: getCurrentRelativeTeamUrl(state), currentRelativeTeamUrl: getCurrentRelativeTeamUrl(state),
announcementBarCount: getAnnouncementBarCount(state), announcementBarCount: getAnnouncementBarCount(state),

Просмотреть файл

@@ -42,7 +42,7 @@ import MenuItemViewPinnedPosts from './menu_items/view_pinned_posts';
export type Props = { export type Props = {
user: UserProfile; user: UserProfile;
channel: Channel; channel?: Channel;
isDefault: boolean; isDefault: boolean;
isFavorite: boolean; isFavorite: boolean;
isReadonly: boolean; isReadonly: boolean;
@@ -69,6 +69,10 @@ export default class ChannelHeaderDropdown extends React.PureComponent<Props> {
isLicensedForLDAPGroups, isLicensedForLDAPGroups,
} = this.props; } = this.props;
if (!channel) {
return null;
}
const isPrivate = channel.type === Constants.PRIVATE_CHANNEL; const isPrivate = channel.type === Constants.PRIVATE_CHANNEL;
const isGroupConstrained = channel.group_constrained === true; const isGroupConstrained = channel.group_constrained === true;
const channelMembersPermission = isPrivate ? Permissions.MANAGE_PRIVATE_CHANNEL_MEMBERS : Permissions.MANAGE_PUBLIC_CHANNEL_MEMBERS; const channelMembersPermission = isPrivate ? Permissions.MANAGE_PRIVATE_CHANNEL_MEMBERS : Permissions.MANAGE_PUBLIC_CHANNEL_MEMBERS;
@@ -92,7 +96,7 @@ export default class ChannelHeaderDropdown extends React.PureComponent<Props> {
key={item.id + '_pluginmenuitem'} key={item.id + '_pluginmenuitem'}
onClick={() => { onClick={() => {
if (item.action) { if (item.action) {
item.action(this.props.channel.id); item.action(channel.id);
} }
}} }}
text={item.text} text={item.text}

Просмотреть файл

@@ -37,7 +37,7 @@ const getTeammateId = createSelector(
getCurrentChannel, getCurrentChannel,
getCurrentUserId, getCurrentUserId,
(channel, currentUserId) => { (channel, currentUserId) => {
if (channel.type !== Constants.DM_CHANNEL) { if (channel?.type !== Constants.DM_CHANNEL) {
return null; return null;
} }

Просмотреть файл

@@ -18,7 +18,7 @@ import MobileChannelHeaderDropdownAnimation from './mobile_channel_header_dropdo
type Props = { type Props = {
user: UserProfile; user: UserProfile;
channel: Channel; channel?: Channel;
teammateId: string | null; teammateId: string | null;
teammateIsBot?: boolean; teammateIsBot?: boolean;
teammateStatus?: string; teammateStatus?: string;
@@ -36,6 +36,10 @@ const MobileChannelHeaderDropdown = ({
const intl = useIntl(); const intl = useIntl();
const getChannelTitle = () => { const getChannelTitle = () => {
if (!channel) {
return '';
}
if (channel.type === Constants.DM_CHANNEL) { if (channel.type === Constants.DM_CHANNEL) {
if (user.id === teammateId) { if (user.id === teammateId) {
return ( return (

Просмотреть файл

@@ -46,9 +46,9 @@ function mapStateToProps(state: GlobalState) {
const isInvitingPeople = isModalOpen(state, ModalIdentifiers.CHANNEL_INVITE) || isModalOpen(state, ModalIdentifiers.CREATE_DM_CHANNEL); const isInvitingPeople = isModalOpen(state, ModalIdentifiers.CHANNEL_INVITE) || isModalOpen(state, ModalIdentifiers.CREATE_DM_CHANNEL);
const isMobile = getIsMobileView(state); const isMobile = getIsMobileView(state);
const isPrivate = channel.type === Constants.PRIVATE_CHANNEL; const isPrivate = channel?.type === Constants.PRIVATE_CHANNEL;
const canManageMembers = haveIChannelPermission(state, currentTeam?.id, channel.id, isPrivate ? Permissions.MANAGE_PRIVATE_CHANNEL_MEMBERS : Permissions.MANAGE_PUBLIC_CHANNEL_MEMBERS); const canManageMembers = haveIChannelPermission(state, currentTeam?.id, channel?.id, isPrivate ? Permissions.MANAGE_PRIVATE_CHANNEL_MEMBERS : Permissions.MANAGE_PUBLIC_CHANNEL_MEMBERS);
const canManageProperties = haveIChannelPermission(state, currentTeam?.id, channel.id, isPrivate ? Permissions.MANAGE_PRIVATE_CHANNEL_PROPERTIES : Permissions.MANAGE_PUBLIC_CHANNEL_PROPERTIES); const canManageProperties = haveIChannelPermission(state, currentTeam?.id, channel?.id, isPrivate ? Permissions.MANAGE_PRIVATE_CHANNEL_PROPERTIES : Permissions.MANAGE_PUBLIC_CHANNEL_PROPERTIES);
const channelMembers = getProfilesInCurrentChannel(state); const channelMembers = getProfilesInCurrentChannel(state);
@@ -67,7 +67,7 @@ function mapStateToProps(state: GlobalState) {
channelMembers, channelMembers,
} as Props; } as Props;
if (channel.type === Constants.DM_CHANNEL) { if (channel?.type === Constants.DM_CHANNEL) {
const user = getUser(state, getUserIdFromChannelId(channel.name, currentUser.id)); const user = getUser(state, getUserIdFromChannelId(channel.name, currentUser.id));
props.dmUser = { props.dmUser = {
user, user,

Просмотреть файл

@@ -26,7 +26,7 @@ export type Props = {
/* /*
* Object containing information on the current selected channel, used to define BackButton's url * Object containing information on the current selected channel, used to define BackButton's url
*/ */
currentChannel: Channel; currentChannel?: Channel;
/* /*
* String containing the custom branding's text * String containing the custom branding's text

Просмотреть файл

@@ -67,7 +67,7 @@ function makeMapStateToProps() {
const userId = getCurrentUserId(state); const userId = getCurrentUserId(state);
const channel = getChannel(state, post.channel_id); const channel = getChannel(state, post.channel_id);
const currentTeam = getCurrentTeam(state); const currentTeam = getCurrentTeam(state);
const team = getTeam(state, channel.team_id); const team = channel ? getTeam(state, channel.team_id) : undefined;
const teamUrl = `${getSiteURL()}/${team?.name || currentTeam?.name}`; const teamUrl = `${getSiteURL()}/${team?.name || currentTeam?.name}`;
const isMilitaryTime = getBool(state, Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.USE_MILITARY_TIME, false); const isMilitaryTime = getBool(state, Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.USE_MILITARY_TIME, false);
@@ -123,7 +123,7 @@ function makeMapStateToProps() {
isMobileView: getIsMobileView(state), isMobileView: getIsMobileView(state),
timezone: getCurrentTimezone(state), timezone: getCurrentTimezone(state),
isMilitaryTime, isMilitaryTime,
canMove: canWrangler(state, channel.type, threadReplyCount), canMove: channel ? canWrangler(state, channel.type, threadReplyCount) : false,
}; };
}; };
} }

Просмотреть файл

@@ -27,7 +27,7 @@ import PanelBody from '../panel/panel_body';
import Header from '../panel/panel_header'; import Header from '../panel/panel_header';
type Props = { type Props = {
channel: Channel; channel?: Channel;
channelUrl: string; channelUrl: string;
displayName: string; displayName: string;
draftId: string; draftId: string;
@@ -51,6 +51,7 @@ function ChannelDraft({
user, user,
value, value,
isRemote, isRemote,
id: channelId,
}: Props) { }: Props) {
const dispatch = useDispatch(); const dispatch = useDispatch();
const history = useHistory(); const history = useHistory();
@@ -60,16 +61,20 @@ function ChannelDraft({
}, [history, channelUrl]); }, [history, channelUrl]);
const handleOnDelete = useCallback((id: string) => { const handleOnDelete = useCallback((id: string) => {
dispatch(removeDraft(id, channel.id)); dispatch(removeDraft(id, channelId));
}, [dispatch, channel.id]); }, [dispatch, channelId]);
const doSubmit = useCallback((id: string, post: Post) => { const doSubmit = useCallback((id: string, post: Post) => {
dispatch(createPost(post, value.fileInfos)); dispatch(createPost(post, value.fileInfos));
dispatch(removeDraft(id, channel.id)); dispatch(removeDraft(id, channelId));
history.push(channelUrl); history.push(channelUrl);
}, [dispatch, history, value.fileInfos, channel.id, channelUrl]); }, [dispatch, history, value.fileInfos, channelId, channelUrl]);
const showPersistNotificationModal = useCallback((id: string, post: Post) => { const showPersistNotificationModal = useCallback((id: string, post: Post) => {
if (!channel) {
return;
}
dispatch(openModal({ dispatch(openModal({
modalId: ModalIdentifiers.PERSIST_NOTIFICATION_CONFIRM_MODAL, modalId: ModalIdentifiers.PERSIST_NOTIFICATION_CONFIRM_MODAL,
dialogType: PersistNotificationConfirmModal, dialogType: PersistNotificationConfirmModal,
@@ -80,7 +85,7 @@ function ChannelDraft({
onConfirm: () => doSubmit(id, post), onConfirm: () => doSubmit(id, post),
}, },
})); }));
}, [channel.type, dispatch, doSubmit]); }, [channel, dispatch, doSubmit]);
const handleOnSend = useCallback(async (id: string) => { const handleOnSend = useCallback(async (id: string) => {
const post = {} as Post; const post = {} as Post;
@@ -135,7 +140,7 @@ function ChannelDraft({
remote={isRemote || false} remote={isRemote || false}
/> />
<PanelBody <PanelBody
channelId={channel.id} channelId={channelId}
displayName={displayName} displayName={displayName}
fileInfos={value.fileInfos} fileInfos={value.fileInfos}
message={value.message} message={value.message}

Просмотреть файл

@@ -24,7 +24,7 @@ function makeMapStateToProps() {
const channel = getChannel(state, ownProps); const channel = getChannel(state, ownProps);
const teamId = getCurrentTeamId(state); const teamId = getCurrentTeamId(state);
const channelUrl = getChannelURL(state, channel, teamId); const channelUrl = channel ? getChannelURL(state, channel, teamId) : '';
return { return {
channel, channel,

Просмотреть файл

@@ -24,7 +24,7 @@ import PanelBody from '../panel/panel_body';
import Header from '../panel/panel_header'; import Header from '../panel/panel_header';
type Props = { type Props = {
channel: Channel; channel?: Channel;
displayName: string; displayName: string;
draftId: string; draftId: string;
rootId: UserThread['id'] | UserThreadSynthetic['id']; rootId: UserThread['id'] | UserThreadSynthetic['id'];
@@ -57,29 +57,29 @@ function ThreadDraft({
}, [thread?.id]); }, [thread?.id]);
const onSubmit = useMemo(() => { const onSubmit = useMemo(() => {
if (thread) { if (thread?.id) {
return makeOnSubmit(channel.id, thread.id, ''); return makeOnSubmit(value.channelId, thread.id, '');
} }
return () => Promise.resolve({data: true}); return () => Promise.resolve({data: true});
}, [channel.id, thread?.id]); }, [value.channelId, thread?.id]);
const handleOnDelete = useCallback((id: string) => { const handleOnDelete = useCallback((id: string) => {
dispatch(removeDraft(id, channel.id, rootId)); dispatch(removeDraft(id, value.channelId, rootId));
}, [channel.id, rootId]); }, [value.channelId, rootId, dispatch]);
const handleOnEdit = useCallback(() => { const handleOnEdit = useCallback(() => {
dispatch(selectPost({id: rootId, channel_id: channel.id} as Post)); dispatch(selectPost({id: rootId, channel_id: value.channelId} as Post));
}, [channel]); }, [value.channelId, dispatch, rootId]);
const handleOnSend = useCallback(async (id: string) => { const handleOnSend = useCallback(async (id: string) => {
await dispatch(onSubmit(value)); await dispatch(onSubmit(value));
handleOnDelete(id); handleOnDelete(id);
handleOnEdit(); handleOnEdit();
}, [value, onSubmit]); }, [value, onSubmit, dispatch, handleOnDelete, handleOnEdit]);
if (!thread) { if (!thread || !channel) {
return null; return null;
} }

Просмотреть файл

@@ -55,7 +55,7 @@ function mapStateToProps(state: GlobalState) {
teamId, teamId,
channelId, channelId,
maxPostSize: parseInt(config.MaxPostSize || '0', 10) || Constants.DEFAULT_CHARACTER_LIMIT, maxPostSize: parseInt(config.MaxPostSize || '0', 10) || Constants.DEFAULT_CHARACTER_LIMIT,
readOnlyChannel: !isCurrentUserSystemAdmin(state) && channel.name === Constants.DEFAULT_CHANNEL, readOnlyChannel: !isCurrentUserSystemAdmin(state) && channel?.name === Constants.DEFAULT_CHANNEL,
useChannelMentions, useChannelMentions,
isRHSOpened: getIsRhsOpen(state), isRHSOpened: getIsRhsOpen(state),
isEditHistoryShowing: getRhsState(state) === RHSStates.EDIT_HISTORY, isEditHistoryShowing: getRhsState(state) === RHSStates.EDIT_HISTORY,

Просмотреть файл

@@ -29,7 +29,7 @@ function mapStateToProps(state: GlobalState, ownProps: OwnProps) {
return { return {
channelDisplayName: '', channelDisplayName: '',
channelType: channel.type, channelType: channel?.type,
}; };
} }

Просмотреть файл

@@ -1,58 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {connect} from 'react-redux';
import type {ConnectedProps} from 'react-redux';
import {bindActionCreators} from 'redux';
import type {Dispatch} from 'redux';
import type {Channel} from '@mattermost/types/channels';
import type {Post} from '@mattermost/types/posts';
import type {ActionResult} from 'mattermost-redux/types/actions';
import {openDirectChannelToUserId} from 'actions/channel_actions';
import {joinChannelById, switchToChannel} from 'actions/views/channel';
import {forwardPost} from 'actions/views/posts';
import ForwardPostModal from './forward_post_modal';
export type PropsFromRedux = ConnectedProps<typeof connector>;
export type ActionProps = {
// join the selected channel when necessary
joinChannelById: (channelId: string) => Promise<ActionResult>;
// switch to the selected channel
switchToChannel: (channel: Channel) => Promise<ActionResult>;
// switch to the selected channel
openDirectChannelToUserId: (userId: string) => Promise<ActionResult<Channel>>;
// action called to forward the post with an optional comment
forwardPost: (post: Post, channelId: Channel, message?: string) => Promise<ActionResult>;
}
export type OwnProps = {
// The function called immediately after the modal is hidden
onExited?: () => void;
// the post that is going to be forwarded
post: Post;
};
function mapDispatchToProps(dispatch: Dispatch) {
return {
actions: bindActionCreators({
joinChannelById,
switchToChannel,
forwardPost,
openDirectChannelToUserId,
}, dispatch),
};
}
const connector = connect(null, mapDispatchToProps);
export default connector(ForwardPostModal);

Просмотреть файл

@@ -4,11 +4,11 @@
import classNames from 'classnames'; import classNames from 'classnames';
import React, {useCallback, useRef, useState} from 'react'; import React, {useCallback, useRef, useState} from 'react';
import {FormattedList, FormattedMessage, useIntl} from 'react-intl'; import {FormattedList, FormattedMessage, useIntl} from 'react-intl';
import {useSelector} from 'react-redux'; import {useDispatch, useSelector} from 'react-redux';
import type {ValueType} from 'react-select'; import type {ValueType} from 'react-select';
import {GenericModal} from '@mattermost/components'; import {GenericModal} from '@mattermost/components';
import type {PostPreviewMetadata} from '@mattermost/types/posts'; import type {Post, PostPreviewMetadata} from '@mattermost/types/posts';
import {General, Permissions} from 'mattermost-redux/constants'; import {General, Permissions} from 'mattermost-redux/constants';
import {makeGetChannel} from 'mattermost-redux/selectors/entities/channels'; import {makeGetChannel} from 'mattermost-redux/selectors/entities/channels';
@@ -16,6 +16,9 @@ import {haveIChannelPermission} from 'mattermost-redux/selectors/entities/roles'
import {getCurrentTeam} from 'mattermost-redux/selectors/entities/teams'; import {getCurrentTeam} from 'mattermost-redux/selectors/entities/teams';
import type {ActionResult} from 'mattermost-redux/types/actions'; import type {ActionResult} from 'mattermost-redux/types/actions';
import {openDirectChannelToUserId} from 'actions/channel_actions';
import {joinChannelById, switchToChannel} from 'actions/views/channel';
import {forwardPost} from 'actions/views/posts';
import {getPermalinkURL} from 'selectors/urls'; import {getPermalinkURL} from 'selectors/urls';
import NotificationBox from 'components/notification_box'; import NotificationBox from 'components/notification_box';
@@ -30,16 +33,22 @@ import ForwardPostChannelSelect, {makeSelectedChannelOption} from './forward_pos
import type {ChannelOption} from './forward_post_channel_select'; import type {ChannelOption} from './forward_post_channel_select';
import ForwardPostCommentInput from './forward_post_comment_input'; import ForwardPostCommentInput from './forward_post_comment_input';
import type {ActionProps, OwnProps, PropsFromRedux} from './index';
import './forward_post_modal.scss'; import './forward_post_modal.scss';
export type Props = PropsFromRedux & OwnProps & { actions: ActionProps }; type Props = {
// The function called immediately after the modal is hidden
onExited?: () => void;
// the post that is going to be forwarded
post: Post;
};
const noop = () => {}; const noop = () => {};
const ForwardPostModal = ({onExited, post, actions}: Props) => { const ForwardPostModal = ({onExited, post}: Props) => {
const {formatMessage} = useIntl(); const {formatMessage} = useIntl();
const dispatch = useDispatch();
const getChannel = makeGetChannel(); const getChannel = makeGetChannel();
@@ -49,7 +58,7 @@ const ForwardPostModal = ({onExited, post, actions}: Props) => {
const relativePermaLink = useSelector((state: GlobalState) => (currentTeam ? getPermalinkURL(state, currentTeam.id, post.id) : '')); const relativePermaLink = useSelector((state: GlobalState) => (currentTeam ? getPermalinkURL(state, currentTeam.id, post.id) : ''));
const permaLink = `${getSiteURL()}${relativePermaLink}`; const permaLink = `${getSiteURL()}${relativePermaLink}`;
const isPrivateConversation = channel.type !== Constants.OPEN_CHANNEL; const isPrivateConversation = channel?.type !== Constants.OPEN_CHANNEL;
const [comment, setComment] = useState(''); const [comment, setComment] = useState('');
const [bodyHeight, setBodyHeight] = useState<number>(0); const [bodyHeight, setBodyHeight] = useState<number>(0);
@@ -77,7 +86,7 @@ const ForwardPostModal = ({onExited, post, actions}: Props) => {
const canPostInSelectedChannel = useSelector( const canPostInSelectedChannel = useSelector(
(state: GlobalState) => { (state: GlobalState) => {
const channelId = isPrivateConversation ? channel.id : selectedChannelId; const channelId = isPrivateConversation ? post.channel_id : selectedChannelId;
const isDMChannel = selectedChannel?.details?.type === Constants.DM_CHANNEL; const isDMChannel = selectedChannel?.details?.type === Constants.DM_CHANNEL;
const teamId = isPrivateConversation ? currentTeam?.id : selectedChannel?.details?.team_id; const teamId = isPrivateConversation ? currentTeam?.id : selectedChannel?.details?.team_id;
@@ -123,15 +132,15 @@ const ForwardPostModal = ({onExited, post, actions}: Props) => {
post, post,
post_id: post.id, post_id: post.id,
team_name: currentTeam?.name || '', team_name: currentTeam?.name || '',
channel_display_name: channel.display_name, channel_display_name: channel?.display_name || '',
channel_type: channel.type, channel_type: channel?.type || 'O',
channel_id: channel.id, channel_id: post.channel_id,
}; };
let notification; let notification;
if (isPrivateConversation) { if (isPrivateConversation) {
let notificationText; let notificationText;
if (channel.type === General.PRIVATE_CHANNEL) { if (channel?.type === General.PRIVATE_CHANNEL) {
const channelName = `~${channel.display_name}`; const channelName = `~${channel.display_name}`;
notificationText = ( notificationText = (
<FormattedMessage <FormattedMessage
@@ -144,7 +153,7 @@ const ForwardPostModal = ({onExited, post, actions}: Props) => {
/> />
); );
} else { } else {
const allParticipants = channel.display_name.split(', '); const allParticipants = channel?.display_name.split(', ') || [];
const participants = allParticipants.map((participant) => <strong key={participant}>{participant}</strong>); const participants = allParticipants.map((participant) => <strong key={participant}>{participant}</strong>);
notificationText = ( notificationText = (
@@ -179,6 +188,10 @@ const ForwardPostModal = ({onExited, post, actions}: Props) => {
return Promise.resolve(); return Promise.resolve();
} }
if (!channel) {
return Promise.resolve();
}
const channelToForward = isPrivateConversation ? makeSelectedChannelOption(channel) : selectedChannel; const channelToForward = isPrivateConversation ? makeSelectedChannelOption(channel) : selectedChannel;
if (!channelToForward) { if (!channelToForward) {
@@ -189,7 +202,7 @@ const ForwardPostModal = ({onExited, post, actions}: Props) => {
return Promise.resolve().then(() => { return Promise.resolve().then(() => {
if (type === Constants.DM_CHANNEL && userId) { if (type === Constants.DM_CHANNEL && userId) {
return actions.openDirectChannelToUserId(userId); return dispatch(openDirectChannelToUserId(userId));
} }
return {data: false} as ActionResult; return {data: false} as ActionResult;
}).then(({data}) => { }).then(({data}) => {
@@ -197,20 +210,20 @@ const ForwardPostModal = ({onExited, post, actions}: Props) => {
channelToForward.details.id = data.id; channelToForward.details.id = data.id;
} }
return actions.forwardPost( return dispatch(forwardPost(
post, post,
channelToForward.details, channelToForward.details,
comment, comment,
); ));
}).then(() => { }).then(() => {
if (type === Constants.MENTION_MORE_CHANNELS && type === Constants.OPEN_CHANNEL) { if (type === Constants.MENTION_MORE_CHANNELS && type === Constants.OPEN_CHANNEL) {
return actions.joinChannelById(channelToForward.details.id); return dispatch(joinChannelById(channelToForward.details.id));
} }
return {data: false}; return {data: false};
}).then(() => { }).then(() => {
// only switch channels when we are not in a private conversation // only switch channels when we are not in a private conversation
if (!isPrivateConversation) { if (!isPrivateConversation) {
return actions.switchToChannel(channelToForward.details); return dispatch(switchToChannel(channelToForward.details));
} }
return {data: false}; return {data: false};
}).then(() => { }).then(() => {
@@ -227,7 +240,7 @@ const ForwardPostModal = ({onExited, post, actions}: Props) => {
defaultMessage: 'Originally posted in ~{channel}', defaultMessage: 'Originally posted in ~{channel}',
}, },
{ {
channel: channel.display_name, channel: channel?.display_name || '',
}); });
return ( return (

Просмотреть файл

@@ -58,7 +58,7 @@ export type Props = {
) => Promise<ActionResult<InviteResults>>; ) => Promise<ActionResult<InviteResults>>;
}; };
currentTeam?: Team; currentTeam?: Team;
currentChannel: Channel; currentChannel?: Channel;
townSquareDisplayName: string; townSquareDisplayName: string;
invitableChannels: Channel[]; invitableChannels: Channel[];
emailInvitationsEnabled: boolean; emailInvitationsEnabled: boolean;

Просмотреть файл

@@ -56,7 +56,7 @@ export type Props = InviteState & {
onChannelsInputChange: (channelsInputValue: string) => void; onChannelsInputChange: (channelsInputValue: string) => void;
onClose: () => void; onClose: () => void;
currentTeam: Team; currentTeam: Team;
currentChannel: Channel; currentChannel?: Channel;
setCustomMessage: (message: string) => void; setCustomMessage: (message: string) => void;
toggleCustomMessage: () => void; toggleCustomMessage: () => void;
channelsLoader: (value: string, callback?: (channels: Channel[]) => void) => Promise<Channel[]>; channelsLoader: (value: string, callback?: (channels: Channel[]) => void) => Promise<Channel[]>;

Просмотреть файл

@@ -48,11 +48,14 @@ const getChannelURLAction = (channelId: string, teamId: string, url: string): Th
const state = getState(); const state = getState();
if (url && isPermalinkURL(url)) { if (url && isPermalinkURL(url)) {
return getHistory().push(url); getHistory().push(url);
return;
} }
const channel = getChannel(state, channelId); const channel = getChannel(state, channelId);
return getHistory().push(getChannelURL(state, channel, teamId)); if (channel) {
getHistory().push(getChannelURL(state, channel, teamId));
}
}; };
function mapDispatchToProps(dispatch: Dispatch) { function mapDispatchToProps(dispatch: Dispatch) {

Просмотреть файл

@@ -41,8 +41,14 @@ const getUsersAndActionsToDisplay = createSelector(
channelMember: ChannelMembership; channelMember: ChannelMembership;
}; };
} = {}; } = {};
const usersToDisplay = []; const usersToDisplay: UserProfile[] = [];
if (!channel) {
return {
usersToDisplay,
actionUserProps,
};
}
for (let i = 0; i < users.length; i++) { for (let i = 0; i < users.length; i++) {
const user = users[i]; const user = users[i];

Просмотреть файл

@@ -104,10 +104,10 @@ const MoveThreadModal = ({onExited, post, actions}: Props) => {
post, post,
post_id: post.id, post_id: post.id,
team_name: currentTeam?.name || '', team_name: currentTeam?.name || '',
channel_display_name: originalChannel.display_name, channel_display_name: originalChannel?.display_name || '',
channel_type: originalChannel.type, channel_type: originalChannel?.type || 'O',
channel_id: originalChannel.id, channel_id: originalChannel?.id || '',
}), [post, currentTeam?.name, originalChannel.display_name, originalChannel.type, originalChannel.id]); }), [post, currentTeam?.name, originalChannel?.display_name, originalChannel?.type, originalChannel?.id]);
const notificationText = formatMessage({ const notificationText = formatMessage({
id: 'move_thread_modal.notification.dm_or_gm', id: 'move_thread_modal.notification.dm_or_gm',
@@ -180,7 +180,7 @@ const MoveThreadModal = ({onExited, post, actions}: Props) => {
defaultMessage: 'Originally posted in ~{channelName}', defaultMessage: 'Originally posted in ~{channelName}',
}, },
{ {
channelName: originalChannel.display_name, channelName: originalChannel?.display_name || '',
}); });
return ( return (

Просмотреть файл

@@ -16,7 +16,8 @@ import PostEditHistory from './post_edit_history';
function mapStateToProps(state: GlobalState) { function mapStateToProps(state: GlobalState) {
const selectedPostId = getSelectedPostId(state) || ''; const selectedPostId = getSelectedPostId(state) || '';
const originalPost = getPost(state, selectedPostId); const originalPost = getPost(state, selectedPostId);
const channelDisplayName = getCurrentChannel(state) ? getCurrentChannel(state).display_name : getChannel(state, originalPost.channel_id).display_name; const channel = getCurrentChannel(state) ?? getChannel(state, originalPost.channel_id);
const channelDisplayName = channel?.display_name || '';
return { return {
channelDisplayName, channelDisplayName,

Просмотреть файл

@@ -65,13 +65,13 @@ export default class PostMarkdown extends React.PureComponent<Props> {
let message = this.props.message; let message = this.props.message;
if (this.props.post) { if (this.props.post) {
const renderedSystemMessage = renderSystemMessage(this.props.post, const renderedSystemMessage = this.props.channel ? renderSystemMessage(this.props.post,
this.props.currentTeam?.name ?? '', this.props.currentTeam?.name ?? '',
this.props.channel, this.props.channel,
this.props.hideGuestTags, this.props.hideGuestTags,
this.props.isUserCanManageMembers, this.props.isUserCanManageMembers,
this.props.isMilitaryTime, this.props.isMilitaryTime,
this.props.timezone); this.props.timezone) : null;
if (renderedSystemMessage) { if (renderedSystemMessage) {
return <div>{renderedSystemMessage}</div>; return <div>{renderedSystemMessage}</div>;
} }

Просмотреть файл

@@ -34,7 +34,7 @@ import PluggableIntroButtons from './pluggable_intro_buttons';
type Props = { type Props = {
currentUserId: string; currentUserId: string;
channel: Channel; channel?: Channel;
fullWidth: boolean; fullWidth: boolean;
locale: string; locale: string;
channelProfiles: UserProfileType[]; channelProfiles: UserProfileType[];
@@ -59,6 +59,10 @@ type Props = {
export default class ChannelIntroMessage extends React.PureComponent<Props> { export default class ChannelIntroMessage extends React.PureComponent<Props> {
toggleFavorite = () => { toggleFavorite = () => {
if (!this.props.channel) {
return;
}
if (this.props.isFavorite) { if (this.props.isFavorite) {
this.props.actions.unfavoriteChannel(this.props.channel.id); this.props.actions.unfavoriteChannel(this.props.channel.id);
} else { } else {
@@ -98,6 +102,10 @@ export default class ChannelIntroMessage extends React.PureComponent<Props> {
centeredIntro = 'channel-intro--centered'; centeredIntro = 'channel-intro--centered';
} }
if (!channel) {
return null;
}
if (channel.type === Constants.DM_CHANNEL) { if (channel.type === Constants.DM_CHANNEL) {
return createDMIntroMessage(channel, centeredIntro, currentUser, isFavorite, isMobileView, this.toggleFavorite, teammate, teammateName); return createDMIntroMessage(channel, centeredIntro, currentUser, isFavorite, isMobileView, this.toggleFavorite, teammate, teammateName);
} else if (channel.type === Constants.GM_CHANNEL) { } else if (channel.type === Constants.GM_CHANNEL) {

Просмотреть файл

@@ -28,11 +28,11 @@ function mapStateToProps(state: GlobalState) {
const enableUserCreation = config.EnableUserCreation === 'true'; const enableUserCreation = config.EnableUserCreation === 'true';
const isReadOnly = false; const isReadOnly = false;
const team = getCurrentTeam(state); const team = getCurrentTeam(state);
const channel = getCurrentChannel(state) || {}; const channel = getCurrentChannel(state);
const channelMember = getMyCurrentChannelMembership(state); const channelMember = getMyCurrentChannelMembership(state);
const teammate = getDirectTeammate(state, channel.id); const teammate = channel ? getDirectTeammate(state, channel.id) : undefined;
const currentUser = getCurrentUser(state); const currentUser = getCurrentUser(state);
const creator = getUser(state, channel.creator_id); const creator = channel ? getUser(state, channel.creator_id) : undefined;
const usersLimit = 10; const usersLimit = 10;

Просмотреть файл

@@ -49,7 +49,7 @@ function makeMapStateToProps() {
} }
if (ownProps.metadata.channel_type === General.DM_CHANNEL) { if (ownProps.metadata.channel_type === General.DM_CHANNEL) {
channelDisplayName = getChannel(state, {id: ownProps.metadata.channel_id}).display_name; channelDisplayName = getChannel(state, {id: ownProps.metadata.channel_id})?.display_name || '';
} }
return { return {

Просмотреть файл

@@ -18,7 +18,7 @@ import type {FakePost, RhsState} from 'types/store/rhs';
type Props = { type Props = {
currentTeam?: Team; currentTeam?: Team;
posts: Post[]; posts: Post[];
channel: Channel | null; channel?: Channel;
selected: Post | FakePost; selected: Post | FakePost;
previousRhsState?: RhsState; previousRhsState?: RhsState;
} }

Просмотреть файл

@@ -37,11 +37,11 @@ function makeMapStateToProps() {
const currentChannelId = getCurrentChannelId(state); const currentChannelId = getCurrentChannelId(state);
const unreadCount = getUnreadCount(state, channel.id); const unreadCount = getUnreadCount(state, channel?.id || '');
return { return {
channel, channel,
isCurrentChannel: channel.id === currentChannelId, isCurrentChannel: channel?.id === currentChannelId,
currentTeamName: currentTeam?.name, currentTeamName: currentTeam?.name,
unreadMentions: unreadCount.mentions, unreadMentions: unreadCount.mentions,
isUnread: unreadCount.showUnread, isUnread: unreadCount.showUnread,

Просмотреть файл

@@ -32,6 +32,9 @@ function SidebarChannel({
autoSortedCategoryIds, autoSortedCategoryIds,
}: Props) { }: Props) {
const [show, setShow] = useState(true); const [show, setShow] = useState(true);
if (!channel) {
return null;
}
if (!currentTeamName) { if (!currentTeamName) {
return null; return null;
@@ -43,7 +46,7 @@ function SidebarChannel({
function setRef(refMethod?: (element: HTMLLIElement) => void) { function setRef(refMethod?: (element: HTMLLIElement) => void) {
return (ref: HTMLLIElement) => { return (ref: HTMLLIElement) => {
setChannelRef(channel.id, ref); setChannelRef(channel?.id || '', ref);
refMethod?.(ref); refMethod?.(ref);
}; };
} }

Просмотреть файл

@@ -30,7 +30,7 @@ import type {RhsState} from 'types/store/rhs';
export type Props = { export type Props = {
isExpanded: boolean; isExpanded: boolean;
isOpen: boolean; isOpen: boolean;
channel: Channel; channel?: Channel;
team?: Team; team?: Team;
teamId: Team['id']; teamId: Team['id'];
productId: ProductIdentifier; productId: ProductIdentifier;
@@ -44,7 +44,7 @@ export type Props = {
isPluginView: boolean; isPluginView: boolean;
isPostEditHistory: boolean; isPostEditHistory: boolean;
previousRhsState: RhsState; previousRhsState: RhsState;
rhsChannel: Channel; rhsChannel?: Channel;
selectedPostId: string; selectedPostId: string;
selectedPostCardId: string; selectedPostCardId: string;
actions: { actions: {
@@ -150,11 +150,11 @@ export default class SidebarRight extends React.PureComponent<Props, State> {
} }
const {actions, isChannelFiles, isPinnedPosts, rhsChannel, channel} = this.props; const {actions, isChannelFiles, isPinnedPosts, rhsChannel, channel} = this.props;
if (isPinnedPosts && prevProps.isPinnedPosts === isPinnedPosts && rhsChannel.id !== prevProps.rhsChannel.id) { if (isPinnedPosts && prevProps.isPinnedPosts === isPinnedPosts && rhsChannel && rhsChannel.id !== prevProps.rhsChannel?.id) {
actions.showPinnedPosts(rhsChannel.id); actions.showPinnedPosts(rhsChannel.id);
} }
if (isChannelFiles && prevProps.isChannelFiles === isChannelFiles && rhsChannel.id !== prevProps.rhsChannel.id) { if (isChannelFiles && prevProps.isChannelFiles === isChannelFiles && rhsChannel && rhsChannel.id !== prevProps.rhsChannel?.id) {
actions.showChannelFiles(rhsChannel.id); actions.showChannelFiles(rhsChannel.id);
} }

Просмотреть файл

@@ -1452,7 +1452,7 @@ export class AppCommandParser {
}; };
// getChannel gets the channel in which the user is typing the command // getChannel gets the channel in which the user is typing the command
private getChannel = (): Channel | null => { private getChannel = (): Channel | undefined => {
const state = this.store.getState(); const state = this.store.getState();
return selectChannel(state, this.channelID); return selectChannel(state, this.channelID);
}; };

Просмотреть файл

@@ -50,7 +50,7 @@ function makeMapStateToProps() {
let postIds: string[] = []; let postIds: string[] = [];
let userThread: UserThread | null = null; let userThread: UserThread | null = null;
let channel: Channel | null = null; let channel: Channel | undefined;
if (selected) { if (selected) {
postIds = getPostIdsForThread(state, selected.id); postIds = getPostIdsForThread(state, selected.id);

Просмотреть файл

@@ -31,7 +31,7 @@ export type Props = Attrs & {
isCollapsedThreadsEnabled: boolean; isCollapsedThreadsEnabled: boolean;
appsEnabled: boolean; appsEnabled: boolean;
userThread?: UserThread | null; userThread?: UserThread | null;
channel: Channel | null; channel?: Channel;
selected?: Post | FakePost; selected?: Post | FakePost;
currentUserId: string; currentUserId: string;
currentTeamId: string; currentTeamId: string;

Просмотреть файл

@@ -36,7 +36,7 @@ export function makeGetRootPosts() {
return Object.values(allPosts).filter((post) => { return Object.values(allPosts).filter((post) => {
return ( return (
post.root_id === '' && post.root_id === '' &&
post.channel_id === channel.id && post.channel_id === channel?.id &&
post.state !== Posts.POST_DELETED post.state !== Posts.POST_DELETED
); );
}).reduce((map: Record<string, boolean>, obj) => { }).reduce((map: Record<string, boolean>, obj) => {

Просмотреть файл

@@ -1279,7 +1279,7 @@ export function favoriteChannel(channelId: string): ActionFuncAsync {
return async (dispatch, getState) => { return async (dispatch, getState) => {
const state = getState(); const state = getState();
const channel = getChannelSelector(state, channelId); const channel = getChannelSelector(state, channelId);
const category = getCategoryInTeamByType(state, channel.team_id || getCurrentTeamId(state), CategoryTypes.FAVORITES); const category = getCategoryInTeamByType(state, channel?.team_id || getCurrentTeamId(state), CategoryTypes.FAVORITES);
Client4.trackEvent('action', 'action_channels_favorite'); Client4.trackEvent('action', 'action_channels_favorite');
@@ -1296,6 +1296,10 @@ export function unfavoriteChannel(channelId: string): ActionFuncAsync {
return async (dispatch, getState) => { return async (dispatch, getState) => {
const state = getState(); const state = getState();
const channel = getChannelSelector(state, channelId); const channel = getChannelSelector(state, channelId);
if (!channel) {
return {data: false};
}
const category = getCategoryInTeamByType( const category = getCategoryInTeamByType(
state, state,
channel.team_id || getCurrentTeamId(state), channel.team_id || getCurrentTeamId(state),

Просмотреть файл

@@ -424,13 +424,16 @@ export function decrementThreadCounts(post: ExtendedPost): ActionFunc {
const channel = getChannel(state, post.channel_id); const channel = getChannel(state, post.channel_id);
const teamId = channel?.team_id || getCurrentTeamId(state); const teamId = channel?.team_id || getCurrentTeamId(state);
dispatch({ if (channel) {
type: ThreadTypes.DECREMENT_THREAD_COUNTS, dispatch({
teamId, type: ThreadTypes.DECREMENT_THREAD_COUNTS,
replies: thread.unread_replies, teamId,
mentions: thread.unread_mentions, replies: thread.unread_replies,
channelType: channel.type, mentions: thread.unread_mentions,
}); channelType: channel.type,
});
}
return {data: true}; return {data: true};
}; };
} }

Просмотреть файл

@@ -151,7 +151,7 @@ export function getChannelMember(state: GlobalState, channelId: string, userId:
// - The display_name set to the other user(s) names, following the Teammate Name Display setting // - The display_name set to the other user(s) names, following the Teammate Name Display setting
// - The teammate_id for DM channels // - The teammate_id for DM channels
// - The status of the other user in a DM channel // - The status of the other user in a DM channel
export function makeGetChannel(): (state: GlobalState, props: {id: string}) => Channel { export function makeGetChannel(): (state: GlobalState, props: {id: string}) => Channel | undefined {
return createSelector( return createSelector(
'makeGetChannel', 'makeGetChannel',
getCurrentUserId, getCurrentUserId,
@@ -183,11 +183,11 @@ export function makeGetChannel(): (state: GlobalState, props: {id: string}) => C
// getChannel returns a channel as it exists in the store without filling in any additional details such as the // getChannel returns a channel as it exists in the store without filling in any additional details such as the
// display_name for DM/GM channels. // display_name for DM/GM channels.
export function getChannel(state: GlobalState, id: string) { export function getChannel(state: GlobalState, id: string): Channel | undefined {
return getAllChannels(state)[id]; return getAllChannels(state)[id];
} }
export function getMyChannelMembership(state: GlobalState, channelId: string): ChannelMembership { export function getMyChannelMembership(state: GlobalState, channelId: string): ChannelMembership | undefined {
return getMyChannelMemberships(state)[channelId]; return getMyChannelMemberships(state)[channelId];
} }
@@ -205,7 +205,7 @@ export function makeGetChannelsForIds(): (state: GlobalState, ids: string[]) =>
); );
} }
export const getCurrentChannel: (state: GlobalState) => Channel = createSelector( export const getCurrentChannel: (state: GlobalState) => Channel | undefined = createSelector(
'getCurrentChannel', 'getCurrentChannel',
getAllChannels, getAllChannels,
getCurrentChannelId, getCurrentChannelId,
@@ -255,7 +255,7 @@ export const getMyChannelMember: (state: GlobalState, channelId: string) => Chan
}, },
); );
export const getCurrentChannelStats: (state: GlobalState) => ChannelStats = createSelector( export const getCurrentChannelStats: (state: GlobalState) => ChannelStats | undefined = createSelector(
'getCurrentChannelStats', 'getCurrentChannelStats',
getAllChannelStats, getAllChannelStats,
getCurrentChannelId, getCurrentChannelId,
@@ -296,7 +296,7 @@ export const isMutedChannel: (state: GlobalState, channelId: string) => boolean
export const isCurrentChannelArchived: (state: GlobalState) => boolean = createSelector( export const isCurrentChannelArchived: (state: GlobalState) => boolean = createSelector(
'isCurrentChannelArchived', 'isCurrentChannelArchived',
getCurrentChannel, getCurrentChannel,
(channel) => channel.delete_at !== 0, (channel) => channel?.delete_at !== 0,
); );
export const isCurrentChannelDefault: (state: GlobalState) => boolean = createSelector( export const isCurrentChannelDefault: (state: GlobalState) => boolean = createSelector(
@@ -313,15 +313,15 @@ export function isChannelReadOnlyById(state: GlobalState, channelId: string): bo
return isChannelReadOnly(state, getChannel(state, channelId)); return isChannelReadOnly(state, getChannel(state, channelId));
} }
export function isChannelReadOnly(state: GlobalState, channel: Channel): boolean { export function isChannelReadOnly(state: GlobalState, channel?: Channel): boolean {
return channel && channel.name === General.DEFAULT_CHANNEL && !isCurrentUserSystemAdmin(state); return Boolean(channel && channel.name === General.DEFAULT_CHANNEL && !isCurrentUserSystemAdmin(state));
} }
export function getChannelMessageCounts(state: GlobalState): RelationOneToOne<Channel, ChannelMessageCount> { export function getChannelMessageCounts(state: GlobalState): RelationOneToOne<Channel, ChannelMessageCount> {
return state.entities.channels.messageCounts; return state.entities.channels.messageCounts;
} }
export function getChannelMessageCount(state: GlobalState, channelId: string): ChannelMessageCount { export function getChannelMessageCount(state: GlobalState, channelId: string): ChannelMessageCount | undefined {
return getChannelMessageCounts(state)[channelId]; return getChannelMessageCounts(state)[channelId];
} }
@@ -334,8 +334,8 @@ export const countCurrentChannelUnreadMessages: (state: GlobalState) => number =
getCurrentChannelMessageCount, getCurrentChannelMessageCount,
getMyCurrentChannelMembership, getMyCurrentChannelMembership,
isCollapsedThreadsEnabled, isCollapsedThreadsEnabled,
(messageCount: ChannelMessageCount, membership?: ChannelMembership, crtEnabled?: boolean): number => { (messageCount?: ChannelMessageCount, membership?: ChannelMembership, crtEnabled?: boolean): number => {
if (!membership) { if (!membership || !messageCount) {
return 0; return 0;
} }
return crtEnabled ? messageCount.root - membership.msg_count_root : messageCount.total - membership.msg_count; return crtEnabled ? messageCount.root - membership.msg_count_root : messageCount.total - membership.msg_count;
@@ -348,7 +348,7 @@ export function makeGetChannelUnreadCount(): (state: GlobalState, channelId: str
(state: GlobalState, channelId: string) => getChannelMessageCount(state, channelId), (state: GlobalState, channelId: string) => getChannelMessageCount(state, channelId),
(state: GlobalState, channelId: string) => getMyChannelMembership(state, channelId), (state: GlobalState, channelId: string) => getMyChannelMembership(state, channelId),
isCollapsedThreadsEnabled, isCollapsedThreadsEnabled,
(messageCount: ChannelMessageCount, member: ChannelMembership, crtEnabled) => (messageCount: ChannelMessageCount | undefined, member: ChannelMembership | undefined, crtEnabled) =>
calculateUnreadCount(messageCount, member, crtEnabled), calculateUnreadCount(messageCount, member, crtEnabled),
); );
} }
@@ -878,7 +878,7 @@ export const canManageChannelMembers: (state: GlobalState) => boolean = createSe
Permissions.MANAGE_PUBLIC_CHANNEL_MEMBERS, Permissions.MANAGE_PUBLIC_CHANNEL_MEMBERS,
), ),
( (
channel: Channel, channel: Channel | undefined,
managePrivateMembers: boolean, managePrivateMembers: boolean,
managePublicMembers: boolean, managePublicMembers: boolean,
): boolean => { ): boolean => {

Просмотреть файл

@@ -208,7 +208,7 @@ export const haveIGroupPermission: (state: GlobalState, groupID: string, permiss
}, },
); );
export function haveIChannelPermission(state: GlobalState, teamId: string | undefined, channelId: string, permission: string): boolean { export function haveIChannelPermission(state: GlobalState, teamId: string | undefined, channelId: string | undefined, permission: string): boolean {
if (getMySystemPermissions(state).has(permission)) { if (getMySystemPermissions(state).has(permission)) {
return true; return true;
} }
@@ -217,7 +217,11 @@ export function haveIChannelPermission(state: GlobalState, teamId: string | unde
return true; return true;
} }
return getMyPermissionsByChannel(state)[channelId]?.has(permission); if (channelId) {
return getMyPermissionsByChannel(state)[channelId]?.has(permission);
}
return false;
} }
export function haveICurrentTeamPermission(state: GlobalState, permission: string): boolean { export function haveICurrentTeamPermission(state: GlobalState, permission: string): boolean {

Просмотреть файл

@@ -151,8 +151,8 @@ export function getGroupDisplayNameFromUserIds(userIds: Set<string>, profiles: I
return names.sort(sortUsernames).join(', '); return names.sort(sortUsernames).join(', ');
} }
export function isDefault(channel: Channel): boolean { export function isDefault(channel?: Channel): boolean {
return channel.name === General.DEFAULT_CHANNEL; return channel?.name === General.DEFAULT_CHANNEL;
} }
export function completeDirectGroupInfo(usersState: UsersState, teammateNameDisplay: string, channel: Channel, omitCurrentUser = true) { export function completeDirectGroupInfo(usersState: UsersState, teammateNameDisplay: string, channel: Channel, omitCurrentUser = true) {

Просмотреть файл

@@ -20,7 +20,7 @@ import type {PluginComponent} from 'types/store/plugins';
import './call_button.scss'; import './call_button.scss';
type Props = { type Props = {
currentChannel: Channel; currentChannel?: Channel;
channelMember?: ChannelMembership; channelMember?: ChannelMembership;
pluginCallComponents: PluginComponent[]; pluginCallComponents: PluginComponent[];
sidebarOpen: boolean; sidebarOpen: boolean;

Просмотреть файл

@@ -61,7 +61,7 @@ export function findNextUnreadChannelId(curChannelId: string, allChannelIds: str
return -1; return -1;
} }
export function isArchivedChannel(channel: Channel) { export function isArchivedChannel(channel?: Channel) {
return Boolean(channel && channel.delete_at !== 0); return Boolean(channel && channel.delete_at !== 0);
} }

Просмотреть файл

@@ -95,7 +95,7 @@ export function getImageSrc(src: string, hasImageProxy = false): string {
return src; return src;
} }
export function canDeletePost(state: GlobalState, post: Post, channel: Channel): boolean { export function canDeletePost(state: GlobalState, post: Post, channel?: Channel): boolean {
if (post.type === Constants.PostTypes.FAKE_PARENT_DELETED) { if (post.type === Constants.PostTypes.FAKE_PARENT_DELETED) {
return false; return false;
} }
@@ -645,6 +645,9 @@ export function areConsecutivePostsBySameUser(post: Post, previousPost: Post): b
// Note: In the case of DM_CHANNEL, users must be fetched beforehand. // Note: In the case of DM_CHANNEL, users must be fetched beforehand.
export function getPostURL(state: GlobalState, post: Post): string { export function getPostURL(state: GlobalState, post: Post): string {
const channel = getChannel(state, post.channel_id); const channel = getChannel(state, post.channel_id);
if (!channel) {
return '';
}
const currentUserId = getCurrentUserId(state); const currentUserId = getCurrentUserId(state);
const team = getTeam(state, channel.team_id || getCurrentTeamId(state)); const team = getTeam(state, channel.team_id || getCurrentTeamId(state));
if (!team) { if (!team) {