import { escapeHtml, raw, type Raw } from './html.ts'; import type { PlatformId } from '../types.ts'; /** Where an @mention or #hashtag points on each platform. */ const PROFILE_BASE: Record = { x: 'https://x.com/', threads: 'https://www.threads.com/@', instagram: 'https://www.instagram.com/', tiktok: 'https://www.tiktok.com/@', bluesky: 'https://bsky.app/profile/', reddit: 'https://www.reddit.com/user/', }; const TAG_BASE: Partial> = { x: 'https://x.com/hashtag/', instagram: 'https://www.instagram.com/explore/tags/', tiktok: 'https://www.tiktok.com/tag/', }; const TOKEN = /(https?:\/\/[^\s<]+)|(^|\s)(@[\w.-]+)|(^|\s)(#[\w]+)/g; function link(href: string, label: string): string { return `${escapeHtml(label)}`; } /** * Turn post text into markup: escape everything, then make links, mentions * and hashtags clickable back to the platform they came from. */ export function linkify(text: string, platform: PlatformId): Raw { let out = ''; let cursor = 0; for (const match of text.matchAll(TOKEN)) { const index = match.index; out += escapeHtml(text.slice(cursor, index)); const [whole, url, mentionLead, mention, tagLead, tag] = match; if (url) { out += link(url, url.replace(/^https?:\/\/(www\.)?/, '')); } else if (mention) { out += escapeHtml(mentionLead ?? '') + link(PROFILE_BASE[platform] + mention.slice(1), mention); } else if (tag) { const base = TAG_BASE[platform]; out += escapeHtml(tagLead ?? '') + (base ? link(base + tag.slice(1), tag) : escapeHtml(tag)); } else { out += escapeHtml(whole); } cursor = index + whole.length; } out += escapeHtml(text.slice(cursor)); return raw(out.replace(/\n/g, '
')); }