diff --git a/app/actions/remote/channel.ts b/app/actions/remote/channel.ts index f43e4dce68..793da32fa3 100644 --- a/app/actions/remote/channel.ts +++ b/app/actions/remote/channel.ts @@ -4,11 +4,12 @@ /* eslint-disable max-lines */ import {Model} from '@nozbe/watermelondb'; import {IntlShape} from 'react-intl'; +import {DeviceEventEmitter} from 'react-native'; import {addChannelToDefaultCategory, storeCategories} from '@actions/local/category'; import {removeCurrentUserFromChannel, setChannelDeleteAt, storeMyChannelsForTeam, switchToChannel} from '@actions/local/channel'; import {switchToGlobalThreads} from '@actions/local/thread'; -import {General, Preferences, Screens} from '@constants'; +import {Events, General, Preferences, Screens} from '@constants'; import DatabaseManager from '@database/manager'; import {privateChannelJoinPrompt} from '@helpers/api/channel'; import {getTeammateNameDisplaySetting} from '@helpers/api/preference'; @@ -1101,12 +1102,16 @@ export async function switchToChannelById(serverUrl: string, channelId: string, return {error: `${serverUrl} database not found`}; } + DeviceEventEmitter.emit(Events.CHANNEL_SWITCH, true); + fetchPostsForChannel(serverUrl, channelId); await switchToChannel(serverUrl, channelId, teamId, skipLastUnread); setDirectChannelVisible(serverUrl, channelId); markChannelAsRead(serverUrl, channelId); fetchChannelStats(serverUrl, channelId); + DeviceEventEmitter.emit(Events.CHANNEL_SWITCH, false); + return {}; } diff --git a/app/components/autocomplete/at_mention/index.ts b/app/components/autocomplete/at_mention/index.ts index 187c46aa52..03fd09d5b6 100644 --- a/app/components/autocomplete/at_mention/index.ts +++ b/app/components/autocomplete/at_mention/index.ts @@ -42,9 +42,9 @@ const enhanced = withObservables([], ({database, channelId}: WithDatabaseArgs & switchMap((c) => of$(Boolean(c?.isGroupConstrained))), ); - useChannelMentions = combineLatest([currentUser, currentChannel]).pipe(switchMap(([u, c]) => (u && c ? observePermissionForChannel(database, c, u, Permissions.USE_CHANNEL_MENTIONS, false) : of$(false)))); + useChannelMentions = combineLatest([currentUser, currentChannel]).pipe(switchMap(([u, c]) => observePermissionForChannel(database, c, u, Permissions.USE_CHANNEL_MENTIONS, false))); useGroupMentions = combineLatest([currentUser, currentChannel, hasLicense]).pipe( - switchMap(([u, c, lcs]) => (lcs && u && c ? observePermissionForChannel(database, c, u, Permissions.USE_GROUP_MENTIONS, false) : of$(false))), + switchMap(([u, c, lcs]) => (lcs ? observePermissionForChannel(database, c, u, Permissions.USE_GROUP_MENTIONS, false) : of$(false))), ); } else { useChannelMentions = of$(false); diff --git a/app/components/channel_actions/set_header_box/index.ts b/app/components/channel_actions/set_header_box/index.ts index 6c2396a812..a8dd81e3ca 100644 --- a/app/components/channel_actions/set_header_box/index.ts +++ b/app/components/channel_actions/set_header_box/index.ts @@ -7,7 +7,7 @@ import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider'; import withObservables from '@nozbe/with-observables'; import {of as of$} from 'rxjs'; -import {switchMap} from 'rxjs/operators'; +import {switchMap, distinctUntilChanged} from 'rxjs/operators'; import {observeChannelInfo} from '@queries/servers/channel'; @@ -23,6 +23,11 @@ const enhanced = withObservables(['channelId'], ({channelId, database}: OwnProps const channelInfo = observeChannelInfo(database, channelId); const isHeaderSet = channelInfo.pipe( switchMap((c) => of$(Boolean(c?.header))), + + // Channel info is fetched when we switch to the channel, and should update + // the member count whenever a user joins or leaves the channel, so this should + // save us a few renders. + distinctUntilChanged(), ); return { diff --git a/app/components/markdown/at_mention/at_mention.tsx b/app/components/markdown/at_mention/at_mention.tsx index 3011d5f6ff..1385b92c0d 100644 --- a/app/components/markdown/at_mention/at_mention.tsx +++ b/app/components/markdown/at_mention/at_mention.tsx @@ -142,7 +142,9 @@ const AtMention = ({ // Effects useEffect(() => { // Fetches and updates the local db store with the mention - fetchUserOrGroupsByMentionsInBatch(serverUrl, mentionName); + if (!user.username) { + fetchUserOrGroupsByMentionsInBatch(serverUrl, mentionName); + } }, []); const openUserProfile = () => { diff --git a/app/components/post_draft/post_input/index.ts b/app/components/post_draft/post_input/index.ts index 4255e43fc0..8485f48095 100644 --- a/app/components/post_draft/post_input/index.ts +++ b/app/components/post_draft/post_input/index.ts @@ -5,9 +5,9 @@ import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider'; import withObservables from '@nozbe/with-observables'; import React from 'react'; import {of as of$} from 'rxjs'; -import {switchMap} from 'rxjs/operators'; +import {switchMap, distinctUntilChanged} from 'rxjs/operators'; -import {observeChannel, observeCurrentChannel} from '@queries/servers/channel'; +import {observeChannel} from '@queries/servers/channel'; import {observeConfig} from '@queries/servers/system'; import PostInput from './post_input'; @@ -17,10 +17,9 @@ import type ChannelInfoModel from '@typings/database/models/servers/channel_info type OwnProps = { channelId: string; - rootId?: string; } -const enhanced = withObservables([], ({database, channelId, rootId}: WithDatabaseArgs & OwnProps) => { +const enhanced = withObservables(['channelId'], ({database, channelId}: WithDatabaseArgs & OwnProps) => { const config = observeConfig(database); const timeBetweenUserTypingUpdatesMilliseconds = config.pipe( switchMap((cfg) => of$(parseInt(cfg?.TimeBetweenUserTypingUpdatesMilliseconds || '0', 10))), @@ -34,12 +33,7 @@ const enhanced = withObservables([], ({database, channelId, rootId}: WithDatabas switchMap((cfg) => of$(parseInt(cfg?.MaxNotificationsPerChannel || '0', 10))), ); - let channel; - if (rootId) { - channel = observeChannel(database, channelId); - } else { - channel = observeCurrentChannel(database); - } + const channel = observeChannel(database, channelId); const channelDisplayName = channel.pipe( switchMap((c) => of$(c?.displayName)), @@ -47,8 +41,8 @@ const enhanced = withObservables([], ({database, channelId, rootId}: WithDatabas const membersInChannel = channel.pipe( switchMap((c) => (c ? c.info.observe() : of$({memberCount: 0}))), - ).pipe( switchMap((i: ChannelInfoModel) => of$(i.memberCount)), + distinctUntilChanged(), ); return { diff --git a/app/components/post_list/combined_user_activity/index.ts b/app/components/post_list/combined_user_activity/index.ts index 0fad263e8a..126d321446 100644 --- a/app/components/post_list/combined_user_activity/index.ts +++ b/app/components/post_list/combined_user_activity/index.ts @@ -28,7 +28,7 @@ const withCombinedPosts = withObservables(['postId'], ({database, postId}: WithD const posts = queryPostsById(database, postIds).observe(); const post = posts.pipe(map((ps) => generateCombinedPost(postId, ps))); const canDelete = combineLatest([posts, currentUser]).pipe( - switchMap(([ps, u]) => (u && ps.length ? observePermissionForPost(database, ps[0], u, Permissions.DELETE_OTHERS_POSTS, false) : of$(false))), + switchMap(([ps, u]) => (ps.length ? observePermissionForPost(database, ps[0], u, Permissions.DELETE_OTHERS_POSTS, false) : of$(false))), ); const usernamesById = post.pipe( diff --git a/app/components/post_list/post/body/index.tsx b/app/components/post_list/post/body/index.tsx index 360c18542b..97c61bbd5b 100644 --- a/app/components/post_list/post/body/index.tsx +++ b/app/components/post_list/post/body/index.tsx @@ -23,7 +23,7 @@ import type {SearchPattern} from '@typings/global/markdown'; type BodyProps = { appsEnabled: boolean; - filesCount: number; + hasFiles: boolean; hasReactions: boolean; highlight: boolean; highlightReplyBar: boolean; @@ -74,7 +74,7 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => { }); const Body = ({ - appsEnabled, filesCount, hasReactions, highlight, highlightReplyBar, + appsEnabled, hasFiles, hasReactions, highlight, highlightReplyBar, isEphemeral, isFirstReply, isJumboEmoji, isLastReply, isPendingOrFailed, isPostAddChannelMember, location, post, searchPatterns, showAddReaction, theme, }: BodyProps) => { @@ -170,7 +170,7 @@ const Body = ({ theme={theme} /> } - {filesCount > 0 && + {hasFiles && ((c && c.deleteAt > 0) || (c?.name === General.DEFAULT_CHANNEL && !isSystemAdmin(u?.roles || '') && readOnly))), ); - const canAddReaction = currentUser.pipe(switchMap((u) => (u ? observePermissionForPost(database, post, u, Permissions.ADD_REACTION, true) : of$(true)))); - const canRemoveReaction = currentUser.pipe(switchMap((u) => (u ? observePermissionForPost(database, post, u, Permissions.REMOVE_REACTION, true) : of$(true)))); + const canAddReaction = currentUser.pipe(switchMap((u) => observePermissionForPost(database, post, u, Permissions.ADD_REACTION, true))); + const canRemoveReaction = currentUser.pipe(switchMap((u) => observePermissionForPost(database, post, u, Permissions.REMOVE_REACTION, true))); return { canAddReaction, diff --git a/app/components/post_list/post/index.ts b/app/components/post_list/post/index.ts index 8b5bec0b0e..7642409457 100644 --- a/app/components/post_list/post/index.ts +++ b/app/components/post_list/post/index.ts @@ -96,7 +96,7 @@ const withSystem = withObservables([], ({database}: WithDatabaseArgs) => ({ const withPost = withObservables( ['currentUser', 'isCRTEnabled', 'post', 'previousPost', 'nextPost'], - ({appsEnabled, currentUser, database, isCRTEnabled, post, previousPost, nextPost}: PropsInput) => { + ({currentUser, database, isCRTEnabled, post, previousPost, nextPost}: PropsInput) => { let isJumboEmoji = of$(false); let isLastReply = of$(true); let isPostAddChannelMember = of$(false); @@ -145,11 +145,20 @@ const withPost = withObservables( distinctUntilChanged(), ); + const hasFiles = post.files.observe().pipe( + switchMap((ff) => of$(Boolean(ff.length))), + distinctUntilChanged(), + ); + + const hasReactions = post.reactions.observe().pipe( + switchMap((rr) => of$(Boolean(rr.length))), + distinctUntilChanged(), + ); + return { - appsEnabled: of$(appsEnabled), canDelete, differentThreadSequence: of$(differentThreadSequence), - filesCount: post.files.observeCount(), + hasFiles, hasReplies, highlightReplyBar, isConsecutivePost, @@ -161,7 +170,7 @@ const withPost = withObservables( isPostAddChannelMember, post: post.observe(), thread: isCRTEnabled ? observeThreadById(database, post.id) : of$(undefined), - reactionsCount: post.reactions.observeCount(), + hasReactions, }; }); diff --git a/app/components/post_list/post/post.tsx b/app/components/post_list/post/post.tsx index 3fda8dc737..f805bd0bd4 100644 --- a/app/components/post_list/post/post.tsx +++ b/app/components/post_list/post/post.tsx @@ -38,7 +38,7 @@ type PostProps = { canDelete: boolean; currentUser: UserModel; differentThreadSequence: boolean; - filesCount: number; + hasFiles: boolean; hasReplies: boolean; highlight?: boolean; highlightPinnedOrSaved?: boolean; @@ -54,7 +54,7 @@ type PostProps = { location: string; post: PostModel; previousPost?: PostModel; - reactionsCount: number; + hasReactions: boolean; searchPatterns?: SearchPattern[]; shouldRenderReplyButton?: boolean; showAddReaction?: boolean; @@ -103,9 +103,9 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => { }); const Post = ({ - appsEnabled, canDelete, currentUser, differentThreadSequence, filesCount, hasReplies, highlight, highlightPinnedOrSaved = true, highlightReplyBar, + appsEnabled, canDelete, currentUser, differentThreadSequence, hasFiles, hasReplies, highlight, highlightPinnedOrSaved = true, highlightReplyBar, isCRTEnabled, isConsecutivePost, isEphemeral, isFirstReply, isSaved, isJumboEmoji, isLastReply, isPostAddChannelMember, - location, post, reactionsCount, searchPatterns, shouldRenderReplyButton, skipSavedHeader, skipPinnedHeader, showAddReaction = true, style, + location, post, hasReactions, searchPatterns, shouldRenderReplyButton, skipSavedHeader, skipPinnedHeader, showAddReaction = true, style, testID, thread, previousPost, }: PostProps) => { const pressDetected = useRef(false); @@ -274,8 +274,8 @@ const Post = ({ body = ( 0} + hasFiles={hasFiles} + hasReactions={hasReactions} highlight={Boolean(highlightedStyle)} highlightReplyBar={highlightReplyBar} isEphemeral={isEphemeral} diff --git a/app/constants/events.ts b/app/constants/events.ts index fd023e0870..7ca1d70967 100644 --- a/app/constants/events.ts +++ b/app/constants/events.ts @@ -6,6 +6,7 @@ import keyMirror from '@utils/key_mirror'; export default keyMirror({ ACCOUNT_SELECT_TABLET_VIEW: null, CHANNEL_ARCHIVED: null, + CHANNEL_SWITCH: null, CLOSE_BOTTOM_SHEET: null, CONFIG_CHANGED: null, FETCHING_POSTS: null, diff --git a/app/hooks/channel_switch.ts b/app/hooks/channel_switch.ts new file mode 100644 index 0000000000..7479ca2b6b --- /dev/null +++ b/app/hooks/channel_switch.ts @@ -0,0 +1,33 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import {useEffect, useState} from 'react'; +import {DeviceEventEmitter} from 'react-native'; + +import {Events} from '@constants'; + +export const useChannelSwitch = () => { + const [loading, setLoading] = useState(false); + useEffect(() => { + let time: NodeJS.Timeout | undefined; + const l = DeviceEventEmitter.addListener(Events.CHANNEL_SWITCH, (switching: boolean) => { + if (time) { + clearTimeout(time); + } + if (switching) { + setLoading(true); + } else { + // eslint-disable-next-line max-nested-callbacks + time = setTimeout(() => setLoading(false), 0); + } + }); + return () => { + l.remove(); + if (time) { + clearTimeout(time); + } + }; + }, []); + + return loading; +}; diff --git a/app/queries/servers/role.ts b/app/queries/servers/role.ts index f36106fe3e..b55921f0f1 100644 --- a/app/queries/servers/role.ts +++ b/app/queries/servers/role.ts @@ -37,7 +37,10 @@ export const queryRolesByNames = (database: Database, names: string[]) => { return database.get(ROLE).query(Q.where('name', Q.oneOf(names))); }; -export function observePermissionForChannel(database: Database, channel: ChannelModel, user: UserModel, permission: string, defaultValue: boolean) { +export function observePermissionForChannel(database: Database, channel: ChannelModel | null | undefined, user: UserModel | undefined, permission: string, defaultValue: boolean) { + if (!user || !channel) { + return of$(defaultValue); + } const myChannel = observeMyChannel(database, channel.id); const myTeam = channel.teamId ? observeMyTeam(database, channel.teamId) : of$(undefined); @@ -57,7 +60,11 @@ export function observePermissionForChannel(database: Database, channel: Channel ); } -export function observePermissionForTeam(database: Database, team: TeamModel, user: UserModel, permission: string, defaultValue: boolean) { +export function observePermissionForTeam(database: Database, team: TeamModel | undefined, user: UserModel | undefined, permission: string, defaultValue: boolean) { + if (!team || !user) { + return of$(defaultValue); + } + return observeMyTeam(database, team.id).pipe( switchMap((myTeam) => { const rolesArray = [...user.roles.split(' ')]; @@ -74,9 +81,9 @@ export function observePermissionForTeam(database: Database, team: TeamModel, us ); } -export function observePermissionForPost(database: Database, post: PostModel, user: UserModel, permission: string, defaultValue: boolean) { +export function observePermissionForPost(database: Database, post: PostModel, user: UserModel | undefined, permission: string, defaultValue: boolean) { return observeChannel(database, post.channelId).pipe( - switchMap((c) => (c ? observePermissionForChannel(database, c, user, permission, defaultValue) : of$(defaultValue))), + switchMap((c) => observePermissionForChannel(database, c, user, permission, defaultValue)), distinctUntilChanged(), ); } diff --git a/app/screens/channel/channel.tsx b/app/screens/channel/channel.tsx index 24c7afcda2..6d64666be0 100644 --- a/app/screens/channel/channel.tsx +++ b/app/screens/channel/channel.tsx @@ -10,6 +10,7 @@ import FreezeScreen from '@components/freeze_screen'; import PostDraft from '@components/post_draft'; import {Events} from '@constants'; import {ACCESSORIES_CONTAINER_NATIVE_ID} from '@constants/post_draft'; +import {useChannelSwitch} from '@hooks/channel_switch'; import {useAppState, useIsTablet} from '@hooks/device'; import {useDefaultHeaderHeight} from '@hooks/header'; import {useTeamSwitch} from '@hooks/team_switch'; @@ -39,9 +40,12 @@ const Channel = ({channelId, componentId}: ChannelProps) => { const insets = useSafeAreaInsets(); const [shouldRenderPosts, setShouldRenderPosts] = useState(false); const switchingTeam = useTeamSwitch(); + const switchingChannels = useChannelSwitch(); const defaultHeight = useDefaultHeaderHeight(); const postDraftRef = useRef(null); + const shouldRender = !switchingTeam && !switchingChannels && shouldRenderPosts && Boolean(channelId); + useEffect(() => { const listener = DeviceEventEmitter.addListener(Events.PAUSE_KEYBOARD_TRACKING_VIEW, (pause: boolean) => { if (pause) { @@ -99,8 +103,11 @@ const Channel = ({channelId, componentId}: ChannelProps) => { edges={edges} testID='channel.screen' > - - {!switchingTeam && shouldRenderPosts && Boolean(channelId) && + + {shouldRender && <> { +const enhanced = withObservables(['channelId'], ({channelId, database}: WithDatabaseArgs & {channelId: string}) => { const currentUserId = observeCurrentUserId(database); - const channelId = observeCurrentChannelId(database); const teamId = observeCurrentTeamId(database); - const channel = channelId.pipe( - switchMap((id) => observeChannel(database, id)), - ); + const channel = observeChannel(database, channelId); const channelType = channel.pipe(switchMap((c) => of$(c?.type))); - const channelInfo = channelId.pipe( - switchMap((id) => observeChannelInfo(database, id)), - ); + const channelInfo = observeChannelInfo(database, channelId); const dmUser = currentUserId.pipe( combineLatestWith(channel), @@ -74,7 +69,6 @@ const enhanced = withObservables([], ({database}: WithDatabaseArgs) => { switchMap(([ci, dm]) => of$(dm ? undefined : ci?.memberCount))); return { - channelId, channelType, customStatus, displayName, diff --git a/app/screens/find_channels/quick_options/index.ts b/app/screens/find_channels/quick_options/index.ts index ccc7956bc2..5baf3bf59a 100644 --- a/app/screens/find_channels/quick_options/index.ts +++ b/app/screens/find_channels/quick_options/index.ts @@ -21,15 +21,15 @@ const enhanced = withObservables([], ({database}: WithDatabaseArgs) => { const currentUser = observeCurrentUser(database); const canJoinChannels = combineLatest([currentUser, team]).pipe( - switchMap(([u, t]) => (t && u ? observePermissionForTeam(database, t, u, Permissions.JOIN_PUBLIC_CHANNELS, true) : of$(false))), + switchMap(([u, t]) => observePermissionForTeam(database, t, u, Permissions.JOIN_PUBLIC_CHANNELS, true)), ); const canCreatePublicChannels = combineLatest([currentUser, team]).pipe( - switchMap(([u, t]) => (t && u ? observePermissionForTeam(database, t, u, Permissions.CREATE_PUBLIC_CHANNEL, true) : of$(false))), + switchMap(([u, t]) => observePermissionForTeam(database, t, u, Permissions.CREATE_PUBLIC_CHANNEL, true)), ); const canCreatePrivateChannels = combineLatest([currentUser, team]).pipe( - switchMap(([u, t]) => (t && u ? observePermissionForTeam(database, t, u, Permissions.CREATE_PRIVATE_CHANNEL, false) : of$(false))), + switchMap(([u, t]) => observePermissionForTeam(database, t, u, Permissions.CREATE_PRIVATE_CHANNEL, false)), ); const canCreateChannels = combineLatest([canCreatePublicChannels, canCreatePrivateChannels]).pipe( diff --git a/app/screens/home/channel_list/categories_list/header/index.ts b/app/screens/home/channel_list/categories_list/header/index.ts index 797e8105f1..82a25f25d2 100644 --- a/app/screens/home/channel_list/categories_list/header/index.ts +++ b/app/screens/home/channel_list/categories_list/header/index.ts @@ -4,7 +4,7 @@ import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider'; import withObservables from '@nozbe/with-observables'; import {combineLatest, of as of$} from 'rxjs'; -import {switchMap, distinctUntilChanged} from 'rxjs/operators'; +import {switchMap} from 'rxjs/operators'; import {Permissions} from '@constants'; import {observePermissionForTeam} from '@queries/servers/role'; @@ -22,18 +22,15 @@ const enhanced = withObservables([], ({database}: WithDatabaseArgs) => { const currentUser = observeCurrentUser(database); const canJoinChannels = combineLatest([currentUser, team]).pipe( - switchMap(([u, t]) => (t && u ? observePermissionForTeam(database, t, u, Permissions.JOIN_PUBLIC_CHANNELS, true) : of$(false))), - distinctUntilChanged(), + switchMap(([u, t]) => observePermissionForTeam(database, t, u, Permissions.JOIN_PUBLIC_CHANNELS, true)), ); const canCreatePublicChannels = combineLatest([currentUser, team]).pipe( - switchMap(([u, t]) => (t && u ? observePermissionForTeam(database, t, u, Permissions.CREATE_PUBLIC_CHANNEL, true) : of$(false))), - distinctUntilChanged(), + switchMap(([u, t]) => observePermissionForTeam(database, t, u, Permissions.CREATE_PUBLIC_CHANNEL, true)), ); const canCreatePrivateChannels = combineLatest([currentUser, team]).pipe( - switchMap(([u, t]) => (t && u ? observePermissionForTeam(database, t, u, Permissions.CREATE_PRIVATE_CHANNEL, false) : of$(false))), - distinctUntilChanged(), + switchMap(([u, t]) => observePermissionForTeam(database, t, u, Permissions.CREATE_PRIVATE_CHANNEL, false)), ); const canCreateChannels = combineLatest([canCreatePublicChannels, canCreatePrivateChannels]).pipe( diff --git a/app/screens/post_options/index.ts b/app/screens/post_options/index.ts index 438b597cee..57c2be72af 100644 --- a/app/screens/post_options/index.ts +++ b/app/screens/post_options/index.ts @@ -79,11 +79,11 @@ const enhanced = withObservables([], ({combinedPost, post, showAddReaction, loca const serverVersion = config.pipe(switchMap((cfg) => of$(cfg?.Version || ''))); const postEditTimeLimit = config.pipe(switchMap((cfg) => of$(parseInt(cfg?.PostEditTimeLimit || '-1', 10)))); - const canPostPermission = combineLatest([channel, currentUser]).pipe(switchMap(([c, u]) => ((c && u) ? observePermissionForChannel(database, c, u, Permissions.CREATE_POST, false) : of$(false)))); - const hasAddReactionPermission = currentUser.pipe(switchMap((u) => (u ? observePermissionForPost(database, post, u, Permissions.ADD_REACTION, true) : of$(false)))); + const canPostPermission = combineLatest([channel, currentUser]).pipe(switchMap(([c, u]) => observePermissionForChannel(database, c, u, Permissions.CREATE_POST, false))); + const hasAddReactionPermission = currentUser.pipe(switchMap((u) => observePermissionForPost(database, post, u, Permissions.ADD_REACTION, true))); const canDeletePostPermission = currentUser.pipe(switchMap((u) => { const isOwner = post.userId === u?.id; - return u ? observePermissionForPost(database, post, u, isOwner ? Permissions.DELETE_POST : Permissions.DELETE_OTHERS_POSTS, false) : of$(false); + return observePermissionForPost(database, post, u, isOwner ? Permissions.DELETE_POST : Permissions.DELETE_OTHERS_POSTS, false); })); const experimentalTownSquareIsReadOnly = config.pipe(switchMap((value) => of$(value?.ExperimentalTownSquareIsReadOnly === 'true')));