[Gekiodu - MM-44156] Channel ordering is different between web and mobile (#6304)

* new approach added queries for sorting channels

* crt reply checking added for post remote actions

* Fix category_body test

* crt replay check fixed for fetchPostsForChannel

* sortedchannels re-rendering fix

* Only fetch Direct channels if there are more/less

Co-authored-by: Elias Nahum <nahumhbl@gmail.com>
This commit is contained in:
Krm
2022-05-26 21:23:40 +03:00
committed by GitHub
parent 4825a54de0
commit efeab53d0b
10 changed files with 186 additions and 131 deletions

View File

@@ -137,9 +137,12 @@ export async function createPost(serverUrl: string, post: Partial<Post>, files:
posts: [created],
prepareRecordsOnly: true,
});
const {member} = await updateLastPostAt(serverUrl, created.channel_id, created.create_at, true);
if (member) {
models.push(member);
const isCrtReply = isCRTEnabled && created.root_id !== '';
if (!isCrtReply) {
const {member} = await updateLastPostAt(serverUrl, created.channel_id, created.create_at, true);
if (member) {
models.push(member);
}
}
if (isCRTEnabled) {
const {models: threadModels} = await createThreadFromNewPost(serverUrl, created, true);
@@ -201,6 +204,9 @@ export const retryFailedPost = async (serverUrl: string, post: PostModel) => {
return {error};
}
const {database} = operator;
const isCRTEnabled = await getIsCRTEnabled(database);
try {
const timestamp = Date.now();
const apiPost = await post.toApi();
@@ -232,9 +238,12 @@ export const retryFailedPost = async (serverUrl: string, post: PostModel) => {
posts: [created],
prepareRecordsOnly: true,
});
const {member} = await updateLastPostAt(serverUrl, created.channel_id, created.create_at, true);
if (member) {
models.push(member);
const isCrtReply = isCRTEnabled && created.root_id !== '';
if (!isCrtReply) {
const {member} = await updateLastPostAt(serverUrl, created.channel_id, created.create_at, true);
if (member) {
models.push(member);
}
}
await operator.batchRecords(models);
} catch (error: any) {
@@ -322,7 +331,8 @@ export async function fetchPostsForChannel(serverUrl: string, channelId: string,
let lastPostAt = 0;
for (const post of data.posts) {
if (!isCRTEnabled || post.root_id) {
const isCrtReply = isCRTEnabled && post.root_id !== '';
if (!isCrtReply) {
lastPostAt = post.create_at > lastPostAt ? post.create_at : lastPostAt;
}
}
@@ -727,7 +737,8 @@ export async function fetchMissingChannelsFromPosts(serverUrl: string, posts: Po
const channelMemberships = await Promise.all(userPromises);
if (!fetchOnly && channels.length && channelMemberships.length) {
const modelPromises = prepareMissingChannelsForAllTeams(operator, channels, channelMemberships);
const isCRTEnabled = await getIsCRTEnabled(operator.database);
const modelPromises = prepareMissingChannelsForAllTeams(operator, channels, channelMemberships, isCRTEnabled);
if (modelPromises.length) {
const channelModelsArray = await Promise.all(modelPromises);
if (channelModelsArray.length) {
@@ -993,7 +1004,8 @@ export async function fetchSavedPosts(serverUrl: string, teamId?: string, channe
}
if (channels?.length && channelMemberships?.length) {
const channelPromises = prepareMissingChannelsForAllTeams(operator, channels, channelMemberships);
const isCRTEnabled = await getIsCRTEnabled(operator.database);
const channelPromises = prepareMissingChannelsForAllTeams(operator, channels, channelMemberships, isCRTEnabled);
if (channelPromises.length) {
promises.push(...channelPromises);
}

View File

@@ -85,7 +85,7 @@ export async function fetchRecentMentions(serverUrl: string): Promise<PostSearch
}
if (channels?.length && channelMemberships?.length) {
const channelPromises = prepareMissingChannelsForAllTeams(operator, channels, channelMemberships);
const channelPromises = prepareMissingChannelsForAllTeams(operator, channels, channelMemberships, isCRTEnabled);
if (channelPromises.length) {
promises.push(...channelPromises);
}

View File

@@ -74,9 +74,14 @@ export async function handleNewPostEvent(serverUrl: string, msg: WebSocketMessag
// Ensure the channel membership
let myChannel = await getMyChannel(database, post.channel_id);
if (myChannel) {
const {member} = await updateLastPostAt(serverUrl, post.channel_id, post.create_at, false);
if (member) {
myChannel = member;
const isCrtReply = isCRTEnabled && post.root_id !== '';
// Don't change lastPostAt if the post is thread post
if (!isCrtReply) {
const {member} = await updateLastPostAt(serverUrl, post.channel_id, post.create_at, false);
if (member) {
myChannel = member;
}
}
} else {
const myChannelRequest = await fetchMyChannel(serverUrl, '', post.channel_id, true);

View File

@@ -9,11 +9,14 @@ import {FAVORITES_CATEGORY} from '@constants/categories';
import {MM_TABLES} from '@constants/database';
import {makeCategoryChannelId} from '@utils/categories';
import {observeChannelsByLastPostAt} from './channel';
import type ServerDataOperator from '@database/operator/server_data_operator';
import type CategoryModel from '@typings/database/models/servers/category';
import type CategoryChannelModel from '@typings/database/models/servers/category_channel';
import type ChannelModel from '@typings/database/models/servers/channel';
const {SERVER: {CATEGORY, CATEGORY_CHANNEL}} = MM_TABLES;
const {SERVER: {CATEGORY, CATEGORY_CHANNEL, CHANNEL}} = MM_TABLES;
export const getCategoryById = async (database: Database, categoryId: string) => {
try {
@@ -104,3 +107,24 @@ export const observeIsChannelFavorited = (database: Database, teamId: string, ch
distinctUntilChanged(),
);
};
export const observeChannelsByCategoryChannelSortOrder = (database: Database, category: CategoryModel, excludeIds?: string[]) => {
return category.categoryChannelsBySortOrder.observeWithColumns(['sort_order']).pipe(
switchMap((categoryChannels) => {
const ids = categoryChannels.map((cc) => cc.channelId);
const idsStr = `'${ids.join("','")}'`;
const exclude = excludeIds?.length ? `AND c.id NOT IN ('${excludeIds.join("','")}')` : '';
return database.get<ChannelModel>(CHANNEL).query(
Q.unsafeSqlQuery(`SELECT DISTINCT c.* FROM ${CHANNEL} c INNER JOIN
${CATEGORY_CHANNEL} cc ON cc.channel_id=c.id AND c.id IN (${idsStr}) ${exclude}
ORDER BY cc.sort_order`),
).observe();
}),
);
};
export const observeChannelsByLastPostAtInCategory = (database: Database, category: CategoryModel, excludeIds?: string[]) => {
return category.myChannels.observeWithColumns(['last_post_at']).pipe(
switchMap((myChannels) => observeChannelsByLastPostAt(database, myChannels, excludeIds)),
);
};

View File

@@ -24,11 +24,11 @@ import type UserModel from '@typings/database/models/servers/user';
const {SERVER: {CHANNEL, MY_CHANNEL, CHANNEL_MEMBERSHIP, MY_CHANNEL_SETTINGS, CHANNEL_INFO, USER}} = MM_TABLES;
export function prepareMissingChannelsForAllTeams(operator: ServerDataOperator, channels: Channel[], channelMembers: ChannelMembership[]): Array<Promise<Model[]>> {
export function prepareMissingChannelsForAllTeams(operator: ServerDataOperator, channels: Channel[], channelMembers: ChannelMembership[], isCRTEnabled?: boolean): Array<Promise<Model[]>> {
const channelInfos: ChannelInfo[] = [];
const memberships = channelMembers.map((cm) => ({...cm, id: cm.channel_id}));
const channelMap: Record<string, Channel> = {};
for (const c of channels) {
channelMap[c.id] = c;
channelInfos.push({
id: c.id,
header: c.header,
@@ -39,11 +39,21 @@ export function prepareMissingChannelsForAllTeams(operator: ServerDataOperator,
});
}
const memberships = channelMembers.map((cm) => {
const channel = channelMap[cm.channel_id];
return {
...cm,
id: cm.channel_id,
last_post_at: channel.last_post_at,
last_root_post_at: channel.last_root_post_at,
};
});
try {
const channelRecords = operator.handleChannel({channels, prepareRecordsOnly: true});
const channelInfoRecords = operator.handleChannelInfo({channelInfos, prepareRecordsOnly: true});
const membershipRecords = operator.handleChannelMembership({channelMemberships: memberships, prepareRecordsOnly: true});
const myChannelRecords = operator.handleMyChannel({channels, myChannels: memberships, prepareRecordsOnly: true});
const myChannelRecords = operator.handleMyChannel({channels, myChannels: memberships, prepareRecordsOnly: true, isCRTEnabled});
const myChannelSettingsRecords = operator.handleMyChannelSettings({settings: memberships, prepareRecordsOnly: true});
return [channelRecords, channelInfoRecords, membershipRecords, myChannelRecords, myChannelSettingsRecords];
@@ -99,6 +109,7 @@ export const prepareMyChannelsForTeam = async (operator: ServerDataOperator, tea
const member = membershipsMap[c.id];
if (member) {
member.last_post_at = c.last_post_at;
member.last_root_post_at = c.last_root_post_at;
}
channelInfos.push({
@@ -399,6 +410,16 @@ export const observeAllMyChannelNotifyProps = (database: Database) => {
);
};
export const observeNotifyPropsByChannels = (database: Database, channels: ChannelModel[]|MyChannelModel[]) => {
const ids = channels.map((c) => c.id);
return queryMyChannelSettingsByIds(database, ids).observeWithColumns(['notify_props']).pipe(
map$((settings) => settings.reduce<Record<string, Partial<ChannelNotifyProps>>>((obj, setting) => {
obj[setting.id] = setting.notifyProps;
return obj;
}, {})),
);
};
export const queryChannelsByNames = (database: Database, names: string[]) => {
return database.get<ChannelModel>(CHANNEL).query(Q.where('name', Q.oneOf(names)));
};
@@ -563,3 +584,14 @@ export const observeChannelSettings = (database: Database, channelId: string) =>
switchMap((result) => (result.length ? result[0].observe() : of$(undefined))),
);
};
export const observeChannelsByLastPostAt = (database: Database, myChannels: MyChannelModel[], excludeIds?: string[]) => {
const ids = myChannels.map((c) => c.id);
const idsStr = `'${ids.join("','")}'`;
const exclude = excludeIds?.length ? `AND c.id NOT IN ('${excludeIds.join("','")}')` : '';
return database.get<ChannelModel>(CHANNEL).query(
Q.unsafeSqlQuery(`SELECT DISTINCT c.* FROM ${CHANNEL} c INNER JOIN
${MY_CHANNEL} mc ON mc.id=c.id AND c.id IN (${idsStr}) ${exclude}
ORDER BY CASE mc.last_post_at WHEN 0 THEN c.create_at ELSE mc.last_post_at END DESC`),
).observe();
};

View File

@@ -12,9 +12,29 @@ import TestHelper from '@test/test_helper';
import CategoryBody from '.';
import type CategoryModel from '@typings/database/models/servers/category';
import type CategoryChannelModel from '@typings/database/models/servers/category_channel';
import type ChannelModel from '@typings/database/models/servers/channel';
const {SERVER: {CATEGORY}} = MM_TABLES;
jest.mock('@queries/servers/categories', () => {
const Queries = jest.requireActual('@queries/servers/categories');
const switchMap = jest.requireActual('rxjs/operators').switchMap;
const mQ = jest.requireActual('@nozbe/watermelondb').Q;
return {
...Queries,
observeChannelsByCategoryChannelSortOrder: (database: Database, category: CategoryModel, excludeIds?: string[]) => {
return category.categoryChannelsBySortOrder.observeWithColumns(['sort_order']).pipe(
switchMap((categoryChannels: CategoryChannelModel[]) => {
const ids = categoryChannels.filter((cc) => excludeIds?.includes(cc.channelId)).map((cc) => cc.channelId);
return database.get<ChannelModel>('Channel').query(mQ.where('id', mQ.oneOf(ids))).observe();
}),
);
},
};
});
describe('components/channel_list/categories/body', () => {
let database: Database;
let category: CategoryModel;

View File

@@ -16,7 +16,6 @@ import type ChannelModel from '@typings/database/models/servers/channel';
type Props = {
sortedChannels: ChannelModel[];
hiddenChannelIds: Set<string>;
category: CategoryModel;
limit: number;
onChannelSwitch: (channelId: string) => void;
@@ -25,21 +24,16 @@ type Props = {
const extractKey = (item: ChannelModel) => item.id;
const CategoryBody = ({sortedChannels, category, hiddenChannelIds, limit, onChannelSwitch, unreadChannels}: Props) => {
const CategoryBody = ({sortedChannels, category, limit, onChannelSwitch, unreadChannels}: Props) => {
const serverUrl = useServerUrl();
const ids = useMemo(() => {
let filteredChannels = sortedChannels;
// Remove all closed gm/dms
if (hiddenChannelIds.size) {
filteredChannels = sortedChannels.filter((item) => item && !hiddenChannelIds.has(item.id));
}
const filteredChannels = sortedChannels;
if (category.type === DMS_CATEGORY && limit > 0) {
return filteredChannels.slice(0, limit);
}
return filteredChannels;
}, [category.type, limit, hiddenChannelIds, sortedChannels.length]);
}, [category.type, limit, sortedChannels]);
const directChannels = useMemo(() => {
return ids.concat(unreadChannels).filter(isDMorGM);

View File

@@ -1,16 +1,17 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {Database} from '@nozbe/watermelondb';
import {Database, Q} from '@nozbe/watermelondb';
import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider';
import withObservables from '@nozbe/with-observables';
import {combineLatest, of as of$} from 'rxjs';
import {map, switchMap, concatAll, combineLatestWith} from 'rxjs/operators';
import {map, switchMap, combineLatestWith} from 'rxjs/operators';
import {General, Preferences} from '@constants';
import {DMS_CATEGORY} from '@constants/categories';
import {getPreferenceAsBool} from '@helpers/api/preference';
import {observeAllMyChannelNotifyProps, queryChannelsByNames, queryMyChannelSettingsByIds} from '@queries/servers/channel';
import {observeChannelsByCategoryChannelSortOrder, observeChannelsByLastPostAtInCategory} from '@queries/servers/categories';
import {observeNotifyPropsByChannels, queryChannelsByNames} from '@queries/servers/channel';
import {queryPreferencesByCategoryAndName} from '@queries/servers/preference';
import {observeCurrentChannelId, observeCurrentUserId, observeLastUnreadChannelId} from '@queries/servers/system';
import {WithDatabaseArgs} from '@typings/database/database';
@@ -19,16 +20,20 @@ import {getDirectChannelName} from '@utils/channel';
import CategoryBody from './category_body';
import type CategoryModel from '@typings/database/models/servers/category';
import type CategoryChannelModel from '@typings/database/models/servers/category_channel';
import type ChannelModel from '@typings/database/models/servers/channel';
import type MyChannelModel from '@typings/database/models/servers/my_channel';
import type MyChannelSettingsModel from '@typings/database/models/servers/my_channel_settings';
import type PreferenceModel from '@typings/database/models/servers/preference';
type ChannelData = Pick<ChannelModel, 'id' | 'displayName'> & {
isMuted: boolean;
};
type EnhanceProps = {
category: CategoryModel;
locale: string;
currentUserId: string;
isTablet: boolean;
} & WithDatabaseArgs
const sortAlpha = (locale: string, a: ChannelData, b: ChannelData) => {
if (a.isMuted && !b.isMuted) {
return 1;
@@ -43,23 +48,18 @@ const filterArchived = (channels: Array<ChannelModel | null>, currentChannelId:
return channels.filter((c): c is ChannelModel => c != null && ((c.deleteAt > 0 && c.id === currentChannelId) || !c.deleteAt));
};
const buildAlphaData = (channels: ChannelModel[], settings: MyChannelSettingsModel[], locale: string) => {
const settingsById = settings.reduce((result: Record<string, MyChannelSettingsModel>, s) => {
result[s.id] = s;
return result;
}, {});
const buildAlphaData = (channels: ChannelModel[], notifyProps: Record<string, Partial<ChannelNotifyProps>>, locale: string) => {
const chanelsById = channels.reduce((result: Record<string, ChannelModel>, c) => {
result[c.id] = c;
return result;
}, {});
const combined = channels.map((c) => {
const s = settingsById[c.id];
const s = notifyProps[c.id];
return {
id: c.id,
displayName: c.displayName,
isMuted: s?.notifyProps?.mark_unread === General.MENTION,
isMuted: s?.mark_unread === General.MENTION,
};
});
@@ -67,37 +67,20 @@ const buildAlphaData = (channels: ChannelModel[], settings: MyChannelSettingsMod
return of$(combined.map((cdata) => chanelsById[cdata.id]));
};
const observeSettings = (database: Database, channels: ChannelModel[]) => {
const ids = channels.map((c) => c.id);
return queryMyChannelSettingsByIds(database, ids).observeWithColumns(['notify_props']);
};
export const getChannelsFromRelation = async (relations: CategoryChannelModel[] | MyChannelModel[]) => {
return Promise.all(relations.map((r) => r.channel?.fetch()));
};
const getSortedChannels = (database: Database, category: CategoryModel, locale: string) => {
const observeSortedChannels = (database: Database, category: CategoryModel, excludeIds: string[], locale: string) => {
switch (category.sorting) {
case 'alpha': {
const channels = category.channels.observeWithColumns(['display_name']);
const settings = channels.pipe(
switchMap((cs) => observeSettings(database, cs)),
);
return combineLatest([channels, settings]).pipe(
switchMap(([cs, st]) => buildAlphaData(cs, st, locale)),
const channels = category.channels.extend(Q.where('id', Q.notIn(excludeIds))).observeWithColumns(['display_name']);
const notifyProps = channels.pipe(switchMap((cs) => observeNotifyPropsByChannels(database, cs)));
return combineLatest([channels, notifyProps]).pipe(
switchMap(([cs, np]) => buildAlphaData(cs, np, locale)),
);
}
case 'manual': {
return category.categoryChannelsBySortOrder.observeWithColumns(['sort_order']).pipe(
map(getChannelsFromRelation),
concatAll(),
);
return observeChannelsByCategoryChannelSortOrder(database, category, excludeIds);
}
default:
return category.myChannels.observeWithColumns(['last_post_at']).pipe(
map(getChannelsFromRelation),
concatAll(),
);
return observeChannelsByLastPostAtInCategory(database, category, excludeIds);
}
};
@@ -105,25 +88,13 @@ const mapPrefName = (prefs: PreferenceModel[]) => of$(prefs.map((p) => p.name));
const mapChannelIds = (channels: ChannelModel[]) => of$(channels.map((c) => c.id));
type EnhanceProps = {
category: CategoryModel;
locale: string;
currentUserId: string;
isTablet: boolean;
} & WithDatabaseArgs
const withUserId = withObservables([], ({database}: WithDatabaseArgs) => ({currentUserId: observeCurrentUserId(database)}));
const enhance = withObservables(['category', 'isTablet', 'locale'], ({category, locale, isTablet, database, currentUserId}: EnhanceProps) => {
const dmMap = (p: PreferenceModel) => getDirectChannelName(p.name, currentUserId);
const observedCategory = category.observe();
const currentChannelId = observeCurrentChannelId(database);
const sortedChannels = observedCategory.pipe(
switchMap((c) => getSortedChannels(database, c, locale)),
combineLatestWith(currentChannelId),
map(([cs, ccId]) => filterArchived(cs, ccId)),
);
const dmMap = (p: PreferenceModel) => getDirectChannelName(p.name, currentUserId);
const hiddenDmIds = queryPreferencesByCategoryAndName(database, Preferences.CATEGORY_DIRECT_CHANNEL_SHOW, undefined, 'false').
observeWithColumns(['value']).pipe(
@@ -137,8 +108,19 @@ const enhance = withObservables(['category', 'isTablet', 'locale'], ({category,
}),
);
const hiddenGmIds = queryPreferencesByCategoryAndName(database, Preferences.CATEGORY_GROUP_CHANNEL_SHOW, undefined, 'false').
observeWithColumns(['value']).pipe(switchMap(mapPrefName));
const hiddenChannelIds = queryPreferencesByCategoryAndName(database, Preferences.CATEGORY_GROUP_CHANNEL_SHOW, undefined, 'false').
observeWithColumns(['value']).pipe(
switchMap(mapPrefName),
combineLatestWith(hiddenDmIds),
switchMap(([a, b]) => of$(new Set(a.concat(b)))),
);
const sortedChannels = observedCategory.pipe(
combineLatestWith(hiddenChannelIds),
switchMap(([c, excludeIds]) => observeSortedChannels(database, c, Array.from(excludeIds), locale)),
combineLatestWith(currentChannelId),
map(([channels, ccId]) => filterArchived(channels, ccId)),
);
let limit = of$(Preferences.CHANNEL_SIDEBAR_LIMIT_DMS_DEFAULT);
if (category.type === DMS_CATEGORY) {
@@ -150,19 +132,15 @@ const enhance = withObservables(['category', 'isTablet', 'locale'], ({category,
);
}
const hiddenChannelIds = combineLatest([hiddenDmIds, hiddenGmIds]).pipe(switchMap(
([a, b]) => of$(new Set(a.concat(b))),
));
const unreadsOnTop = queryPreferencesByCategoryAndName(database, Preferences.CATEGORY_SIDEBAR_SETTINGS, Preferences.CHANNEL_SIDEBAR_GROUP_UNREADS).
observeWithColumns(['value']).
pipe(
switchMap((prefs: PreferenceModel[]) => of$(getPreferenceAsBool(prefs, Preferences.CATEGORY_SIDEBAR_SETTINGS, Preferences.CHANNEL_SIDEBAR_GROUP_UNREADS, false))),
);
const notifyProps = observeAllMyChannelNotifyProps(database);
const lastUnreadId = isTablet ? observeLastUnreadChannelId(database) : of$(undefined);
const unreadChannels = category.myChannels.observeWithColumns(['mentions_count', 'is_unread']);
const notifyProps = unreadChannels.pipe(switchMap((myChannels) => observeNotifyPropsByChannels(database, myChannels)));
const filterUnreads = unreadChannels.pipe(
combineLatestWith(notifyProps, lastUnreadId),
map(([my, settings, lastUnread]) => {
@@ -185,7 +163,6 @@ const enhance = withObservables(['category', 'isTablet', 'locale'], ({category,
return {
limit,
hiddenChannelIds,
sortedChannels: filtered,
category: observedCategory,
unreadChannels: sortedChannels.pipe(

View File

@@ -3,17 +3,15 @@
import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider';
import withObservables from '@nozbe/with-observables';
import {of as of$, combineLatest} from 'rxjs';
import {combineLatestWith, concatAll, map, switchMap} from 'rxjs/operators';
import {of as of$} from 'rxjs';
import {combineLatestWith, map, switchMap} from 'rxjs/operators';
import {Preferences} from '@constants';
import {getPreferenceAsBool} from '@helpers/api/preference';
import {getChannelById, observeAllMyChannelNotifyProps, queryMyChannelUnreads} from '@queries/servers/channel';
import {getChannelById, observeChannelsByLastPostAt, observeNotifyPropsByChannels, queryMyChannelUnreads} from '@queries/servers/channel';
import {queryPreferencesByCategoryAndName} from '@queries/servers/preference';
import {observeLastUnreadChannelId} from '@queries/servers/system';
import {getChannelsFromRelation} from '../body';
import UnreadCategories from './unreads';
import type {WithDatabaseArgs} from '@typings/database/database';
@@ -32,36 +30,31 @@ type CA = [
b: ChannelModel | undefined,
]
const concatenateChannelsArray = ([a, b]: CA) => {
return of$(b ? a.filter((c) => c && c.id !== b.id).concat(b) : a);
};
type NotifyProps = {
[key: string]: Partial<ChannelNotifyProps>;
}
const mostRecentFirst = (a: MyChannelModel, b: MyChannelModel) => {
return b.lastPostAt - a.lastPostAt;
};
/**
* Filtering / Sorting:
*
* Unreads, Mentions, and Muted Mentions Only
*
* Mentions on top, then unreads, then muted channels with mentions.
* Secondary sorting within each of those is by recent posting or by recent root post if CRT is enabled.
*/
type FilterAndSortMyChannelsArgs = [
type FilterAndSortMyChannelsArgs = [
MyChannelModel[],
Record<string, ChannelModel>,
NotifyProps,
]
const filterAndSortMyChannels = ([myChannels, notifyProps]: FilterAndSortMyChannelsArgs): MyChannelModel[] => {
const mentions: MyChannelModel[] = [];
const unreads: MyChannelModel[] = [];
const mutedMentions: MyChannelModel[] = [];
const concatenateChannelsArray = ([a, b]: CA) => {
return of$(b ? a.filter((c) => c && c.id !== b.id).concat(b) : a);
};
const filterAndSortMyChannels = ([myChannels, channels, notifyProps]: FilterAndSortMyChannelsArgs): ChannelModel[] => {
const mentions: ChannelModel[] = [];
const unreads: ChannelModel[] = [];
const mutedMentions: ChannelModel[] = [];
const isMuted = (id: string) => {
return notifyProps[id]?.mark_unread === 'mention';
@@ -71,32 +64,34 @@ const filterAndSortMyChannels = ([myChannels, notifyProps]: FilterAndSortMyChann
const id = myChannel.id;
// is it a mention?
if (!isMuted(id) && myChannel.mentionsCount > 0) {
mentions.push(myChannel);
if (!isMuted(id) && myChannel.mentionsCount > 0 && channels[id]) {
mentions.push(channels[id]);
continue;
}
// is it unread?
if (!isMuted(myChannel.id) && myChannel.isUnread) {
unreads.push(myChannel);
if (!isMuted(myChannel.id) && myChannel.isUnread && channels[id]) {
unreads.push(channels[id]);
continue;
}
// is it a muted mention?
if (isMuted(myChannel.id) && myChannel.mentionsCount > 0) {
mutedMentions.push(myChannel);
if (isMuted(myChannel.id) && myChannel.mentionsCount > 0 && channels[id]) {
mutedMentions.push(channels[id]);
continue;
}
}
// Sort
mentions.sort(mostRecentFirst);
unreads.sort(mostRecentFirst);
mutedMentions.sort(mostRecentFirst);
return [...mentions, ...unreads, ...mutedMentions];
};
const makeChannelsMap = (channels: ChannelModel[]) => {
return channels.reduce<Record<string, ChannelModel>>((result, c) => {
result[c.id] = c;
return result;
}, {});
};
const enhanced = withObservables(['currentTeamId', 'isTablet', 'onlyUnreads'], ({currentTeamId, isTablet, database, onlyUnreads}: WithDatabaseProps) => {
const unreadsOnTop = queryPreferencesByCategoryAndName(database, Preferences.CATEGORY_SIDEBAR_SETTINGS, Preferences.CHANNEL_SIDEBAR_GROUP_UNREADS).
observeWithColumns(['value']).
@@ -108,23 +103,18 @@ const enhanced = withObservables(['currentTeamId', 'isTablet', 'onlyUnreads'], (
const unreadChannels = unreadsOnTop.pipe(switchMap((gU) => {
if (gU || onlyUnreads) {
const lastUnread = isTablet ? observeLastUnreadChannelId(database).pipe(
switchMap(getC),
) : of$('');
const notifyProps = observeAllMyChannelNotifyProps(database);
const lastUnread = isTablet ? observeLastUnreadChannelId(database).pipe(switchMap(getC)) : of$(undefined);
const myUnreadChannels = queryMyChannelUnreads(database, currentTeamId).observeWithColumns(['last_post_at']);
const notifyProps = myUnreadChannels.pipe(switchMap((cs) => observeNotifyPropsByChannels(database, cs)));
const channels = myUnreadChannels.pipe(switchMap((myChannels) => observeChannelsByLastPostAt(database, myChannels)));
const channelsMap = channels.pipe(switchMap((cs) => of$(makeChannelsMap(cs))));
const unreads = queryMyChannelUnreads(database, currentTeamId).observeWithColumns(['last_post_at']).pipe(
combineLatestWith(notifyProps),
return queryMyChannelUnreads(database, currentTeamId).observeWithColumns(['last_post_at']).pipe(
combineLatestWith(channelsMap, notifyProps),
map(filterAndSortMyChannels),
map(getChannelsFromRelation),
concatAll(),
);
const combined = combineLatest([unreads, lastUnread]).pipe(
combineLatestWith(lastUnread),
switchMap(concatenateChannelsArray),
);
return combined;
}
return of$([]);
}));

View File

@@ -27,6 +27,7 @@ type Channel = {
header: string;
purpose: string;
last_post_at: number;
last_root_post_at?: number;
total_msg_count: number;
total_msg_count_root?: number;
extra_update_at: number;