import type { ContentEntity, LinkEntity, MentionEntity, ParsedMention, ProcessedContent, TagEntity, } from "./types"; export const mentionLinkRegexp = /@\[[^\]]+]\([0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}\)/g; export const parseMention = (mention: string): ParsedMention | null => { const regex = /@\[([^\]]+)\]\(([\w-]{36})\)/; const match = mention.match(regex); if (!match) { return null; } const mentionText = match[1]; const mentionId = match[2]; if (!mentionText || !mentionId) { return null; } return { mention: `@${mentionText}`, id: mentionId, }; }; export const findMentions = (text: string): MentionEntity[] => { let match: RegExpExecArray | null; const matches: MentionEntity[] = []; while ((match = mentionLinkRegexp.exec(text)) !== null) { const parsed = parseMention(match[0]); const baseMention: Omit = { start: match.index, end: mentionLinkRegexp.lastIndex, text: match[0], type: "mention", displayText: parsed?.mention ?? match[0], }; matches.push(parsed?.id ? { ...baseMention, userId: parsed.id } : baseMention); } return matches; }; export const findTags = (content: string): TagEntity[] => { const regex = /#[^\s]{1,201}/g; const results: TagEntity[] = []; let match: RegExpExecArray | null; while ((match = regex.exec(content)) !== null) { const value = match[0]; results.push({ start: match.index, end: match.index + value.length, text: value, type: "tag", tag: value.replace("#", ""), }); } return results; }; export const findLinks = (content: string): LinkEntity[] => { const regex = /\b((https?:\/\/)?(?:[\w-]+\.)+[a-z]{2,}(\/[\w\-._~:/?#[\]@!$&'()*+,;=]*)?)/gi; const results: LinkEntity[] = []; let match: RegExpExecArray | null; while ((match = regex.exec(content)) !== null) { const rawUrl = match[0]; const hasProtocol = /^https?:\/\//i.test(rawUrl); const fullUrl = hasProtocol ? rawUrl : `https://${rawUrl}`; results.push({ start: match.index, end: match.index + rawUrl.length, text: rawUrl, url: fullUrl, type: "link", }); } return results; }; export const findAllEntities = (content: string): ContentEntity[] => { const mentions = findMentions(content); const tags = findTags(content); const links = findLinks(content); return [...mentions, ...tags, ...links].sort((a, b) => a.start - b.start); }; export const processContent = (content: string): ProcessedContent => { const processedText = content.replace(mentionLinkRegexp, match => { const parsed = parseMention(match); return parsed ? parsed.mention : match; }); const tags = findTags(content).map(tag => tag.tag); return { processedText, tags, }; };