[Gekidou] Find channels (remote) (#6203)
* Display local results * Fix queryPreferencesByCategoryAndName to observeWithColumns value * Find channels (remote) * ux feedback review * dev review * dev review 2 * Fetch deleted channels from other teams
This commit is contained in:
parent
bb42339c42
commit
f973ac8016
38 changed files with 899 additions and 100 deletions
|
|
@ -386,7 +386,7 @@ describe('switchToChannel', () => {
|
|||
|
||||
const listenerCallback = jest.fn();
|
||||
const listener = DeviceEventEmitter.addListener(Navigation.NAVIGATION_HOME, listenerCallback);
|
||||
const {models, error} = await switchToChannel(serverUrl, channelId, teamId, true);
|
||||
const {models, error} = await switchToChannel(serverUrl, channelId, teamId, false, true);
|
||||
for (const model of models!) {
|
||||
model.cancelPrepareUpdate();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ import {displayGroupMessageName, displayUsername, getUserIdFromChannelName} from
|
|||
import type ChannelModel from '@typings/database/models/servers/channel';
|
||||
import type UserModel from '@typings/database/models/servers/user';
|
||||
|
||||
export async function switchToChannel(serverUrl: string, channelId: string, teamId?: string, prepareRecordsOnly = false) {
|
||||
export async function switchToChannel(serverUrl: string, channelId: string, teamId?: string, skipLastUnread = false, prepareRecordsOnly = false) {
|
||||
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
|
||||
if (!operator) {
|
||||
return {error: `${serverUrl} database not found`};
|
||||
|
|
@ -63,7 +63,7 @@ export async function switchToChannel(serverUrl: string, channelId: string, team
|
|||
}
|
||||
|
||||
const commonValues: PrepareCommonSystemValuesArgs = {
|
||||
lastUnreadChannelId: member.isUnread ? channelId : '',
|
||||
lastUnreadChannelId: member.isUnread && !skipLastUnread ? channelId : '',
|
||||
};
|
||||
|
||||
if ((system.currentTeamId !== toTeamId) || (system.currentChannelId !== channelId)) {
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ import NetworkManager from '@managers/network_manager';
|
|||
import {prepareMyChannelsForTeam, getChannelById, getChannelByName, getMyChannel, getChannelInfo} from '@queries/servers/channel';
|
||||
import {queryPreferencesByCategoryAndName} from '@queries/servers/preference';
|
||||
import {getCommonSystemValues, getCurrentTeamId, getCurrentUserId} from '@queries/servers/system';
|
||||
import {prepareMyTeams, getNthLastChannelFromTeam, getMyTeamById, getTeamById, getTeamByName} from '@queries/servers/team';
|
||||
import {prepareMyTeams, getNthLastChannelFromTeam, getMyTeamById, getTeamById, getTeamByName, queryMyTeams} from '@queries/servers/team';
|
||||
import {getCurrentUser} from '@queries/servers/user';
|
||||
import EphemeralStore from '@store/ephemeral_store';
|
||||
import {generateChannelNameFromDisplayName, getDirectChannelName, isDMorGM} from '@utils/channel';
|
||||
|
|
@ -440,10 +440,12 @@ export async function joinChannel(serverUrl: string, userId: string, teamId: str
|
|||
let channel: Channel | undefined;
|
||||
try {
|
||||
if (channelId) {
|
||||
EphemeralStore.addJoiningChannel(channelId);
|
||||
member = await client.addToChannel(userId, channelId);
|
||||
channel = await client.getChannel(channelId);
|
||||
} else if (channelName) {
|
||||
channel = await client.getChannelByName(teamId, channelName, true);
|
||||
EphemeralStore.addJoiningChannel(channel.id);
|
||||
if (isDMorGM(channel)) {
|
||||
member = await client.getChannelMember(channel.id, userId);
|
||||
} else {
|
||||
|
|
@ -451,6 +453,9 @@ export async function joinChannel(serverUrl: string, userId: string, teamId: str
|
|||
}
|
||||
}
|
||||
} catch (error) {
|
||||
if (channelId || channel?.id) {
|
||||
EphemeralStore.removeJoiningChanel(channelId || channel!.id);
|
||||
}
|
||||
forceLogoutIfNecessary(serverUrl, error as ClientErrorProps);
|
||||
return {error};
|
||||
}
|
||||
|
|
@ -478,12 +483,37 @@ export async function joinChannel(serverUrl: string, userId: string, teamId: str
|
|||
}
|
||||
}
|
||||
} catch (error) {
|
||||
if (channelId || channel?.id) {
|
||||
EphemeralStore.removeJoiningChanel(channelId || channel!.id);
|
||||
}
|
||||
return {error};
|
||||
}
|
||||
|
||||
if (channelId || channel?.id) {
|
||||
EphemeralStore.removeJoiningChanel(channelId || channel!.id);
|
||||
}
|
||||
return {channel, member};
|
||||
}
|
||||
|
||||
export async function joinChannelIfNeeded(serverUrl: string, channelId: string) {
|
||||
const database = DatabaseManager.serverDatabases[serverUrl]?.database;
|
||||
if (!database) {
|
||||
return {error: `${serverUrl} database not found`};
|
||||
}
|
||||
|
||||
try {
|
||||
const myChannel = await getMyChannel(database, channelId);
|
||||
if (myChannel) {
|
||||
return {error: undefined};
|
||||
}
|
||||
|
||||
const userId = await getCurrentUserId(database);
|
||||
return joinChannel(serverUrl, userId, '', channelId);
|
||||
} catch (error) {
|
||||
return {error};
|
||||
}
|
||||
}
|
||||
|
||||
export async function markChannelAsRead(serverUrl: string, channelId: string) {
|
||||
try {
|
||||
const client = NetworkManager.getClient(serverUrl);
|
||||
|
|
@ -933,14 +963,14 @@ export async function getChannelTimezones(serverUrl: string, channelId: string)
|
|||
}
|
||||
}
|
||||
|
||||
export async function switchToChannelById(serverUrl: string, channelId: string, teamId?: string) {
|
||||
export async function switchToChannelById(serverUrl: string, channelId: string, teamId?: string, skipLastUnread = false) {
|
||||
const database = DatabaseManager.serverDatabases[serverUrl]?.database;
|
||||
if (!database) {
|
||||
return {error: `${serverUrl} database not found`};
|
||||
}
|
||||
|
||||
fetchPostsForChannel(serverUrl, channelId);
|
||||
await switchToChannel(serverUrl, channelId, teamId);
|
||||
await switchToChannel(serverUrl, channelId, teamId, skipLastUnread);
|
||||
markChannelAsRead(serverUrl, channelId);
|
||||
fetchChannelStats(serverUrl, channelId);
|
||||
|
||||
|
|
@ -1002,3 +1032,25 @@ export async function fetchChannelById(serverUrl: string, id: string) {
|
|||
return {error};
|
||||
}
|
||||
}
|
||||
|
||||
export async function searchAllChannels(serverUrl: string, term: string, archivedOnly = false) {
|
||||
const database = DatabaseManager.serverDatabases[serverUrl]?.database;
|
||||
if (!database) {
|
||||
return {error: `${serverUrl} database not found`};
|
||||
}
|
||||
|
||||
let client: Client;
|
||||
try {
|
||||
client = NetworkManager.getClient(serverUrl);
|
||||
} catch (error) {
|
||||
return {error};
|
||||
}
|
||||
|
||||
try {
|
||||
const myTeamIds = await queryMyTeams(database).fetchIds();
|
||||
const channels = await client.searchAllChannels(term, myTeamIds, archivedOnly);
|
||||
return {channels};
|
||||
} catch (error) {
|
||||
return {error};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -196,7 +196,7 @@ export const fetchTeamsChannelsAndUnreadPosts = async (serverUrl: string, since:
|
|||
const myTeams = teams.filter((t) => membershipSet.has(t.id) && t.id !== excludeTeamId);
|
||||
|
||||
for await (const team of myTeams) {
|
||||
const {channels, memberships: members} = await fetchMyChannelsForTeam(serverUrl, team.id, since > 0, since, false, true);
|
||||
const {channels, memberships: members} = await fetchMyChannelsForTeam(serverUrl, team.id, true, since, false, true);
|
||||
|
||||
if (channels?.length && members?.length) {
|
||||
fetchPostsForUnreadChannels(serverUrl, channels, members);
|
||||
|
|
|
|||
|
|
@ -420,7 +420,11 @@ export const searchProfiles = async (serverUrl: string, term: string, options: a
|
|||
const users = await client.searchUsers(term, options);
|
||||
|
||||
if (!fetchOnly) {
|
||||
const toStore = removeUserFromList(currentUserId, users);
|
||||
const {database} = operator;
|
||||
const existing = await queryUsersById(database, users.map((u) => u.id)).fetchIds();
|
||||
const existingSet = new Set(existing);
|
||||
const usersToAdd = users.filter((u) => !existingSet.has(u.id));
|
||||
const toStore = removeUserFromList(currentUserId, usersToAdd);
|
||||
if (toStore.length) {
|
||||
await operator.handleUsers({
|
||||
users: toStore,
|
||||
|
|
|
|||
|
|
@ -251,9 +251,10 @@ export async function handleUserAddedToChannelEvent(serverUrl: string, msg: any)
|
|||
|
||||
try {
|
||||
if (userId === currentUser?.id) {
|
||||
if (EphemeralStore.isAddingToTeam(teamId)) {
|
||||
if (EphemeralStore.isAddingToTeam(teamId) || EphemeralStore.isJoiningChannel(channelId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const {channels, memberships} = await fetchMyChannel(serverUrl, teamId, channelId, true);
|
||||
if (channels && memberships) {
|
||||
const prepare = await prepareMyChannelsForTeam(operator, teamId, channels, memberships);
|
||||
|
|
@ -300,7 +301,10 @@ export async function handleUserAddedToChannelEvent(serverUrl: string, msg: any)
|
|||
}));
|
||||
}
|
||||
}
|
||||
await operator.batchRecords(models);
|
||||
|
||||
if (models.length) {
|
||||
await operator.batchRecords(models);
|
||||
}
|
||||
|
||||
await fetchChannelStats(serverUrl, channelId, false);
|
||||
} catch {
|
||||
|
|
@ -358,7 +362,7 @@ export async function handleUserRemovedFromChannelEvent(serverUrl: string, msg:
|
|||
models.push(...switchToGlobalThreadsModels);
|
||||
}
|
||||
} else {
|
||||
const {models: switchChannelModels} = await switchToChannel(serverUrl, channelToJumpTo, '', true);
|
||||
const {models: switchChannelModels} = await switchToChannel(serverUrl, channelToJumpTo, '', false, true);
|
||||
if (switchChannelModels) {
|
||||
models.push(...switchChannelModels);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@ export interface ClientChannelsMix {
|
|||
autocompleteChannelsForSearch: (teamId: string, name: string) => Promise<Channel[]>;
|
||||
searchChannels: (teamId: string, term: string) => Promise<Channel[]>;
|
||||
searchArchivedChannels: (teamId: string, term: string) => Promise<Channel[]>;
|
||||
searchAllChannels: (term: string, teamIds: string[], archivedOnly?: boolean) => Promise<Channel[]>;
|
||||
}
|
||||
|
||||
const ClientChannels = (superclass: any) => class extends superclass {
|
||||
|
|
@ -313,6 +314,24 @@ const ClientChannels = (superclass: any) => class extends superclass {
|
|||
{method: 'post', body: {term}},
|
||||
);
|
||||
};
|
||||
|
||||
searchAllChannels = async (term: string, teamIds: string[], archivedOnly = false) => {
|
||||
const queryParams = {include_deleted: false, system_console: false, exclude_default_channels: false};
|
||||
const body = {
|
||||
term,
|
||||
team_ids: teamIds,
|
||||
deleted: archivedOnly,
|
||||
exclude_default_channels: true,
|
||||
exclude_group_constrained: true,
|
||||
public: true,
|
||||
private: false,
|
||||
};
|
||||
|
||||
return this.doFetch(
|
||||
`${this.getChannelsRoute()}/search${buildQueryString(queryParams)}`,
|
||||
{method: 'post', body},
|
||||
);
|
||||
};
|
||||
};
|
||||
|
||||
export default ClientChannels;
|
||||
|
|
|
|||
|
|
@ -26,9 +26,10 @@ const enhanced = withObservables([], ({database}: WithDatabaseArgs) => {
|
|||
of$(emptyEmojiList)),
|
||||
),
|
||||
),
|
||||
skinTone: queryPreferencesByCategoryAndName(database, Preferences.CATEGORY_EMOJI, Preferences.EMOJI_SKINTONE).observe().pipe(
|
||||
switchMap((prefs) => of$(prefs?.[0]?.value ?? 'default')),
|
||||
),
|
||||
skinTone: queryPreferencesByCategoryAndName(database, Preferences.CATEGORY_EMOJI, Preferences.EMOJI_SKINTONE).
|
||||
observeWithColumns(['value']).pipe(
|
||||
switchMap((prefs) => of$(prefs?.[0]?.value ?? 'default')),
|
||||
),
|
||||
};
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -175,7 +175,7 @@ const ChannelListItem = ({
|
|||
],
|
||||
[height, isActive, isInfo, styles]);
|
||||
|
||||
if ((channel.deleteAt > 0 && !isActive) || !myChannel || !isVisible) {
|
||||
if (!myChannel || !isVisible) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
@ -229,7 +229,12 @@ const ChannelListItem = ({
|
|||
</Text>
|
||||
}
|
||||
</View>
|
||||
{Boolean(teammateId) && <CustomStatus userId={teammateId!}/>}
|
||||
{Boolean(teammateId) &&
|
||||
<CustomStatus
|
||||
isInfo={isInfo}
|
||||
userId={teammateId!}
|
||||
/>
|
||||
}
|
||||
{isInfo && Boolean(teamDisplayName) && isTablet &&
|
||||
<Text
|
||||
ellipsizeMode='tail'
|
||||
|
|
|
|||
|
|
@ -10,17 +10,21 @@ type Props = {
|
|||
customStatus?: UserCustomStatus;
|
||||
customStatusExpired: boolean;
|
||||
isCustomStatusEnabled: boolean;
|
||||
isInfo?: boolean;
|
||||
}
|
||||
|
||||
const style = StyleSheet.create({
|
||||
customStatusEmoji: {
|
||||
color: '#000',
|
||||
marginHorizontal: 5,
|
||||
marginTop: 1,
|
||||
top: -4,
|
||||
},
|
||||
info: {
|
||||
marginHorizontal: -15,
|
||||
},
|
||||
});
|
||||
|
||||
const CustomStatus = ({customStatus, customStatusExpired, isCustomStatusEnabled}: Props) => {
|
||||
const CustomStatus = ({customStatus, customStatusExpired, isCustomStatusEnabled, isInfo}: Props) => {
|
||||
const showCustomStatusEmoji = Boolean(isCustomStatusEnabled && customStatus && !customStatusExpired);
|
||||
|
||||
if (!showCustomStatusEmoji) {
|
||||
|
|
@ -30,7 +34,7 @@ const CustomStatus = ({customStatus, customStatusExpired, isCustomStatusEnabled}
|
|||
return (
|
||||
<CustomStatusEmoji
|
||||
customStatus={customStatus!}
|
||||
style={style.customStatusEmoji}
|
||||
style={[style.customStatusEmoji, isInfo && style.info]}
|
||||
testID={`channel_list_item.custom_status.${customStatus!.emoji}-${customStatus!.text}`}
|
||||
/>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -2,14 +2,14 @@
|
|||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import {Text, TextStyle} from 'react-native';
|
||||
import {StyleProp, Text, TextStyle} from 'react-native';
|
||||
|
||||
import Emoji from '@components/emoji';
|
||||
|
||||
interface ComponentProps {
|
||||
customStatus: UserCustomStatus;
|
||||
emojiSize?: number;
|
||||
style?: TextStyle;
|
||||
style?: StyleProp<TextStyle>;
|
||||
testID?: string;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -126,11 +126,12 @@ const CustomStatusExpiry = ({currentUser, isMilitaryTime, showPrefix, showTimeCo
|
|||
};
|
||||
|
||||
const enhanced = withObservables([], ({database}: WithDatabaseArgs) => ({
|
||||
isMilitaryTime: queryPreferencesByCategoryAndName(database, Preferences.CATEGORY_DISPLAY_SETTINGS).observe().pipe(
|
||||
switchMap(
|
||||
(preferences) => of$(getPreferenceAsBool(preferences, Preferences.CATEGORY_DISPLAY_SETTINGS, 'use_military_time', false)),
|
||||
isMilitaryTime: queryPreferencesByCategoryAndName(database, Preferences.CATEGORY_DISPLAY_SETTINGS).
|
||||
observeWithColumns(['value']).pipe(
|
||||
switchMap(
|
||||
(preferences) => of$(getPreferenceAsBool(preferences, Preferences.CATEGORY_DISPLAY_SETTINGS, 'use_military_time', false)),
|
||||
),
|
||||
),
|
||||
),
|
||||
}));
|
||||
|
||||
export default withDatabase(enhanced(CustomStatusExpiry));
|
||||
|
|
|
|||
|
|
@ -129,9 +129,10 @@ const enhanced = withObservables([], ({database}: WithDatabaseArgs) => ({
|
|||
customEmojisEnabled: observeConfigBooleanValue(database, 'EnableCustomEmoji'),
|
||||
customEmojis: queryAllCustomEmojis(database).observe(),
|
||||
recentEmojis: observeRecentReactions(database),
|
||||
skinTone: queryPreferencesByCategoryAndName(database, Preferences.CATEGORY_EMOJI, Preferences.EMOJI_SKINTONE).observe().pipe(
|
||||
switchMap((prefs) => of$(prefs?.[0]?.value ?? 'default')),
|
||||
),
|
||||
skinTone: queryPreferencesByCategoryAndName(database, Preferences.CATEGORY_EMOJI, Preferences.EMOJI_SKINTONE).
|
||||
observeWithColumns(['value']).pipe(
|
||||
switchMap((prefs) => of$(prefs?.[0]?.value ?? 'default')),
|
||||
),
|
||||
}));
|
||||
|
||||
export default withDatabase(enhanced(EmojiPicker));
|
||||
|
|
|
|||
|
|
@ -73,7 +73,7 @@ const ChannelMention = ({
|
|||
if (result.error || !result.channel) {
|
||||
const joinFailedMessage = {
|
||||
id: t('mobile.join_channel.error'),
|
||||
defaultMessage: "We couldn't join the channel {displayName}. Please check your connection and try again.",
|
||||
defaultMessage: "We couldn't join the channel {displayName}.",
|
||||
};
|
||||
alertErrorWithFallback(intl, result.error || {}, joinFailedMessage, {displayName: c.display_name});
|
||||
} else if (result.channel) {
|
||||
|
|
|
|||
|
|
@ -22,7 +22,8 @@ const enhance = withObservables(
|
|||
}
|
||||
|
||||
const config = observeConfig(database);
|
||||
const linkPreviewPreference = queryPreferencesByCategoryAndName(database, Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.LINK_PREVIEW_DISPLAY).observe();
|
||||
const linkPreviewPreference = queryPreferencesByCategoryAndName(database, Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.LINK_PREVIEW_DISPLAY).
|
||||
observeWithColumns(['value']);
|
||||
const showLinkPreviews = combineLatest([config, linkPreviewPreference], (cfg, pref) => {
|
||||
const previewsEnabled = getPreferenceAsBool(pref, Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.LINK_PREVIEW_DISPLAY, true);
|
||||
return of$(previewsEnabled && cfg?.EnableLinkPreviews === 'true');
|
||||
|
|
|
|||
|
|
@ -28,7 +28,8 @@ const withHeaderProps = withObservables(
|
|||
({post, database, differentThreadSequence}: WithDatabaseArgs & HeaderInputProps) => {
|
||||
const config = observeConfig(database);
|
||||
const license = observeLicense(database);
|
||||
const preferences = queryPreferencesByCategoryAndName(database, Preferences.CATEGORY_DISPLAY_SETTINGS).observe();
|
||||
const preferences = queryPreferencesByCategoryAndName(database, Preferences.CATEGORY_DISPLAY_SETTINGS).
|
||||
observeWithColumns(['value']);
|
||||
const author = post.author.observe();
|
||||
const enablePostUsernameOverride = config.pipe(map((cfg) => cfg?.EnablePostUsernameOverride === 'true'));
|
||||
const isTimezoneEnabled = config.pipe(map((cfg) => cfg?.ExperimentalTimezone === 'true'));
|
||||
|
|
|
|||
|
|
@ -103,9 +103,10 @@ const withPost = withObservables(
|
|||
const author = post.userId ? post.author.observe() : of$(null);
|
||||
const canDelete = observePermissionForPost(post, currentUser, isOwner ? Permissions.DELETE_POST : Permissions.DELETE_OTHERS_POSTS, false);
|
||||
const isEphemeral = of$(isPostEphemeral(post));
|
||||
const isSaved = queryPreferencesByCategoryAndName(database, Preferences.CATEGORY_SAVED_POST, post.id).observe().pipe(
|
||||
switchMap((pref) => of$(Boolean(pref.length))),
|
||||
);
|
||||
const isSaved = queryPreferencesByCategoryAndName(database, Preferences.CATEGORY_SAVED_POST, post.id).
|
||||
observeWithColumns(['value']).pipe(
|
||||
switchMap((pref) => of$(Boolean(pref.length))),
|
||||
);
|
||||
|
||||
if (post.props?.add_channel_member && isPostEphemeral(post)) {
|
||||
isPostAddChannelMember = observeCanManageChannelMembers(post, currentUser);
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ const enhanced = withObservables(
|
|||
return {
|
||||
rootPost: observePost(database, rootId),
|
||||
isSaved: queryPreferencesByCategoryAndName(database, Preferences.CATEGORY_SAVED_POST, rootId).
|
||||
observe().
|
||||
observeWithColumns(['value']).
|
||||
pipe(
|
||||
switchMap((pref) => of$(Boolean(pref[0]?.value === 'true'))),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -81,7 +81,8 @@ const SystemHeader = ({isMilitaryTime, isTimezoneEnabled, createAt, theme, user}
|
|||
|
||||
const enhanced = withObservables([], ({database}: WithDatabaseArgs) => {
|
||||
const config = observeConfig(database);
|
||||
const preferences = queryPreferencesByCategoryAndName(database, Preferences.CATEGORY_DISPLAY_SETTINGS, 'use_military_time').observe();
|
||||
const preferences = queryPreferencesByCategoryAndName(database, Preferences.CATEGORY_DISPLAY_SETTINGS, 'use_military_time').
|
||||
observeWithColumns(['value']);
|
||||
const isTimezoneEnabled = config.pipe(map((cfg) => cfg?.ExperimentalTimezone === 'true'));
|
||||
const isMilitaryTime = preferences.pipe(
|
||||
map((prefs) => getPreferenceAsBool(prefs, Preferences.CATEGORY_DISPLAY_SETTINGS, 'use_military_time', false)),
|
||||
|
|
|
|||
|
|
@ -21,9 +21,10 @@ const withTeams = withObservables([], ({database}: WithDatabaseArgs) => {
|
|||
const teamIds = queryJoinedTeams(database).observe().pipe(
|
||||
map((ts) => ts.map((t) => ({id: t.id, displayName: t.displayName}))),
|
||||
);
|
||||
const order = queryPreferencesByCategoryAndName(database, Preferences.TEAMS_ORDER).observe().pipe(
|
||||
switchMap((p) => (p.length ? of$(p[0].value.split(',')) : of$([]))),
|
||||
);
|
||||
const order = queryPreferencesByCategoryAndName(database, Preferences.TEAMS_ORDER).
|
||||
observeWithColumns(['value']).pipe(
|
||||
switchMap((p) => (p.length ? of$(p[0].value.split(',')) : of$([]))),
|
||||
);
|
||||
const myOrderedTeams = combineLatest([myTeams, order, teamIds]).pipe(
|
||||
map(([ts, o, tids]) => {
|
||||
let ids: string[] = o;
|
||||
|
|
|
|||
|
|
@ -11,7 +11,8 @@ import {hasPermission} from '@utils/role';
|
|||
|
||||
import {prepareDeletePost} from './post';
|
||||
import {queryRoles} from './role';
|
||||
import {observeCurrentChannelId, getCurrentChannelId} from './system';
|
||||
import {observeCurrentChannelId, getCurrentChannelId, observeCurrentUserId} from './system';
|
||||
import {observeTeammateNameDisplay} from './user';
|
||||
|
||||
import type ServerDataOperator from '@database/operator/server_data_operator';
|
||||
import type ChannelModel from '@typings/database/models/servers/channel';
|
||||
|
|
@ -431,3 +432,103 @@ export function queryMyChannelsByUnread(database: Database, isUnread: boolean, s
|
|||
Q.take(take),
|
||||
);
|
||||
}
|
||||
|
||||
export const observeDirectChannelsByTerm = (database: Database, term: string, take = 20, matchStart = false) => {
|
||||
const onlyDMs = term.startsWith('@') ? "AND c.type='D'" : '';
|
||||
const value = Q.sanitizeLikeString(term.startsWith('@') ? term.substring(1) : term);
|
||||
let username = `u.username LIKE '${value}%'`;
|
||||
let displayname = `c.display_name LIKE '${value}%'`;
|
||||
if (!matchStart) {
|
||||
username = `u.username LIKE '%${value}%' AND u.username NOT LIKE '${value}%'`;
|
||||
displayname = `(c.display_name LIKE '%${value}%' AND c.display_name NOT LIKE '${value}%')`;
|
||||
}
|
||||
const currentUserId = observeCurrentUserId(database);
|
||||
return currentUserId.pipe(
|
||||
switchMap((uId) => {
|
||||
return database.get<MyChannelModel>(CHANNEL).query(
|
||||
Q.unsafeSqlQuery(`SELECT DISTINCT my.* FROM ${MY_CHANNEL} my
|
||||
INNER JOIN ${CHANNEL} c ON c.id=my.id AND c.team_id='' AND c.delete_at=0 ${onlyDMs}
|
||||
INNER JOIN ${CHANNEL_MEMBERSHIP} cm ON cm.channel_id=my.id
|
||||
INNER JOIN ${USER} u ON u.id=cm.user_id AND (cm.user_id != '${uId}' AND ${username})
|
||||
OR ${displayname}
|
||||
ORDER BY my.last_viewed_at DESC
|
||||
LIMIT ${take}`),
|
||||
).observe();
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
export const observeNotDirectChannelsByTerm = (database: Database, term: string, take = 20, matchStart = false) => {
|
||||
const teammateNameSetting = observeTeammateNameDisplay(database);
|
||||
|
||||
const value = Q.sanitizeLikeString(term.startsWith('@') ? term.substring(1) : term);
|
||||
let username = `u.username LIKE '${value}%'`;
|
||||
let nickname = `u.nickname LIKE '${value}%'`;
|
||||
let displayname = `(u.first_name || ' ' || u.last_name) LIKE '${value}%'`;
|
||||
if (!matchStart) {
|
||||
username = `(u.username LIKE '%${value}%' AND u.username NOT LIKE '${value}%')`;
|
||||
nickname = `(u.nickname LIKE '%${value}%' AND u.nickname NOT LIKE '${value}%')`;
|
||||
displayname = `((u.first_name || ' ' || u.last_name) LIKE '%${value}%' AND (u.first_name || ' ' || u.last_name) NOT LIKE '${value}%')`;
|
||||
}
|
||||
|
||||
return teammateNameSetting.pipe(
|
||||
switchMap((setting) => {
|
||||
let sortBy = '';
|
||||
switch (setting) {
|
||||
case General.TEAMMATE_NAME_DISPLAY.SHOW_NICKNAME_FULLNAME:
|
||||
sortBy = "ORDER BY CASE u.nickname WHEN '' THEN 1 ELSE 0 END, CASE (u.first_name || ' ' || u.last_name) WHEN '' THEN 1 ELSE 0 END, u.username";
|
||||
break;
|
||||
case General.TEAMMATE_NAME_DISPLAY.SHOW_FULLNAME:
|
||||
sortBy = "ORDER BY CASE (u.first_name || ' ' || u.last_name) WHEN '' THEN 1 ELSE 0 END, u.username";
|
||||
break;
|
||||
default:
|
||||
sortBy = 'ORDER BY u.username';
|
||||
break;
|
||||
}
|
||||
|
||||
return database.get<UserModel>(USER).query(
|
||||
Q.unsafeSqlQuery(`SELECT DISTINCT u.* FROM User u
|
||||
LEFT JOIN ChannelMembership cm ON cm.user_id=u.id
|
||||
LEFT JOIN Channel c ON c.id=cm.id AND c.type='${General.DM_CHANNEL}'
|
||||
WHERE cm.user_id IS NULL AND (${displayname} OR ${username} OR ${nickname})
|
||||
${sortBy} LIMIT ${take}`),
|
||||
).observe();
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
export const observeJoinedChannelsByTerm = (database: Database, term: string, take = 20, matchStart = false) => {
|
||||
if (term.startsWith('@')) {
|
||||
return of$([]);
|
||||
}
|
||||
|
||||
const value = Q.sanitizeLikeString(term);
|
||||
let displayname = `c.display_name LIKE '${value}%'`;
|
||||
if (!matchStart) {
|
||||
displayname = `c.display_name LIKE '%${value}%' AND c.display_name NOT LIKE '${value}%'`;
|
||||
}
|
||||
return database.get<MyChannelModel>(MY_CHANNEL).query(
|
||||
Q.unsafeSqlQuery(`SELECT DISTINCT my.* FROM ${MY_CHANNEL} my
|
||||
INNER JOIN ${CHANNEL} c ON c.id=my.id AND c.delete_at=0 AND c.team_id !='' AND ${displayname}
|
||||
ORDER BY my.last_viewed_at DESC
|
||||
LIMIT ${take}`),
|
||||
).observe();
|
||||
};
|
||||
|
||||
export const observeArchiveChannelsByTerm = (database: Database, term: string, take = 20) => {
|
||||
if (term.startsWith('@')) {
|
||||
return of$([]);
|
||||
}
|
||||
|
||||
const value = Q.sanitizeLikeString(term);
|
||||
const displayname = `%${value}%`;
|
||||
return database.get<MyChannelModel>(MY_CHANNEL).query(
|
||||
Q.on(CHANNEL, Q.and(
|
||||
Q.where('delete_at', Q.gt(0)),
|
||||
Q.where('team_id', Q.notEq('')),
|
||||
Q.where('display_name', Q.like(displayname)),
|
||||
)),
|
||||
Q.sortBy('last_viewed_at'),
|
||||
Q.take(take),
|
||||
).observe();
|
||||
};
|
||||
|
|
|
|||
|
|
@ -55,11 +55,12 @@ export const observePost = (database: Database, postId: string) => {
|
|||
};
|
||||
|
||||
export const observePostSaved = (database: Database, postId: string) => {
|
||||
return queryPreferencesByCategoryAndName(database, Preferences.CATEGORY_SAVED_POST, postId).observe().pipe(
|
||||
switchMap(
|
||||
(pref) => of$(Boolean(pref[0]?.value === 'true')),
|
||||
),
|
||||
);
|
||||
return queryPreferencesByCategoryAndName(database, Preferences.CATEGORY_SAVED_POST, postId).
|
||||
observeWithColumns(['value']).pipe(
|
||||
switchMap(
|
||||
(pref) => of$(Boolean(pref[0]?.value === 'true')),
|
||||
),
|
||||
);
|
||||
};
|
||||
|
||||
export const queryPostsInChannel = (database: Database, channelId: string) => {
|
||||
|
|
|
|||
|
|
@ -65,7 +65,8 @@ export async function prepareUsers(operator: ServerDataOperator, users: UserProf
|
|||
export const observeTeammateNameDisplay = (database: Database) => {
|
||||
const config = observeConfig(database);
|
||||
const license = observeLicense(database);
|
||||
const preferences = queryPreferencesByCategoryAndName(database, Preferences.CATEGORY_DISPLAY_SETTINGS).observe();
|
||||
const preferences = queryPreferencesByCategoryAndName(database, Preferences.CATEGORY_DISPLAY_SETTINGS).
|
||||
observeWithColumns(['value']);
|
||||
return combineLatest([config, license, preferences]).pipe(
|
||||
switchMap(
|
||||
([cfg, lcs, prefs]) => of$(getTeammateNameDisplaySetting(prefs, cfg, lcs)),
|
||||
|
|
|
|||
|
|
@ -47,9 +47,10 @@ const enhanced = withObservables(['channelId', 'forceQueryAfterAppState'], ({dat
|
|||
return queryPostsBetween(database, earliest, latest, Q.desc, '', channelId, isCRTEnabled ? '' : undefined).observe();
|
||||
}),
|
||||
),
|
||||
shouldShowJoinLeaveMessages: queryPreferencesByCategoryAndName(database, Preferences.CATEGORY_ADVANCED_SETTINGS, Preferences.ADVANCED_FILTER_JOIN_LEAVE).observe().pipe(
|
||||
switchMap((preferences) => of$(getPreferenceAsBool(preferences, Preferences.CATEGORY_ADVANCED_SETTINGS, Preferences.ADVANCED_FILTER_JOIN_LEAVE, true))),
|
||||
),
|
||||
shouldShowJoinLeaveMessages: queryPreferencesByCategoryAndName(database, Preferences.CATEGORY_ADVANCED_SETTINGS, Preferences.ADVANCED_FILTER_JOIN_LEAVE).
|
||||
observeWithColumns(['value']).pipe(
|
||||
switchMap((preferences) => of$(getPreferenceAsBool(preferences, Preferences.CATEGORY_ADVANCED_SETTINGS, Preferences.ADVANCED_FILTER_JOIN_LEAVE, true))),
|
||||
),
|
||||
};
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -23,7 +23,8 @@ const enhanced = withObservables([], ({channel, database}: {channel: ChannelMode
|
|||
if (channel.creatorId) {
|
||||
const config = observeConfig(database);
|
||||
const license = observeLicense(database);
|
||||
const preferences = queryPreferencesByCategoryAndName(database, Preferences.CATEGORY_DISPLAY_SETTINGS).observe();
|
||||
const preferences = queryPreferencesByCategoryAndName(database, Preferences.CATEGORY_DISPLAY_SETTINGS).
|
||||
observeWithColumns(['value']);
|
||||
const me = observeCurrentUser(database);
|
||||
const profile = observeUser(database, channel.creatorId);
|
||||
|
||||
|
|
|
|||
|
|
@ -113,11 +113,12 @@ const DateTimeSelector = ({timezone, handleChange, isMilitaryTime, theme}: Props
|
|||
};
|
||||
|
||||
const enhanced = withObservables([], ({database}: WithDatabaseArgs) => ({
|
||||
isMilitaryTime: queryPreferencesByCategoryAndName(database, Preferences.CATEGORY_DISPLAY_SETTINGS).observe().pipe(
|
||||
switchMap(
|
||||
(preferences) => of$(getPreferenceAsBool(preferences, Preferences.CATEGORY_DISPLAY_SETTINGS, 'use_military_time', false)),
|
||||
isMilitaryTime: queryPreferencesByCategoryAndName(database, Preferences.CATEGORY_DISPLAY_SETTINGS).
|
||||
observeWithColumns(['value']).pipe(
|
||||
switchMap(
|
||||
(preferences) => of$(getPreferenceAsBool(preferences, Preferences.CATEGORY_DISPLAY_SETTINGS, 'use_military_time', false)),
|
||||
),
|
||||
),
|
||||
),
|
||||
}));
|
||||
|
||||
export default withDatabase(enhanced(DateTimeSelector));
|
||||
|
|
|
|||
|
|
@ -1,26 +1,59 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {useCallback, useMemo, useState} from 'react';
|
||||
import {FlatList, ListRenderItemInfo, StyleSheet, View} from 'react-native';
|
||||
import {debounce, DebouncedFunc} from 'lodash';
|
||||
import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react';
|
||||
import {useIntl} from 'react-intl';
|
||||
import {Alert, FlatList, ListRenderItemInfo, StyleSheet, View} from 'react-native';
|
||||
import Animated, {FadeInDown, FadeOutUp} from 'react-native-reanimated';
|
||||
|
||||
import {switchToChannelById} from '@actions/remote/channel';
|
||||
import {joinChannelIfNeeded, makeDirectChannel, searchAllChannels, switchToChannelById} from '@actions/remote/channel';
|
||||
import {searchProfiles} from '@actions/remote/user';
|
||||
import ChannelItem from '@components/channel_item';
|
||||
import Loading from '@components/loading';
|
||||
import NoResultsWithTerm from '@components/no_results_with_term';
|
||||
import {useServerUrl} from '@context/server';
|
||||
import {useTheme} from '@context/theme';
|
||||
import {sortChannelsByDisplayName} from '@utils/channel';
|
||||
import {displayUsername} from '@utils/user';
|
||||
|
||||
import RemoteChannelItem from './remote_channel_item';
|
||||
import UserItem from './user_item';
|
||||
|
||||
import type ChannelModel from '@typings/database/models/servers/channel';
|
||||
import type UserModel from '@typings/database/models/servers/user';
|
||||
|
||||
type RemoteChannels = {
|
||||
archived: Channel[];
|
||||
startWith: Channel[];
|
||||
matches: Channel[];
|
||||
}
|
||||
|
||||
type Props = {
|
||||
archivedChannels: ChannelModel[];
|
||||
close: () => Promise<void>;
|
||||
channelsMatch: ChannelModel[];
|
||||
channelsMatchStart: ChannelModel[];
|
||||
currentTeamId: string;
|
||||
keyboardHeight: number;
|
||||
loading: boolean;
|
||||
onLoading: (loading: boolean) => void;
|
||||
restrictDirectMessage: boolean;
|
||||
showTeamName: boolean;
|
||||
teamIds: Set<string>;
|
||||
teammateDisplayNameSetting: string;
|
||||
term: string;
|
||||
usersMatch: UserModel[];
|
||||
usersMatchStart: UserModel[];
|
||||
}
|
||||
|
||||
const style = StyleSheet.create({
|
||||
flex: {flex: 1},
|
||||
loading: {
|
||||
height: 32,
|
||||
width: 32,
|
||||
justifyContent: 'center',
|
||||
},
|
||||
noResultContainer: {
|
||||
flexGrow: 1,
|
||||
alignItems: 'center',
|
||||
|
|
@ -28,17 +61,138 @@ const style = StyleSheet.create({
|
|||
},
|
||||
});
|
||||
|
||||
const UnfilteredList = ({close, keyboardHeight, showTeamName, term}: Props) => {
|
||||
const serverUrl = useServerUrl();
|
||||
const [data] = useState([]);
|
||||
const flatListStyle = useMemo(() => ({flexGrow: 1, paddingBottom: keyboardHeight}), [keyboardHeight]);
|
||||
export const MAX_RESULTS = 20;
|
||||
|
||||
const onPress = useCallback(async (channelId: string) => {
|
||||
const sortByLastPostAt = (a: Channel, b: Channel) => {
|
||||
return a.last_post_at > b.last_post_at ? 1 : -1;
|
||||
};
|
||||
|
||||
const sortByUserOrChannel = <T extends Channel |UserModel>(locale: string, teammateDisplayNameSetting: string, a: T, b: T): number => {
|
||||
const aDisplayName = 'display_name' in a ? a.display_name : displayUsername(a, locale, teammateDisplayNameSetting);
|
||||
const bDisplayName = 'display_name' in b ? b.display_name : displayUsername(b, locale, teammateDisplayNameSetting);
|
||||
|
||||
return aDisplayName.toLowerCase().localeCompare(bDisplayName.toLowerCase(), locale, {numeric: true});
|
||||
};
|
||||
|
||||
const FilteredList = ({
|
||||
archivedChannels, close, channelsMatch, channelsMatchStart, currentTeamId,
|
||||
keyboardHeight, loading, onLoading, restrictDirectMessage, showTeamName,
|
||||
teamIds, teammateDisplayNameSetting, term, usersMatch, usersMatchStart,
|
||||
}: Props) => {
|
||||
const bounce = useRef<DebouncedFunc<() => void>>();
|
||||
const mounted = useRef(false);
|
||||
const serverUrl = useServerUrl();
|
||||
const theme = useTheme();
|
||||
const {locale, formatMessage} = useIntl();
|
||||
const flatListStyle = useMemo(() => ({flexGrow: 1, paddingBottom: keyboardHeight}), [keyboardHeight]);
|
||||
const [remoteChannels, setRemoteChannels] = useState<RemoteChannels>({archived: [], startWith: [], matches: []});
|
||||
const totalLocalResults = channelsMatchStart.length + channelsMatch.length + usersMatchStart.length;
|
||||
|
||||
const search = async () => {
|
||||
onLoading(true);
|
||||
if (mounted.current) {
|
||||
setRemoteChannels({archived: [], startWith: [], matches: []});
|
||||
}
|
||||
const lowerCasedTerm = (term.startsWith('@') ? term.substring(1) : term).toLowerCase();
|
||||
if ((channelsMatchStart.length + channelsMatch.length) < MAX_RESULTS) {
|
||||
if (restrictDirectMessage) {
|
||||
searchProfiles(serverUrl, lowerCasedTerm, {team_id: currentTeamId, allow_inactive: true});
|
||||
} else {
|
||||
searchProfiles(serverUrl, lowerCasedTerm, {allow_inactive: true});
|
||||
}
|
||||
}
|
||||
|
||||
if (!term.startsWith('@')) {
|
||||
if (totalLocalResults < MAX_RESULTS) {
|
||||
const {channels} = await searchAllChannels(serverUrl, lowerCasedTerm, true);
|
||||
if (channels) {
|
||||
const existingChannelIds = new Set(channelsMatchStart.concat(channelsMatch).concat(archivedChannels).map((c) => c.id));
|
||||
const [startWith, matches, archived] = channels.reduce<[Channel[], Channel[], Channel[]]>(([s, m, a], c) => {
|
||||
if (existingChannelIds.has(c.id) || !teamIds.has(c.team_id)) {
|
||||
return [s, m, a];
|
||||
}
|
||||
if (!c.delete_at) {
|
||||
if (c.display_name.toLowerCase().startsWith(lowerCasedTerm)) {
|
||||
return [[...s, c], m, a];
|
||||
}
|
||||
if (c.display_name.toLowerCase().includes(lowerCasedTerm)) {
|
||||
return [s, [...m, c], a];
|
||||
}
|
||||
return [s, m, a];
|
||||
}
|
||||
|
||||
if (c.display_name.toLowerCase().includes(lowerCasedTerm)) {
|
||||
return [s, m, [...a, c]];
|
||||
}
|
||||
|
||||
return [s, m, a];
|
||||
}, [[], [], []]);
|
||||
|
||||
if (mounted.current) {
|
||||
setRemoteChannels({
|
||||
archived: archived.sort(sortChannelsByDisplayName.bind(null, locale)).slice(0, MAX_RESULTS + 1),
|
||||
startWith: startWith.sort(sortByLastPostAt).slice(0, MAX_RESULTS + 1),
|
||||
matches: matches.sort(sortChannelsByDisplayName.bind(null, locale)).slice(0, MAX_RESULTS + 1),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onLoading(false);
|
||||
};
|
||||
|
||||
const onJoinChannel = useCallback(async (channelId: string, displayName: string) => {
|
||||
const {error} = await joinChannelIfNeeded(serverUrl, channelId);
|
||||
if (error) {
|
||||
Alert.alert(
|
||||
'',
|
||||
formatMessage({
|
||||
id: 'mobile.join_channel.error',
|
||||
defaultMessage: "We couldn't join the channel {displayName}.",
|
||||
}, {displayName}),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
await close();
|
||||
switchToChannelById(serverUrl, channelId, undefined, true);
|
||||
}, [serverUrl, close, locale]);
|
||||
|
||||
const onOpenDirectMessage = useCallback(async (teammateId: string, displayName: string) => {
|
||||
const {data, error} = await makeDirectChannel(serverUrl, teammateId, displayName, false);
|
||||
if (error || !data) {
|
||||
Alert.alert(
|
||||
'',
|
||||
formatMessage({
|
||||
id: 'mobile.direct_message.error',
|
||||
defaultMessage: "We couldn't open a DM with {displayName}.",
|
||||
}, {displayName}),
|
||||
);
|
||||
return;
|
||||
return;
|
||||
}
|
||||
|
||||
await close();
|
||||
switchToChannelById(serverUrl, data.id);
|
||||
}, [serverUrl, close, locale]);
|
||||
|
||||
const onSwitchToChannel = useCallback(async (channelId: string) => {
|
||||
await close();
|
||||
switchToChannelById(serverUrl, channelId);
|
||||
}, [serverUrl, close]);
|
||||
|
||||
const renderNoResults = useCallback(() => {
|
||||
const renderEmpty = useCallback(() => {
|
||||
if (loading) {
|
||||
return (
|
||||
<Loading
|
||||
containerStyle={style.noResultContainer}
|
||||
style={style.loading}
|
||||
color={theme.buttonBg}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (term) {
|
||||
return (
|
||||
<View style={style.noResultContainer}>
|
||||
|
|
@ -48,19 +202,88 @@ const UnfilteredList = ({close, keyboardHeight, showTeamName, term}: Props) => {
|
|||
}
|
||||
|
||||
return null;
|
||||
}, [term]);
|
||||
}, [term, loading, theme]);
|
||||
|
||||
const renderItem = useCallback(({item}: ListRenderItemInfo<ChannelModel|Channel|UserModel>) => {
|
||||
if ('teamId' in item) {
|
||||
return (
|
||||
<ChannelItem
|
||||
channel={item}
|
||||
collapsed={false}
|
||||
isInfo={true}
|
||||
onPress={onSwitchToChannel}
|
||||
showTeamName={showTeamName}
|
||||
/>
|
||||
);
|
||||
} else if ('username' in item) {
|
||||
return (
|
||||
<UserItem
|
||||
onPress={onOpenDirectMessage}
|
||||
user={item}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const renderItem = useCallback(({item}: ListRenderItemInfo<ChannelModel>) => {
|
||||
return (
|
||||
<ChannelItem
|
||||
<RemoteChannelItem
|
||||
channel={item}
|
||||
collapsed={false}
|
||||
isInfo={true}
|
||||
onPress={onPress}
|
||||
onPress={onJoinChannel}
|
||||
showTeamName={showTeamName}
|
||||
/>
|
||||
);
|
||||
}, [onPress, showTeamName]);
|
||||
}, [onJoinChannel, onOpenDirectMessage, onSwitchToChannel, showTeamName, teammateDisplayNameSetting]);
|
||||
|
||||
const data = useMemo(() => {
|
||||
const items: Array<ChannelModel|Channel|UserModel> = [...channelsMatchStart];
|
||||
|
||||
// Channels that matches
|
||||
if (items.length < MAX_RESULTS) {
|
||||
items.push(...channelsMatch);
|
||||
}
|
||||
|
||||
// Users that start with
|
||||
if (items.length < MAX_RESULTS) {
|
||||
items.push(...usersMatchStart);
|
||||
}
|
||||
|
||||
// Remote Channels that start with
|
||||
if (items.length < MAX_RESULTS) {
|
||||
items.push(...remoteChannels.startWith);
|
||||
}
|
||||
|
||||
// Users & Channels that matches
|
||||
if (items.length < MAX_RESULTS) {
|
||||
const sortedByAlpha = [...usersMatch, ...remoteChannels.matches].
|
||||
sort(sortByUserOrChannel.bind(null, locale, teammateDisplayNameSetting));
|
||||
items.push(...sortedByAlpha.slice(0, MAX_RESULTS + 1));
|
||||
}
|
||||
|
||||
// Archived channels
|
||||
if (items.length < MAX_RESULTS) {
|
||||
const archivedAlpha = [...archivedChannels, ...remoteChannels.archived].
|
||||
sort(sortChannelsByDisplayName.bind(null, locale));
|
||||
items.push(...archivedAlpha.slice(0, MAX_RESULTS + 1));
|
||||
}
|
||||
|
||||
return [...new Set(items)].slice(0, MAX_RESULTS + 1);
|
||||
}, [archivedChannels, channelsMatchStart, channelsMatch, remoteChannels, usersMatch, usersMatchStart, locale, teammateDisplayNameSetting]);
|
||||
|
||||
useEffect(() => {
|
||||
mounted.current = true;
|
||||
return () => {
|
||||
mounted.current = false;
|
||||
};
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
bounce.current = debounce(search, 250);
|
||||
bounce.current();
|
||||
return () => {
|
||||
if (bounce.current) {
|
||||
bounce.current.cancel();
|
||||
}
|
||||
};
|
||||
}, [term]);
|
||||
|
||||
return (
|
||||
<Animated.View
|
||||
|
|
@ -72,7 +295,7 @@ const UnfilteredList = ({close, keyboardHeight, showTeamName, term}: Props) => {
|
|||
contentContainerStyle={flatListStyle}
|
||||
keyboardDismissMode='interactive'
|
||||
keyboardShouldPersistTaps='handled'
|
||||
ListEmptyComponent={renderNoResults}
|
||||
ListEmptyComponent={renderEmpty}
|
||||
renderItem={renderItem}
|
||||
data={data}
|
||||
showsVerticalScrollIndicator={false}
|
||||
|
|
@ -81,4 +304,4 @@ const UnfilteredList = ({close, keyboardHeight, showTeamName, term}: Props) => {
|
|||
);
|
||||
};
|
||||
|
||||
export default UnfilteredList;
|
||||
export default FilteredList;
|
||||
|
|
|
|||
|
|
@ -3,12 +3,17 @@
|
|||
|
||||
import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider';
|
||||
import withObservables from '@nozbe/with-observables';
|
||||
import {of as of$} from 'rxjs';
|
||||
import {combineLatest, of as of$} from 'rxjs';
|
||||
import {switchMap} from 'rxjs/operators';
|
||||
|
||||
import {General} from '@constants';
|
||||
import {observeArchiveChannelsByTerm, observeDirectChannelsByTerm, observeJoinedChannelsByTerm, observeNotDirectChannelsByTerm} from '@queries/servers/channel';
|
||||
import {observeConfig, observeCurrentTeamId} from '@queries/servers/system';
|
||||
import {queryJoinedTeams} from '@queries/servers/team';
|
||||
import {observeTeammateNameDisplay} from '@queries/servers/user';
|
||||
import {retrieveChannels} from '@screens/find_channels/utils';
|
||||
|
||||
import FilteredList from './filtered_list';
|
||||
import FilteredList, {MAX_RESULTS} from './filtered_list';
|
||||
|
||||
import type {WithDatabaseArgs} from '@typings/database/database';
|
||||
|
||||
|
|
@ -16,11 +21,50 @@ type EnhanceProps = WithDatabaseArgs & {
|
|||
term: string;
|
||||
}
|
||||
|
||||
const enhanced = withObservables([], ({database}: EnhanceProps) => {
|
||||
const teamsCount = queryJoinedTeams(database).observeCount();
|
||||
const enhanced = withObservables(['term'], ({database, term}: EnhanceProps) => {
|
||||
const teamIds = queryJoinedTeams(database).observe().pipe(
|
||||
// eslint-disable-next-line max-nested-callbacks
|
||||
switchMap((teams) => of$(new Set(teams.map((t) => t.id)))),
|
||||
);
|
||||
const joinedChannelsMatchStart = observeJoinedChannelsByTerm(database, term, MAX_RESULTS, true);
|
||||
const joinedChannelsMatch = observeJoinedChannelsByTerm(database, term, MAX_RESULTS);
|
||||
const directChannelsMatchStart = observeDirectChannelsByTerm(database, term, MAX_RESULTS, true);
|
||||
const directChannelsMatch = observeDirectChannelsByTerm(database, term, MAX_RESULTS);
|
||||
|
||||
const channelsMatchStart = combineLatest([joinedChannelsMatchStart, directChannelsMatchStart]).pipe(
|
||||
switchMap((matchStart) => {
|
||||
return retrieveChannels(database, matchStart.flat(), true);
|
||||
}),
|
||||
);
|
||||
|
||||
const channelsMatch = combineLatest([joinedChannelsMatch, directChannelsMatch]).pipe(
|
||||
switchMap((matched) => retrieveChannels(database, matched.flat(), true)),
|
||||
);
|
||||
|
||||
const archivedChannels = observeArchiveChannelsByTerm(database, term, MAX_RESULTS).pipe(
|
||||
switchMap((archived) => retrieveChannels(database, archived)),
|
||||
);
|
||||
|
||||
const usersMatchStart = observeNotDirectChannelsByTerm(database, term, MAX_RESULTS, true);
|
||||
const usersMatch = observeNotDirectChannelsByTerm(database, term, MAX_RESULTS);
|
||||
|
||||
const restrictDirectMessage = observeConfig(database).pipe(
|
||||
switchMap((cfg) => of$(cfg?.RestrictDirectMessage !== General.RESTRICT_DIRECT_MESSAGE_ANY)),
|
||||
);
|
||||
|
||||
const teammateDisplayNameSetting = observeTeammateNameDisplay(database);
|
||||
|
||||
return {
|
||||
showTeamName: teamsCount.pipe(switchMap((count) => of$(count > 1))),
|
||||
archivedChannels,
|
||||
channelsMatch,
|
||||
channelsMatchStart,
|
||||
currentTeamId: observeCurrentTeamId(database),
|
||||
restrictDirectMessage,
|
||||
showTeamName: teamIds.pipe(switchMap((ids) => of$(ids.size > 1))),
|
||||
teamIds,
|
||||
teammateDisplayNameSetting,
|
||||
usersMatchStart,
|
||||
usersMatch,
|
||||
};
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,34 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
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 {observeTeam} from '@queries/servers/team';
|
||||
|
||||
import RemoteChannelItem from './remote_channel_item';
|
||||
|
||||
import type {WithDatabaseArgs} from '@typings/database/database';
|
||||
|
||||
type EnhanceProps = WithDatabaseArgs & {
|
||||
channel: Channel;
|
||||
showTeamName?: boolean;
|
||||
}
|
||||
|
||||
const enhance = withObservables(['channel', 'showTeamName'], ({channel, database, showTeamName}: EnhanceProps) => {
|
||||
let teamDisplayName = of$('');
|
||||
if (channel.team_id && showTeamName) {
|
||||
teamDisplayName = observeTeam(database, channel.team_id).pipe(
|
||||
switchMap((team) => of$(team?.displayName || '')),
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
teamDisplayName,
|
||||
};
|
||||
});
|
||||
|
||||
export default React.memo(withDatabase(enhance(RemoteChannelItem)));
|
||||
|
|
@ -0,0 +1,128 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {useCallback, useMemo} from 'react';
|
||||
import {StyleSheet, Text, TouchableOpacity, View} from 'react-native';
|
||||
|
||||
import ChannelIcon from '@components/channel_icon';
|
||||
import {useTheme} from '@context/theme';
|
||||
import {useIsTablet} from '@hooks/device';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
import {typography} from '@utils/typography';
|
||||
|
||||
type Props = {
|
||||
onPress: (channelId: string, displayName: string) => void;
|
||||
channel: Channel;
|
||||
teamDisplayName?: string;
|
||||
testID?: string;
|
||||
}
|
||||
|
||||
export const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({
|
||||
container: {
|
||||
flexDirection: 'row',
|
||||
paddingHorizontal: 0,
|
||||
minHeight: 44,
|
||||
alignItems: 'center',
|
||||
marginVertical: 2,
|
||||
},
|
||||
wrapper: {
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
},
|
||||
text: {
|
||||
marginTop: -1,
|
||||
color: theme.centerChannelColor,
|
||||
paddingLeft: 12,
|
||||
paddingRight: 20,
|
||||
...typography('Body', 200, 'Regular'),
|
||||
},
|
||||
teamName: {
|
||||
color: changeOpacity(theme.centerChannelColor, 0.64),
|
||||
paddingLeft: 12,
|
||||
marginTop: 4,
|
||||
...typography('Body', 75),
|
||||
},
|
||||
teamNameTablet: {
|
||||
marginLeft: -12,
|
||||
paddingLeft: 0,
|
||||
marginTop: 0,
|
||||
paddingBottom: 0,
|
||||
top: 5,
|
||||
},
|
||||
}));
|
||||
|
||||
export const textStyle = StyleSheet.create({
|
||||
bright: typography('Body', 200, 'SemiBold'),
|
||||
regular: typography('Body', 200, 'Regular'),
|
||||
});
|
||||
|
||||
const RemoteChannelItem = ({onPress, channel, teamDisplayName, testID}: Props) => {
|
||||
const theme = useTheme();
|
||||
const isTablet = useIsTablet();
|
||||
const styles = getStyleSheet(theme);
|
||||
const height = (teamDisplayName && !isTablet) ? 58 : 44;
|
||||
|
||||
const handleOnPress = useCallback(() => {
|
||||
onPress(channel.id, channel.display_name);
|
||||
}, [channel]);
|
||||
|
||||
const containerStyle = useMemo(() => [
|
||||
styles.container,
|
||||
{minHeight: height},
|
||||
],
|
||||
[height, styles]);
|
||||
|
||||
return (
|
||||
<TouchableOpacity onPress={handleOnPress}>
|
||||
<>
|
||||
<View
|
||||
style={containerStyle}
|
||||
testID={`${testID}.${channel.name}`}
|
||||
>
|
||||
<View style={styles.wrapper}>
|
||||
<ChannelIcon
|
||||
isInfo={true}
|
||||
isArchived={channel.delete_at > 0}
|
||||
name={channel.name}
|
||||
shared={channel.shared}
|
||||
size={24}
|
||||
type={channel.type}
|
||||
/>
|
||||
<View>
|
||||
<Text
|
||||
ellipsizeMode='tail'
|
||||
numberOfLines={1}
|
||||
style={styles.text}
|
||||
testID={`${testID}.${channel.name}.display_name`}
|
||||
>
|
||||
{channel.display_name}
|
||||
</Text>
|
||||
{Boolean(teamDisplayName) && !isTablet &&
|
||||
<Text
|
||||
ellipsizeMode='tail'
|
||||
numberOfLines={1}
|
||||
testID={`${testID}.${teamDisplayName}.display_name`}
|
||||
style={styles.teamName}
|
||||
>
|
||||
{teamDisplayName}
|
||||
</Text>
|
||||
}
|
||||
</View>
|
||||
{Boolean(teamDisplayName) && isTablet &&
|
||||
<Text
|
||||
ellipsizeMode='tail'
|
||||
numberOfLines={1}
|
||||
testID={`${testID}.${teamDisplayName}.display_name`}
|
||||
style={[styles.teamName, styles.teamNameTablet]}
|
||||
>
|
||||
{teamDisplayName}
|
||||
</Text>
|
||||
}
|
||||
</View>
|
||||
</View>
|
||||
</>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
};
|
||||
|
||||
export default RemoteChannelItem;
|
||||
26
app/screens/find_channels/filtered_list/user_item/index.ts
Normal file
26
app/screens/find_channels/filtered_list/user_item/index.ts
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider';
|
||||
import withObservables from '@nozbe/with-observables';
|
||||
import React from 'react';
|
||||
|
||||
import {observeCurrentUserId} from '@queries/servers/system';
|
||||
import {observeTeammateNameDisplay} from '@queries/servers/user';
|
||||
|
||||
import UserItem from './user_item';
|
||||
|
||||
import type {WithDatabaseArgs} from '@typings/database/database';
|
||||
import type UserModel from '@typings/database/models/servers/user';
|
||||
|
||||
type EnhanceProps = WithDatabaseArgs & {
|
||||
user: UserModel;
|
||||
}
|
||||
|
||||
const enhance = withObservables(['user'], ({database, user}: EnhanceProps) => ({
|
||||
currentUserId: observeCurrentUserId(database),
|
||||
teammateDisplayNameSetting: observeTeammateNameDisplay(database),
|
||||
user: user.observe(),
|
||||
}));
|
||||
|
||||
export default React.memo(withDatabase(enhance(UserItem)));
|
||||
|
|
@ -0,0 +1,97 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {useCallback} from 'react';
|
||||
import {useIntl} from 'react-intl';
|
||||
import {Text, TouchableOpacity, View} from 'react-native';
|
||||
|
||||
import CustomStatus from '@components/channel_item/custom_status';
|
||||
import ProfilePicture from '@components/profile_picture';
|
||||
import {useTheme} from '@context/theme';
|
||||
import {makeStyleSheetFromTheme} from '@utils/theme';
|
||||
import {typography} from '@utils/typography';
|
||||
import {displayUsername} from '@utils/user';
|
||||
|
||||
import type UserModel from '@typings/database/models/servers/user';
|
||||
|
||||
type Props = {
|
||||
currentUserId: string;
|
||||
onPress: (channelId: string, displayName: string) => void;
|
||||
teammateDisplayNameSetting?: string;
|
||||
testID?: string;
|
||||
user: UserModel;
|
||||
}
|
||||
|
||||
export const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({
|
||||
container: {
|
||||
flexDirection: 'row',
|
||||
paddingHorizontal: 0,
|
||||
height: 44,
|
||||
alignItems: 'center',
|
||||
marginVertical: 2,
|
||||
},
|
||||
wrapper: {
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
},
|
||||
text: {
|
||||
marginTop: -1,
|
||||
color: theme.centerChannelColor,
|
||||
paddingLeft: 12,
|
||||
paddingRight: 20,
|
||||
...typography('Body', 200, 'Regular'),
|
||||
},
|
||||
avatar: {marginLeft: 4},
|
||||
status: {
|
||||
backgroundColor: theme.centerChannelBg,
|
||||
borderWidth: 0,
|
||||
},
|
||||
}));
|
||||
|
||||
const UserItem = ({currentUserId, onPress, teammateDisplayNameSetting, testID, user}: Props) => {
|
||||
const {formatMessage, locale} = useIntl();
|
||||
const theme = useTheme();
|
||||
const styles = getStyleSheet(theme);
|
||||
const isOwnDirectMessage = currentUserId === user.id;
|
||||
const displayName = displayUsername(user, locale, teammateDisplayNameSetting);
|
||||
|
||||
const handleOnPress = useCallback(() => {
|
||||
onPress(user.id, displayName);
|
||||
}, [user.id, displayName, onPress]);
|
||||
|
||||
return (
|
||||
<TouchableOpacity onPress={handleOnPress}>
|
||||
<>
|
||||
<View
|
||||
style={styles.container}
|
||||
testID={`${testID}.${user.id}`}
|
||||
>
|
||||
<View style={styles.wrapper}>
|
||||
<View style={styles.avatar}>
|
||||
<ProfilePicture
|
||||
author={user}
|
||||
size={24}
|
||||
showStatus={true}
|
||||
statusSize={12}
|
||||
statusStyle={styles.status}
|
||||
/>
|
||||
</View>
|
||||
<View>
|
||||
<Text
|
||||
ellipsizeMode='tail'
|
||||
numberOfLines={1}
|
||||
style={styles.text}
|
||||
testID={`${testID}.${user.id}.display_name`}
|
||||
>
|
||||
{isOwnDirectMessage ? formatMessage({id: 'channel_header.directchannel.you', defaultMessage: '{displayName} (you)'}, {displayName}) : displayName}
|
||||
</Text>
|
||||
</View>
|
||||
<CustomStatus userId={user.id}/>
|
||||
</View>
|
||||
</View>
|
||||
</>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
};
|
||||
|
||||
export default UserItem;
|
||||
|
|
@ -43,6 +43,7 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({
|
|||
const FindChannels = ({closeButtonId, componentId}: Props) => {
|
||||
const theme = useTheme();
|
||||
const [term, setTerm] = useState('');
|
||||
const [loading, setLoading] = useState(false);
|
||||
const styles = getStyleSheet(theme);
|
||||
const color = useMemo(() => changeOpacity(theme.centerChannelColor, 0.72), [theme]);
|
||||
const keyboardHeight = useKeyboardHeight();
|
||||
|
|
@ -66,6 +67,9 @@ const FindChannels = ({closeButtonId, componentId}: Props) => {
|
|||
|
||||
const onChangeText = useCallback((text) => {
|
||||
setTerm(text);
|
||||
if (!text) {
|
||||
setLoading(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
|
|
@ -83,16 +87,17 @@ const FindChannels = ({closeButtonId, componentId}: Props) => {
|
|||
<SearchBar
|
||||
autoCapitalize='none'
|
||||
autoFocus={true}
|
||||
keyboardAppearance={getKeyboardAppearanceFromTheme(theme)}
|
||||
clearIconColor={color}
|
||||
searchIconColor={color}
|
||||
cancelButtonProps={cancelButtonProps}
|
||||
placeholderTextColor={color}
|
||||
inputStyle={styles.inputStyle}
|
||||
clearIconColor={color}
|
||||
inputContainerStyle={styles.inputContainerStyle}
|
||||
selectionColor={color}
|
||||
inputStyle={styles.inputStyle}
|
||||
keyboardAppearance={getKeyboardAppearanceFromTheme(theme)}
|
||||
onCancel={onCancel}
|
||||
onChangeText={onChangeText}
|
||||
placeholderTextColor={color}
|
||||
searchIconColor={color}
|
||||
selectionColor={color}
|
||||
showLoading={loading}
|
||||
value={term}
|
||||
/>
|
||||
{term === '' && <QuickOptions close={close}/>}
|
||||
|
|
@ -107,6 +112,8 @@ const FindChannels = ({closeButtonId, componentId}: Props) => {
|
|||
<FilteredList
|
||||
close={close}
|
||||
keyboardHeight={keyboardHeight}
|
||||
loading={loading}
|
||||
onLoading={setLoading}
|
||||
term={term}
|
||||
/>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,26 +9,21 @@ import {switchMap} from 'rxjs/operators';
|
|||
|
||||
import {queryMyChannelsByUnread} from '@queries/servers/channel';
|
||||
import {queryJoinedTeams} from '@queries/servers/team';
|
||||
import {retrieveChannels} from '@screens/find_channels/utils';
|
||||
|
||||
import UnfilteredList from './unfiltered_list';
|
||||
|
||||
import type {WithDatabaseArgs} from '@typings/database/database';
|
||||
import type ChannelModel from '@typings/database/models/servers/channel';
|
||||
import type MyChannelModel from '@typings/database/models/servers/my_channel';
|
||||
|
||||
const MAX_UNREAD_CHANNELS = 10;
|
||||
const MAX_CHANNELS = 20;
|
||||
|
||||
const observeChannels = async (myChannels: MyChannelModel[]) => {
|
||||
const channels = await Promise.all(myChannels.map((m) => m.channel.fetch()));
|
||||
return channels.filter((c): c is ChannelModel => c !== null);
|
||||
};
|
||||
|
||||
const observeRecentChannels = (database: Database, unreads: ChannelModel[]) => {
|
||||
const count = MAX_CHANNELS - unreads.length;
|
||||
const unreadIds = unreads.map((u) => u.id);
|
||||
return queryMyChannelsByUnread(database, false, 'last_viewed_at', count, unreadIds).observe().pipe(
|
||||
switchMap((myChannels) => observeChannels(myChannels)),
|
||||
switchMap((myChannels) => retrieveChannels(database, myChannels, true)),
|
||||
);
|
||||
};
|
||||
|
||||
|
|
@ -37,7 +32,7 @@ const enhanced = withObservables([], ({database}: WithDatabaseArgs) => {
|
|||
|
||||
const unreadChannels = queryMyChannelsByUnread(database, true, 'last_post_at', MAX_UNREAD_CHANNELS).
|
||||
observeWithColumns(['last_post_at']).pipe(
|
||||
switchMap((myChannels) => observeChannels(myChannels)),
|
||||
switchMap((myChannels) => retrieveChannels(database, myChannels)),
|
||||
);
|
||||
|
||||
const recentChannels = unreadChannels.pipe(
|
||||
|
|
|
|||
27
app/screens/find_channels/utils.ts
Normal file
27
app/screens/find_channels/utils.ts
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {Database, Q} from '@nozbe/watermelondb';
|
||||
import {of as of$} from 'rxjs';
|
||||
|
||||
import {MM_TABLES} from '@constants/database';
|
||||
|
||||
import type ChannelModel from '@typings/database/models/servers/channel';
|
||||
import type MyChannelModel from '@typings/database/models/servers/my_channel';
|
||||
|
||||
const {SERVER: {CHANNEL, MY_CHANNEL}} = MM_TABLES;
|
||||
|
||||
export const retrieveChannels = (database: Database, myChannels: MyChannelModel[], orderedByLastViewedAt = false) => {
|
||||
const ids = myChannels.map((m) => m.id);
|
||||
if (ids.length) {
|
||||
const idsStr = `'${ids.join("','")}'`;
|
||||
const order = orderedByLastViewedAt ? 'order by my.last_viewed_at desc' : '';
|
||||
return database.get<ChannelModel>(CHANNEL).query(
|
||||
Q.unsafeSqlQuery(`select distinct c.* from ${MY_CHANNEL} my
|
||||
inner join ${CHANNEL} c on c.id=my.id and c.id in (${idsStr})
|
||||
${order}`),
|
||||
).observe();
|
||||
}
|
||||
|
||||
return of$([]);
|
||||
};
|
||||
|
|
@ -117,7 +117,7 @@ const enhance = withObservables(['category', 'locale'], ({category, locale, data
|
|||
const dmMap = (p: PreferenceModel) => getDirectChannelName(p.name, currentUserId);
|
||||
|
||||
const hiddenDmIds = queryPreferencesByCategoryAndName(database, Preferences.CATEGORY_DIRECT_CHANNEL_SHOW, undefined, 'false').
|
||||
observe().pipe(
|
||||
observeWithColumns(['value']).pipe(
|
||||
switchMap((prefs: PreferenceModel[]) => {
|
||||
const names = prefs.map(dmMap);
|
||||
const channels = queryChannelsByNames(database, names).observe();
|
||||
|
|
@ -129,15 +129,16 @@ const enhance = withObservables(['category', 'locale'], ({category, locale, data
|
|||
);
|
||||
|
||||
const hiddenGmIds = queryPreferencesByCategoryAndName(database, Preferences.CATEGORY_GROUP_CHANNEL_SHOW, undefined, 'false').
|
||||
observe().pipe(switchMap(mapPrefName));
|
||||
observeWithColumns(['value']).pipe(switchMap(mapPrefName));
|
||||
|
||||
let limit = of$(Preferences.CHANNEL_SIDEBAR_LIMIT_DMS_DEFAULT);
|
||||
if (category.type === DMS_CATEGORY) {
|
||||
limit = queryPreferencesByCategoryAndName(database, Preferences.CATEGORY_SIDEBAR_SETTINGS, Preferences.CHANNEL_SIDEBAR_LIMIT_DMS).observe().pipe(
|
||||
switchMap((val) => {
|
||||
return val[0] ? of$(parseInt(val[0].value, 10)) : of$(Preferences.CHANNEL_SIDEBAR_LIMIT_DMS_DEFAULT);
|
||||
}),
|
||||
);
|
||||
limit = queryPreferencesByCategoryAndName(database, Preferences.CATEGORY_SIDEBAR_SETTINGS, Preferences.CHANNEL_SIDEBAR_LIMIT_DMS).
|
||||
observeWithColumns(['value']).pipe(
|
||||
switchMap((val) => {
|
||||
return val[0] ? of$(parseInt(val[0].value, 10)) : of$(Preferences.CHANNEL_SIDEBAR_LIMIT_DMS_DEFAULT);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
const hiddenChannelIds = combineLatest([hiddenDmIds, hiddenGmIds]).pipe(switchMap(
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ class EphemeralStore {
|
|||
// of the extra computation time. We use this to track the events that are being handled
|
||||
// and make sure we only handle one.
|
||||
private addingTeam = new Set<string>();
|
||||
private joiningChannels = new Set<string>();
|
||||
|
||||
addNavigationComponentId = (componentId: string) => {
|
||||
this.addToNavigationComponentIdStack(componentId);
|
||||
|
|
@ -121,6 +122,19 @@ class EphemeralStore {
|
|||
}
|
||||
};
|
||||
|
||||
// Ephemeral control when joining a channel locally
|
||||
addJoiningChannel = (channelId: string) => {
|
||||
this.joiningChannels.add(channelId);
|
||||
};
|
||||
|
||||
isJoiningChannel = (channelId: string) => {
|
||||
return this.joiningChannels.has(channelId);
|
||||
};
|
||||
|
||||
removeJoiningChanel = (channelId: string) => {
|
||||
this.joiningChannels.delete(channelId);
|
||||
};
|
||||
|
||||
startAddingToTeam = (teamId: string) => {
|
||||
this.addingTeam.add(teamId);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -223,7 +223,6 @@
|
|||
"global_threads.options.mark_as_read": "Mark as Read",
|
||||
"global_threads.options.open_in_channel": "Open in Channel",
|
||||
"global_threads.options.title": "THREAD ACTIONS",
|
||||
"global_threads.options.unfollow": "Unfollow Thread",
|
||||
"global_threads.unreads": "Unread Threads",
|
||||
"home.header.plus_menu": "Options",
|
||||
"interactive_dialog.submit": "Submit",
|
||||
|
|
@ -311,6 +310,7 @@
|
|||
"mobile.custom_status.clear_after": "Clear After",
|
||||
"mobile.custom_status.clear_after.title": "Clear Custom Status After",
|
||||
"mobile.custom_status.modal_confirm": "Done",
|
||||
"mobile.direct_message.error": "We couldn't open a DM with {displayName}.",
|
||||
"mobile.document_preview.failed_description": "An error occurred while opening the document. Please make sure you have a {fileType} viewer installed and try again.\n",
|
||||
"mobile.document_preview.failed_title": "Open Document failed",
|
||||
"mobile.downloader.disabled_description": "File downloads are disabled on this server. Please contact your System Admin for more details.\n",
|
||||
|
|
@ -335,7 +335,7 @@
|
|||
"mobile.gallery.title": "{index} of {total}",
|
||||
"mobile.ios.photos_permission_denied_description": "Upload photos and videos to your server or save them to your device. Open Settings to grant {applicationName} Read and Write access to your photo and video library.",
|
||||
"mobile.ios.photos_permission_denied_title": "{applicationName} would like to access your photos",
|
||||
"mobile.join_channel.error": "We couldn't join the channel {displayName}. Please check your connection and try again.",
|
||||
"mobile.join_channel.error": "We couldn't join the channel {displayName}.",
|
||||
"mobile.link.error.text": "Unable to open the link.",
|
||||
"mobile.link.error.title": "Error",
|
||||
"mobile.login_options.cant_heading": "Can't Log In",
|
||||
|
|
@ -567,6 +567,7 @@
|
|||
"thread.header.thread_dm": "Direct Message Thread",
|
||||
"thread.header.thread_in": "in {channelName}",
|
||||
"thread.noReplies": "No replies yet",
|
||||
"thread.options.title": "THREAD ACTIONS",
|
||||
"thread.repliesCount": "{repliesCount, number} {repliesCount, plural, one {reply} other {replies}}",
|
||||
"threads": "Threads",
|
||||
"threads.deleted": "Original Message Deleted",
|
||||
|
|
|
|||
Loading…
Reference in a new issue