87 lines
3.5 KiB
TypeScript
87 lines
3.5 KiB
TypeScript
import { CHAT_PERMISSIONS } from "@hivegrid/chat";
|
|
import { buildChatMessageDeepLink } from "@hivegrid/chat/deep-links";
|
|
import { listChatMessageDiscovery, type ChatMessageDiscoveryRecord } from "@hivegrid/chat/message-discovery.server";
|
|
import { buildChatMessageDiscoveryVM, type ChatMessageDiscoveryMode, type ChatMessageDiscoveryVM } from "@hivegrid/view-models";
|
|
|
|
import { requireChatPermission, type ChatRouteContext } from "../routes/chatAccess.server";
|
|
import { chatIndexPath, chatMentionsPath, chatSearchPath } from "../routes/chatRoutePaths";
|
|
|
|
export type ChatMessageDiscoveryLabels = Readonly<{
|
|
directMessage: string;
|
|
unnamedGroup: string;
|
|
unnamedProject: string;
|
|
unknownUser: string;
|
|
}>;
|
|
|
|
export async function loadChatMessageDiscovery(input: {
|
|
readonly context: ChatRouteContext;
|
|
readonly tenantSlug: string;
|
|
readonly mode: ChatMessageDiscoveryMode;
|
|
readonly query?: string;
|
|
readonly beforeCursor?: string;
|
|
readonly limit?: number;
|
|
readonly labels: ChatMessageDiscoveryLabels;
|
|
}): Promise<ChatMessageDiscoveryVM> {
|
|
const context = requireChatPermission(input.context, CHAT_PERMISSIONS.readOwnChannels);
|
|
const result = await listChatMessageDiscovery({
|
|
tenantId: context.tenantId,
|
|
userId: context.userId,
|
|
permissions: context.permissions,
|
|
mode: input.mode,
|
|
...(input.query !== undefined ? { query: input.query } : {}),
|
|
...(input.beforeCursor ? { beforeCursor: input.beforeCursor } : {}),
|
|
...(input.limit !== undefined ? { limit: input.limit } : {}),
|
|
});
|
|
|
|
return buildChatMessageDiscoveryVM({
|
|
mode: input.mode,
|
|
query: input.query,
|
|
nextCursor: result.nextCursor,
|
|
hrefs: {
|
|
chat: chatIndexPath(input.tenantSlug),
|
|
search: chatSearchPath(input.tenantSlug),
|
|
mentions: chatMentionsPath(input.tenantSlug),
|
|
},
|
|
rows: result.messages.map((message) => toRow(message, context.userId, input.tenantSlug, input.labels)),
|
|
});
|
|
}
|
|
|
|
function toRow(message: ChatMessageDiscoveryRecord, currentUserId: string, tenantSlug: string, labels: ChatMessageDiscoveryLabels) {
|
|
const author = message.participants.find((participant) => participant.userId === message.authorUserId);
|
|
const authorLabel = author?.displayName ?? labels.unknownUser;
|
|
const channelTitle = resolveChannelTitle(message, currentUserId, labels);
|
|
return {
|
|
id: message.id,
|
|
chatId: message.chatId,
|
|
channelKind: message.channelKind,
|
|
channelTitle,
|
|
authorLabel,
|
|
authorAvatar: {
|
|
imageUrl: author?.avatarUrl ?? null,
|
|
fallbackLabel: initials(authorLabel),
|
|
},
|
|
body: message.body,
|
|
createdAtIso: message.createdAtIso,
|
|
detailHref: buildChatMessageDeepLink({
|
|
tenantSlug,
|
|
chatId: message.chatId,
|
|
messageId: message.id,
|
|
parentMessageId: message.parentMessageId,
|
|
}),
|
|
isThreadReply: message.parentMessageId !== null,
|
|
} as const;
|
|
}
|
|
|
|
function resolveChannelTitle(message: ChatMessageDiscoveryRecord, currentUserId: string, labels: ChatMessageDiscoveryLabels): string {
|
|
if (message.channelKind === "dm") {
|
|
return message.participants.find((participant) => participant.userId !== currentUserId)?.displayName ?? labels.directMessage;
|
|
}
|
|
return message.channelName ?? (message.channelKind === "project" ? labels.unnamedProject : labels.unnamedGroup);
|
|
}
|
|
|
|
function initials(label: string): string {
|
|
const normalized = label.trim();
|
|
const parts = normalized.split(/\s+/).filter(Boolean);
|
|
return (parts.length > 1 ? `${parts[0]?.[0] ?? ""}${parts.at(-1)?.[0] ?? ""}` : normalized.slice(0, 2)).toUpperCase() || "?";
|
|
}
|