/** * Unified mention serialization and parsing utilities. * Shared across room chat, issues, and PR comments. */ export type MentionType = | 'user' | 'channel' | 'ai' | 'repo' | 'command' | 'special_here' | 'special_channel'; export const MENTION_TYPES: MentionType[] = [ 'user', 'channel', 'ai', 'repo', 'command', 'special_here', 'special_channel', ]; export interface MentionSpan { type: MentionType; id: string; label: string; } export interface ExtractedMentions { safeContent: string; mentions: MentionSpan[]; } /** * Parse @[type:id:label] tokens from message content. * Returns the safe content (with mentions replaced by placeholders) and the parsed mention list. * * Placeholders use zero-width spaces to prevent markdown from interpreting the text. */ const PLACEHOLDER_PREFIX = '​MENTION_'; const PLACEHOLDER_SUFFIX = '​'; const MENTION_RE = /@\[([a-z_]+):([^:\]]+):([^\]]+)\]/g; const PLACEHOLDER_RE = /​MENTION_(\d+)​/g; export function extractMentions(content: string): ExtractedMentions { const mentions: MentionSpan[] = []; const safeContent = content.replace(MENTION_RE, (_match, type, id, label) => { const idx = mentions.length; mentions.push({ type: type as MentionType, id, label }); return `${PLACEHOLDER_PREFIX}${idx}${PLACEHOLDER_SUFFIX}`; }); return { safeContent, mentions }; } export function restoreMentions( text: string, mentions: MentionSpan[], ): string { return text.replace(PLACEHOLDER_RE, (match, idxStr) => { const idx = parseInt(idxStr, 10); const m = mentions[idx]; return m ? `@[${m.type}:${m.id}:${m.label}]` : match; }); } /** Build a mention token string for serialization. */ export function serializeMention(type: MentionType, id: string, label: string): string { return `@[${type}:${id}:${label}]`; } /** Build a mention token from a user mention. */ export function serializeUserMention(userId: string, displayName: string): string { return serializeMention('user', userId, displayName); } /** Build a mention token from a channel mention. */ export function serializeChannelMention(channelId: string, channelName: string): string { return serializeMention('channel', channelId, channelName); } /** Build a mention token from an AI mention. */ export function serializeAiMention(aiId: string, aiName: string): string { return serializeMention('ai', aiId, aiName); } /** Build a mention token from a repo mention. */ export function serializeRepoMention(repoName: string, label?: string): string { return serializeMention('repo', repoName, label ?? repoName); } /** Build a mention token from a command mention. */ export function serializeCommandMention(commandId: string, commandName: string): string { return serializeMention('command', commandId, commandName); } /** Build a mention token for @here. */ export function serializeHereMention(): string { return '@[special_here:here:here]'; } /** Build a mention token for @channel. */ export function serializeChannelBroadcastMention(): string { return '@[special_channel:channel:channel]'; }