// Lesson note storage format, shared by the client and the server. // // Each lesson's notes are stored as one string. Current format (v2): // {"v":2,"entries":[{"id","anchor","html","updatedAt"}]} // Legacy format (v1) was a JSON object of { anchor: html }, and older still was // plain text. Both are still read and are upgraded to v2 on the next save. export const NOTE_ANCHORS = ['scripture', 'commentary', 'questions', 'general'] export const NOTE_ANCHOR_LABELS = { scripture: 'Scripture Text', commentary: 'Commentary', questions: 'Reflection Questions', general: 'General Notes', } export function newNoteId() { return `n${Date.now().toString(36)}${Math.random().toString(36).slice(2, 7)}` } export function parseNoteEntries(raw) { if (typeof raw !== 'string' || !raw.trim()) return [] let parsed = null try { parsed = JSON.parse(raw) } catch { // Legacy plain-text note. return [{ id: newNoteId(), anchor: 'general', html: raw, updatedAt: null }] } if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) { return [{ id: newNoteId(), anchor: 'general', html: raw, updatedAt: null }] } if (parsed.v === 2 && Array.isArray(parsed.entries)) { return parsed.entries .filter(e => e && typeof e.html === 'string' && e.html.trim()) .map(e => ({ id: typeof e.id === 'string' && e.id ? e.id : newNoteId(), anchor: NOTE_ANCHORS.includes(e.anchor) ? e.anchor : 'general', html: e.html, updatedAt: typeof e.updatedAt === 'string' ? e.updatedAt : null, })) } return Object.entries(parsed) .filter(([, html]) => typeof html === 'string' && html.trim()) .map(([anchor, html]) => ({ id: `legacy-${anchor}`, anchor: NOTE_ANCHORS.includes(anchor) ? anchor : 'general', html, updatedAt: null, })) } export function serializeNoteEntries(entries) { const kept = entries.filter(e => e.html && e.html.trim()) if (kept.length === 0) return '' return JSON.stringify({ v: 2, entries: kept }) } export function noteHtmlToText(html) { return String(html) .replace(/<\/(p|li|h[1-6]|blockquote)>/gi, '\n') .replace(//gi, '\n') .replace(/<[^>]*>/g, '') .replace(/ /g, ' ') .replace(/</g, '<') .replace(/>/g, '>') .replace(/"/g, '"') .replace(/'/g, "'") .replace(/&/g, '&') .replace(/\n{3,}/g, '\n\n') .trim() } // Plain-text rendering of a whole lesson's notes (used by the Word export). export function noteEntriesToText(raw) { return parseNoteEntries(raw) .map(e => `${NOTE_ANCHOR_LABELS[e.anchor]}\n${noteHtmlToText(e.html)}`) .join('\n\n') }