|
1 | 1 | /** |
2 | 2 | * Shared serialization helpers used across entity serializers. |
3 | 3 | */ |
4 | | -import { renderMarkdown } from '@cfp/shared'; |
| 4 | +import { renderMarkdown as rawRenderMarkdown, type RenderMarkdownResult } from '@cfp/shared'; |
5 | 5 | import type { Person, Tag } from '@cfp/shared/schemas'; |
6 | 6 |
|
| 7 | +/** |
| 8 | + * Boot-installed renderer. Defaults to the bare `@cfp/shared` pipeline so |
| 9 | + * tests + dev code that import serializers directly without booting the |
| 10 | + * markdown plugin keep working. The markdown plugin |
| 11 | + * (`apps/api/src/plugins/markdown.ts`) calls `setRenderMarkdown` at boot |
| 12 | + * to swap in a renderer bound to `CFP_SITE_HOST` + the live |
| 13 | + * `inMemoryState.personIdBySlug` lookup, so all serializer output applies |
| 14 | + * the external-link + `@mention` transforms from |
| 15 | + * specs/behaviors/markdown-rendering.md. |
| 16 | + * |
| 17 | + * Module-level state is justified here over per-call threading: every |
| 18 | + * serializer currently routes through `renderMarkdown(source)` without |
| 19 | + * carrying an `app` or `FastifyInstance` reference, and a per-process |
| 20 | + * single binding matches the runtime's actual shape (one Fastify app, |
| 21 | + * one renderer config). Hot-reload preserves the state Maps in place so |
| 22 | + * the closure stays correct. |
| 23 | + */ |
| 24 | +let currentRender: (source: string) => RenderMarkdownResult = rawRenderMarkdown; |
| 25 | + |
| 26 | +export function setRenderMarkdown(fn: (source: string) => RenderMarkdownResult): void { |
| 27 | + currentRender = fn; |
| 28 | +} |
| 29 | + |
| 30 | +/** Render a markdown source through the boot-installed renderer. */ |
| 31 | +export function renderMarkdown(source: string): RenderMarkdownResult { |
| 32 | + return currentRender(source); |
| 33 | +} |
| 34 | + |
7 | 35 | /** PersonAvatar shape used in many nested contexts. */ |
8 | 36 | export interface PersonAvatar { |
9 | 37 | readonly slug: string; |
@@ -53,12 +81,6 @@ export function groupTagsByNamespace( |
53 | 81 | return { topic, tech, event }; |
54 | 82 | } |
55 | 83 |
|
56 | | -/** Render markdown to HTML + an excerpt. Returns empty string for null/empty source. */ |
57 | | -export function renderField(source: string | null | undefined): { html: string; excerpt: string } { |
58 | | - if (!source) return { html: '', excerpt: '' }; |
59 | | - const { html, excerpt } = renderMarkdown(source); |
60 | | - return { html, excerpt }; |
61 | | -} |
62 | 84 |
|
63 | 85 | /** Truncate a plain-text string at a word boundary. */ |
64 | 86 | export function truncate(text: string, maxLength: number): string { |
|
0 commit comments