function esc(str) { return String(str) .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"') } export function nodeToHtml(node) { if (!node) return '' const ch = () => (node.content || []).map(nodeToHtml).join('') switch (node.type) { case 'doc': return ch() case 'paragraph': return `

${ch() || ' '}

\n` case 'heading': { const l = node.attrs?.level || 1 return `${ch()}\n` } case 'text': { let t = esc(node.text || '') for (const m of (node.marks || [])) { if (m.type === 'bold') t = `${t}` if (m.type === 'italic') t = `${t}` if (m.type === 'underline') t = `${t}` } return t } case 'image': { const { src = '', alt = '', width = '100%', align = 'center' } = node.attrs || {} const ml = align === 'left' ? '0' : 'auto' const mr = align === 'right' ? '0' : 'auto' return `${esc(alt)}\n` } case 'bulletList': return `\n` case 'orderedList': return `
    \n${ch()}
\n` case 'listItem': return `
  • ${ch()}
  • \n` case 'blockquote': return `
    \n${ch()}
    \n` case 'hardBreak': return '
    ' default: return ch() } } export function extractChapters(doc) { const chapters = [] for (const node of (doc?.content || [])) { if (node.type === 'heading' && node.attrs?.level === 1) { chapters.push((node.content || []).map(n => n.text || '').join('') || 'Chapter') } } return chapters } // Splits the document into chapter sections at each H1 boundary export function splitByChapters(doc, fallbackTitle = 'Story') { const nodes = doc?.content || [] if (!nodes.length) return [{ title: fallbackTitle, html: '' }] const groups = [] let current = { title: null, nodes: [] } for (const node of nodes) { if (node.type === 'heading' && node.attrs?.level === 1) { if (current.nodes.length > 0 || current.title !== null) { groups.push({ title: current.title || fallbackTitle, html: current.nodes.map(nodeToHtml).join('') }) } current = { title: (node.content || []).map(n => n.text || '').join('') || 'Chapter', nodes: [node], } } else { current.nodes.push(node) } } if (current.nodes.length > 0 || current.title !== null) { groups.push({ title: current.title || fallbackTitle, html: current.nodes.map(nodeToHtml).join('') }) } return groups.length ? groups : [{ title: fallbackTitle, html: nodes.map(nodeToHtml).join('') }] }