agent-ecosystem/src/renderer/utils/mentionLinkify.ts
iliya 7b13a4c398 refactor: update README and enhance team UI components
- Changed image format for agent comments screenshot in README.
- Improved table of contents in README for better navigation.
- Refactored imports in IPC config and discovery services for consistency.
- Added shimmer effect for waiting members in CSS.
- Enhanced dashboard view to display active teams with online indicators.
- Updated team provisioning components to support message severity.
- Improved task detail dialog layout for related tasks and dependencies.
- Adjusted team model selector default value and refined member status handling.
- Fixed minor styling issues in messages panel and tab bar.

These changes aim to improve user experience and maintainability across the application.
2026-03-23 12:58:38 +02:00

90 lines
3.8 KiB
TypeScript

/**
* Shared utility for converting @memberName and @teamName mentions in plain text
* to markdown links with mention:// and team:// protocols.
*
* Used by UserChatGroup, TeammateMessageItem, ActivityItem, TaskCommentsSection.
* MarkdownViewer already handles rendering mention:// and team:// links as colored badges.
*/
/**
* Convert `@memberName` in plain text to markdown links with mention:// protocol.
* Encodes color in the URL so MarkdownViewer can render colored badges without extra context.
* Greedy match: longer names are tried first to avoid partial matches.
*
* @param text - The plain text to process
* @param memberColorMap - Map of member name → color key (e.g. "blue", "red")
* @returns Text with @mentions replaced by markdown links
*/
export function linkifyMentionsInMarkdown(
text: string,
memberColorMap: Map<string, string>
): string {
if (memberColorMap.size === 0) return text;
// Sort by name length descending for greedy matching
const names = [...memberColorMap.keys()].sort((a, b) => b.length - a.length);
const escaped = names.map((n) => n.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'));
const pattern = new RegExp(
// eslint-disable-next-line no-useless-escape -- backslash-quote and backslash-hyphen needed in template literal for RegExp
`(^|[\\s(\\[{"\'])@(${escaped.join('|')})(?=[\\s,.:;!?)\\]}'\u2019-]|$)`,
'gi'
);
return text.replace(pattern, (_match: string, prefix: string, name: string) => {
// Find the canonical name (case-insensitive lookup)
const canonical = names.find((n) => n.toLowerCase() === name.toLowerCase()) ?? name;
const color = memberColorMap.get(canonical) ?? '';
return `${prefix}[@${canonical}](mention://${encodeURIComponent(color)}/${encodeURIComponent(canonical)})`;
});
}
/**
* Convert `@teamName` in plain text to markdown links with team:// protocol.
* Greedy match: longer names are tried first to avoid partial matches.
*
* @param text - The plain text to process
* @param teamNames - Set or array of known team names
* @returns Text with @teamName replaced by markdown links
*/
export function linkifyTeamMentionsInMarkdown(
text: string,
teamNames: ReadonlySet<string> | readonly string[]
): string {
const names: readonly string[] = Array.isArray(teamNames) ? teamNames : [...teamNames];
if (names.length === 0) return text;
// Sort by name length descending for greedy matching
const sorted = [...names].sort((a, b) => b.length - a.length);
const escaped = sorted.map((n) => n.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'));
const pattern = new RegExp(
// eslint-disable-next-line no-useless-escape -- backslash-quote and backslash-hyphen needed in template literal for RegExp
`(^|[\\s(\\[{"\'])@(${escaped.join('|')})(?=[\\s,.:;!?)\\]}'\u2019-]|$)`,
'gi'
);
return text.replace(pattern, (_match: string, prefix: string, name: string) => {
const canonical = sorted.find((n) => n.toLowerCase() === name.toLowerCase()) ?? name;
return `${prefix}[${canonical}](team://${encodeURIComponent(canonical)})`;
});
}
/**
* Apply both member and team linkification. Team names are matched first to avoid
* team names being captured as member mentions when they share similar names.
*
* @param text - The plain text to process
* @param memberColorMap - Map of member name → color key
* @param teamNames - Known team names
* @returns Text with both @member and @team mentions replaced by markdown links
*/
export function linkifyAllMentionsInMarkdown(
text: string,
memberColorMap: Map<string, string>,
teamNames: ReadonlySet<string> | readonly string[] = []
): string {
// Apply team linkification first (team names tend to be longer / more specific)
let result = linkifyTeamMentionsInMarkdown(text, teamNames);
// Then member linkification on the remaining text
result = linkifyMentionsInMarkdown(result, memberColorMap);
return result;
}