Features added: - Database schema for pages and media files with content types (Rich Text, HTML, Markdown) - Admin API routes for full page CRUD operations - Image upload functionality with file management - Rich text editor using TinyMCE with image insertion - Admin interface for creating/editing pages with SEO options - Dynamic navigation and footer integration - Public page display routes with proper SEO metadata - Support for featured images and content excerpts Admin features: - Create/edit/delete pages with rich content editor - Upload and manage images through media library - Configure pages to appear in navigation or footer - Set page status (Draft, Published, Archived) - SEO title and description management - Real-time preview of content changes 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
144 lines
5.4 KiB
JavaScript
144 lines
5.4 KiB
JavaScript
#!/usr/bin/env node
|
|
const fs = require('fs')
|
|
const path = require('path')
|
|
|
|
const SRC = process.env.BSB_MD_PATH || path.join('bibles', 'bible-bsb.md')
|
|
|
|
function canon() {
|
|
const OT = [
|
|
['Genesis', ['Genesis'], 50],
|
|
['Exodus', ['Exodus'], 40],
|
|
['Leviticus', ['Leviticus'], 27],
|
|
['Numbers', ['Numbers'], 36],
|
|
['Deuteronomy', ['Deuteronomy'], 34],
|
|
['Joshua', ['Joshua'], 24],
|
|
['Judges', ['Judges'], 21],
|
|
['Ruth', ['Ruth'], 4],
|
|
['1 Samuel', ['1 Samuel','1 Samuel'], 31],
|
|
['2 Samuel', ['2 Samuel','2 Samuel'], 24],
|
|
['1 Kings', ['1 Kings','1 Kings'], 22],
|
|
['2 Kings', ['2 Kings','2 Kings'], 25],
|
|
['1 Chronicles', ['1 Chronicles','1 Chronicles'], 29],
|
|
['2 Chronicles', ['2 Chronicles','2 Chronicles'], 36],
|
|
['Ezra', ['Ezra'], 10],
|
|
['Nehemiah', ['Nehemiah'], 13],
|
|
['Esther', ['Esther'], 10],
|
|
['Job', ['Job'], 42],
|
|
['Psalms', ['Psalms','Psalm'], 150],
|
|
['Proverbs', ['Proverbs'], 31],
|
|
['Ecclesiastes', ['Ecclesiastes'], 12],
|
|
['Song of Songs', ['Song of Songs','Song of Solomon'], 8],
|
|
['Isaiah', ['Isaiah'], 66],
|
|
['Jeremiah', ['Jeremiah'], 52],
|
|
['Lamentations', ['Lamentations'], 5],
|
|
['Ezekiel', ['Ezekiel'], 48],
|
|
['Daniel', ['Daniel'], 12],
|
|
['Hosea', ['Hosea'], 14],
|
|
['Joel', ['Joel'], 3],
|
|
['Amos', ['Amos'], 9],
|
|
['Obadiah', ['Obadiah'], 1],
|
|
['Jonah', ['Jonah'], 4],
|
|
['Micah', ['Micah'], 7],
|
|
['Nahum', ['Nahum'], 3],
|
|
['Habakkuk', ['Habakkuk'], 3],
|
|
['Zephaniah', ['Zephaniah'], 3],
|
|
['Haggai', ['Haggai'], 2],
|
|
['Zechariah', ['Zechariah'], 14],
|
|
['Malachi', ['Malachi'], 4]
|
|
]
|
|
const NT = [
|
|
['Matthew', ['Matthew'], 28],
|
|
['Mark', ['Mark'], 16],
|
|
['Luke', ['Luke'], 24],
|
|
['John', ['John'], 21],
|
|
['Acts', ['Acts'], 28],
|
|
['Romans', ['Romans'], 16],
|
|
['1 Corinthians', ['1 Corinthians','1 Corinthians'], 16],
|
|
['2 Corinthians', ['2 Corinthians','2 Corinthians'], 13],
|
|
['Galatians', ['Galatians'], 6],
|
|
['Ephesians', ['Ephesians'], 6],
|
|
['Philippians', ['Philippians'], 4],
|
|
['Colossians', ['Colossians'], 4],
|
|
['1 Thessalonians', ['1 Thessalonians','1 Thessalonians'], 5],
|
|
['2 Thessalonians', ['2 Thessalonians','2 Thessalonians'], 3],
|
|
['1 Timothy', ['1 Timothy','1 Timothy'], 6],
|
|
['2 Timothy', ['2 Timothy','2 Timothy'], 4],
|
|
['Titus', ['Titus'], 3],
|
|
['Philemon', ['Philemon'], 1],
|
|
['Hebrews', ['Hebrews'], 13],
|
|
['James', ['James'], 5],
|
|
['1 Peter', ['1 Peter','1 Peter'], 5],
|
|
['2 Peter', ['2 Peter','2 Peter'], 3],
|
|
['1 John', ['1 John','1 John'], 5],
|
|
['2 John', ['2 John','2 John'], 1],
|
|
['3 John', ['3 John','3 John'], 1],
|
|
['Jude', ['Jude'], 1],
|
|
['Revelation', ['Revelation'], 22]
|
|
]
|
|
return [
|
|
...OT.map(([n,v,c]) => ({ name:n, variants:v, expectedChapters:c, testament:'OT' })),
|
|
...NT.map(([n,v,c]) => ({ name:n, variants:v, expectedChapters:c, testament:'NT' })),
|
|
]
|
|
}
|
|
|
|
function main() {
|
|
if (!fs.existsSync(SRC)) {
|
|
console.error('Missing source file:', SRC)
|
|
process.exit(1)
|
|
}
|
|
const md = fs.readFileSync(SRC, 'utf-8')
|
|
const books = canon()
|
|
|
|
const report = { file: SRC, totals: { versesTagged: 0 }, books: [] }
|
|
|
|
for (const b of books) {
|
|
const patterns = b.variants.map(v => v.replace(/\s+/g, '\\s+'))
|
|
const names = patterns.join('|')
|
|
const re = new RegExp(`(?:^|[\n\r\f\s\|\(])(?:${names})\\s+(\\d+):(\\d+)`, 'gi')
|
|
const chapters = new Set()
|
|
let m
|
|
let verseCount = 0
|
|
while ((m = re.exec(md)) !== null) {
|
|
const nums = m.slice(1).filter(Boolean)
|
|
const ch = parseInt(nums[0] || '0', 10)
|
|
const vs = parseInt(nums[1] || '0', 10)
|
|
if (Number.isFinite(ch) && ch > 0) chapters.add(ch)
|
|
if (Number.isFinite(vs) && vs > 0) verseCount++
|
|
}
|
|
|
|
// Heuristic: some one-chapter books may lack inline verse references; accept header presence
|
|
const oneChapterBooks = new Set(['Obadiah','Philemon','2 John','3 John','Jude'])
|
|
if (chapters.size === 0 && oneChapterBooks.has(b.name)) {
|
|
const headerRe = new RegExp(`[\f\n\r]\s*${b.variants.map(v=>v.replace(/\s+/g,'\\s+')).join('|')}\s*[\n\r]`, 'i')
|
|
if (headerRe.test(md)) {
|
|
chapters.add(1)
|
|
}
|
|
}
|
|
report.totals.versesTagged += verseCount
|
|
report.books.push({
|
|
name: b.name,
|
|
testament: b.testament,
|
|
expectedChapters: b.expectedChapters,
|
|
detectedChapters: Array.from(chapters).sort((a,b)=>a-b),
|
|
detectedCount: chapters.size,
|
|
coverage: b.expectedChapters > 0 ? +(100 * chapters.size / b.expectedChapters).toFixed(2) : null,
|
|
verseMarkers: verseCount
|
|
})
|
|
}
|
|
|
|
const missingBooks = report.books.filter(x => x.detectedCount === 0).map(x=>x.name)
|
|
const partialBooks = report.books.filter(x => x.detectedCount > 0 && x.detectedCount < x.expectedChapters).map(x=>({name:x.name, det:x.detectedCount, exp:x.expectedChapters}))
|
|
|
|
console.log('Validation summary for', SRC)
|
|
console.log('Total verse markers found:', report.totals.versesTagged)
|
|
console.log('Books missing markers:', missingBooks.length ? missingBooks.join(', ') : 'None')
|
|
console.log('Books partially detected (chapters):', partialBooks.length ? JSON.stringify(partialBooks.slice(0,10)) : 'None')
|
|
|
|
const outDir = path.join('data','en_bible','BSB_VALIDATION')
|
|
fs.mkdirSync(outDir, { recursive: true })
|
|
fs.writeFileSync(path.join(outDir,'report.json'), JSON.stringify(report, null, 2), 'utf-8')
|
|
console.log('Wrote detailed report to', path.join(outDir,'report.json'))
|
|
}
|
|
|
|
main()
|