// What a read-only outline link shows: the outlines of a study (the whole-study one and each chunk's) // and nothing else — no observations, interpretations, applications, scripts or word notes. The // outline data comes from the browser, so it is rebuilt field by field instead of passed through. const MAX_NODES = 2000; const MAX_DEPTH = 8; const KINDS = new Set(['', 'illustration', 'application', 'quote', 'question', 'transition']); const str = (v, max) => (typeof v === 'string' ? v.slice(0, max) : ''); function cleanTree(nodes, depth, counter) { if (!Array.isArray(nodes) || depth > MAX_DEPTH) return []; const out = []; for (const n of nodes) { if (!n || typeof n !== 'object' || counter.n >= MAX_NODES) continue; counter.n += 1; const minutes = Number.isFinite(n.minutes) && n.minutes >= 0 && n.minutes <= 600 ? n.minutes : null; out.push({ id: str(n.id, 64) || `n${counter.n}`, text: str(n.text, 2000), ref: str(n.ref, 200), kind: KINDS.has(n.kind) ? n.kind : '', minutes, note: str(n.note, 8000), scripture: n.scripture && typeof n.scripture.text === 'string' ? { ref: str(n.scripture.ref, 200), text: str(n.scripture.text, 6000), translation: str(n.scripture.translation, 12) } : null, attachments: (Array.isArray(n.attachments) ? n.attachments : []).slice(0, 50) .filter((a) => a && typeof a === 'object') .map((a) => ({ id: str(a.id, 100), type: a.type === 'word' ? 'word' : 'xref', label: str(a.label, 200), ...(a.detail ? { detail: str(a.detail, 500) } : {}) })), children: cleanTree(n.children, depth + 1, counter), }); } return out; } const chunkLabel = (chapters, chapterIndex, chunk) => { const ch = chapters[chapterIndex]; const next = chapters[chapterIndex + 1]; if (Number.isInteger(chunk.spilloverEndVerse) && next && next.bookAbbrev === ch.bookAbbrev) { return `${ch.book} ${ch.chapter}:${chunk.startVerse}–${next.chapter}:${chunk.spilloverEndVerse}`; } return chunk.startVerse === chunk.endVerse ? `${ch.book} ${ch.chapter}:${chunk.startVerse}` : `${ch.book} ${ch.chapter}:${chunk.startVerse}–${chunk.endVerse}`; }; // -> { title, translation, outlines: [{ scope, label, tree }] } with only the outlines that have points export function buildOutlinePayload(project) { const counter = { n: 0 }; const outlines = []; const whole = cleanTree(project?.outline, 0, counter); if (whole.length > 0) outlines.push({ scope: 'study', label: 'Whole study', tree: whole }); const chapters = Array.isArray(project?.chapters) ? project.chapters : []; chapters.forEach((ch, chapterIndex) => { for (const chunk of Array.isArray(ch?.chunks) ? ch.chunks : []) { const tree = cleanTree(chunk?.outline, 0, counter); if (tree.length > 0) outlines.push({ scope: str(chunk.id, 64), label: str(chunkLabel(chapters, chapterIndex, chunk), 200), tree }); } }); return { title: str(project?.title, 500), translation: str(project?.translation, 12) || 'BSB', outlines }; }