/** * 纯文本 URL 切分(QQ 风格:识别 http(s):// 与 www.) * 仅产出 http/https,拒绝其它协议。 */ const URL_RE = /(?:https?:\/\/|www\.)[^\s\u4e00-\u9fff<>"'`]+/gi; /** 常见尾随标点从 URL 剥离(中英) */ const TRAIL_PUNCT = /[.,;:!?。,;:!?、)\]}》〉』」"'`]+$/; export type LinkifyPart = | { type: "text"; value: string } | { type: "url"; value: string; href: string }; function toHref(raw: string): string | null { const href = /^www\./i.test(raw) ? `https://${raw}` : raw; try { const u = new URL(href); if (u.protocol !== "http:" && u.protocol !== "https:") return null; return u.href; } catch { return null; } } function peel(raw: string): { core: string; trail: string } { const m = raw.match(TRAIL_PUNCT); if (!m) return { core: raw, trail: "" }; return { core: raw.slice(0, -m[0].length), trail: m[0] }; } /** 将纯文本切成文本段与可安全打开的链接段 */ export function linkifyParts(text: string): LinkifyPart[] { if (!text) return []; const parts: LinkifyPart[] = []; let last = 0; URL_RE.lastIndex = 0; let m: RegExpExecArray | null; while ((m = URL_RE.exec(text)) !== null) { if (m.index > last) { parts.push({ type: "text", value: text.slice(last, m.index) }); } const { core, trail } = peel(m[0]); const href = core ? toHref(core) : null; if (href && core) { parts.push({ type: "url", value: core, href }); if (trail) parts.push({ type: "text", value: trail }); } else { parts.push({ type: "text", value: m[0] }); } last = m.index + m[0].length; } if (last < text.length) parts.push({ type: "text", value: text.slice(last) }); return parts.length ? parts : [{ type: "text", value: text }]; }