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>
228 lines
9.6 KiB
TypeScript
228 lines
9.6 KiB
TypeScript
import fs from 'fs'
|
|
import path from 'path'
|
|
|
|
type Verse = { verseNum: number; text: string }
|
|
type Chapter = { chapterNum: number; verses: Verse[] }
|
|
type Book = { name: string; chapters: Chapter[] }
|
|
|
|
// Mapping to determine testament + order, reused to split OT / NT
|
|
const BOOK_MAPPINGS: Record<string, { testament: 'Old Testament' | 'New Testament'; orderNum: number }> = {
|
|
// Old Testament
|
|
'Geneza': { testament: 'Old Testament', orderNum: 1 },
|
|
'Exodul': { testament: 'Old Testament', orderNum: 2 },
|
|
'Leviticul': { testament: 'Old Testament', orderNum: 3 },
|
|
'Numerii': { testament: 'Old Testament', orderNum: 4 },
|
|
'Numeri': { testament: 'Old Testament', orderNum: 4 },
|
|
'Deuteronomul': { testament: 'Old Testament', orderNum: 5 },
|
|
'Deuteronom': { testament: 'Old Testament', orderNum: 5 },
|
|
'Iosua': { testament: 'Old Testament', orderNum: 6 },
|
|
'Judecătorii': { testament: 'Old Testament', orderNum: 7 },
|
|
'Judecători': { testament: 'Old Testament', orderNum: 7 },
|
|
'Rut': { testament: 'Old Testament', orderNum: 8 },
|
|
'1 Samuel': { testament: 'Old Testament', orderNum: 9 },
|
|
'2 Samuel': { testament: 'Old Testament', orderNum: 10 },
|
|
'1 Împăraţi': { testament: 'Old Testament', orderNum: 11 },
|
|
'2 Împăraţi': { testament: 'Old Testament', orderNum: 12 },
|
|
'1 Imparati': { testament: 'Old Testament', orderNum: 11 },
|
|
'2 Imparati': { testament: 'Old Testament', orderNum: 12 },
|
|
'1 Cronici': { testament: 'Old Testament', orderNum: 13 },
|
|
'2 Cronici': { testament: 'Old Testament', orderNum: 14 },
|
|
'Ezra': { testament: 'Old Testament', orderNum: 15 },
|
|
'Neemia': { testament: 'Old Testament', orderNum: 16 },
|
|
'Estera': { testament: 'Old Testament', orderNum: 17 },
|
|
'Iov': { testament: 'Old Testament', orderNum: 18 },
|
|
'Psalmii': { testament: 'Old Testament', orderNum: 19 },
|
|
'Proverbele': { testament: 'Old Testament', orderNum: 20 },
|
|
'Proverbe': { testament: 'Old Testament', orderNum: 20 },
|
|
'Eclesiastul': { testament: 'Old Testament', orderNum: 21 },
|
|
'Cântarea Cântărilor': { testament: 'Old Testament', orderNum: 22 },
|
|
'Isaia': { testament: 'Old Testament', orderNum: 23 },
|
|
'Ieremia': { testament: 'Old Testament', orderNum: 24 },
|
|
'Plângerile': { testament: 'Old Testament', orderNum: 25 },
|
|
'Ezechiel': { testament: 'Old Testament', orderNum: 26 },
|
|
'Daniel': { testament: 'Old Testament', orderNum: 27 },
|
|
'Osea': { testament: 'Old Testament', orderNum: 28 },
|
|
'Ioel': { testament: 'Old Testament', orderNum: 29 },
|
|
'Amos': { testament: 'Old Testament', orderNum: 30 },
|
|
'Obadia': { testament: 'Old Testament', orderNum: 31 },
|
|
'Iona': { testament: 'Old Testament', orderNum: 32 },
|
|
'Mica': { testament: 'Old Testament', orderNum: 33 },
|
|
'Naum': { testament: 'Old Testament', orderNum: 34 },
|
|
'Habacuc': { testament: 'Old Testament', orderNum: 35 },
|
|
'Ţefania': { testament: 'Old Testament', orderNum: 36 },
|
|
'Țefania': { testament: 'Old Testament', orderNum: 36 },
|
|
'Hagai': { testament: 'Old Testament', orderNum: 37 },
|
|
'Zaharia': { testament: 'Old Testament', orderNum: 38 },
|
|
'Maleahi': { testament: 'Old Testament', orderNum: 39 },
|
|
// New Testament
|
|
'Matei': { testament: 'New Testament', orderNum: 40 },
|
|
'Marcu': { testament: 'New Testament', orderNum: 41 },
|
|
'Luca': { testament: 'New Testament', orderNum: 42 },
|
|
'Ioan': { testament: 'New Testament', orderNum: 43 },
|
|
'Faptele Apostolilor': { testament: 'New Testament', orderNum: 44 },
|
|
'Romani': { testament: 'New Testament', orderNum: 45 },
|
|
'1 Corinteni': { testament: 'New Testament', orderNum: 46 },
|
|
'2 Corinteni': { testament: 'New Testament', orderNum: 47 },
|
|
'Galateni': { testament: 'New Testament', orderNum: 48 },
|
|
'Efeseni': { testament: 'New Testament', orderNum: 49 },
|
|
'Filipeni': { testament: 'New Testament', orderNum: 50 },
|
|
'Coloseni': { testament: 'New Testament', orderNum: 51 },
|
|
'1 Tesaloniceni': { testament: 'New Testament', orderNum: 52 },
|
|
'2 Tesaloniceni': { testament: 'New Testament', orderNum: 53 },
|
|
'1 Timotei': { testament: 'New Testament', orderNum: 54 },
|
|
'2 Timotei': { testament: 'New Testament', orderNum: 55 },
|
|
'Tit': { testament: 'New Testament', orderNum: 56 },
|
|
'Titus': { testament: 'New Testament', orderNum: 56 },
|
|
'Filimon': { testament: 'New Testament', orderNum: 57 },
|
|
'Evrei': { testament: 'New Testament', orderNum: 58 },
|
|
'Iacov': { testament: 'New Testament', orderNum: 59 },
|
|
'1 Petru': { testament: 'New Testament', orderNum: 60 },
|
|
'2 Petru': { testament: 'New Testament', orderNum: 61 },
|
|
'1 Ioan': { testament: 'New Testament', orderNum: 62 },
|
|
'2 Ioan': { testament: 'New Testament', orderNum: 63 },
|
|
'3 Ioan': { testament: 'New Testament', orderNum: 64 },
|
|
'Iuda': { testament: 'New Testament', orderNum: 65 },
|
|
'Apocalipsa': { testament: 'New Testament', orderNum: 66 },
|
|
'Revelaţia': { testament: 'New Testament', orderNum: 66 },
|
|
'Revelația': { testament: 'New Testament', orderNum: 66 }
|
|
}
|
|
|
|
function parseRomanianBibleMarkdown(md: string): Book[] {
|
|
const lines = md.split(/\r?\n/)
|
|
|
|
const books: Book[] = []
|
|
let currentBook: Book | null = null
|
|
let currentChapter: Chapter | null = null
|
|
|
|
// The MD file uses a pattern of a centered title line with ellipses like: … GENEZA …
|
|
// We detect those as book delimiters.
|
|
const isBookHeader = (line: string) => /^…\s*.+\s*…$/.test(line.trim())
|
|
|
|
const normalizeBookName = (raw: string) => {
|
|
// Strip leading/trailing ellipsis and extra spaces
|
|
const name = raw.replace(/^…\s*|\s*…$/g, '').trim()
|
|
// Try to map known variants to our mapping keys
|
|
// The MD sometimes uses slightly different diacritics or spacing; leave as-is if not found
|
|
return name
|
|
}
|
|
|
|
for (let i = 0; i < lines.length; i++) {
|
|
const raw = lines[i].trim()
|
|
if (!raw) continue
|
|
|
|
if (isBookHeader(raw)) {
|
|
// Save previous chapter and book if they have content
|
|
if (currentChapter && currentChapter.verses.length > 0 && currentBook) {
|
|
currentBook.chapters.push(currentChapter)
|
|
}
|
|
if (currentBook && currentBook.chapters.length > 0) {
|
|
books.push(currentBook)
|
|
}
|
|
|
|
const bookName = normalizeBookName(raw)
|
|
currentBook = { name: bookName, chapters: [] }
|
|
currentChapter = null
|
|
continue
|
|
}
|
|
|
|
// Detect chapter markers like: Capitolul X (case-insensitive, diacritics tolerant)
|
|
const chapterMatch = raw.match(/^[cC][aA][pP][iI][tT][oO][lL][uU][lL]\s+(\d+)$/)
|
|
if (chapterMatch && currentBook) {
|
|
// Save previous chapter if exists
|
|
if (currentChapter && currentChapter.verses.length > 0) {
|
|
currentBook.chapters.push(currentChapter)
|
|
}
|
|
currentChapter = { chapterNum: parseInt(chapterMatch[1], 10), verses: [] }
|
|
continue
|
|
}
|
|
|
|
// Detect verses that begin with a number: "1 text..."
|
|
const verseMatch = raw.match(/^(\d+)\s+(.+)$/)
|
|
if (verseMatch && currentChapter) {
|
|
const verseNum = parseInt(verseMatch[1], 10)
|
|
let verseText = verseMatch[2].trim()
|
|
|
|
// Remove paragraph markers if present
|
|
verseText = verseText.replace(/^¶\s*/, '')
|
|
|
|
// Merge continuation lines that don't start with a verse or chapter/book markers
|
|
let j = i + 1
|
|
while (j < lines.length) {
|
|
const lookahead = lines[j].trim()
|
|
if (!lookahead) break
|
|
if (/^(\d+)\s+/.test(lookahead)) break // next verse
|
|
if (isBookHeader(lookahead)) break // next book
|
|
if (/^[cC][aA][pP][iI][tT][oO][lL][uU][lL]\s+\d+$/.test(lookahead)) break // next chapter
|
|
verseText += ' ' + lookahead
|
|
j++
|
|
}
|
|
i = j - 1
|
|
|
|
verseText = verseText.replace(/\s+/g, ' ').trim()
|
|
if (verseText) {
|
|
currentChapter.verses.push({ verseNum, text: verseText })
|
|
}
|
|
}
|
|
}
|
|
|
|
// Save last chapter and book
|
|
if (currentChapter && currentChapter.verses.length > 0 && currentBook) {
|
|
currentBook.chapters.push(currentChapter)
|
|
}
|
|
if (currentBook && currentBook.chapters.length > 0) {
|
|
books.push(currentBook)
|
|
}
|
|
|
|
return books
|
|
}
|
|
|
|
function writeTestamentJson(books: Book[], outPath: string, testament: 'Old Testament' | 'New Testament') {
|
|
const data = {
|
|
testament,
|
|
books: books.map(b => ({ name: b.name, chapters: b.chapters }))
|
|
}
|
|
fs.mkdirSync(path.dirname(outPath), { recursive: true })
|
|
fs.writeFileSync(outPath, JSON.stringify(data, null, 2), 'utf-8')
|
|
console.log(`Wrote ${outPath} with ${books.length} books.`)
|
|
}
|
|
|
|
async function main() {
|
|
const mdPath = path.join(process.cwd(), 'bibles', 'Biblia-Fidela-limba-romana.md')
|
|
if (!fs.existsSync(mdPath)) {
|
|
throw new Error(`Markdown not found at ${mdPath}`)
|
|
}
|
|
|
|
console.log(`Reading: ${mdPath}`)
|
|
const md = fs.readFileSync(mdPath, 'utf-8')
|
|
const parsedBooks = parseRomanianBibleMarkdown(md)
|
|
console.log(`Parsed ${parsedBooks.length} books from Markdown`)
|
|
|
|
// Filter and sort into OT and NT using the mapping
|
|
const otBooks: Book[] = []
|
|
const ntBooks: Book[] = []
|
|
for (const b of parsedBooks) {
|
|
const meta = BOOK_MAPPINGS[b.name]
|
|
if (!meta) {
|
|
console.warn(`Skipping unknown book in mapping: ${b.name}`)
|
|
continue
|
|
}
|
|
if (meta.testament === 'Old Testament') otBooks.push(b)
|
|
else ntBooks.push(b)
|
|
}
|
|
|
|
// Sort by canonical order
|
|
otBooks.sort((a, b) => BOOK_MAPPINGS[a.name].orderNum - BOOK_MAPPINGS[b.name].orderNum)
|
|
ntBooks.sort((a, b) => BOOK_MAPPINGS[a.name].orderNum - BOOK_MAPPINGS[b.name].orderNum)
|
|
|
|
// Write JSON outputs compatible with import-romanian-versioned.ts
|
|
const otOut = path.join(process.cwd(), 'data', 'old_testament.json')
|
|
const ntOut = path.join(process.cwd(), 'data', 'new_testament.json')
|
|
writeTestamentJson(otBooks, otOut, 'Old Testament')
|
|
writeTestamentJson(ntBooks, ntOut, 'New Testament')
|
|
}
|
|
|
|
main().catch(err => {
|
|
console.error('Conversion failed:', err)
|
|
process.exit(1)
|
|
})
|