renderPost
A blog post as HTML: its title (the # heading), what's under it with its code highlighted, and its first paragraph
(for the list of posts, and link previews). Its links are to the site from its root (/docs/apps.html) or elsewhere
in full: one to a docs page that docs (the docs site's files) doesn't have is a problem, as is a relative one. A
definition named as a doc comment names one (@link) links to its page in the docs' code browser.
import type { Highlight } from "#codebase/tcmts/content"; import { codeBlock, esc, linkExtension, plain, slug } from "#codebase/tcmts/site"; import type { Post, RenderedPost } from "#codebase/tcmts/site"; import { Marked } from "marked"; import type { Tokens } from "marked"; export function renderPost(post: Post, hl: Highlight, docs: Set<string>): RenderedPost { const problems: string[] = []; let title = ""; let titleHtml = ""; let summary = ""; const marked = new Marked({ gfm: true, extensions: [ linkExtension((name) => name.startsWith("tcmts.") ? `/docs/code/tcm/${name.slice("tcmts.".length).replaceAll(".", "/")}.html` : undefined, ), ], renderer: { heading({ tokens, depth, text }: Tokens.Heading) { const inner = this.parser.parseInline(tokens); if (depth > 1) return `<h${depth} id="${slug(plain(text))}">${inner}</h${depth}>\n`; title = plain(text); titleHtml = inner; return ""; }, paragraph({ tokens }: Tokens.Paragraph) { const inner = this.parser.parseInline(tokens); summary ||= inner; return `<p>${inner}</p>\n`; }, link({ href, tokens }: Tokens.Link) { const text = this.parser.parseInline(tokens); const doc = /^\/docs\/([^#?]*)/.exec(href); if (doc && !docs.has(doc[1] || "index.html")) problems.push(`link to ${href}: no such docs page`); else if (!/^([a-z]+:|\/)/i.test(href)) problems.push(`link to ${href}: write it from the site's root (/docs/…), or in full`); return `<a href="${esc(href)}">${text}</a>`; }, code({ text, lang }: Tokens.Code) { return codeBlock(hl, text, lang); }, }, }); const body = marked.parse(post.md) as string; if (!title) problems.push("no title: a post starts with a # heading"); const description = summary .replace(/<[^>]+>/g, "") .replace( /&(amp|lt|gt|quot|#39);/g, (_, e: string) => ({ amp: "&", lt: "<", gt: ">", quot: '"', "#39": "'" })[e]!, ) .replace(/\s+/g, " ") .trim(); return { title, titleHtml, body, summary, description, problems }; }
uses
- tcmts.content.Highlight
- tcmts.site.Post
- tcmts.site.RenderedPost
- tcmts.site.codeBlock
- tcmts.site.esc
- tcmts.site.linkExtension
- tcmts.site.plain
- tcmts.site.slug