mirror of
https://github.com/fluxerapp/fluxer
synced 2026-10-18 13:35:42 +09:00
518 lines
20 KiB
TypeScript
518 lines
20 KiB
TypeScript
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
|
|
import {
|
|
compareSearchResults,
|
|
type ForwardChannelCandidate,
|
|
type ForwardSearchResult,
|
|
type ForwardUserCandidate,
|
|
searchChannels,
|
|
searchGroupDMs,
|
|
searchGuilds,
|
|
searchUsers,
|
|
} from '@app/features/app/components/dialogs/shared/ForwardDestinationSearch';
|
|
import type {Channel} from '@app/features/channel/models/Channel';
|
|
import Channels from '@app/features/channel/state/Channels';
|
|
import {compareChannels} from '@app/features/channel/utils/ChannelUtils';
|
|
import Guilds from '@app/features/guild/state/Guilds';
|
|
import {MENTIONS_DESCRIPTOR} from '@app/features/i18n/utils/CommonMessageDescriptors';
|
|
import type {GuildMember} from '@app/features/member/models/GuildMember';
|
|
import GuildMembers from '@app/features/member/state/GuildMembers';
|
|
import Messages from '@app/features/messaging/state/MessagingMessages';
|
|
import Navigation from '@app/features/navigation/state/Navigation';
|
|
import SelectedChannel from '@app/features/navigation/state/SelectedChannel';
|
|
import SelectedGuild from '@app/features/navigation/state/SelectedGuild';
|
|
import Permission from '@app/features/permissions/state/Permission';
|
|
import ReadStates from '@app/features/read_state/state/ReadStates';
|
|
import Relationships from '@app/features/relationship/state/Relationships';
|
|
import {buildChannelCandidate} from '@app/features/search/state/QuickSwitcherCandidateBuilder';
|
|
import {candidateToResult, createHeaderResult} from '@app/features/search/state/QuickSwitcherResultConverters';
|
|
import type {
|
|
Candidate,
|
|
CandidateSets,
|
|
QuickSwitcherExecutableResult,
|
|
QuickSwitcherQueryMode,
|
|
QuickSwitcherResult,
|
|
UserCandidate,
|
|
} from '@app/features/search/state/QuickSwitcherTypes';
|
|
import type {ForwardSearchCandidates} from '@app/features/search/utils/DestinationSearchSources';
|
|
import {createSearchTerm, scoreSearchTerm} from '@app/features/search/utils/SearchTextMatching';
|
|
import UserGuildSettings from '@app/features/user/state/UserGuildSettings';
|
|
import Users from '@app/features/user/state/Users';
|
|
import * as NicknameUtils from '@app/features/user/utils/NicknameUtils';
|
|
import {ME} from '@fluxer/constants/src/AppConstants';
|
|
import {ChannelTypes, Permissions} from '@fluxer/constants/src/ChannelConstants';
|
|
import {MessageNotifications} from '@fluxer/constants/src/NotificationConstants';
|
|
import {QuickSwitcherResultTypes} from '@fluxer/constants/src/QuickSwitcherConstants';
|
|
import {RelationshipTypes} from '@fluxer/constants/src/UserConstants';
|
|
import type {I18n} from '@lingui/core';
|
|
import {msg} from '@lingui/core/macro';
|
|
|
|
const PREVIOUS_CHANNELS_DESCRIPTOR = msg({
|
|
message: 'Previous channels',
|
|
comment: 'Quick switcher section header above the channels the user visited most recently.',
|
|
});
|
|
const UNREAD_CHANNELS_DESCRIPTOR = msg({
|
|
message: 'Unread channels',
|
|
comment: 'Quick switcher section header above the unread channels of the current community.',
|
|
});
|
|
const SEARCHING_TEXT_CHANNELS_DESCRIPTOR = msg({
|
|
message: 'Searching text channels',
|
|
comment: 'Quick switcher header shown while the query starts with # and only text channels are searched.',
|
|
});
|
|
const SEARCHING_VOICE_CHANNELS_DESCRIPTOR = msg({
|
|
message: 'Searching voice channels',
|
|
comment: 'Quick switcher header shown while the query starts with ! and only voice channels are searched.',
|
|
});
|
|
const SEARCHING_COMMUNITIES_DESCRIPTOR = msg({
|
|
message: 'Searching communities',
|
|
comment: 'Quick switcher header shown while the query starts with * and only communities are searched.',
|
|
});
|
|
const SEARCHING_ALL_USERS_DESCRIPTOR = msg({
|
|
message: 'Searching all users',
|
|
comment: 'Quick switcher header shown while the query starts with @ outside a community, or with @@.',
|
|
});
|
|
const SEARCHING_USERS_IN_COMMUNITY_DESCRIPTOR = msg({
|
|
message: 'Searching friends and members of {communityName}',
|
|
comment:
|
|
'Quick switcher header shown while the query starts with @ inside a community. communityName is the community name.',
|
|
});
|
|
|
|
const HISTORY_LIMIT = 8;
|
|
const PREVIOUS_CHANNELS_WITH_SECTIONS = 3;
|
|
const PREVIOUS_CHANNELS_ALONE = 7;
|
|
const HISTORY_HIDDEN_FROM_SECTIONS = 3;
|
|
const CATEGORY_LIMIT = 5;
|
|
const MODE_LIMIT = 100;
|
|
const RECENT_AUTHOR_LIMIT = 100;
|
|
const SCORE_SCALE = 1000;
|
|
const LISTING_TEXT_SCORE = 7;
|
|
const LISTING_VOICE_IN_TEXT_SCORE = 6;
|
|
const LISTING_FRECENCY_BONUS = 3;
|
|
|
|
export type QuickSwitcherModeVariant = 'all_users' | null;
|
|
|
|
interface RankedCandidate {
|
|
readonly candidate: Candidate;
|
|
readonly matchedText: string;
|
|
readonly score: number;
|
|
}
|
|
|
|
interface SearchContext {
|
|
readonly confusables: ReadonlyMap<string, string>;
|
|
readonly sets: CandidateSets;
|
|
readonly sources: ForwardSearchCandidates;
|
|
}
|
|
|
|
function getCurrentChannelId(): string | null {
|
|
return Navigation.channelId ?? SelectedChannel.currentChannelId ?? null;
|
|
}
|
|
|
|
function getCurrentGuildId(): string | null {
|
|
const guildId = SelectedGuild.selectedGuildId;
|
|
if (guildId == null || guildId === ME || Guilds.getGuild(guildId) == null) return null;
|
|
return guildId;
|
|
}
|
|
|
|
function isChannelVisible(channel: Channel): boolean {
|
|
if (channel.guildId == null) return true;
|
|
return Permission.can(Permissions.VIEW_CHANNEL, channel);
|
|
}
|
|
|
|
function channelResult(channel: Channel, i18n: I18n): QuickSwitcherExecutableResult | null {
|
|
const candidate = buildChannelCandidate(channel, i18n);
|
|
return candidate == null ? null : candidateToResult(candidate, i18n);
|
|
}
|
|
|
|
function collectChannelResults(
|
|
channelIds: ReadonlyArray<string>,
|
|
i18n: I18n,
|
|
seen: Set<string>,
|
|
): Array<QuickSwitcherExecutableResult> {
|
|
const results: Array<QuickSwitcherExecutableResult> = [];
|
|
for (const channelId of channelIds) {
|
|
if (seen.has(channelId)) continue;
|
|
const channel = Channels.getChannel(channelId);
|
|
if (channel == null || !isChannelVisible(channel)) continue;
|
|
const result = channelResult(channel, i18n);
|
|
if (result == null) continue;
|
|
seen.add(channelId);
|
|
results.push(result);
|
|
}
|
|
return results;
|
|
}
|
|
|
|
function isUnreadInCurrentCommunity(channel: Channel): boolean {
|
|
if (channel.type !== ChannelTypes.GUILD_TEXT || !isChannelVisible(channel)) return false;
|
|
if (UserGuildSettings.isMutedAtAnyLevel(channel.guildId ?? null, channel.id)) return false;
|
|
if (!ReadStates.hasUnread(channel.id)) return false;
|
|
const unreadLevel = UserGuildSettings.resolvedGuildUnreadBadgesLevel({
|
|
id: channel.id,
|
|
guildId: channel.guildId ?? undefined,
|
|
parentId: channel.parentId ?? undefined,
|
|
type: channel.type,
|
|
});
|
|
return unreadLevel === MessageNotifications.ALL_MESSAGES;
|
|
}
|
|
|
|
export function generateDefaultResults(i18n: I18n): Array<QuickSwitcherResult> {
|
|
const currentChannelId = getCurrentChannelId();
|
|
const history = [...new Set(SelectedChannel.sortedRecentVisits.map((visit) => visit.channelId))]
|
|
.filter((channelId) => channelId !== currentChannelId)
|
|
.slice(0, HISTORY_LIMIT - 1);
|
|
const previous = collectChannelResults(history, i18n, new Set());
|
|
const seen = new Set(previous.slice(0, HISTORY_HIDDEN_FROM_SECTIONS).map((result) => resultChannelId(result)));
|
|
if (currentChannelId != null) seen.add(currentChannelId);
|
|
const sections: Array<QuickSwitcherResult> = [];
|
|
const mentions = collectChannelResults([...ReadStates.mentionChannelIds].reverse(), i18n, seen);
|
|
if (mentions.length > 0) {
|
|
sections.push(createHeaderResult('section-mentions', i18n._(MENTIONS_DESCRIPTOR)), ...mentions);
|
|
}
|
|
const guildId = getCurrentGuildId();
|
|
if (guildId != null) {
|
|
const unreadIds = [...Channels.getGuildChannels(guildId)]
|
|
.filter(isUnreadInCurrentCommunity)
|
|
.sort(compareChannels)
|
|
.map((channel) => channel.id);
|
|
const unread = collectChannelResults(unreadIds, i18n, seen);
|
|
if (unread.length > 0) {
|
|
sections.push(createHeaderResult('section-unread', i18n._(UNREAD_CHANNELS_DESCRIPTOR)), ...unread);
|
|
}
|
|
}
|
|
const previousCap = sections.length > 0 ? PREVIOUS_CHANNELS_WITH_SECTIONS : PREVIOUS_CHANNELS_ALONE;
|
|
const shownPrevious = previous.slice(0, previousCap);
|
|
if (shownPrevious.length === 0) return sections;
|
|
return [createHeaderResult('section-previous', i18n._(PREVIOUS_CHANNELS_DESCRIPTOR)), ...shownPrevious, ...sections];
|
|
}
|
|
|
|
function resultChannelId(result: QuickSwitcherExecutableResult): string {
|
|
if (result.type === QuickSwitcherResultTypes.USER) return result.dmChannelId ?? result.id;
|
|
return result.id;
|
|
}
|
|
|
|
function getModeHeader(
|
|
prefixMode: QuickSwitcherQueryMode,
|
|
variant: QuickSwitcherModeVariant,
|
|
i18n: I18n,
|
|
): QuickSwitcherResult {
|
|
switch (prefixMode) {
|
|
case QuickSwitcherResultTypes.USER: {
|
|
const guild = variant === 'all_users' ? null : getCurrentGuildId();
|
|
const title =
|
|
guild == null
|
|
? i18n._(SEARCHING_ALL_USERS_DESCRIPTOR)
|
|
: i18n._(SEARCHING_USERS_IN_COMMUNITY_DESCRIPTOR, {communityName: Guilds.getGuild(guild)?.name ?? ''});
|
|
return createHeaderResult(`query-${prefixMode}`, title);
|
|
}
|
|
case QuickSwitcherResultTypes.TEXT_CHANNEL:
|
|
return createHeaderResult(`query-${prefixMode}`, i18n._(SEARCHING_TEXT_CHANNELS_DESCRIPTOR));
|
|
case QuickSwitcherResultTypes.VOICE_CHANNEL:
|
|
return createHeaderResult(`query-${prefixMode}`, i18n._(SEARCHING_VOICE_CHANNELS_DESCRIPTOR));
|
|
default:
|
|
return createHeaderResult(`query-${prefixMode}`, i18n._(SEARCHING_COMMUNITIES_DESCRIPTOR));
|
|
}
|
|
}
|
|
|
|
function buildUserCandidate(userId: string, sets: CandidateSets): UserCandidate | null {
|
|
const existing = sets.users.find((candidate) => candidate.id === userId);
|
|
if (existing != null) return existing;
|
|
const user = Users.getUser(userId);
|
|
if (user == null) return null;
|
|
const title = NicknameUtils.getNickname(user, null);
|
|
const subtitle = NicknameUtils.formatUserTagForStreamerMode(user);
|
|
return {
|
|
type: QuickSwitcherResultTypes.USER,
|
|
id: user.id,
|
|
title,
|
|
subtitle,
|
|
user,
|
|
dmChannelId: null,
|
|
searchValues: [title, subtitle],
|
|
sortWeight: 0,
|
|
};
|
|
}
|
|
|
|
function resolveSearchCandidate(result: ForwardSearchResult, sets: CandidateSets): Candidate | null {
|
|
switch (result.type) {
|
|
case 'user':
|
|
return buildUserCandidate(result.id, sets);
|
|
case 'group_dm':
|
|
return sets.groupDMByChannelId.get(result.id) ?? sets.userByChannelId.get(result.id) ?? null;
|
|
default:
|
|
return sets.channelById.get(result.id) ?? null;
|
|
}
|
|
}
|
|
|
|
function toRanked(results: ReadonlyArray<ForwardSearchResult>, sets: CandidateSets): Array<RankedCandidate> {
|
|
const ranked: Array<RankedCandidate> = [];
|
|
for (const result of results) {
|
|
const candidate = resolveSearchCandidate(result, sets);
|
|
if (candidate != null) ranked.push({candidate, matchedText: result.matchedText, score: result.score});
|
|
}
|
|
return ranked;
|
|
}
|
|
|
|
function compareRanked(left: RankedCandidate, right: RankedCandidate): number {
|
|
return compareSearchResults(
|
|
{matchedText: left.matchedText, id: left.candidate.id, score: left.score, type: rankedType(left)},
|
|
{matchedText: right.matchedText, id: right.candidate.id, score: right.score, type: rankedType(right)},
|
|
);
|
|
}
|
|
|
|
function rankedType(ranked: RankedCandidate): ForwardSearchResult['type'] {
|
|
return ranked.candidate.type === QuickSwitcherResultTypes.USER ? 'user' : 'text_channel';
|
|
}
|
|
|
|
function rankedToResult(ranked: RankedCandidate, i18n: I18n): QuickSwitcherExecutableResult {
|
|
const result = candidateToResult(ranked.candidate, i18n);
|
|
if (result.type !== QuickSwitcherResultTypes.USER) return result;
|
|
const {user} = result;
|
|
const tag = NicknameUtils.formatUserTagForStreamerMode(user);
|
|
const matched = ranked.matchedText;
|
|
if (matched === '' || matched === user.username || matched === user.id || matched === tag) return result;
|
|
return {...result, title: NicknameUtils.formatNicknameForStreamerMode(matched)};
|
|
}
|
|
|
|
function currentUserBlacklist(): ReadonlySet<string> {
|
|
const currentUserId = Users.currentUserId;
|
|
return new Set(currentUserId == null ? [] : [currentUserId]);
|
|
}
|
|
|
|
function mergeMemberSearchUsers(
|
|
users: ReadonlyArray<ForwardUserCandidate>,
|
|
memberSearchResults: ReadonlyArray<GuildMember>,
|
|
): ReadonlyArray<ForwardUserCandidate> {
|
|
if (memberSearchResults.length === 0) return users;
|
|
const known = new Set(users.map((user) => user.id));
|
|
const extra: Array<ForwardUserCandidate> = [];
|
|
for (const member of memberSearchResults) {
|
|
if (known.has(member.user.id)) continue;
|
|
known.add(member.user.id);
|
|
extra.push({
|
|
friendAlias: null,
|
|
globalName: member.user.globalName,
|
|
id: member.user.id,
|
|
nicknames: member.nick == null ? [] : [member.nick],
|
|
username: member.user.username,
|
|
});
|
|
}
|
|
return [...users, ...extra];
|
|
}
|
|
|
|
function filterUsersToCommunity(
|
|
users: ReadonlyArray<ForwardUserCandidate>,
|
|
guildId: string,
|
|
): ReadonlyArray<ForwardUserCandidate> {
|
|
return users.filter((user) => {
|
|
if (Relationships.getRelationship(user.id)?.type === RelationshipTypes.FRIEND) return true;
|
|
return GuildMembers.getMember(guildId, user.id) != null;
|
|
});
|
|
}
|
|
|
|
function scoreNavigationCandidates(query: string, candidates: ReadonlyArray<Candidate>, limit: number) {
|
|
const term = createSearchTerm(query.toLocaleLowerCase());
|
|
const ranked: Array<RankedCandidate> = [];
|
|
for (const candidate of candidates) {
|
|
const score = scoreSearchTerm(candidate.title.toLocaleLowerCase(), term);
|
|
if (score > 0) ranked.push({candidate, matchedText: candidate.title, score: SCORE_SCALE * score});
|
|
}
|
|
return ranked.sort((left, right) => right.score - left.score).slice(0, limit);
|
|
}
|
|
|
|
function searchGuildCandidates(
|
|
query: string,
|
|
context: SearchContext,
|
|
limit: number,
|
|
excludedIds: ReadonlySet<string>,
|
|
): Array<RankedCandidate> {
|
|
const guildById = new Map(context.sets.guilds.map((candidate) => [candidate.id, candidate]));
|
|
const ranked: Array<RankedCandidate> = [];
|
|
for (const result of searchGuilds(
|
|
query,
|
|
context.sources.guilds,
|
|
context.sources.weights.guilds,
|
|
excludedIds,
|
|
limit,
|
|
true,
|
|
)) {
|
|
const candidate = guildById.get(result.id);
|
|
if (candidate != null) ranked.push({candidate, matchedText: result.matchedText, score: result.score});
|
|
}
|
|
return ranked;
|
|
}
|
|
|
|
function currentGuildBlacklist(): ReadonlySet<string> {
|
|
const guildId = getCurrentGuildId();
|
|
return new Set(guildId == null ? [] : [guildId]);
|
|
}
|
|
|
|
export function generateGeneralResults(search: string, context: SearchContext, i18n: I18n): Array<QuickSwitcherResult> {
|
|
const {confusables, sets, sources} = context;
|
|
const runCategories = (limit: number): Array<Array<RankedCandidate>> => [
|
|
toRanked(
|
|
searchUsers(search, sources.users, sources.weights.users, currentUserBlacklist(), confusables, limit),
|
|
sets,
|
|
),
|
|
toRanked(searchGroupDMs(search, sources.groupDMs, sources.weights.groupDMs, confusables, limit), sets),
|
|
toRanked(searchChannels(search, sources.channels, 'text', sources.weights.textChannel, limit, true), sets),
|
|
searchGuildCandidates(search, context, limit, currentGuildBlacklist()),
|
|
scoreNavigationCandidates(search, sets.virtualGuilds, limit),
|
|
scoreNavigationCandidates(search, sets.settings, limit),
|
|
];
|
|
let categories = runCategories(CATEGORY_LIMIT);
|
|
if (categories.filter((category) => category.length > 0).length === 1) {
|
|
const index = categories.findIndex((category) => category.length > 0);
|
|
categories = categories.map((category, categoryIndex) =>
|
|
categoryIndex === index ? runCategories(MODE_LIMIT)[index] : category,
|
|
);
|
|
}
|
|
const seen = new Set<string>();
|
|
return categories
|
|
.flat()
|
|
.filter((ranked) => {
|
|
const key = `${ranked.candidate.type}|${ranked.candidate.id}`;
|
|
if (seen.has(key)) return false;
|
|
seen.add(key);
|
|
return true;
|
|
})
|
|
.sort(compareRanked)
|
|
.map((ranked) => rankedToResult(ranked, i18n));
|
|
}
|
|
|
|
function listRecentAuthors(sets: CandidateSets): Array<Candidate> {
|
|
const channelId = getCurrentChannelId();
|
|
if (channelId == null) return [];
|
|
const channel = Channels.getChannel(channelId);
|
|
const currentUserId = Users.currentUserId;
|
|
const authors: Array<Candidate> = [];
|
|
const seen = new Set<string>();
|
|
Messages.getCachedMessages(channelId)?.searchFromNewest((message) => {
|
|
const author = message.author;
|
|
if (author.id === currentUserId || author.bot || seen.has(author.id)) return false;
|
|
if (channel?.guildId != null && GuildMembers.getMember(channel.guildId, author.id) == null) return false;
|
|
seen.add(author.id);
|
|
const candidate = buildUserCandidate(author.id, sets);
|
|
if (candidate != null) authors.push(candidate);
|
|
return authors.length >= RECENT_AUTHOR_LIMIT;
|
|
});
|
|
return authors;
|
|
}
|
|
|
|
function listChannels(kind: 'text' | 'voice', context: SearchContext): Array<Candidate> {
|
|
const guildId = getCurrentGuildId();
|
|
const guildOrder = new Map(Guilds.getGuilds().map((guild, index) => [guild.id, index]));
|
|
const pool: Array<ForwardChannelCandidate> = context.sources.channels.filter((channel) => {
|
|
if (!channel.canAccess) return false;
|
|
if (kind === 'voice' && channel.kind !== 'voice') return false;
|
|
if (kind === 'text' && channel.kind === 'voice' && guildId != null) return false;
|
|
if (guildId == null) return true;
|
|
return Channels.getChannel(channel.id)?.guildId === guildId;
|
|
});
|
|
const scoreOf = (channel: ForwardChannelCandidate): number => {
|
|
const base = kind === 'text' && channel.kind === 'voice' ? LISTING_VOICE_IN_TEXT_SCORE : LISTING_TEXT_SCORE;
|
|
return channel.hasFrecency ? Math.min(base + LISTING_FRECENCY_BONUS, base >= 7 ? 10 : 7) : base;
|
|
};
|
|
const channelOf = (id: string): Channel | undefined => Channels.getChannel(id);
|
|
return pool
|
|
.map((channel) => ({channel, model: channelOf(channel.id), score: scoreOf(channel)}))
|
|
.filter((entry): entry is {channel: ForwardChannelCandidate; model: Channel; score: number} => entry.model != null)
|
|
.sort((left, right) => {
|
|
if (left.score !== right.score) return right.score - left.score;
|
|
const leftGuild = guildOrder.get(left.model.guildId ?? '') ?? 0;
|
|
const rightGuild = guildOrder.get(right.model.guildId ?? '') ?? 0;
|
|
if (leftGuild !== rightGuild) return leftGuild - rightGuild;
|
|
return compareChannels(left.model, right.model);
|
|
})
|
|
.slice(0, MODE_LIMIT)
|
|
.map((entry) => context.sets.channelById.get(entry.channel.id))
|
|
.filter((candidate): candidate is NonNullable<typeof candidate> => candidate != null);
|
|
}
|
|
|
|
function searchModeUsers(
|
|
search: string,
|
|
variant: QuickSwitcherModeVariant,
|
|
context: SearchContext,
|
|
memberSearchResults: ReadonlyArray<GuildMember>,
|
|
): Array<RankedCandidate> {
|
|
const guildId = variant === 'all_users' ? null : getCurrentGuildId();
|
|
let users = mergeMemberSearchUsers(context.sources.users, memberSearchResults);
|
|
if (guildId != null) users = filterUsersToCommunity(users, guildId);
|
|
return toRanked(
|
|
searchUsers(search, users, context.sources.weights.users, currentUserBlacklist(), context.confusables, MODE_LIMIT),
|
|
context.sets,
|
|
);
|
|
}
|
|
|
|
export function generateQueryModeResults(
|
|
prefixMode: QuickSwitcherQueryMode,
|
|
variant: QuickSwitcherModeVariant,
|
|
search: string,
|
|
context: SearchContext,
|
|
i18n: I18n,
|
|
memberSearchResults: ReadonlyArray<GuildMember>,
|
|
): Array<QuickSwitcherResult> {
|
|
const header = getModeHeader(prefixMode, variant, i18n);
|
|
const {sets, sources} = context;
|
|
if (search.length === 0) {
|
|
let listed: Array<Candidate>;
|
|
switch (prefixMode) {
|
|
case QuickSwitcherResultTypes.USER:
|
|
listed = listRecentAuthors(sets);
|
|
break;
|
|
case QuickSwitcherResultTypes.TEXT_CHANNEL:
|
|
listed = listChannels('text', context);
|
|
break;
|
|
case QuickSwitcherResultTypes.VOICE_CHANNEL:
|
|
listed = listChannels('voice', context);
|
|
break;
|
|
default:
|
|
listed = [...sets.guilds, ...sets.virtualGuilds];
|
|
break;
|
|
}
|
|
return [header, ...listed.map((candidate) => candidateToResult(candidate, i18n))];
|
|
}
|
|
let ranked: Array<RankedCandidate>;
|
|
switch (prefixMode) {
|
|
case QuickSwitcherResultTypes.USER:
|
|
ranked = searchModeUsers(search, variant, context, memberSearchResults);
|
|
break;
|
|
case QuickSwitcherResultTypes.TEXT_CHANNEL:
|
|
ranked = toRanked(
|
|
searchChannels(search, sources.channels, 'text', sources.weights.textChannel, MODE_LIMIT, true),
|
|
sets,
|
|
);
|
|
break;
|
|
case QuickSwitcherResultTypes.VOICE_CHANNEL:
|
|
ranked = toRanked(
|
|
searchChannels(search, sources.channels, 'voice', sources.weights.voiceChannel, MODE_LIMIT, true),
|
|
sets,
|
|
);
|
|
break;
|
|
default:
|
|
ranked = [
|
|
...searchGuildCandidates(search, context, MODE_LIMIT, currentGuildBlacklist()),
|
|
...scoreNavigationCandidates(search, sets.virtualGuilds, MODE_LIMIT),
|
|
].sort(compareRanked);
|
|
break;
|
|
}
|
|
return [header, ...ranked.map((entry) => rankedToResult(entry, i18n))];
|
|
}
|
|
|
|
export function resolveTransformedMember(member: {id: string; guildIds?: Array<string>}): GuildMember | null {
|
|
const guildIds = member.guildIds ?? [];
|
|
for (const guildId of guildIds) {
|
|
const record = GuildMembers.getMember(guildId, member.id);
|
|
if (record) {
|
|
return record;
|
|
}
|
|
}
|
|
for (const guild of Guilds.getGuilds()) {
|
|
const record = GuildMembers.getMember(guild.id, member.id);
|
|
if (record) {
|
|
return record;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
export type {SearchContext as QuickSwitcherSearchContext};
|