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));
return {data: true};
}
const channel = getCurrentChannel(state) || {};
const channel = getCurrentChannel(state);
if (!channel) {
return {data: false};
}
if (channel.type === Constants.PRIVATE_CHANNEL) {
dispatch(openModal({modalId: ModalIdentifiers.LEAVE_PRIVATE_CHANNEL_MODAL, dialogType: LeaveChannelModal, dialogProps: {channel}}));
return {data: true};

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

@@ -290,7 +290,7 @@ export function setEditingPost(postId = '', refocusId = '', title = '', isRHS =
const license = state.entities.general.license;
const userId = getCurrentUserId(state);
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);

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

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

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

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

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

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

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

@@ -54,7 +54,7 @@ function makeMapStateToProps() {
const config = getConfig(state);
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 filters = getUserGridFilters(state);

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

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

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

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

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

@@ -65,12 +65,12 @@ function makeMapStateToProps() {
return (state: GlobalState) => {
const config = getConfig(state);
const license = getLicense(state);
const currentChannel = getCurrentChannel(state) || {};
const currentChannelTeammateUsername = getUser(state, currentChannel.teammate_id || '')?.username;
const draft = getChannelDraft(state, currentChannel.id);
const isRemoteDraft = state.views.drafts.remotes[`${StoragePrefixes.DRAFT}${currentChannel.id}`] || false;
const currentChannel = getCurrentChannel(state);
const currentChannelTeammateUsername = currentChannel ? getUser(state, currentChannel.teammate_id || '')?.username : undefined;
const draft = getChannelDraft(state, currentChannel?.id || '');
const isRemoteDraft = (currentChannel && state.views.drafts.remotes[`${StoragePrefixes.DRAFT}${currentChannel.id}`]) || false;
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 enableGifPicker = config.EnableGifPicker === 'true';
const enableConfirmNotificationsToChannel = config.EnableConfirmNotificationsToChannel === 'true';
@@ -82,9 +82,11 @@ function makeMapStateToProps() {
const isLDAPEnabled = license?.IsLicensed === 'true' && license?.LDAPGroups === 'true';
const useCustomGroupMentions = isCustomGroupsEnabled(state) && 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 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 tutorialStep = getInt(state, TutorialTourName.ONBOARDING_TUTORIAL_STEP, currentUserId, 0);

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

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

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

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

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

@@ -19,17 +19,20 @@ const ChannelHeaderTitleFavorite = () => {
const dispatch = useDispatch();
const isFavorite = useSelector(isCurrentChannelFavorite);
const channel = useSelector(getCurrentChannel);
const channelIsArchived = channel.delete_at !== 0;
const channelIsArchived = (channel?.delete_at ?? 0) > 0;
const toggleFavoriteRef = useRef<HTMLButtonElement>(null);
const toggleFavoriteCallback = useCallback((e: React.MouseEvent<HTMLButtonElement>) => {
e.stopPropagation();
if (!channel) {
return;
}
if (isFavorite) {
dispatch(unfavoriteChannel(channel.id));
} else {
dispatch(favoriteChannel(channel.id));
}
}, [isFavorite, channel.id]);
}, [isFavorite, channel?.id]);
const removeTooltipLink = useCallback(() => {
// 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';
const EMPTY_CHANNEL = {};
const EMPTY_CHANNEL_STATS = {member_count: 0, guest_count: 0, pinnedpost_count: 0, files_count: 0};
function makeMapStateToProps() {
const doGetProfilesInChannel = makeGetProfilesInChannel();
const getCustomStatus = makeGetCustomStatus();
let timestampUnits: string[] = [];
return function mapStateToProps(state: GlobalState) {
const channel = getCurrentChannel(state) || EMPTY_CHANNEL;
const channel = getCurrentChannel(state);
const user = getCurrentUser(state);
const teams = getMyTeams(state);
const hasMoreThanOneTeam = teams.length > 1;
@@ -77,7 +74,7 @@ function makeMapStateToProps() {
} else if (channel && channel.type === General.GM_CHANNEL) {
gmMembers = doGetProfilesInChannel(state, channel.id);
}
const stats = getCurrentChannelStats(state) || EMPTY_CHANNEL_STATS;
const stats = getCurrentChannelStats(state);
let isLastActiveEnabled = false;
if (dmUser) {
@@ -89,7 +86,7 @@ function makeMapStateToProps() {
teamId: getCurrentTeamId(state),
channel,
channelMember: getMyCurrentChannelMembership(state),
memberCount: stats.member_count,
memberCount: stats?.member_count || 0,
currentUser: user,
dmUser,
gmMembers,
@@ -98,8 +95,8 @@ function makeMapStateToProps() {
isReadOnly: false,
isMuted: isCurrentChannelMuted(state),
isQuickSwitcherOpen: isModalOpen(state, ModalIdentifiers.QUICK_SWITCH),
hasGuests: stats.guest_count > 0,
pinnedPostsCount: stats.pinnedpost_count,
hasGuests: stats ? stats.guest_count > 0 : false,
pinnedPostsCount: stats?.pinnedpost_count || 0,
hasMoreThanOneTeam,
currentRelativeTeamUrl: getCurrentRelativeTeamUrl(state),
announcementBarCount: getAnnouncementBarCount(state),

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

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

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

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

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

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

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

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

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

@@ -26,7 +26,7 @@ export type Props = {
/*
* 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

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

@@ -67,7 +67,7 @@ function makeMapStateToProps() {
const userId = getCurrentUserId(state);
const channel = getChannel(state, post.channel_id);
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 isMilitaryTime = getBool(state, Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.USE_MILITARY_TIME, false);
@@ -123,7 +123,7 @@ function makeMapStateToProps() {
isMobileView: getIsMobileView(state),
timezone: getCurrentTimezone(state),
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';
type Props = {
channel: Channel;
channel?: Channel;
channelUrl: string;
displayName: string;
draftId: string;
@@ -51,6 +51,7 @@ function ChannelDraft({
user,
value,
isRemote,
id: channelId,
}: Props) {
const dispatch = useDispatch();
const history = useHistory();
@@ -60,16 +61,20 @@ function ChannelDraft({
}, [history, channelUrl]);
const handleOnDelete = useCallback((id: string) => {
dispatch(removeDraft(id, channel.id));
}, [dispatch, channel.id]);
dispatch(removeDraft(id, channelId));
}, [dispatch, channelId]);
const doSubmit = useCallback((id: string, post: Post) => {
dispatch(createPost(post, value.fileInfos));
dispatch(removeDraft(id, channel.id));
dispatch(removeDraft(id, channelId));
history.push(channelUrl);
}, [dispatch, history, value.fileInfos, channel.id, channelUrl]);
}, [dispatch, history, value.fileInfos, channelId, channelUrl]);
const showPersistNotificationModal = useCallback((id: string, post: Post) => {
if (!channel) {
return;
}
dispatch(openModal({
modalId: ModalIdentifiers.PERSIST_NOTIFICATION_CONFIRM_MODAL,
dialogType: PersistNotificationConfirmModal,
@@ -80,7 +85,7 @@ function ChannelDraft({
onConfirm: () => doSubmit(id, post),
},
}));
}, [channel.type, dispatch, doSubmit]);
}, [channel, dispatch, doSubmit]);
const handleOnSend = useCallback(async (id: string) => {
const post = {} as Post;
@@ -135,7 +140,7 @@ function ChannelDraft({
remote={isRemote || false}
/>
<PanelBody
channelId={channel.id}
channelId={channelId}
displayName={displayName}
fileInfos={value.fileInfos}
message={value.message}

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

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

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

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

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

@@ -55,7 +55,7 @@ function mapStateToProps(state: GlobalState) {
teamId,
channelId,
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,
isRHSOpened: getIsRhsOpen(state),
isEditHistoryShowing: getRhsState(state) === RHSStates.EDIT_HISTORY,

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

@@ -29,7 +29,7 @@ function mapStateToProps(state: GlobalState, ownProps: OwnProps) {
return {
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 React, {useCallback, useRef, useState} from 'react';
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 {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 {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 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 NotificationBox from 'components/notification_box';
@@ -30,16 +33,22 @@ import ForwardPostChannelSelect, {makeSelectedChannelOption} from './forward_pos
import type {ChannelOption} from './forward_post_channel_select';
import ForwardPostCommentInput from './forward_post_comment_input';
import type {ActionProps, OwnProps, PropsFromRedux} from './index';
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 ForwardPostModal = ({onExited, post, actions}: Props) => {
const ForwardPostModal = ({onExited, post}: Props) => {
const {formatMessage} = useIntl();
const dispatch = useDispatch();
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 permaLink = `${getSiteURL()}${relativePermaLink}`;
const isPrivateConversation = channel.type !== Constants.OPEN_CHANNEL;
const isPrivateConversation = channel?.type !== Constants.OPEN_CHANNEL;
const [comment, setComment] = useState('');
const [bodyHeight, setBodyHeight] = useState<number>(0);
@@ -77,7 +86,7 @@ const ForwardPostModal = ({onExited, post, actions}: Props) => {
const canPostInSelectedChannel = useSelector(
(state: GlobalState) => {
const channelId = isPrivateConversation ? channel.id : selectedChannelId;
const channelId = isPrivateConversation ? post.channel_id : selectedChannelId;
const isDMChannel = selectedChannel?.details?.type === Constants.DM_CHANNEL;
const teamId = isPrivateConversation ? currentTeam?.id : selectedChannel?.details?.team_id;
@@ -123,15 +132,15 @@ const ForwardPostModal = ({onExited, post, actions}: Props) => {
post,
post_id: post.id,
team_name: currentTeam?.name || '',
channel_display_name: channel.display_name,
channel_type: channel.type,
channel_id: channel.id,
channel_display_name: channel?.display_name || '',
channel_type: channel?.type || 'O',
channel_id: post.channel_id,
};
let notification;
if (isPrivateConversation) {
let notificationText;
if (channel.type === General.PRIVATE_CHANNEL) {
if (channel?.type === General.PRIVATE_CHANNEL) {
const channelName = `~${channel.display_name}`;
notificationText = (
<FormattedMessage
@@ -144,7 +153,7 @@ const ForwardPostModal = ({onExited, post, actions}: Props) => {
/>
);
} else {
const allParticipants = channel.display_name.split(', ');
const allParticipants = channel?.display_name.split(', ') || [];
const participants = allParticipants.map((participant) => <strong key={participant}>{participant}</strong>);
notificationText = (
@@ -179,6 +188,10 @@ const ForwardPostModal = ({onExited, post, actions}: Props) => {
return Promise.resolve();
}
if (!channel) {
return Promise.resolve();
}
const channelToForward = isPrivateConversation ? makeSelectedChannelOption(channel) : selectedChannel;
if (!channelToForward) {
@@ -189,7 +202,7 @@ const ForwardPostModal = ({onExited, post, actions}: Props) => {
return Promise.resolve().then(() => {
if (type === Constants.DM_CHANNEL && userId) {
return actions.openDirectChannelToUserId(userId);
return dispatch(openDirectChannelToUserId(userId));
}
return {data: false} as ActionResult;
}).then(({data}) => {
@@ -197,20 +210,20 @@ const ForwardPostModal = ({onExited, post, actions}: Props) => {
channelToForward.details.id = data.id;
}
return actions.forwardPost(
return dispatch(forwardPost(
post,
channelToForward.details,
comment,
);
));
}).then(() => {
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};
}).then(() => {
// only switch channels when we are not in a private conversation
if (!isPrivateConversation) {
return actions.switchToChannel(channelToForward.details);
return dispatch(switchToChannel(channelToForward.details));
}
return {data: false};
}).then(() => {
@@ -227,7 +240,7 @@ const ForwardPostModal = ({onExited, post, actions}: Props) => {
defaultMessage: 'Originally posted in ~{channel}',
},
{
channel: channel.display_name,
channel: channel?.display_name || '',
});
return (

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

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

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

@@ -56,7 +56,7 @@ export type Props = InviteState & {
onChannelsInputChange: (channelsInputValue: string) => void;
onClose: () => void;
currentTeam: Team;
currentChannel: Channel;
currentChannel?: Channel;
setCustomMessage: (message: string) => void;
toggleCustomMessage: () => void;
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();
if (url && isPermalinkURL(url)) {
return getHistory().push(url);
getHistory().push(url);
return;
}
const channel = getChannel(state, channelId);
return getHistory().push(getChannelURL(state, channel, teamId));
if (channel) {
getHistory().push(getChannelURL(state, channel, teamId));
}
};
function mapDispatchToProps(dispatch: Dispatch) {

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

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

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

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

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

@@ -16,7 +16,8 @@ import PostEditHistory from './post_edit_history';
function mapStateToProps(state: GlobalState) {
const selectedPostId = getSelectedPostId(state) || '';
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 {
channelDisplayName,

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

@@ -65,13 +65,13 @@ export default class PostMarkdown extends React.PureComponent<Props> {
let message = this.props.message;
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.channel,
this.props.hideGuestTags,
this.props.isUserCanManageMembers,
this.props.isMilitaryTime,
this.props.timezone);
this.props.timezone) : null;
if (renderedSystemMessage) {
return <div>{renderedSystemMessage}</div>;
}

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

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

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

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

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

@@ -49,7 +49,7 @@ function makeMapStateToProps() {
}
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 {

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

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

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

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

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

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

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

@@ -30,7 +30,7 @@ import type {RhsState} from 'types/store/rhs';
export type Props = {
isExpanded: boolean;
isOpen: boolean;
channel: Channel;
channel?: Channel;
team?: Team;
teamId: Team['id'];
productId: ProductIdentifier;
@@ -44,7 +44,7 @@ export type Props = {
isPluginView: boolean;
isPostEditHistory: boolean;
previousRhsState: RhsState;
rhsChannel: Channel;
rhsChannel?: Channel;
selectedPostId: string;
selectedPostCardId: string;
actions: {
@@ -150,11 +150,11 @@ export default class SidebarRight extends React.PureComponent<Props, State> {
}
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);
}
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);
}

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

@@ -1452,7 +1452,7 @@ export class AppCommandParser {
};
// 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();
return selectChannel(state, this.channelID);
};

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

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

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

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

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

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

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

@@ -1279,7 +1279,7 @@ export function favoriteChannel(channelId: string): ActionFuncAsync {
return async (dispatch, getState) => {
const state = getState();
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');
@@ -1296,6 +1296,10 @@ export function unfavoriteChannel(channelId: string): ActionFuncAsync {
return async (dispatch, getState) => {
const state = getState();
const channel = getChannelSelector(state, channelId);
if (!channel) {
return {data: false};
}
const category = getCategoryInTeamByType(
state,
channel.team_id || getCurrentTeamId(state),

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

@@ -424,13 +424,16 @@ export function decrementThreadCounts(post: ExtendedPost): ActionFunc {
const channel = getChannel(state, post.channel_id);
const teamId = channel?.team_id || getCurrentTeamId(state);
dispatch({
type: ThreadTypes.DECREMENT_THREAD_COUNTS,
teamId,
replies: thread.unread_replies,
mentions: thread.unread_mentions,
channelType: channel.type,
});
if (channel) {
dispatch({
type: ThreadTypes.DECREMENT_THREAD_COUNTS,
teamId,
replies: thread.unread_replies,
mentions: thread.unread_mentions,
channelType: channel.type,
});
}
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 teammate_id for DM channels
// - 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(
'makeGetChannel',
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
// 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];
}
export function getMyChannelMembership(state: GlobalState, channelId: string): ChannelMembership {
export function getMyChannelMembership(state: GlobalState, channelId: string): ChannelMembership | undefined {
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',
getAllChannels,
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',
getAllChannelStats,
getCurrentChannelId,
@@ -296,7 +296,7 @@ export const isMutedChannel: (state: GlobalState, channelId: string) => boolean
export const isCurrentChannelArchived: (state: GlobalState) => boolean = createSelector(
'isCurrentChannelArchived',
getCurrentChannel,
(channel) => channel.delete_at !== 0,
(channel) => channel?.delete_at !== 0,
);
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));
}
export function isChannelReadOnly(state: GlobalState, channel: Channel): boolean {
return channel && channel.name === General.DEFAULT_CHANNEL && !isCurrentUserSystemAdmin(state);
export function isChannelReadOnly(state: GlobalState, channel?: Channel): boolean {
return Boolean(channel && channel.name === General.DEFAULT_CHANNEL && !isCurrentUserSystemAdmin(state));
}
export function getChannelMessageCounts(state: GlobalState): RelationOneToOne<Channel, ChannelMessageCount> {
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];
}
@@ -334,8 +334,8 @@ export const countCurrentChannelUnreadMessages: (state: GlobalState) => number =
getCurrentChannelMessageCount,
getMyCurrentChannelMembership,
isCollapsedThreadsEnabled,
(messageCount: ChannelMessageCount, membership?: ChannelMembership, crtEnabled?: boolean): number => {
if (!membership) {
(messageCount?: ChannelMessageCount, membership?: ChannelMembership, crtEnabled?: boolean): number => {
if (!membership || !messageCount) {
return 0;
}
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) => getMyChannelMembership(state, channelId),
isCollapsedThreadsEnabled,
(messageCount: ChannelMessageCount, member: ChannelMembership, crtEnabled) =>
(messageCount: ChannelMessageCount | undefined, member: ChannelMembership | undefined, crtEnabled) =>
calculateUnreadCount(messageCount, member, crtEnabled),
);
}
@@ -878,7 +878,7 @@ export const canManageChannelMembers: (state: GlobalState) => boolean = createSe
Permissions.MANAGE_PUBLIC_CHANNEL_MEMBERS,
),
(
channel: Channel,
channel: Channel | undefined,
managePrivateMembers: boolean,
managePublicMembers: 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)) {
return true;
}
@@ -217,7 +217,11 @@ export function haveIChannelPermission(state: GlobalState, teamId: string | unde
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 {

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

@@ -151,8 +151,8 @@ export function getGroupDisplayNameFromUserIds(userIds: Set<string>, profiles: I
return names.sort(sortUsernames).join(', ');
}
export function isDefault(channel: Channel): boolean {
return channel.name === General.DEFAULT_CHANNEL;
export function isDefault(channel?: Channel): boolean {
return channel?.name === General.DEFAULT_CHANNEL;
}
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';
type Props = {
currentChannel: Channel;
currentChannel?: Channel;
channelMember?: ChannelMembership;
pluginCallComponents: PluginComponent[];
sidebarOpen: boolean;

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

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

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

@@ -95,7 +95,7 @@ export function getImageSrc(src: string, hasImageProxy = false): string {
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) {
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.
export function getPostURL(state: GlobalState, post: Post): string {
const channel = getChannel(state, post.channel_id);
if (!channel) {
return '';
}
const currentUserId = getCurrentUserId(state);
const team = getTeam(state, channel.team_id || getCurrentTeamId(state));
if (!team) {