Outline: per-chunk outlines, richer points, print and share

- One outline for the whole study plus one per chunk (picker at the top);
  chunk outlines can start from the chunk's verses, each carrying its words
- Points can have a type (illustration, application, quote, question,
  transition), minutes (with a running total), a longer note, verse text
  fetched from a passage, and cross-references / word studies attached from
  the chunk
- Share (system share sheet, or copy), Print / PDF, Copy as text, Word and
  Markdown downloads for any one outline
- All of it included in the Markdown, HTML/PDF and Word study exports,
  with each chunk's outline under that chunk

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
nmemmertandClaude Sonnet 5.5 committed 2026-10-01 14:24:28 -04:00
1 parent d273c8cb3e
commit 820886e3ab
12 files changed
+741 -103

No files matched your search

+40 -12
View File
@@ -14,7 +14,8 @@ import {
WidthType,
} from 'docx';
import { formatChunkReference, getChunkVerseEntries, htmlToPlainText } from './bibleUtils.jsx';
import { flatten } from './outline.js';
import { detailLines, flatten, timeSummary } from './outline.js';
import { kindLabel } from './outlineLabels.js';
export function createParagraphsFromText(text) {
return text
@@ -73,6 +74,37 @@ export function sectionLabel(text) {
});
}
// The paragraphs of an outline: each point indented by level, with its note, verse and attachments under it.
export const outlineParagraphs = (tree) => flatten(tree).flatMap(({ node, depth, label }) => [
new Paragraph({
indent: { left: 420 * depth, hanging: 420 },
spacing: { after: 60 },
children: [
new TextRun({ text: `${label}\t`, bold: true, color: DOCX_ACCENT }),
...(node.kind ? [new TextRun({ text: `${kindLabel(node.kind)}: `, italics: true, color: '92400E' })] : []),
new TextRun({ text: (node.text ?? '').trim() || '(untitled)' }),
...(node.ref ? [new TextRun({ text: ` (${node.ref})`, italics: true, color: '7A7060' })] : []),
...(Number.isFinite(node.minutes) && node.minutes > 0 ? [new TextRun({ text: ` · ${node.minutes} min`, color: '7A7060' })] : []),
],
}),
...detailLines(node).map((line) => new Paragraph({
indent: { left: 420 * depth + 420 },
spacing: { after: 40 },
children: [new TextRun({ text: line, italics: true, color: '4B5563', size: 20 })],
})),
]);
// Just one outline, as its own Word document.
export async function buildOutlineDocxBlob({ title, subtitle = '', tree }) {
const children = [
new Paragraph({ text: title, heading: HeadingLevel.TITLE }),
new Paragraph({ children: [new TextRun({ text: [subtitle, timeSummary(tree)].filter(Boolean).join(' · '), italics: true, color: '7A7060' })], spacing: { after: 300 } }),
...outlineParagraphs(tree),
];
const doc = new Document({ styles: DOCX_STYLES, sections: [{ children }] });
return Packer.toBlob(doc);
}
export async function buildStudyDocxBlob(project, chapterSummary) {
const children = [
new Paragraph({ text: project.title, heading: HeadingLevel.TITLE }),
@@ -93,17 +125,8 @@ export async function buildStudyDocxBlob(project, chapterSummary) {
// The outline comes first, indented by level like the page
if ((project.outline ?? []).length > 0) {
children.push(new Paragraph({ text: 'Outline', heading: HeadingLevel.HEADING_1, spacing: { before: 240, after: 160 } }));
flatten(project.outline).forEach(({ node, depth, label }) => {
children.push(new Paragraph({
indent: { left: 420 * depth, hanging: 420 },
spacing: { after: 80 },
children: [
new TextRun({ text: `${label}\t`, bold: true, color: DOCX_ACCENT }),
new TextRun({ text: (node.text ?? '').trim() || '(untitled)' }),
...(node.ref ? [new TextRun({ text: ` (${node.ref})`, italics: true, color: '7A7060' })] : []),
],
}));
});
if (timeSummary(project.outline)) children.push(new Paragraph({ children: [new TextRun({ text: timeSummary(project.outline), italics: true, color: '7A7060' })], spacing: { after: 120 } }));
children.push(...outlineParagraphs(project.outline));
}
project.chapters.forEach((ch, chapterIndex) => {
@@ -154,6 +177,11 @@ export async function buildStudyDocxBlob(project, chapterSummary) {
}));
children.push(new Paragraph({ text: '', spacing: { after: 120 } }));
if ((chunk.outline ?? []).length > 0) {
children.push(sectionLabel('Outline'));
children.push(...outlineParagraphs(chunk.outline));
}
if (chunk.generalNotes?.trim()) {
children.push(sectionLabel('Background / General Notes'));
children.push(...createParagraphsFromText(chunk.generalNotes));