New components: - NotificationDrawer: global bell button with unread badge + Sheet drawer - CommandPalette: Cmd+K / Ctrl+Alt+F command palette with real API data - KeyboardShortcutsSheet: ? shortcut reference sheet - GlobalNavigationShortcuts: g+n/i/r/m two-key navigation - useNotification: real-time notification management hook - useCommandRegistry: global command registration hook - useKeyboardShortcut: keyboard shortcut formatting hook - useTypingIndicator: unified typing indicator hook - LinkPreview / CodeBlock / CodeReference: code-aware chat rendering - ContentRenderer: unified content rendering - MiniChat: compact inline chat component - MentionBadge: @mention badge renderer New libs: - libs/api/agent/issue_triage.rs: AI issue triage API endpoint - libs/service/agent/issue_triage.rs: AI triage service - src/lib/mention.ts: mention parsing and rendering - src/lib/link-unfurl.ts: URL pattern detection - src/lib/code-lang-detect.ts: code language detection - src/lib/code-ref-parser.ts: line-level code reference parsing
101 lines
2.8 KiB
TypeScript
101 lines
2.8 KiB
TypeScript
/**
|
||
* Unified mention serialization and parsing utilities.
|
||
* Shared across room chat, issues, and PR comments.
|
||
*/
|
||
|
||
export type MentionType =
|
||
| 'user'
|
||
| 'channel'
|
||
| 'ai'
|
||
| 'command'
|
||
| 'special_here'
|
||
| 'special_channel';
|
||
|
||
export const MENTION_TYPES: MentionType[] = [
|
||
'user',
|
||
'channel',
|
||
'ai',
|
||
'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 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]';
|
||
}
|