1
20
21import { parseInlineAtoms, formatDateAtom, STATUS_VALUES } from "./inline-atoms.js";
22
23function escapeHtml(s: string): string {
24 return s
25 .replace(/&/g, "&")
26 .replace(/</g, "<")
27 .replace(/>/g, ">")
28 .replace(/"/g, """);
29}
30
31function unescapeHtml(s: string): string {
32 return s
33 .replace(/"/g, '"')
34 .replace(/</g, "<")
35 .replace(/>/g, ">")
36 .replace(/&/g, "&");
37}
38
39function atomSpan(kind: string, value: string): string {
40 return `<span class="factory-atom" data-atom-kind="${escapeHtml(kind)}" data-atom-value="${escapeHtml(value)}">${escapeHtml(
41 kind === "date" ? formatDateAtom(value) : kind === "status" ? value : value
42 )}</span>`;
43}
44
45
46export function atomTextToHtml(text: string): string {
47 return parseInlineAtoms(text)
48 .map((seg) =>
49 seg.type === "text" ? escapeHtml(seg.text) : atomSpan(seg.kind, seg.value)
50 )
51 .join("");
52}
53
54
60export function atomHtmlToText(html: string): string | null {
61 if (!html.includes('class="factory-atom"')) return null;
62 const out = html
63 .replace(
64 /<span class="factory-atom" data-atom-kind="([^"]*)" data-atom-value="([^"]*)">[\s\S]*?<\/span>/g,
65 (_m, kind: string, rawValue: string) => {
66 const value = unescapeHtml(rawValue);
67 if (kind === "status" && !STATUS_VALUES.includes(value as (typeof STATUS_VALUES)[number])) {
68 return value;
69 }
70 return `@${kind}(${value})`;
71 }
72 )
73 .replace(/<br\s*\/?>/gi, "\n")
74 .replace(/<\/(p|div)>/gi, "\n")
75 .replace(/<[^>]+>/g, "")
76 .replace(/\n{3,}/g, "\n\n")
77 .trim();
78 return out;
79}
80