- Share on the Outline page opens a sheet: create / copy / turn off a read-only link, share the link with the system share sheet, or send the outline as text - The link has its own token (separate from the whole-study share link) and the public endpoint returns only the outlines, rebuilt field by field: no observations, interpretations, applications, scripts or word notes - Opening the link shows the outline(s) with no sign-in, a switcher between the whole-study and per-chunk outlines, and Print / PDF - New outline_share_token column (added automatically on start) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
60 lines
3.0 KiB
JavaScript
60 lines
3.0 KiB
JavaScript
// 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 };
|
||
}
|