705b1e1062c2 | atagen | 2026-10-23 09:05 UTC | 1 | import type { Mention } from './mentions'; |
705b1e1062c2 | atagen | 2026-10-23 09:05 UTC | 2 | import { linkSpans } from './urls'; |
705b1e1062c2 | atagen | 2026-10-23 09:05 UTC | 3 | |
705b1e1062c2 | atagen | 2026-10-23 09:05 UTC | 4 | export type InlineSegment = |
705b1e1062c2 | atagen | 2026-10-23 09:05 UTC | 5 | | { kind: 'text'; text: string } |
705b1e1062c2 | atagen | 2026-10-23 09:05 UTC | 6 | | { kind: 'mention'; text: string; user_id: string } |
705b1e1062c2 | atagen | 2026-10-23 09:05 UTC | 7 | | { kind: 'link'; text: string; href: string }; |
705b1e1062c2 | atagen | 2026-10-23 09:05 UTC | 8 | |
705b1e1062c2 | atagen | 2026-10-23 09:05 UTC | 9 | interface Span { |
705b1e1062c2 | atagen | 2026-10-23 09:05 UTC | 10 | start: number; |
705b1e1062c2 | atagen | 2026-10-23 09:05 UTC | 11 | end: number; |
705b1e1062c2 | atagen | 2026-10-23 09:05 UTC | 12 | segment: InlineSegment; |
705b1e1062c2 | atagen | 2026-10-23 09:05 UTC | 13 | } |
705b1e1062c2 | atagen | 2026-10-23 09:05 UTC | 14 | |
705b1e1062c2 | atagen | 2026-10-23 09:05 UTC | 15 | const WORD_CHAR = /[\p{L}\p{N}_]/u; |
705b1e1062c2 | atagen | 2026-10-23 09:05 UTC | 16 | |
705b1e1062c2 | atagen | 2026-10-23 09:05 UTC | 17 | function escapeRegExp(text: string): string { |
705b1e1062c2 | atagen | 2026-10-23 09:05 UTC | 18 | return text.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); |
705b1e1062c2 | atagen | 2026-10-23 09:05 UTC | 19 | } |
705b1e1062c2 | atagen | 2026-10-23 09:05 UTC | 20 | |
705b1e1062c2 | atagen | 2026-10-23 09:05 UTC | 21 | function mentionSpans(text: string, mentions: Mention[], taken: Span[]): Span[] { |
705b1e1062c2 | atagen | 2026-10-23 09:05 UTC | 22 | const byText = new Map(mentions.filter((m) => m.text).map((m) => [m.text, m.user_id])); |
705b1e1062c2 | atagen | 2026-10-23 09:05 UTC | 23 | if (byText.size === 0) return []; |
705b1e1062c2 | atagen | 2026-10-23 09:05 UTC | 24 | const names = [...byText.keys()].sort((a, b) => b.length - a.length); |
705b1e1062c2 | atagen | 2026-10-23 09:05 UTC | 25 | const out: Span[] = []; |
705b1e1062c2 | atagen | 2026-10-23 09:05 UTC | 26 | for (const m of text.matchAll(new RegExp(names.map(escapeRegExp).join('|'), 'g'))) { |
705b1e1062c2 | atagen | 2026-10-23 09:05 UTC | 27 | const start = m.index; |
705b1e1062c2 | atagen | 2026-10-23 09:05 UTC | 28 | const end = start + m[0].length; |
705b1e1062c2 | atagen | 2026-10-23 09:05 UTC | 29 | if (WORD_CHAR.test(text[start - 1] ?? '') || WORD_CHAR.test(text[end] ?? '')) continue; |
705b1e1062c2 | atagen | 2026-10-23 09:05 UTC | 30 | if (taken.some((s) => start < s.end && end > s.start)) continue; |
705b1e1062c2 | atagen | 2026-10-23 09:05 UTC | 31 | out.push({ start, end, segment: { kind: 'mention', text: m[0], user_id: byText.get(m[0])! } }); |
705b1e1062c2 | atagen | 2026-10-23 09:05 UTC | 32 | } |
705b1e1062c2 | atagen | 2026-10-23 09:05 UTC | 33 | return out; |
705b1e1062c2 | atagen | 2026-10-23 09:05 UTC | 34 | } |
705b1e1062c2 | atagen | 2026-10-23 09:05 UTC | 35 | |
705b1e1062c2 | atagen | 2026-10-23 09:05 UTC | 36 | export function segmentInline(text: string, mentions: Mention[]): InlineSegment[] { |
705b1e1062c2 | atagen | 2026-10-23 09:05 UTC | 37 | const links: Span[] = linkSpans(text).map((l) => ({ |
705b1e1062c2 | atagen | 2026-10-23 09:05 UTC | 38 | start: l.start, |
705b1e1062c2 | atagen | 2026-10-23 09:05 UTC | 39 | end: l.end, |
705b1e1062c2 | atagen | 2026-10-23 09:05 UTC | 40 | segment: { kind: 'link', text: text.slice(l.start, l.end), href: l.href } |
705b1e1062c2 | atagen | 2026-10-23 09:05 UTC | 41 | })); |
705b1e1062c2 | atagen | 2026-10-23 09:05 UTC | 42 | const spans = [...links, ...mentionSpans(text, mentions, links)].sort((a, b) => a.start - b.start); |
705b1e1062c2 | atagen | 2026-10-23 09:05 UTC | 43 | const out: InlineSegment[] = []; |
705b1e1062c2 | atagen | 2026-10-23 09:05 UTC | 44 | let last = 0; |
705b1e1062c2 | atagen | 2026-10-23 09:05 UTC | 45 | for (const s of spans) { |
705b1e1062c2 | atagen | 2026-10-23 09:05 UTC | 46 | if (s.start > last) out.push({ kind: 'text', text: text.slice(last, s.start) }); |
705b1e1062c2 | atagen | 2026-10-23 09:05 UTC | 47 | out.push(s.segment); |
705b1e1062c2 | atagen | 2026-10-23 09:05 UTC | 48 | last = s.end; |
705b1e1062c2 | atagen | 2026-10-23 09:05 UTC | 49 | } |
705b1e1062c2 | atagen | 2026-10-23 09:05 UTC | 50 | if (last < text.length) out.push({ kind: 'text', text: text.slice(last) }); |
705b1e1062c2 | atagen | 2026-10-23 09:05 UTC | 51 | return out; |
705b1e1062c2 | atagen | 2026-10-23 09:05 UTC | 52 | } |