import { isUrl } from "./utils"; type GithubComment = { body: string; }; const TOKEN = "\uE000github-comment-code"; function decode(value: string): string { return value .replace(/"/g, '"') .replace(/'/g, "'") .replace(/'/gi, "'") .replace(/&/g, "&"); } function space(value: string): boolean { return value === " " || value === "\n" || value === "\r" || value === "\t"; } function attr(source: string, name: string): string | undefined { let goal = name.toLowerCase(); let cursor = 0; while (cursor < source.length) { while (space(source[cursor] || "")) cursor++; let start = cursor; while (cursor < source.length) { let char = source[cursor]!; if (space(char) || char === "=" || char === "/" || char === ">") break; cursor++; } let key = source.slice(start, cursor).toLowerCase(); while (space(source[cursor] || "")) cursor++; if (source[cursor] !== "=") { cursor++; continue; } cursor++; while (space(source[cursor] || "")) cursor++; let quote = source[cursor]; let value = ""; if (quote === '"' || quote === "'") { cursor++; let end = source.indexOf(quote, cursor); if (end === -1) return undefined; value = source.slice(cursor, end); cursor = end + 1; } else { let end = cursor; while (end < source.length && !space(source[end]!) && source[end] !== ">") end++; value = source.slice(cursor, end); cursor = end; } if (key === goal) return decode(value); } } function text(source: string): string { let out = ""; let cursor = 0; while (cursor < source.length) { if (source[cursor] === "<") { let end = tag(source, cursor); if (end !== null) { cursor = end + 1; continue; } out += "<"; cursor++; continue; } out += source[cursor++]; } return decode(out) .replace(/\s+/g, " ") .trim(); } function escapeLabel(value: string): string { return value.replace(/([\\[\]])/g, "\\$1"); } function escapeDestination(value: string): string { return value .replace(/\\/g, "%5C") .replace(//g, "%3E"); } function safeLink(attrs: string, inner: string): string { let img = image(inner); let label = img ? imageLabel(inner, img) : text(inner); let href = attr(attrs, "href"); if (!href || !isUrl(href)) return label; return label ? `[${escapeLabel(label)}](<${escapeDestination(href)}>)` : href; } function imageLabel(inner: string, img: ImageData): string { return img.alt || text(inner) || "Open image"; } function markdownImage(img: ImageData): string { return `![${escapeLabel(img.alt)}](<${escapeDestination(img.src)}>)`; } function tag(source: string, start: number): number | null { let quote: string | null = null; for (let i = start + 1; i < source.length; i++) { let char = source[i]!; if (quote) { if (char === quote) quote = null; continue; } if (char === '"' || char === "'") { quote = char; continue; } if (char === ">") return i; } return null; } function named(source: string, start: number, name: string): boolean { let tag = source.slice(start + 1, start + 1 + name.length).toLowerCase(); if (tag !== name) return false; let next = source[start + 1 + name.length]; return !next || space(next) || next === ">" || next === "/"; } function closing(source: string, start: number, name: string): boolean { if (source[start] !== "<" || source[start + 1] !== "/") return false; let tag = source.slice(start + 2, start + 2 + name.length).toLowerCase(); if (tag !== name) return false; let next = source[start + 2 + name.length]; return !next || space(next) || next === ">" || next === "/"; } function findClosing(source: string, name: string, start: number): number | null { let lower = source.toLowerCase(); let needle = ` { return slots[Number(index)] || ""; }); }, }; } function fence(raw: string, start: number): number | null { if (start > 0 && raw[start - 1] !== "\n") return null; let match = /^( {0,3})(`{3,}|~{3,})[^\n]*(?:\n|$)/.exec(raw.slice(start)); if (!match) return null; let mark = match[2]!; let char = mark[0]!; let size = mark.length; let cursor = start + match[0]!.length; let close = new RegExp(`^ {0,3}\\${char}{${size},}[ \t]*(?:\n|$)`); while (cursor < raw.length) { let next = raw.indexOf("\n", cursor); let end = next === -1 ? raw.length : next + 1; let line = raw.slice(cursor, end); if (close.test(line)) return end; cursor = end; } return raw.length; } function indent(raw: string, start: number): number | null { if (start > 0 && raw[start - 1] !== "\n") return null; let line = raw.slice(start, start + 4); if (line !== " " && raw[start] !== "\t") return null; let cursor = start; while (cursor < raw.length) { let next = raw.indexOf("\n", cursor); let end = next === -1 ? raw.length : next + 1; let text = raw.slice(cursor, next === -1 ? end : next); if (text && !text.startsWith(" ") && !text.startsWith("\t")) break; cursor = end; } return cursor; } function span(raw: string, start: number): number | null { if (raw[start] !== "`") return null; let size = 1; while (raw[start + size] === "`") size++; let mark = "`".repeat(size); let close = raw.indexOf(mark, start + size); return close === -1 ? null : close + size; } function anchors(raw: string): string { let out = ""; let lower = raw.toLowerCase(); let cursor = 0; while (cursor < raw.length) { let start = lower.indexOf("", open === null ? start : open + 1); if (open === null || close === -1) { out += raw.slice(cursor); break; } let attrs = raw.slice(start + 2, open); let inner = raw.slice(open + 1, close); out += raw.slice(cursor, start); out += safeLink(attrs, inner); cursor = close + 4; } return out; } function images(raw: string): string { let out = ""; let lower = raw.toLowerCase(); let cursor = 0; while (cursor < raw.length) { let start = lower.indexOf("