import type { RichTextDocument } from "./ast.js"; import { DocBuilder, type ParseOptions } from "./builder.js"; // Single-pass, backtracking-safe token scan: URL | @mention | #hashtag. const TOKENS = /(https?:\/\/[^\s<>"'`\u0000-\u001F]{1,2048})|(? last) builder.text(line.slice(last, match.index)); if (url !== undefined) { const trimmed = url.replace(TRAILING_PUNCT, ""); consumed = trimmed; builder.link(trimmed, builder.inlineText(trimmed.replace(/^https?:\/\//i, ""))); } else if (handle !== undefined) { const clean = handle.replace(/[.-]+$/, ""); consumed = "@" + clean; builder.mention(clean); } else if (tag !== undefined) { builder.hashtag(tag); } last = match.index + consumed.length; } if (last < line.length) builder.text(line.slice(last)); } /** Plain provider text (Bluesky, Telegram, bios, …): newlines, URLs, mentions, hashtags. */ export function parsePlainText(input: unknown, options: ParseOptions = {}): RichTextDocument { const builder = new DocBuilder(options); if (typeof input !== "string") return builder.finish(); const text = builder.clip(input).replace(/\r\n?/g, "\n"); for (const paragraph of text.split(/\n{2,}/)) { paragraph.split("\n").forEach((line, i) => { if (i > 0) builder.lineBreak(); lineToNodes(builder, line); }); builder.endParagraph(); } return builder.finish(); }