import { convertLexicalToHTML } from "@payloadcms/richtext-lexical/html"; import { richTextToPlain } from "@carpa/typesense"; import { stateHtmlConverters } from "./textStateConverter"; /** * Split a Payload document's Lexical `content` into one paragraph document per * top-level block. Each keeps the block's HTML (for display) and its plain * text (for search), tagged with the parent id (under `parentField`), locale * and order. Empty/spacer blocks are skipped, and `order` is contiguous. * * Colors/fonts are carried on each text node's TextStateFeature `$` state and * emitted as `text-[#hex]`/`font-` classes by `stateHtmlConverters` * (pure, jsdom-free — safe in the Payload/Next live-sync path). This is the * single source for both migrated and admin-authored content. * * `parentField` is the relationship key on the child docs, e.g. "activity" for * activities_paragraphs or "conference" for conferences_paragraphs. */ export function splitParagraphs( doc: Record, parentField: string, ): Record[] { const content = doc?.content; const root = content?.root; if (!root || !Array.isArray(root.children)) return []; const parentId = String(doc.id); const locale = doc.locale ? String(doc.locale) : undefined; const paragraphs: Record[] = []; let order = 0; for (const node of root.children) { const text = richTextToPlain(node).trim(); if (!text) continue; // skip empty spacer blocks const html = convertLexicalToHTML({ data: { ...content, root: { ...root, children: [node] } } as any, disableContainer: true, // Emit color/font from each text node's TextStateFeature `$` state. converters: stateHtmlConverters as any, }).trim(); paragraphs.push({ id: `${parentId}-${order}`, html: html || undefined, text, locale, [parentField]: parentId, order, }); order++; } return paragraphs; } /** Split an activity's content into activities_paragraphs docs. */ export const splitActivityParagraphs = (doc: Record) => splitParagraphs(doc, "activity"); /** Split a conference's content into conferences_paragraphs docs. */ export const splitConferenceParagraphs = (doc: Record) => splitParagraphs(doc, "conference");