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");