Files
jiang13-bbs/frontend/lib/linkify.ts

56 lines
1.7 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 纯文本 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 }];
}