- index and category pages paginate with ?page=N and 404 out of range - category content renders above the list through the html gate - related posts fall back to the latest posts - --journal-* tokens and neutral base styles - pl/en strings registered only for locales the app enables
46 lines
1.7 KiB
TypeScript
46 lines
1.7 KiB
TypeScript
// View shaping for SSR payloads. Pages run API rows through these in
|
|
// useAsyncData's transform, so the serialized payload carries only what the
|
|
// page renders: no raw `content` source, no `translations`, and never an HTML
|
|
// body that failed the Journal gate.
|
|
|
|
import { gateJournalHtml } from './htmlGate.ts'
|
|
import type { JournalPost, JournalPostDetail } from './types.ts'
|
|
|
|
/** A post row as a list or card renders it: without body, source or translations. */
|
|
export function toListPost(post: JournalPost): JournalPost {
|
|
return { ...post, content: null, content_html: null, translations: null }
|
|
}
|
|
|
|
/** A post page's data: the gated body, with `content_blocked` set when the gate refused it. */
|
|
export interface JournalPostView extends JournalPostDetail {
|
|
content_blocked: boolean
|
|
}
|
|
|
|
/**
|
|
* Formats an API timestamp as a long date for `language` (a BCP 47 tag such
|
|
* as "pl-PL"). UTC on both server and browser, so hydration matches. Returns
|
|
* "" for a missing or invalid value.
|
|
*/
|
|
export function formatJournalDate(iso: string | null | undefined, language: string): string {
|
|
if (!iso) return ''
|
|
const d = new Date(iso)
|
|
if (Number.isNaN(d.getTime())) return ''
|
|
try {
|
|
return new Intl.DateTimeFormat(language || 'en', { dateStyle: 'long', timeZone: 'UTC' }).format(d)
|
|
} catch {
|
|
return new Intl.DateTimeFormat('en', { dateStyle: 'long', timeZone: 'UTC' }).format(d)
|
|
}
|
|
}
|
|
|
|
export function toPostView(post: JournalPostDetail): JournalPostView {
|
|
const gated = gateJournalHtml(post.content_html)
|
|
return {
|
|
...post,
|
|
content: null,
|
|
translations: null,
|
|
content_html: gated.html,
|
|
content_blocked: !gated.safe,
|
|
related_posts: (post.related_posts ?? []).map(toListPost),
|
|
}
|
|
}
|