gitdataai/src/lib/mention.ts
ZhenYi c8eba28e7a feat(frontend): add repo type to mention autocomplete system
Add 'repo' to MentionType across all editor types, include repos in the
@ trigger pool, add repo badge (green chip), Repos section in the
mention dropdown, and MentionBadge styles. Wire projectRepos from
room context into IMEditor mentionItems.
2026-04-26 23:58:59 +08:00

108 lines
3.0 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 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]';
}