Load real LibreBible data in browser preview
This commit is contained in:
+7
-2
@@ -29,6 +29,8 @@ function App() {
|
||||
|
||||
async function runSearch(nextQuery = query) {
|
||||
setError(null)
|
||||
const trimmed = nextQuery.trim()
|
||||
setSelectedStrong(/^[gh]\d+$/i.test(trimmed) ? trimmed.toUpperCase() : null)
|
||||
const results = await searchVerses(nextQuery)
|
||||
setVerses(results)
|
||||
setSelectedId(results[0]?.id ?? null)
|
||||
@@ -68,6 +70,7 @@ function App() {
|
||||
selectedVerse.strongs[0]
|
||||
)
|
||||
}, [selectedStrong, selectedVerse])
|
||||
const visibleVerses = useMemo(() => verses.slice(0, 250), [verses])
|
||||
|
||||
function searchStrong(strong: string) {
|
||||
setQuery(strong)
|
||||
@@ -130,8 +133,10 @@ function App() {
|
||||
</section>
|
||||
|
||||
<section className="result-list" aria-label="Search results">
|
||||
<div className="section-title">{verses.length} verses</div>
|
||||
{verses.map((verse) => (
|
||||
<div className="section-title">
|
||||
{verses.length} verses{verses.length > visibleVerses.length ? `, showing ${visibleVerses.length}` : ''}
|
||||
</div>
|
||||
{visibleVerses.map((verse) => (
|
||||
<button
|
||||
key={verse.id}
|
||||
className={verse.id === selectedVerse?.id ? 'result active' : 'result'}
|
||||
|
||||
+159
-8
@@ -1,5 +1,5 @@
|
||||
import { invoke } from '@tauri-apps/api/core'
|
||||
import type { LibraryState, Tag, Verse } from './types'
|
||||
import type { LibraryState, StrongLink, Tag, Verse } from './types'
|
||||
|
||||
type TauriWindow = Window &
|
||||
typeof globalThis & {
|
||||
@@ -7,8 +7,51 @@ type TauriWindow = Window &
|
||||
}
|
||||
|
||||
const browserTagKey = 'libre-study-browser-tags'
|
||||
const libreBibleDevRoot = '/@fs/W:/libre-bible-data/packages/json'
|
||||
|
||||
const browserVerses: Verse[] = [
|
||||
type ImportedVerse = {
|
||||
id: string
|
||||
book: string
|
||||
chapter: number
|
||||
verse: number
|
||||
text: string
|
||||
}
|
||||
|
||||
type ImportedStrongLink = {
|
||||
verse_id: string
|
||||
position: number
|
||||
surface: string
|
||||
strong: string
|
||||
}
|
||||
|
||||
type ImportedStrongEntry = {
|
||||
strongs_number: string
|
||||
language: 'hebrew' | 'greek'
|
||||
lemma?: string
|
||||
transliteration?: string
|
||||
pronunciation?: string
|
||||
strongs_definition?: string
|
||||
kjv_definition?: string
|
||||
}
|
||||
|
||||
type BrowserLexiconEntry = {
|
||||
lemma: string
|
||||
transliteration: string
|
||||
definition: string
|
||||
language: 'Hebrew' | 'Greek'
|
||||
}
|
||||
|
||||
type BrowserStrongData = {
|
||||
linksByVerse: Map<string, StrongLink[]>
|
||||
verseIdsByStrong: Map<string, Set<string>>
|
||||
}
|
||||
|
||||
let browserLibraryCache: Promise<Verse[]> | null = null
|
||||
let browserStrongDataCache: Promise<BrowserStrongData> | null = null
|
||||
let browserStrongData: BrowserStrongData | null = null
|
||||
const browserVerseSourceIds = new Map<number, string>()
|
||||
|
||||
const fallbackBrowserVerses: Verse[] = [
|
||||
{
|
||||
id: 1,
|
||||
book: 'Genesis',
|
||||
@@ -142,10 +185,18 @@ function writeBrowserTags(tagsByVerse: Record<number, Tag[]>) {
|
||||
localStorage.setItem(browserTagKey, JSON.stringify(tagsByVerse))
|
||||
}
|
||||
|
||||
function hydrateBrowserVerses() {
|
||||
async function loadBrowserVerses() {
|
||||
browserLibraryCache ??= loadLibreBibleDevVerses().catch((error) => {
|
||||
console.warn('Falling back to browser seed data.', error)
|
||||
return fallbackBrowserVerses
|
||||
})
|
||||
|
||||
const verses = await browserLibraryCache
|
||||
void loadBrowserStrongData()
|
||||
const tagsByVerse = readBrowserTags()
|
||||
return browserVerses.map((verse) => ({
|
||||
return verses.map((verse) => ({
|
||||
...verse,
|
||||
strongs: browserStrongData?.linksByVerse.get(browserVerseKey(verse)) ?? verse.strongs,
|
||||
tags: tagsByVerse[verse.id] ?? [],
|
||||
}))
|
||||
}
|
||||
@@ -166,7 +217,7 @@ export async function getLibraryState(): Promise<LibraryState> {
|
||||
}
|
||||
|
||||
return {
|
||||
verses: hydrateBrowserVerses(),
|
||||
verses: await loadBrowserVerses(),
|
||||
tags: allBrowserTags(),
|
||||
}
|
||||
}
|
||||
@@ -177,7 +228,7 @@ export async function searchVerses(query: string): Promise<Verse[]> {
|
||||
}
|
||||
|
||||
const trimmed = query.trim().toLowerCase()
|
||||
const verses = hydrateBrowserVerses()
|
||||
const verses = await loadBrowserVerses()
|
||||
if (!trimmed) return verses
|
||||
|
||||
if (trimmed.startsWith('tag:')) {
|
||||
@@ -188,8 +239,10 @@ export async function searchVerses(query: string): Promise<Verse[]> {
|
||||
}
|
||||
|
||||
if (/^[gh]\d+$/.test(trimmed)) {
|
||||
const strongData = await loadBrowserStrongData()
|
||||
const matchingVerseIds = strongData.verseIdsByStrong.get(trimmed.toUpperCase()) ?? new Set()
|
||||
return verses.filter((verse) =>
|
||||
verse.strongs.some((entry) => entry.strong.toLowerCase() === trimmed),
|
||||
matchingVerseIds.has(browserVerseKey(verse)),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -212,7 +265,105 @@ export async function addTagToVerse(verseId: number, label: string, color: strin
|
||||
tagsByVerse[verseId] = existing ? current : [...current, tag]
|
||||
writeBrowserTags(tagsByVerse)
|
||||
|
||||
const verse = hydrateBrowserVerses().find((item) => item.id === verseId)
|
||||
const verse = (await loadBrowserVerses()).find((item) => item.id === verseId)
|
||||
if (!verse) throw new Error(`Verse ${verseId} was not found.`)
|
||||
return verse
|
||||
}
|
||||
|
||||
async function loadLibreBibleDevVerses(): Promise<Verse[]> {
|
||||
const importedVerses = await fetchJsonl<ImportedVerse>(
|
||||
`${libreBibleDevRoot}/kjv-eng-kjv2006/verses.jsonl`,
|
||||
)
|
||||
|
||||
browserVerseSourceIds.clear()
|
||||
return importedVerses.map((verse, index) => {
|
||||
const id = index + 1
|
||||
browserVerseSourceIds.set(id, verse.id)
|
||||
return {
|
||||
id,
|
||||
book: verse.book,
|
||||
chapter: verse.chapter,
|
||||
verse: verse.verse,
|
||||
text: verse.text,
|
||||
strongs: [],
|
||||
tags: [],
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async function loadBrowserStrongData(): Promise<BrowserStrongData> {
|
||||
browserStrongDataCache ??= loadLibreBibleDevStrongData().then((data) => {
|
||||
browserStrongData = data
|
||||
return data
|
||||
})
|
||||
|
||||
return browserStrongDataCache
|
||||
}
|
||||
|
||||
async function loadLibreBibleDevStrongData(): Promise<BrowserStrongData> {
|
||||
const [importedLinks, importedEntries] = await Promise.all([
|
||||
fetchJsonl<ImportedStrongLink>(`${libreBibleDevRoot}/kjv-eng-kjv2006/strongs-links.jsonl`),
|
||||
fetchJsonl<ImportedStrongEntry>(`${libreBibleDevRoot}/strongs-open-scriptures/entries.jsonl`),
|
||||
])
|
||||
|
||||
const lexicon = new Map<string, BrowserLexiconEntry>(
|
||||
importedEntries.map((entry) => [
|
||||
entry.strongs_number,
|
||||
{
|
||||
lemma: entry.lemma ?? '',
|
||||
transliteration: entry.transliteration ?? '',
|
||||
definition: entry.strongs_definition ?? entry.kjv_definition ?? '',
|
||||
language: entry.language === 'hebrew' ? 'Hebrew' : 'Greek',
|
||||
},
|
||||
]),
|
||||
)
|
||||
const linksByVerse = new Map<string, StrongLink[]>()
|
||||
const verseIdsByStrong = new Map<string, Set<string>>()
|
||||
|
||||
for (const link of importedLinks) {
|
||||
const entry = lexicon.get(link.strong)
|
||||
const strongLink: StrongLink = {
|
||||
strong: link.strong,
|
||||
surface: link.surface,
|
||||
position: link.position,
|
||||
lemma: entry?.lemma ?? link.surface,
|
||||
transliteration: entry?.transliteration ?? '',
|
||||
definition: entry?.definition ?? '',
|
||||
language: entry?.language ?? strongLanguage(link.strong),
|
||||
}
|
||||
const current = linksByVerse.get(link.verse_id) ?? []
|
||||
current.push(strongLink)
|
||||
linksByVerse.set(link.verse_id, current)
|
||||
|
||||
const strongMatches = verseIdsByStrong.get(link.strong) ?? new Set<string>()
|
||||
strongMatches.add(link.verse_id)
|
||||
verseIdsByStrong.set(link.strong, strongMatches)
|
||||
}
|
||||
|
||||
for (const links of linksByVerse.values()) {
|
||||
links.sort((a, b) => a.position - b.position)
|
||||
}
|
||||
|
||||
return { linksByVerse, verseIdsByStrong }
|
||||
}
|
||||
|
||||
function strongLanguage(strong: string): 'Hebrew' | 'Greek' {
|
||||
return strong.startsWith('H') ? 'Hebrew' : 'Greek'
|
||||
}
|
||||
|
||||
function browserVerseKey(verse: Verse) {
|
||||
return browserVerseSourceIds.get(verse.id) ?? ''
|
||||
}
|
||||
|
||||
async function fetchJsonl<T>(url: string): Promise<T[]> {
|
||||
const response = await fetch(url)
|
||||
if (!response.ok) {
|
||||
throw new Error(`Unable to load ${url}: ${response.status} ${response.statusText}`)
|
||||
}
|
||||
|
||||
const text = await response.text()
|
||||
return text
|
||||
.split('\n')
|
||||
.filter((line) => line.trim().length > 0)
|
||||
.map((line) => JSON.parse(line) as T)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user