Publish Container / build (push) Successful in 16m3s
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
80 lines
2.6 KiB
JavaScript
80 lines
2.6 KiB
JavaScript
// 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(/<br\s*\/?>/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')
|
|
}
|