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>
104 lines
5.4 KiB
JavaScript
104 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')
|
|
const OUT_ABBR = (process.env.EN_ABBR || 'BSB_MD').toUpperCase()
|
|
const OUT_DIR = process.env.OUTPUT_DIR || path.join('data','en_bible', OUT_ABBR)
|
|
|
|
function ensureDir(p){ fs.mkdirSync(p,{recursive:true}) }
|
|
function writeJson(file,obj){ ensureDir(path.dirname(file)); fs.writeFileSync(file, JSON.stringify(obj,null,2),'utf-8') }
|
|
|
|
const BOOKS = [
|
|
['Genesis', ['Genesis'],'OT'], ['Exodus',['Exodus'],'OT'], ['Leviticus',['Leviticus'],'OT'], ['Numbers',['Numbers'],'OT'], ['Deuteronomy',['Deuteronomy'],'OT'],
|
|
['Joshua',['Joshua'],'OT'], ['Judges',['Judges'],'OT'], ['Ruth',['Ruth'],'OT'], ['1 Samuel',['1\s+Samuel','1\s+Samuel'],'OT'], ['2 Samuel',['2\s+Samuel','2\s+Samuel'],'OT'],
|
|
['1 Kings',['1\s+Kings'],'OT'], ['2 Kings',['2\s+Kings'],'OT'], ['1 Chronicles',['1\s+Chronicles'],'OT'], ['2 Chronicles',['2\s+Chronicles'],'OT'],
|
|
['Ezra',['Ezra'],'OT'], ['Nehemiah',['Nehemiah'],'OT'], ['Esther',['Esther'],'OT'], ['Job',['Job'],'OT'], ['Psalms',['Psalms|Psalm'],'OT'],
|
|
['Proverbs',['Proverbs'],'OT'], ['Ecclesiastes',['Ecclesiastes'],'OT'], ['Song of Songs',['Song\s+of\s+Songs|Song\s+of\s+Solomon'],'OT'], ['Isaiah',['Isaiah'],'OT'],
|
|
['Jeremiah',['Jeremiah'],'OT'], ['Lamentations',['Lamentations'],'OT'], ['Ezekiel',['Ezekiel'],'OT'], ['Daniel',['Daniel'],'OT'],
|
|
['Hosea',['Hosea'],'OT'], ['Joel',['Joel'],'OT'], ['Amos',['Amos'],'OT'], ['Obadiah',['Obadiah'],'OT'], ['Jonah',['Jonah'],'OT'], ['Micah',['Micah'],'OT'],
|
|
['Nahum',['Nahum'],'OT'], ['Habakkuk',['Habakkuk'],'OT'], ['Zephaniah',['Zephaniah'],'OT'], ['Haggai',['Haggai'],'OT'], ['Zechariah',['Zechariah'],'OT'], ['Malachi',['Malachi'],'OT'],
|
|
['Matthew',['Matthew'],'NT'], ['Mark',['Mark'],'NT'], ['Luke',['Luke'],'NT'], ['John',['John'],'NT'], ['Acts',['Acts'],'NT'],
|
|
['Romans',['Romans'],'NT'], ['1 Corinthians',['1\s+Corinthians'],'NT'], ['2 Corinthians',['2\s+Corinthians'],'NT'], ['Galatians',['Galatians'],'NT'], ['Ephesians',['Ephesians'],'NT'],
|
|
['Philippians',['Philippians'],'NT'], ['Colossians',['Colossians'],'NT'], ['1 Thessalonians',['1\s+Thessalonians'],'NT'], ['2 Thessalonians',['2\s+Thessalonians'],'NT'],
|
|
['1 Timothy',['1\s+Timothy'],'NT'], ['2 Timothy',['2\s+Timothy'],'NT'], ['Titus',['Titus'],'NT'], ['Philemon',['Philemon'],'NT'],
|
|
['Hebrews',['Hebrews'],'NT'], ['James',['James'],'NT'], ['1 Peter',['1\\s+Peter'],'NT'], ['2 Peter',['2\\s+Peter'],'NT'],
|
|
['1 John',['1\s+John'],'NT'], ['2 John',['2\s+John'],'NT'], ['3 John',['3\s+John'],'NT'], ['Jude',['Jude'],'NT'], ['Revelation',['Revelation'],'NT']
|
|
]
|
|
|
|
function main(){
|
|
if(!fs.existsSync(SRC)) { console.error('Missing source:', SRC); process.exit(1) }
|
|
const md = fs.readFileSync(SRC,'utf-8')
|
|
|
|
// Collect all verse markers across the entire doc
|
|
const markers = []
|
|
for(const [name, variants] of BOOKS){
|
|
const names = variants.join('|')
|
|
const re = new RegExp('(?:^|[\\n\\r\\f\\s\\|\\(])(?:'+names+')\\s+(\\d+):(\\d+)', 'gi')
|
|
let m
|
|
while((m=re.exec(md))!==null){
|
|
markers.push({ book:name, chapter:parseInt(m[1],10), verse:parseInt(m[2],10), index:m.index, matchLen:m[0].length })
|
|
}
|
|
}
|
|
if(markers.length===0){ console.error('No verse markers found'); process.exit(1) }
|
|
markers.sort((a,b)=>a.index-b.index)
|
|
|
|
// Build text segments per marker (chapter/verse)
|
|
const entries = []
|
|
for(let i=0;i<markers.length;i++){
|
|
const cur = markers[i]
|
|
const start = cur.index + cur.matchLen
|
|
const end = (i+1<markers.length) ? markers[i+1].index : md.length
|
|
let text = md.slice(start, end)
|
|
text = text.replace(/[\u000c\r]+/g,'\n') // formfeed
|
|
text = text.replace(/\s+/g,' ').trim()
|
|
// Stop overly long spill
|
|
if(text.length>1500) text = text.slice(0,1500).trim()
|
|
entries.push({ ...cur, text })
|
|
}
|
|
|
|
// Aggregate into OT/NT JSON
|
|
const bookIndex = new Map(BOOKS.map(([n,_,t],i)=>[n,{testament:t, order:i+1}]))
|
|
const byBook = new Map()
|
|
for(const e of entries){
|
|
if(!byBook.has(e.book)) byBook.set(e.book, new Map())
|
|
const chMap = byBook.get(e.book)
|
|
if(!chMap.has(e.chapter)) chMap.set(e.chapter, new Map())
|
|
const vMap = chMap.get(e.chapter)
|
|
if(!vMap.has(e.verse)) vMap.set(e.verse, e.text)
|
|
}
|
|
|
|
const otBooks=[]; const ntBooks=[]
|
|
for(const [name, chMap] of byBook){
|
|
const meta = bookIndex.get(name)
|
|
const chapters=[]
|
|
for(const [ch, vMap] of Array.from(chMap.entries()).sort((a,b)=>a[0]-b[0])){
|
|
const verses=[]
|
|
for(const [vn, txt] of Array.from(vMap.entries()).sort((a,b)=>a[0]-b[0])){
|
|
verses.push({ verseNum: vn, text: txt })
|
|
}
|
|
if(verses.length>0) chapters.push({ chapterNum: ch, verses })
|
|
}
|
|
const bookObj={ name, chapters }
|
|
if(meta?.testament==='OT') otBooks.push({name,chapters})
|
|
else ntBooks.push({name,chapters})
|
|
}
|
|
|
|
// Sort books in canonical order
|
|
otBooks.sort((a,b)=>bookIndex.get(a.name).order-bookIndex.get(b.name).order)
|
|
ntBooks.sort((a,b)=>bookIndex.get(a.name).order-bookIndex.get(b.name).order)
|
|
|
|
const ot={ testament:'Old Testament', books: otBooks }
|
|
const nt={ testament:'New Testament', books: ntBooks }
|
|
|
|
const otFile = path.join(OUT_DIR,'old_testament.json')
|
|
const ntFile = path.join(OUT_DIR,'new_testament.json')
|
|
writeJson(otFile, ot)
|
|
writeJson(ntFile, nt)
|
|
console.log('Wrote:', otFile)
|
|
console.log('Wrote:', ntFile)
|
|
console.log('Books parsed:', otBooks.length + ntBooks.length)
|
|
}
|
|
|
|
main()
|