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 `
\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('') }]
}