Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
Fetch any URL via Chrome CDP and convert the rendered page to clean markdown with YouTube transcript support.
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
scripts/lib/extract/markdown-renderer.ts
1import TurndownService from "turndown";2import { gfm } from "turndown-plugin-gfm";3import { normalizeMarkdownMediaLinks } from "../media/markdown-media";4import type { ContentBlock, ExtractedDocument } from "./document";56const turndownService = new TurndownService({7codeBlockStyle: "fenced",8headingStyle: "atx",9bulletListMarker: "-",10});1112turndownService.use(gfm);1314function renderBlock(block: ContentBlock): string {15switch (block.type) {16case "paragraph":17return block.text.trim();18case "heading":19return `${"#".repeat(Math.min(Math.max(block.depth, 1), 6))} ${block.text.trim()}`;20case "list":21return block.items22.map((item, index) => (block.ordered ? `${index + 1}. ${item.trim()}` : `- ${item.trim()}`))23.join("\n");24case "quote":25return block.text26.split("\n")27.map((line) => `> ${line}`)28.join("\n");29case "code":30return `\`\`\`${block.language ?? ""}\n${block.code.trimEnd()}\n\`\`\``;31case "image":32return ``;33case "html":34return turndownService.turndown(block.html).trim();35case "markdown":36return block.markdown.trim();37}38}3940function isDefinedValue(value: unknown): boolean {41return value !== undefined && value !== null && value !== "";42}4344function renderFrontmatterValue(value: unknown): string {45if (typeof value === "string") {46if (value.includes("\n")) {47return `|-\n${value48.replace(/\r\n/g, "\n")49.split("\n")50.map((line) => ` ${line}`)51.join("\n")}`;52}53return JSON.stringify(value);54}55if (typeof value === "number" || typeof value === "boolean") {56return String(value);57}58return JSON.stringify(value);59}6061function renderFrontmatter(document: ExtractedDocument): string {62const fields = new Map<string, unknown>();63const preferredOrder = [64"title",65"url",66"requestedUrl",67"author",68"authorName",69"authorUsername",70"authorUrl",71"coverImage",72"siteName",73"publishedAt",74"summary",75"adapter",76];7778fields.set("title", document.title);79fields.set("url", document.canonicalUrl ?? document.url);80fields.set("requestedUrl", document.requestedUrl ?? document.url);81fields.set("author", document.author);82fields.set("siteName", document.siteName);83fields.set("publishedAt", document.publishedAt);84fields.set("summary", document.summary);85fields.set("adapter", document.adapter);8687for (const [key, value] of Object.entries(document.metadata ?? {})) {88if (!fields.has(key)) {89fields.set(key, value);90}91}9293const orderedKeys = [94...preferredOrder.filter((key) => fields.has(key)),95...Array.from(fields.keys()).filter((key) => !preferredOrder.includes(key)).sort(),96];9798const lines = orderedKeys99.map((key) => [key, fields.get(key)] as const)100.filter(([, value]) => isDefinedValue(value))101.map(([key, value]) => `${key}: ${renderFrontmatterValue(value)}`);102103if (lines.length === 0) {104return "";105}106107return `---\n${lines.join("\n")}\n---`;108}109110function cleanMarkdown(markdown: string): string {111return normalizeMarkdownMediaLinks(markdown.replace(/\n{3,}/g, "\n\n").trim());112}113114function normalizeComparableTitle(value: string): string {115return value116.trim()117.toLowerCase()118.replace(/^>\s*/, "")119.replace(/^#+\s+/, "")120.replace(/(?:\.{3}|…)\s*$/, "");121}122123function bodyStartsWithTitle(body: string, title: string): boolean {124const firstMeaningfulLine = body125.replace(/\r\n/g, "\n")126.split("\n")127.map((line) => line.trim())128.find((line) => line && !/^!?\[[^\]]*\]\([^)]+\)$/.test(line));129130if (!firstMeaningfulLine) {131return false;132}133134const comparableTitle = normalizeComparableTitle(title);135const comparableFirstLine = normalizeComparableTitle(firstMeaningfulLine);136if (!comparableTitle || !comparableFirstLine) {137return false;138}139140return (141comparableFirstLine === comparableTitle ||142comparableFirstLine.startsWith(comparableTitle) ||143comparableTitle.startsWith(comparableFirstLine)144);145}146147export function renderMarkdown(document: ExtractedDocument): string {148const sections: string[] = [];149const frontmatter = renderFrontmatter(document);150151if (frontmatter) {152sections.push(frontmatter);153}154155const body = document.content156.map((block) => renderBlock(block))157.filter(Boolean)158.join("\n\n");159160if (document.title && !bodyStartsWithTitle(body, document.title)) {161sections.push(`# ${document.title}`);162}163164if (body) {165sections.push(body);166}167168return cleanMarkdown(sections.join("\n\n"));169}170