forked from Ivasoft/mattermost-mobile
[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:
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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)),
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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();
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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$([]);
|
||||
}));
|
||||
|
||||
1
types/api/channels.d.ts
vendored
1
types/api/channels.d.ts
vendored
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user