We shipped dark mode.
+``` + +When you render many messages or feed items, create the renderer once with `createHtmlRenderer()`. The returned function reuses the same parser and plugins. + +## Add an email button component + +Write the component as an async function. It receives the node as `[tag, attrs, ...children]` and a `render()` helper for the children. Escape attribute values with `escapeHtml()`: + +```typescript [email.ts] +import { createHtmlRenderer } from '@comark/html' +import { escapeHtml } from '@comark/html/utils' + +export const renderEmail = createHtmlRenderer({ + components: { + button: async ([, attrs, ...children], { render }) => + `${await render(children)}`, + }, +}) +``` + +The email content uses the component like any other Comark component: + +```mdc [welcome.md] +Thanks for signing up. + +:button[Confirm your email]{href="https://example.com/confirm"} +``` + +`@comark/html` returns only the content HTML. Put it inside your email layout. Email clients differ in their CSS support. We recommend that you inline the styles of your layout and of the Markdown elements with a separate CSS inliner tool. + +## Pass per-recipient data + +Pass values in the `data` option. Every component function receives them in its context: + +```typescript [order-email.ts] +import { renderHtml } from '@comark/html' +import { escapeHtml } from '@comark/html/utils' + +const html = await renderHtml('::greeting\nYour order has shipped.\n::', { + data: { name: 'Ada' }, + components: { + greeting: async ([, , ...children], { render, data }) => + `Hi ${escapeHtml(String(data?.name))},
${await render(children)}`, + }, +}) +``` + +## Build an RSS feed + +Put the title and the HTML of each item in CDATA sections, and escape the link URL, so the feed stays valid XML. A CDATA section can't contain `]]>`, so split that sequence: + +```typescript [rss.ts] +import { createHtmlRenderer } from '@comark/html' + +const renderHtml = createHtmlRenderer() + +// Keep `]]>` in the value from closing the CDATA section early. +const cdata = (value: string) => `', ']]]]>')}]]>` +const escapeXml = (value: string) => + value.replaceAll('&', '&').replaceAll('<', '<').replaceAll('>', '>') + +export async function renderItem(post: { title: string, url: string, markdown: string }) { + const html = await renderHtml(post.markdown) + return `{{ part.text }}
+{part.text}
+ {:else} +{{ part.text }}
+ } @else { +