json-bible 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.ts +21 -4
- package/lib/Bible.ts +1 -0
- package/lib/api/ApiBible.ts +147 -0
- package/lib/api/converters.ts +120 -0
- package/lib/api/get.ts +156 -0
- package/lib/api/index.ts +293 -0
- package/lib/defaults.ts +38 -10
- package/lib/get.ts +30 -6
- package/lib/reference.ts +10 -5
- package/lib/search.ts +44 -6
- package/package.json +18 -18
package/index.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { Bible } from "./lib/Bible"
|
|
2
2
|
import { BIBLE_SIZE, getBookCategory, getDefault, getDefaultBooks, NT_SIZE, OT_SIZE } from "./lib/defaults"
|
|
3
|
-
import { _getBible, _getBook, _getChapter, _getCloseChapter, _getHTML, _getMetadata, _getRandomVerse, _getShortName, _getText, _getVerse, formatText, getBookIndex, getChapterIndex, getVerseIndex } from "./lib/get"
|
|
3
|
+
import { _getBible, _getBook, _getChapter, _getCloseChapter, _getHTML, _getMetadata, _getRandomVerse, _getShortName, _getText, _getVerse, formatText, getBookAbbreviation, getBookIndex, getChapterIndex, getVerseIndex } from "./lib/get"
|
|
4
4
|
import { getReferenceString, getVerseReferences } from "./lib/reference"
|
|
5
5
|
import { _bookSearch, _textSearch } from "./lib/search"
|
|
6
|
+
import "./lib/api/index" // api alternative reader (same structure)
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* JSON Bible Helper
|
|
@@ -11,7 +12,7 @@ import { _bookSearch, _textSearch } from "./lib/search"
|
|
|
11
12
|
export default async function Bible(bibleOrPath: Bible | string) {
|
|
12
13
|
const bible = await _getBible(bibleOrPath)
|
|
13
14
|
|
|
14
|
-
return { data: bible, getBook, getAbbreviation, getMetadata, getDefaultBooks, getDefault, getOT, getNT, getRandom, getFromReference, bookSearch, textSearch }
|
|
15
|
+
return { data: bible, getBook, getAbbreviation, getMetadata, getBooksData, getDefaultBooks, getDefault, getOT, getNT, getRandom, getFromReference, bookSearch, textSearch }
|
|
15
16
|
|
|
16
17
|
/**
|
|
17
18
|
* Get a specified book or the first one if not provided
|
|
@@ -156,8 +157,7 @@ export default async function Bible(bibleOrPath: Bible | string) {
|
|
|
156
157
|
* @return e.g. "Genesis" = "Gen"
|
|
157
158
|
*/
|
|
158
159
|
function getNameShort() {
|
|
159
|
-
|
|
160
|
-
return abbr[0] + abbr.slice(1).toLowerCase()
|
|
160
|
+
return getBookAbbreviation(bible, bookIndex)
|
|
161
161
|
}
|
|
162
162
|
|
|
163
163
|
/**
|
|
@@ -184,6 +184,23 @@ export default async function Bible(bibleOrPath: Bible | string) {
|
|
|
184
184
|
return _getMetadata(bible)
|
|
185
185
|
}
|
|
186
186
|
|
|
187
|
+
/**
|
|
188
|
+
* Get books data with category info and calculated abbreviation
|
|
189
|
+
* @return array with book data
|
|
190
|
+
*/
|
|
191
|
+
function getBooksData() {
|
|
192
|
+
return bible.books.map((book, bookIndex) => {
|
|
193
|
+
const category = getBookCategory(book)
|
|
194
|
+
return {
|
|
195
|
+
id: book.id,
|
|
196
|
+
name: book.name,
|
|
197
|
+
number: book.number,
|
|
198
|
+
abbreviation: getBookAbbreviation(bible, bookIndex),
|
|
199
|
+
category
|
|
200
|
+
}
|
|
201
|
+
})
|
|
202
|
+
}
|
|
203
|
+
|
|
187
204
|
/**
|
|
188
205
|
* Get all books in the Bible from the Old Testament
|
|
189
206
|
* @return list of max 39 books
|
package/lib/Bible.ts
CHANGED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
// BIBLES
|
|
2
|
+
|
|
3
|
+
// Bible.API format
|
|
4
|
+
export interface BibleListContent {
|
|
5
|
+
id: string
|
|
6
|
+
dblId: string // id without "-01"
|
|
7
|
+
relatedDbl: string | null
|
|
8
|
+
name: string
|
|
9
|
+
nameLocal: string // usually same as name
|
|
10
|
+
abbreviation: string
|
|
11
|
+
abbreviationLocal: string // usually same as abbreviation
|
|
12
|
+
description: string
|
|
13
|
+
descriptionLocal: string // usually same as description
|
|
14
|
+
language: {
|
|
15
|
+
id: string // e.g., "eng"
|
|
16
|
+
name: string // English name
|
|
17
|
+
nameLocal: string // native name
|
|
18
|
+
script: string // e.g., Latin
|
|
19
|
+
scriptDirection: string // e.g., LTR
|
|
20
|
+
}
|
|
21
|
+
countries: { id: string; name: string; nameLocal: string }[]
|
|
22
|
+
type: string // "text" or "audio"
|
|
23
|
+
updatedAt: string // ISO date string
|
|
24
|
+
audioBibles: any[]
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// contentapi.churchapps.org/bibles format
|
|
28
|
+
export interface CustomBibleListContent {
|
|
29
|
+
id: string // custom id
|
|
30
|
+
abbreviation: string
|
|
31
|
+
name: string
|
|
32
|
+
nameLocal: string
|
|
33
|
+
description: string
|
|
34
|
+
source: string // "api.bible"
|
|
35
|
+
sourceKey: string // api.bible id
|
|
36
|
+
language: string // language.id
|
|
37
|
+
copyright: string // custom deals with copyright info
|
|
38
|
+
attributionRequired: boolean // custom deal that needs to show attribution string
|
|
39
|
+
attributionString: string // custom attribution string
|
|
40
|
+
countryList: string[] // countries.id (lowercase)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// BOOK
|
|
44
|
+
|
|
45
|
+
export interface BibleBookContent {
|
|
46
|
+
id: string
|
|
47
|
+
bibleId: string
|
|
48
|
+
abbreviation: string
|
|
49
|
+
name: string
|
|
50
|
+
nameLong: string
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// contentapi.churchapps.org format
|
|
54
|
+
export interface CustomBibleBookContent {
|
|
55
|
+
id: string // custom id
|
|
56
|
+
translationKey: string // bibleId
|
|
57
|
+
keyName: string // id
|
|
58
|
+
abbreviation: string
|
|
59
|
+
name: string
|
|
60
|
+
sort: number // book index
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// CHAPTER
|
|
64
|
+
|
|
65
|
+
export interface BibleChapterContent {
|
|
66
|
+
id: string // e.g., GEN.1
|
|
67
|
+
bibleId: string
|
|
68
|
+
bookId: string // e.g., GEN
|
|
69
|
+
number: string // can be "intro"
|
|
70
|
+
reference: string
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// contentapi.churchapps.org format
|
|
74
|
+
export interface CustomChapterContent {
|
|
75
|
+
id: string // custom id
|
|
76
|
+
translationKey: string // bibleId
|
|
77
|
+
bookKey: string // bookId
|
|
78
|
+
keyName: string // id
|
|
79
|
+
number: number // chapter number
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// VERSE
|
|
83
|
+
|
|
84
|
+
export interface BibleVerseListContent {
|
|
85
|
+
id: string // e.g., GEN.1.1
|
|
86
|
+
orgId: string
|
|
87
|
+
bookId: string // e.g., GEN
|
|
88
|
+
chapterId: string // e.g., GEN.1
|
|
89
|
+
bibleId: string
|
|
90
|
+
reference: string
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// contentapi.churchapps.org format
|
|
94
|
+
export interface CustomVerseListContent {
|
|
95
|
+
id: string // custom id
|
|
96
|
+
translationKey: string // bibleId
|
|
97
|
+
chapterKey: string // chapterId
|
|
98
|
+
keyName: string // id
|
|
99
|
+
number: number // verse number
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export interface BibleVerseContent {
|
|
103
|
+
id: string // e.g., GEN.1.1
|
|
104
|
+
orgId: string
|
|
105
|
+
bookId: string // e.g., GEN
|
|
106
|
+
chapterId: string // e.g., GEN.1
|
|
107
|
+
bibleId: string
|
|
108
|
+
reference: string
|
|
109
|
+
content: string // HTML content
|
|
110
|
+
verseCount: number
|
|
111
|
+
copyright: string
|
|
112
|
+
next: { id: string; number: string } | null
|
|
113
|
+
previous: { id: string; number: string } | null
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// contentapi.churchapps.org format
|
|
117
|
+
export interface CustomVerseContent {
|
|
118
|
+
id: string // custom id
|
|
119
|
+
translationKey: string // bibleId
|
|
120
|
+
verseKey: string // id
|
|
121
|
+
bookKey: string // bookId
|
|
122
|
+
chapterNumber: number // chapter number
|
|
123
|
+
verseNumber: number // verse number
|
|
124
|
+
content: string // plain text content
|
|
125
|
+
newParagraph: boolean // if new paragraph starts here
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// SEARCH
|
|
129
|
+
|
|
130
|
+
export interface BibleContentSearchResult {
|
|
131
|
+
query: string
|
|
132
|
+
limit: number
|
|
133
|
+
offset: number
|
|
134
|
+
total: number
|
|
135
|
+
verseCount: number
|
|
136
|
+
verses: BibleVerseSearchContent[]
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export interface BibleVerseSearchContent {
|
|
140
|
+
id: string // e.g., GEN.1.1
|
|
141
|
+
orgId: string // usually same as id
|
|
142
|
+
bookId: string // e.g., GEN
|
|
143
|
+
bibleId: string // api bible id
|
|
144
|
+
chapterId: string // e.g., GEN.1
|
|
145
|
+
reference: string // e.g., Genesis 1:1
|
|
146
|
+
text: string // plain text content
|
|
147
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import type { Verse } from "../Bible"
|
|
2
|
+
import type { BibleBookContent, BibleChapterContent, BibleListContent, BibleVerseContent, BibleVerseListContent, CustomBibleBookContent, CustomBibleListContent, CustomChapterContent, CustomVerseContent, CustomVerseListContent } from "./ApiBible"
|
|
3
|
+
|
|
4
|
+
export function getCustomBibleListContent(data: (BibleListContent | CustomBibleListContent)[]): CustomBibleListContent[] {
|
|
5
|
+
if (!data?.length) return []
|
|
6
|
+
if ((data[0] as CustomBibleListContent).sourceKey) return data as CustomBibleListContent[]
|
|
7
|
+
|
|
8
|
+
return (data as BibleListContent[]).map((item) => {
|
|
9
|
+
return {
|
|
10
|
+
id: item.id,
|
|
11
|
+
abbreviation: item.abbreviation,
|
|
12
|
+
name: item.name,
|
|
13
|
+
nameLocal: item.nameLocal,
|
|
14
|
+
description: item.description,
|
|
15
|
+
source: "api.bible",
|
|
16
|
+
sourceKey: item.id,
|
|
17
|
+
language: item.language.id,
|
|
18
|
+
copyright: "",
|
|
19
|
+
attributionRequired: false,
|
|
20
|
+
attributionString: "",
|
|
21
|
+
countryList: item.countries.map((country) => country.id.toLowerCase())
|
|
22
|
+
} as CustomBibleListContent
|
|
23
|
+
})
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function getCustomBookContent(data: (BibleBookContent | CustomBibleBookContent)[]): CustomBibleBookContent[] {
|
|
27
|
+
if (!data?.length) return []
|
|
28
|
+
if ((data[0] as CustomBibleBookContent).keyName) return data as CustomBibleBookContent[]
|
|
29
|
+
|
|
30
|
+
return (data as BibleBookContent[]).map((book, i) => {
|
|
31
|
+
return {
|
|
32
|
+
id: book.id,
|
|
33
|
+
translationKey: book.bibleId,
|
|
34
|
+
keyName: book.id,
|
|
35
|
+
abbreviation: book.abbreviation,
|
|
36
|
+
name: book.name,
|
|
37
|
+
sort: i
|
|
38
|
+
} as CustomBibleBookContent
|
|
39
|
+
})
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function getCustomChapterContent(data: (BibleChapterContent | CustomChapterContent)[]): CustomChapterContent[] {
|
|
43
|
+
if (!data?.length) return []
|
|
44
|
+
if ((data[0] as CustomChapterContent).keyName) return data as CustomChapterContent[]
|
|
45
|
+
|
|
46
|
+
return (data as BibleChapterContent[]).map((chapter) => {
|
|
47
|
+
return {
|
|
48
|
+
id: chapter.id,
|
|
49
|
+
translationKey: chapter.bibleId,
|
|
50
|
+
bookKey: chapter.bookId,
|
|
51
|
+
keyName: chapter.id,
|
|
52
|
+
number: parseInt(chapter.number)
|
|
53
|
+
} as CustomChapterContent
|
|
54
|
+
})
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function getCustomVerseContent(data: (BibleVerseListContent | CustomVerseListContent)[]): CustomVerseListContent[] {
|
|
58
|
+
if (!data?.length) return []
|
|
59
|
+
if ((data as CustomVerseListContent[])[0]?.keyName) return data as CustomVerseListContent[]
|
|
60
|
+
|
|
61
|
+
return (data as BibleVerseListContent[]).map((verse) => {
|
|
62
|
+
return {
|
|
63
|
+
keyName: verse.id,
|
|
64
|
+
number: parseInt(verse.id.split(".")[2]),
|
|
65
|
+
id: verse.id,
|
|
66
|
+
translationKey: verse.bibleId,
|
|
67
|
+
chapterKey: verse.chapterId
|
|
68
|
+
} as CustomVerseListContent
|
|
69
|
+
})
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function convertVerseTextToJson(verses: BibleVerseContent | CustomVerseContent[]) {
|
|
73
|
+
// custom data format
|
|
74
|
+
if (Array.isArray(verses)) {
|
|
75
|
+
return verses.map((verse) => {
|
|
76
|
+
const verseNumber = verse.verseNumber
|
|
77
|
+
const plainText = verse.content || ""
|
|
78
|
+
return { number: verseNumber, text: plainText } as Verse
|
|
79
|
+
})
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Parse HTML content to extract verses with their numbers
|
|
83
|
+
const text = verses.content || ""
|
|
84
|
+
const verseRegex = /<span data-number="(\d+)"[^>]*class="v"[^>]*>\d+<\/span>/g
|
|
85
|
+
const versesArray: Verse[] = []
|
|
86
|
+
|
|
87
|
+
let match: RegExpExecArray | null
|
|
88
|
+
while ((match = verseRegex.exec(text)) !== null) {
|
|
89
|
+
const verseNumber = parseInt(match[1])
|
|
90
|
+
const startIndex = match.index + match[0].length
|
|
91
|
+
|
|
92
|
+
// Find the next verse or end of text
|
|
93
|
+
const nextMatch = verseRegex.exec(text)
|
|
94
|
+
const endIndex = nextMatch ? nextMatch.index : text.length
|
|
95
|
+
|
|
96
|
+
// Reset regex position to continue from where we left off
|
|
97
|
+
verseRegex.lastIndex = startIndex
|
|
98
|
+
|
|
99
|
+
// Extract text between current verse and next verse (or end)
|
|
100
|
+
let verseText = text.substring(startIndex, endIndex)
|
|
101
|
+
|
|
102
|
+
// Clean up HTML tags and normalize whitespace
|
|
103
|
+
verseText = verseText
|
|
104
|
+
.replace(/<\/p>/g, " ") // Replace closing p tags with space
|
|
105
|
+
.replace(/<[^>]*>/g, " ") // Remove all other HTML tags
|
|
106
|
+
.replace(/\s+/g, " ") // Normalize whitespace
|
|
107
|
+
.trim()
|
|
108
|
+
|
|
109
|
+
if (verseText) {
|
|
110
|
+
versesArray.push({ number: verseNumber, text: verseText } as Verse)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// If we found a next match, we need to process it in the next iteration
|
|
114
|
+
if (nextMatch) {
|
|
115
|
+
verseRegex.lastIndex = nextMatch.index
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return versesArray
|
|
120
|
+
}
|
package/lib/api/get.ts
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import type { Bible, Book, Chapter } from "../Bible"
|
|
2
|
+
import type { BibleContentSearchResult, BibleVerseContent, CustomVerseContent } from "./ApiBible"
|
|
3
|
+
import { convertVerseTextToJson, getCustomBibleListContent, getCustomBookContent, getCustomChapterContent, getCustomVerseContent } from "./converters"
|
|
4
|
+
|
|
5
|
+
const APIBIBLE_URL = "https://api.scripture.api.bible/v1/bibles"
|
|
6
|
+
|
|
7
|
+
export default function ApiBibleHelper(key: string, customApiUrl?: string) {
|
|
8
|
+
if (!key) throw new Error("No API key!")
|
|
9
|
+
|
|
10
|
+
let apiUrl = customApiUrl || APIBIBLE_URL
|
|
11
|
+
if (apiUrl.endsWith("/")) apiUrl = apiUrl.slice(0, -1)
|
|
12
|
+
|
|
13
|
+
const headers: { [key: string]: string } = { "api-key": key }
|
|
14
|
+
|
|
15
|
+
return { getBibles, bible, clearCache }
|
|
16
|
+
|
|
17
|
+
async function getBibles() {
|
|
18
|
+
return getCustomBibleListContent(await fetchWrapper(apiUrl, headers, 3))
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async function bible(bibleId: string) {
|
|
22
|
+
const bibleUrl = `${apiUrl}/${bibleId}`
|
|
23
|
+
|
|
24
|
+
// BOOKS
|
|
25
|
+
|
|
26
|
+
const booksData = await getApiBooks()
|
|
27
|
+
|
|
28
|
+
const json: Bible = {
|
|
29
|
+
name: "",
|
|
30
|
+
abbreviation: "",
|
|
31
|
+
metadata: {},
|
|
32
|
+
books: booksData.map((book, i) => {
|
|
33
|
+
return { number: i + 1, name: book.name, id: book.keyName, chapters: [] } as Book
|
|
34
|
+
})
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return { json, booksData, getChapters, getVerses, contentSearch }
|
|
38
|
+
|
|
39
|
+
async function getApiBooks() {
|
|
40
|
+
const booksUrl = `${bibleUrl}/books`
|
|
41
|
+
return getCustomBookContent(await fetchWrapper(booksUrl, headers, 60))
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// CHAPTERS
|
|
45
|
+
|
|
46
|
+
// GEN
|
|
47
|
+
async function getChapters(bookId: string) {
|
|
48
|
+
const chaptersData = await getApiChapters(bookId)
|
|
49
|
+
|
|
50
|
+
const json = chaptersData.map((chapter) => {
|
|
51
|
+
return { number: chapter.number, verses: [] } as Chapter
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
return { json, chaptersData }
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async function getApiChapters(bookId: string) {
|
|
58
|
+
const chaptersUrl = customApiUrl ? `${bibleUrl}/${bookId}/chapters` : `${bibleUrl}/books/${bookId}/chapters`
|
|
59
|
+
return getCustomChapterContent(await fetchWrapper(chaptersUrl, headers, 60)).filter((a) => !a.keyName.includes("intro"))
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// VERSES
|
|
63
|
+
|
|
64
|
+
// GEN.1
|
|
65
|
+
async function getVerses(chapterId: string) {
|
|
66
|
+
const versesData = await getApiVerses(chapterId)
|
|
67
|
+
const reference = versesData[0].keyName + "-" + versesData[versesData.length - 1].keyName
|
|
68
|
+
const versesContent = await getApiVersesContent(reference)
|
|
69
|
+
|
|
70
|
+
const json = convertVerseTextToJson(versesContent)
|
|
71
|
+
|
|
72
|
+
return { json, versesData }
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async function getApiVerses(chapterId: string) {
|
|
76
|
+
const versesUrl = `${bibleUrl}/chapters/${chapterId}/verses`
|
|
77
|
+
return getCustomVerseContent(await fetchWrapper(versesUrl, headers, 60))
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// GEN.1.1-GEN.1.10
|
|
81
|
+
async function getApiVersesContent(reference: string) {
|
|
82
|
+
const versesReferenceUrl = `${bibleUrl}/verses/${reference}`
|
|
83
|
+
return (await fetchWrapper(versesReferenceUrl, headers, 60)) as BibleVerseContent | CustomVerseContent[]
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// SEARCH
|
|
87
|
+
|
|
88
|
+
// no api key needed, just the bible id
|
|
89
|
+
async function contentSearch(query: string, { limit } = { limit: 20 }) {
|
|
90
|
+
const url = `${bibleUrl}/search?query=${query}&limit=${limit}`
|
|
91
|
+
return ((await fetchWrapper(url, headers, 14)) as BibleContentSearchResult).verses
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function clearCache(key: string) {
|
|
96
|
+
clearCachedContent(key)
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// HTTP
|
|
101
|
+
|
|
102
|
+
export function fetchWrapper(url: string, headers: any, cacheTimeDays: number) {
|
|
103
|
+
if (getCachedContent(url)) return Promise.resolve(getCachedContent(url, cacheTimeDays))
|
|
104
|
+
|
|
105
|
+
// console.info("Fetching:", url)
|
|
106
|
+
return fetch(url, { headers })
|
|
107
|
+
.then((response) => {
|
|
108
|
+
if (response.status !== 200) {
|
|
109
|
+
throw new Error("Bad response from server: " + response.status)
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return response.json()
|
|
113
|
+
})
|
|
114
|
+
.then((data) => {
|
|
115
|
+
if (!Array.isArray(data) && data?.data) data = data.data
|
|
116
|
+
if (!data) throw new Error("No data found")
|
|
117
|
+
|
|
118
|
+
cacheContent(url, data)
|
|
119
|
+
return data
|
|
120
|
+
})
|
|
121
|
+
.catch((e) => {
|
|
122
|
+
throw new Error(e)
|
|
123
|
+
})
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// CACHE
|
|
127
|
+
|
|
128
|
+
export function cacheContent(key: string, data: any) {
|
|
129
|
+
if (typeof localStorage === "undefined") return
|
|
130
|
+
|
|
131
|
+
const cacheEntry = { data, timestamp: Date.now() }
|
|
132
|
+
localStorage.setItem(key, JSON.stringify(cacheEntry))
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const ONE_DAY_MS = 24 * 60 * 60 * 1000
|
|
136
|
+
export function getCachedContent(key: string, maxAgeDays: number = 7) {
|
|
137
|
+
if (typeof localStorage === "undefined") return null
|
|
138
|
+
|
|
139
|
+
const cached = localStorage.getItem(key)
|
|
140
|
+
if (!cached) return null
|
|
141
|
+
|
|
142
|
+
const { data, timestamp } = JSON.parse(cached)
|
|
143
|
+
const age = Date.now() - timestamp
|
|
144
|
+
|
|
145
|
+
if (age > maxAgeDays * ONE_DAY_MS) {
|
|
146
|
+
clearCachedContent(key)
|
|
147
|
+
return null
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return data
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export function clearCachedContent(key: string) {
|
|
154
|
+
if (typeof localStorage === "undefined") return
|
|
155
|
+
localStorage.removeItem(key)
|
|
156
|
+
}
|
package/lib/api/index.ts
ADDED
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
import { BIBLE_SIZE, getBookCategory, getDefault, getDefaultBooks, NT_SIZE, OT_SIZE } from "../defaults"
|
|
2
|
+
import { _getBook, _getChapter, _getCloseChapter, _getHTML, _getMetadata, _getShortName, _getText, _getVerse, formatText, getBookAbbreviation, getBookIndex, getChapterIndex, getVerseIndex } from "../get"
|
|
3
|
+
import { getReferenceString, getVerseReferences, VerseReference } from "../reference"
|
|
4
|
+
import { _bookSearch } from "../search"
|
|
5
|
+
import ApiBibleHelper from "./get"
|
|
6
|
+
|
|
7
|
+
///// API BIBLE (requests one part at a time) /////
|
|
8
|
+
// Works with the Bible.API format
|
|
9
|
+
// Any custom API URL uses a smaller, but similar format (see ApiBible.ts)
|
|
10
|
+
// Used by FreeShow Presentation Software through the ChurchApps ContentAPI
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* API.Bible Bibles List
|
|
14
|
+
* @param apiKey API key from https://api.bible
|
|
15
|
+
* @param apiUrl custom api URL
|
|
16
|
+
* @returns
|
|
17
|
+
*/
|
|
18
|
+
export function ApiBiblesList(apiKey: string, apiUrl?: string) {
|
|
19
|
+
return ApiBibleHelper(apiKey, apiUrl).getBibles()
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* API.Bible Helper
|
|
24
|
+
* @param apiKey API key from https://api.bible
|
|
25
|
+
* @param apiUrl custom api URL
|
|
26
|
+
*/
|
|
27
|
+
export async function ApiBible(apiKey: string, bibleKey: string, apiUrl?: string) {
|
|
28
|
+
const apiData = await ApiBibleHelper(apiKey, apiUrl)
|
|
29
|
+
const bibleData = await apiData.bible(bibleKey)
|
|
30
|
+
const bible = bibleData.json
|
|
31
|
+
|
|
32
|
+
return { data: bible, getBook, getAbbreviation, getMetadata, getBooksData, getDefaultBooks, getDefault, getOT, getNT, getFromReference, bookSearch, textSearch }
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Get a specified book or the first one if not provided
|
|
36
|
+
* @param number book number/name/id
|
|
37
|
+
* @return book data
|
|
38
|
+
*/
|
|
39
|
+
async function getBook(numberOrId?: string | number) {
|
|
40
|
+
if (numberOrId === undefined) numberOrId = bible.books[0].number
|
|
41
|
+
let bookIndex = getBookIndex(bible, numberOrId)
|
|
42
|
+
const book = _getBook(bible, bookIndex)
|
|
43
|
+
|
|
44
|
+
const bookId = bible.books[bookIndex]?.id || ""
|
|
45
|
+
const chapters = await bibleData.getChapters(bookId)
|
|
46
|
+
book.chapters = chapters.json
|
|
47
|
+
|
|
48
|
+
return { data: book, index: bookIndex, number: book.number, name: book.name, getChapter, getAbbreviation: getNameShort, getCategory }
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Get a specified chapter or the first one if no number provided
|
|
52
|
+
* @param number chapter number
|
|
53
|
+
* @return chapter data
|
|
54
|
+
*/
|
|
55
|
+
async function getChapter(number?: number) {
|
|
56
|
+
if (number === undefined) number = book.chapters[0].number
|
|
57
|
+
let chapterIndex = getChapterIndex(book, number)
|
|
58
|
+
const chapter = _getChapter(book, chapterIndex)
|
|
59
|
+
|
|
60
|
+
const chapterId = `${bookId}.${number}`
|
|
61
|
+
const verses = await bibleData.getVerses(chapterId)
|
|
62
|
+
chapter.verses = verses.json
|
|
63
|
+
|
|
64
|
+
return { data: chapter, index: chapterIndex, number, getVerse, getVerses, getNext, getPrevious, getReference }
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Get a specified verse or the first one if no number provided
|
|
68
|
+
* @param number verse number
|
|
69
|
+
* @return verse data
|
|
70
|
+
*/
|
|
71
|
+
function getVerse(number?: number) {
|
|
72
|
+
if (number === undefined) number = chapter.verses[0].number
|
|
73
|
+
let verseIndex = getVerseIndex(chapter, number)
|
|
74
|
+
const verse = _getVerse(chapter, verseIndex)
|
|
75
|
+
|
|
76
|
+
return { data: verse, index: verseIndex, number, getText, getHTML, getReference }
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* A plain text string of the verse
|
|
80
|
+
* @param includeNumbers should verse numbers be included
|
|
81
|
+
* @return verse text
|
|
82
|
+
*/
|
|
83
|
+
function getText(includeNumber: boolean = false) {
|
|
84
|
+
return _getText(verse, includeNumber)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* A HTML string of the verse with parsed markdown
|
|
89
|
+
* @param includeNumbers should verse numbers be included
|
|
90
|
+
* @return HTML string value
|
|
91
|
+
*/
|
|
92
|
+
function getHTML(includeNumber: boolean = false) {
|
|
93
|
+
return _getHTML(verse, includeNumber)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Get verse reference
|
|
98
|
+
* @param addBibleVersion add Bible abbreviation at the end of the reference
|
|
99
|
+
* @return reference string, e.g. "Genesis 1:1"
|
|
100
|
+
*/
|
|
101
|
+
function getReference(addBibleVersion: boolean = false) {
|
|
102
|
+
return _getReference([verse.number], addBibleVersion)
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Get a selection of verses based in an array of numbers
|
|
108
|
+
* @param verseNumbers verse numbers
|
|
109
|
+
* @param html should markdown text be parsed to HTML
|
|
110
|
+
* @return verses data
|
|
111
|
+
*/
|
|
112
|
+
function getVerses(verseNumbers: number[] = [], html: boolean = false) {
|
|
113
|
+
let verses = chapter.verses
|
|
114
|
+
if (verseNumbers.length) verses = verses.filter((a) => verseNumbers.includes(a.number))
|
|
115
|
+
|
|
116
|
+
const data = verses.reduce((obj, { number, text }) => ({ ...obj, [number]: formatText(text, html) }), {} as { [key: number]: string })
|
|
117
|
+
const numbers = verseNumbers.length ? verseNumbers : verses.map((a) => a.number)
|
|
118
|
+
|
|
119
|
+
return { data, numbers, getText, getHTML, getReference }
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* A plain text string of the verses
|
|
123
|
+
* @param includeNumbers should verse numbers be included
|
|
124
|
+
* @return verse text
|
|
125
|
+
*/
|
|
126
|
+
function getText(includeNumbers: boolean = false) {
|
|
127
|
+
return verses.map((v) => _getText(v, includeNumbers)).join(" ")
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* A HTML string of the verses with parsed markdown
|
|
132
|
+
* @param includeNumbers should verse numbers be included
|
|
133
|
+
* @return HTML string value
|
|
134
|
+
*/
|
|
135
|
+
function getHTML(includeNumbers: boolean = false) {
|
|
136
|
+
return verses.map((v, i) => _getHTML(v, includeNumbers, i === 0)).join(" ")
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Get verses reference
|
|
141
|
+
* @param addBibleVersion add Bible abbreviation at the end of the reference
|
|
142
|
+
* @return reference string, e.g. "Genesis 1:1-3"
|
|
143
|
+
*/
|
|
144
|
+
function getReference(addBibleVersion: boolean = false) {
|
|
145
|
+
return _getReference(numbers, addBibleVersion)
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Get next chapter relative to the one currently selected
|
|
151
|
+
* @return a reference to the next chapter, including possibly changed book - null if none available
|
|
152
|
+
*/
|
|
153
|
+
function getNext() {
|
|
154
|
+
return _getCloseChapter(bible, book.number, chapter.number, true)
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Get previous chapter relative to the one currently selected
|
|
159
|
+
* @return a reference to the previous chapter, including possibly changed book - null if none available
|
|
160
|
+
*/
|
|
161
|
+
function getPrevious() {
|
|
162
|
+
return _getCloseChapter(bible, book.number, chapter.number, false)
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Get chapter reference
|
|
167
|
+
* @param addBibleVersion add Bible abbreviation at the end of the reference
|
|
168
|
+
* @return reference string, e.g. "Genesis 1"
|
|
169
|
+
*/
|
|
170
|
+
function getReference(addBibleVersion: boolean = false) {
|
|
171
|
+
return _getReference([], addBibleVersion)
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// HELPER
|
|
175
|
+
function _getReference(verses: number[], addBibleVersion: boolean = false) {
|
|
176
|
+
return getReferenceString({ book: book.number, chapter: chapter.number, verses }, bible) + (addBibleVersion ? ` ${getAbbreviation()}` : "")
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Get three letter book abbreviation/short name
|
|
182
|
+
* @return e.g. "Genesis" = "Gen"
|
|
183
|
+
*/
|
|
184
|
+
function getNameShort() {
|
|
185
|
+
return getBookAbbreviation(bible, bookIndex)
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Get current book category name/color, all categories are:
|
|
190
|
+
* "The Law", "History", "Poetry & Wisdom", "Prophets", "The Gospels & Acts", "Letters" & "Apocalyptic"
|
|
191
|
+
* @return book category object
|
|
192
|
+
*/
|
|
193
|
+
function getCategory() {
|
|
194
|
+
return getBookCategory(book)
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* @return bible name abbreviation/identifier
|
|
200
|
+
*/
|
|
201
|
+
function getAbbreviation() {
|
|
202
|
+
return _getShortName(bible)
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* @return trimmed metadata object
|
|
207
|
+
*/
|
|
208
|
+
function getMetadata() {
|
|
209
|
+
return _getMetadata(bible)
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Get books data with category info and calculated abbreviation
|
|
214
|
+
* @return array with book data
|
|
215
|
+
*/
|
|
216
|
+
function getBooksData() {
|
|
217
|
+
return bible.books.map((book, bookIndex) => {
|
|
218
|
+
const category = getBookCategory(book)
|
|
219
|
+
return {
|
|
220
|
+
id: book.id,
|
|
221
|
+
name: book.name,
|
|
222
|
+
number: book.number,
|
|
223
|
+
abbreviation: getBookAbbreviation(bible, bookIndex),
|
|
224
|
+
category
|
|
225
|
+
}
|
|
226
|
+
})
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Get all books in the Bible from the Old Testament
|
|
231
|
+
* @return list of max 39 books
|
|
232
|
+
*/
|
|
233
|
+
function getOT() {
|
|
234
|
+
if (bible.books.length === OT_SIZE) return bible.books
|
|
235
|
+
if (bible.books.length === BIBLE_SIZE) return bible.books.slice(0, OT_SIZE)
|
|
236
|
+
return bible.books.filter((a) => a.number >= 1 && a.number <= OT_SIZE)
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Get all books in the Bible from the New Testament
|
|
241
|
+
* @return list of max 27 books
|
|
242
|
+
*/
|
|
243
|
+
function getNT() {
|
|
244
|
+
if (bible.books.length === NT_SIZE) return bible.books
|
|
245
|
+
if (bible.books.length === BIBLE_SIZE) return bible.books.slice(OT_SIZE)
|
|
246
|
+
return bible.books.filter((a) => a.number > OT_SIZE && a.number <= BIBLE_SIZE)
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Get book/chapter/verses from a string reference
|
|
251
|
+
* @param value e.g. "Genesis 1:1-3" / "GEN.1.2-3" / "1.1.1"
|
|
252
|
+
* @return matching content
|
|
253
|
+
*/
|
|
254
|
+
function getFromReference(value: string) {
|
|
255
|
+
return getVerseReferences(bible, value)
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Search for book/chapter/verses with autocomplete
|
|
260
|
+
* @param value e.g. "Genesis 1:1-3"
|
|
261
|
+
* @return matching content
|
|
262
|
+
*/
|
|
263
|
+
function bookSearch(value: string) {
|
|
264
|
+
return _bookSearch(bible, value)
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Search for text content in the entire Bible
|
|
269
|
+
* @param value search string
|
|
270
|
+
* @param limit max results, lower number means quicker search
|
|
271
|
+
* @param bookNumber search only in a specified book
|
|
272
|
+
* @return an array of verses
|
|
273
|
+
*/
|
|
274
|
+
async function textSearch(value: string, limit: number = 50) {
|
|
275
|
+
const result = await bibleData.contentSearch(value, { limit })
|
|
276
|
+
|
|
277
|
+
// convert result to VerseReference[]
|
|
278
|
+
return result.map((r) => {
|
|
279
|
+
const bookIndex = bible.books.findIndex((b) => b.id === r.bookId)
|
|
280
|
+
const book = bookIndex >= 0 ? bookIndex + 1 : r.bookId
|
|
281
|
+
const chapter = Number(r.id.split(".")[1])
|
|
282
|
+
const verse = Number(r.id.split(".")[2])
|
|
283
|
+
|
|
284
|
+
return {
|
|
285
|
+
book,
|
|
286
|
+
chapter,
|
|
287
|
+
verse: { number: verse, text: r.text },
|
|
288
|
+
reference: r.reference,
|
|
289
|
+
id: `${r.bookId}.${chapter}.${verse}`
|
|
290
|
+
} as VerseReference
|
|
291
|
+
})
|
|
292
|
+
}
|
|
293
|
+
}
|
package/lib/defaults.ts
CHANGED
|
@@ -32,6 +32,7 @@ export function getDefaultMetadata() {
|
|
|
32
32
|
// BOOKS
|
|
33
33
|
|
|
34
34
|
const BOOKS = {
|
|
35
|
+
// GT
|
|
35
36
|
GEN: "Genesis",
|
|
36
37
|
EXO: "Exodus",
|
|
37
38
|
LEV: "Leviticus",
|
|
@@ -57,21 +58,22 @@ const BOOKS = {
|
|
|
57
58
|
ISA: "Isaiah",
|
|
58
59
|
JER: "Jeremiah",
|
|
59
60
|
LAM: "Lamentations",
|
|
60
|
-
|
|
61
|
+
EZK: "Ezekiel",
|
|
61
62
|
DAN: "Daniel",
|
|
62
63
|
HOS: "Hosea",
|
|
63
|
-
|
|
64
|
+
JOL: "Joel",
|
|
64
65
|
AMO: "Amos",
|
|
65
66
|
OBA: "Obadiah",
|
|
66
67
|
JON: "Jonah",
|
|
67
68
|
MIC: "Micah",
|
|
68
|
-
|
|
69
|
+
NAM: "Nahum",
|
|
69
70
|
HAB: "Habakkuk",
|
|
70
71
|
ZEP: "Zephaniah",
|
|
71
72
|
HAG: "Haggai",
|
|
72
73
|
ZEC: "Zechariah",
|
|
73
74
|
MAL: "Malachi",
|
|
74
75
|
|
|
76
|
+
// NT
|
|
75
77
|
MAT: "Matthew",
|
|
76
78
|
MRK: "Mark",
|
|
77
79
|
LUK: "Luke",
|
|
@@ -98,7 +100,18 @@ const BOOKS = {
|
|
|
98
100
|
"2JN": "2 John",
|
|
99
101
|
"3JN": "3 John",
|
|
100
102
|
JUD: "Jude",
|
|
101
|
-
REV: "Revelation"
|
|
103
|
+
REV: "Revelation",
|
|
104
|
+
|
|
105
|
+
// APOCRYPHA
|
|
106
|
+
TOB: "Tobit",
|
|
107
|
+
JDT: "Judith",
|
|
108
|
+
ESG: "Esther (Greek)",
|
|
109
|
+
WIS: "Wisdom",
|
|
110
|
+
SIR: "Sirach",
|
|
111
|
+
BAR: "Baruch",
|
|
112
|
+
LJE: "Letter of Jeremiah",
|
|
113
|
+
"1MA": "1 Maccabees",
|
|
114
|
+
"2MA": "2 Maccabees"
|
|
102
115
|
}
|
|
103
116
|
type BookId = keyof typeof BOOKS
|
|
104
117
|
|
|
@@ -109,7 +122,8 @@ const categories = [
|
|
|
109
122
|
{ id: "prophets", start: "ISA", name: "Prophets", color: "#42e84d" },
|
|
110
123
|
{ id: "gospels", start: "MAT", name: "The Gospels & Acts", color: "#42c4e8" },
|
|
111
124
|
{ id: "letters", start: "ROM", name: "Letters", color: "#e8de42" }, // #b542e8
|
|
112
|
-
{ id: "prophecy", start: "REV", name: "Apocalyptic", color: "#e842e5" }
|
|
125
|
+
{ id: "prophecy", start: "REV", name: "Apocalyptic", color: "#e842e5" },
|
|
126
|
+
{ id: "apocrypha", start: "TOB", name: "Apocrypha", color: "#8269fa" }
|
|
113
127
|
]
|
|
114
128
|
|
|
115
129
|
export function getDefaultBooks() {
|
|
@@ -128,9 +142,23 @@ export function getDefaultBooks() {
|
|
|
128
142
|
}
|
|
129
143
|
|
|
130
144
|
export function getBookCategory(book: Book) {
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
145
|
+
const bookId = book.id
|
|
146
|
+
const bookNumber = book.number
|
|
147
|
+
|
|
148
|
+
const defaults = getDefaultBooks()
|
|
149
|
+
|
|
150
|
+
let currentIndex = defaults.ids.indexOf(bookId as any)
|
|
151
|
+
if (bookId?.length === 3 && currentIndex < 0) return null
|
|
152
|
+
|
|
153
|
+
if (currentIndex < 0) currentIndex = bookNumber - 1
|
|
154
|
+
if (currentIndex > defaults.names.length - 1) return null
|
|
155
|
+
|
|
156
|
+
// find category based on current index
|
|
157
|
+
let categoryIndex = 0
|
|
158
|
+
for (let i = 0; i < categories.length; i++) {
|
|
159
|
+
const startIndex = defaults.ids.indexOf(categories[i].start as any)
|
|
160
|
+
if (currentIndex >= startIndex) categoryIndex = i
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return categories[categoryIndex]
|
|
136
164
|
}
|
package/lib/get.ts
CHANGED
|
@@ -34,28 +34,28 @@ export function _getShortName(bible: Bible) {
|
|
|
34
34
|
// MAIN //
|
|
35
35
|
|
|
36
36
|
export function getBookIndex(bible: Bible, numberOrName: string | number) {
|
|
37
|
-
if (isNumber(numberOrName)) return bible.books.findIndex((a) => a.number === Number(numberOrName))
|
|
37
|
+
if (isNumber(numberOrName)) return bible.books.findIndex((a) => Number(a.number) === Number(numberOrName))
|
|
38
38
|
return bible.books.findIndex((a) => a.name === numberOrName || a.id === numberOrName)
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
export function _getBook(bible: Bible, index: number) {
|
|
42
|
-
return bible.books[index] || getDefault().book
|
|
42
|
+
return bible.books[index] || bible.books[0] || getDefault().book
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
export function getChapterIndex(book: Book, number: number) {
|
|
46
|
-
return book.chapters.findIndex((a) => a.number === Number(number))
|
|
46
|
+
return book.chapters.findIndex((a) => Number(a.number) === Number(number))
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
export function _getChapter(book: Book, index: number) {
|
|
50
|
-
return book.chapters[index] || getDefault().chapter
|
|
50
|
+
return book.chapters[index] || book.chapters[0] || getDefault().chapter
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
export function getVerseIndex(chapter: Chapter, number: number) {
|
|
54
|
-
return chapter.verses.findIndex((a) => a.number === number || (a.endNumber && number > a.number && number <= a.endNumber))
|
|
54
|
+
return chapter.verses.findIndex((a) => Number(a.number) === Number(number) || (a.endNumber && Number(number) > Number(a.number) && Number(number) <= Number(a.endNumber)))
|
|
55
55
|
}
|
|
56
56
|
|
|
57
57
|
export function _getVerse(chapter: Chapter, index: number) {
|
|
58
|
-
return chapter.verses[index] || getDefault().verse
|
|
58
|
+
return chapter.verses[index] || chapter.verses[0] || getDefault().verse
|
|
59
59
|
}
|
|
60
60
|
|
|
61
61
|
// BOOK //
|
|
@@ -76,6 +76,30 @@ export function getBookName(numberOrId: number | string, bible?: Bible) {
|
|
|
76
76
|
return getDefaultBooks().byId(numberOrId.toString())
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
+
export function getBookAbbreviation(bible: Bible, bookIndex: number) {
|
|
80
|
+
const currentBook = bible.books[bookIndex]
|
|
81
|
+
if (!currentBook) return ""
|
|
82
|
+
|
|
83
|
+
if (currentBook.abbreviation) return currentBook.abbreviation
|
|
84
|
+
|
|
85
|
+
const abbr = currentBook.id || ""
|
|
86
|
+
const name = currentBook.name
|
|
87
|
+
const defaultBookName = (getDefaultBooks().data as any)[abbr]
|
|
88
|
+
|
|
89
|
+
if (name === defaultBookName) return abbr[0] + abbr.slice(1).toLowerCase()
|
|
90
|
+
|
|
91
|
+
const hasNumber = isNaN(parseInt(name[0]))
|
|
92
|
+
let shortName = hasNumber ? name.slice(0, 3) : name.replace(/[^\w]/g, "").slice(0, 4)
|
|
93
|
+
|
|
94
|
+
// use four characters if same short name ("Jud"ges="Jud"e)
|
|
95
|
+
if (shortName.length === 3 && bible.books.some((a) => a.abbreviation === shortName)) {
|
|
96
|
+
shortName = name.slice(0, 4)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
currentBook.abbreviation = shortName
|
|
100
|
+
return shortName
|
|
101
|
+
}
|
|
102
|
+
|
|
79
103
|
// CHAPTER //
|
|
80
104
|
|
|
81
105
|
export function _getCloseChapter(bible: Bible, currentBookNumberOrName: number | string, currentChapterNumber: number, next: boolean) {
|
package/lib/reference.ts
CHANGED
|
@@ -110,7 +110,7 @@ function parseUniversalReference(ref: UniversalReference) {
|
|
|
110
110
|
|
|
111
111
|
// Genesis 1:1-3 / GEN 1:1-3 / GEN.1.1-3 = 1.1.1-3
|
|
112
112
|
function referenceStringToId(ref: string) {
|
|
113
|
-
const regex = /^(?:[
|
|
113
|
+
const regex = /^(?:[\p{Lu}]+|[0-9]+)(?:\.[0-9]+)?(?:\.[0-9+-]+)?$/u
|
|
114
114
|
const isId = ref.match(regex) !== null
|
|
115
115
|
if (isId) return parseReferenceId(ref)
|
|
116
116
|
|
|
@@ -127,7 +127,7 @@ function referenceStringToId(ref: string) {
|
|
|
127
127
|
// "Genesis 1:1-3" = { book: "Genesis ", chapter: 1, verses: [1, 2, 3] }
|
|
128
128
|
function splitReferenceString(ref: string) {
|
|
129
129
|
// (:,.) allowed between chapter/verse - (-+) allowed between verses
|
|
130
|
-
const regex = /(?<book>[
|
|
130
|
+
const regex = /(?<book>(?:\d+\.?\s?)?[\p{L}](?:[\p{L}\s']*[\p{L}])?)(?:\s(?<chapter>\d+))?(?:[:,.](?<verses>[0-9,.\-+]+))?/u
|
|
131
131
|
const match = ref.match(regex)
|
|
132
132
|
|
|
133
133
|
if (!match) return { book: "", chapter: "", verses: "" }
|
|
@@ -143,9 +143,14 @@ function splitReferenceString(ref: string) {
|
|
|
143
143
|
// VERSE REFERENCE //
|
|
144
144
|
|
|
145
145
|
// [1, 2, 3] = "1-3" / [4, 2, 7, 1, 9, 10] = "1-2+4+7+9-10"
|
|
146
|
-
|
|
146
|
+
// WIP reference array might also contain subverses e.g. ["1_b", 2, "6_a"], which will become "1b-2+6a"
|
|
147
|
+
function getVerseReference(verses: (number | string)[]) {
|
|
147
148
|
// sort in ascending order
|
|
148
|
-
verses.sort((a, b) =>
|
|
149
|
+
verses.sort((a, b) => {
|
|
150
|
+
const numA = typeof a === "number" ? a : Number(a.split("_")[0])
|
|
151
|
+
const numB = typeof b === "number" ? b : Number(b.split("_")[0])
|
|
152
|
+
return numA - numB
|
|
153
|
+
})
|
|
149
154
|
|
|
150
155
|
let verseRef = ""
|
|
151
156
|
let i = 0
|
|
@@ -153,7 +158,7 @@ function getVerseReference(verses: number[]) {
|
|
|
153
158
|
while (i < verses.length) {
|
|
154
159
|
// get consecutive verses
|
|
155
160
|
let start = verses[i]
|
|
156
|
-
while (i < verses.length - 1 && verses[i] + 1 === verses[i + 1]) i++
|
|
161
|
+
while (i < verses.length - 1 && Number(verses[i]) + 1 === Number(verses[i + 1])) i++
|
|
157
162
|
let end = verses[i]
|
|
158
163
|
|
|
159
164
|
// if start and end are the same add the number, if not add the range
|
package/lib/search.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Bible } from "./Bible"
|
|
2
|
+
import { getDefaultBooks } from "./defaults"
|
|
2
3
|
import { getBookIndex } from "./get"
|
|
3
4
|
import { getReferenceFromSearchString, getReferenceString, getVerseReferences, SearchReference, VerseReference } from "./reference"
|
|
4
5
|
|
|
@@ -31,9 +32,25 @@ export function _bookSearch(bible: Bible, searchValue: string) {
|
|
|
31
32
|
returnValue.chapter = chapter.number
|
|
32
33
|
|
|
33
34
|
const verses = findVerses(reference.verses)
|
|
35
|
+
if (!verses.length) {
|
|
36
|
+
// add divider (:) automatically if space at end
|
|
37
|
+
if (searchValue.endsWith(" ") && !searchValue.trim().includes(":")) returnValue.autocompleted = returnValue.autocompleted.trim() + ":"
|
|
38
|
+
else returnValue.autocompleted = returnValue.autocompleted.trim()
|
|
39
|
+
|
|
40
|
+
return finish()
|
|
41
|
+
}
|
|
34
42
|
returnValue.verses = verses.map(({ number }) => number)
|
|
35
43
|
returnValue.versesContent = verses
|
|
36
44
|
|
|
45
|
+
// add divider (+/-) automatically if space at end
|
|
46
|
+
if (searchValue.endsWith(" ") && !searchValue.trim().endsWith("-") && !searchValue.trim().endsWith("+")) {
|
|
47
|
+
const minus = (searchValue.match(/-/g) || []).length
|
|
48
|
+
const plus = (searchValue.match(/\+/g) || []).length
|
|
49
|
+
returnValue.autocompleted = returnValue.autocompleted.trim() + (minus === plus ? "-" : "+")
|
|
50
|
+
} else {
|
|
51
|
+
returnValue.autocompleted = returnValue.autocompleted.trim()
|
|
52
|
+
}
|
|
53
|
+
|
|
37
54
|
return finish()
|
|
38
55
|
|
|
39
56
|
/////
|
|
@@ -44,16 +61,21 @@ export function _bookSearch(bible: Bible, searchValue: string) {
|
|
|
44
61
|
}
|
|
45
62
|
|
|
46
63
|
function findBooks(name: string) {
|
|
47
|
-
|
|
48
|
-
name = formatText(name)
|
|
64
|
+
name = removeSpaces(formatText(name))
|
|
49
65
|
|
|
50
66
|
let matches = []
|
|
51
67
|
for (let book of bible.books) {
|
|
52
|
-
const bookName = formatText(book.name)
|
|
68
|
+
const bookName = removeSpaces(formatText(book.name))
|
|
53
69
|
if (bookName === name) return [book]
|
|
54
70
|
if (bookName.includes(name)) matches.push(book)
|
|
55
71
|
}
|
|
56
72
|
|
|
73
|
+
// find any abbreviation matches
|
|
74
|
+
for (let book of bible.books) {
|
|
75
|
+
let abbr = getDefaultBooks().ids[book.number - 1] || ""
|
|
76
|
+
if (abbr.toLowerCase() === name) return [book]
|
|
77
|
+
}
|
|
78
|
+
|
|
57
79
|
// remove books with numbers if no number at search start (John)
|
|
58
80
|
const hasNum = (str: string) => /\d/.test(str)
|
|
59
81
|
if (!hasNum(name[0])) matches = matches.filter((book) => !hasNum(book.name))
|
|
@@ -62,11 +84,11 @@ export function _bookSearch(bible: Bible, searchValue: string) {
|
|
|
62
84
|
}
|
|
63
85
|
|
|
64
86
|
function findChapter(number: number) {
|
|
65
|
-
return book.chapters.find((a) => a.number === number)
|
|
87
|
+
return book.chapters.find((a) => Number(a.number) === number)
|
|
66
88
|
}
|
|
67
89
|
|
|
68
90
|
function findVerses(verses: number[]) {
|
|
69
|
-
return chapter?.verses.filter((a) => verses.includes(a.number)) || []
|
|
91
|
+
return chapter?.verses.filter((a) => verses.includes(Number(a.number))) || []
|
|
70
92
|
}
|
|
71
93
|
}
|
|
72
94
|
|
|
@@ -74,7 +96,6 @@ export function _bookSearch(bible: Bible, searchValue: string) {
|
|
|
74
96
|
|
|
75
97
|
let textSearchCache: { [key: string]: string } = {}
|
|
76
98
|
export function _textSearch(bible: Bible, searchValue: string, limit: number, bookNumber?: number) {
|
|
77
|
-
const formatText = (a: string) => a.replace(/[`!*()-?;:'",.]/gi, "").toLowerCase()
|
|
78
99
|
searchValue = formatText(searchValue).trim()
|
|
79
100
|
if (!searchValue.length) return []
|
|
80
101
|
|
|
@@ -120,3 +141,20 @@ export function _textSearch(bible: Bible, searchValue: string, limit: number, bo
|
|
|
120
141
|
return matches
|
|
121
142
|
}
|
|
122
143
|
}
|
|
144
|
+
|
|
145
|
+
// HELPERS //
|
|
146
|
+
|
|
147
|
+
function formatText(text: string) {
|
|
148
|
+
return (
|
|
149
|
+
text
|
|
150
|
+
// replace diacritic values like á -> a & ö -> o
|
|
151
|
+
.normalize("NFD")
|
|
152
|
+
.replace(/\p{Diacritic}/gu, "")
|
|
153
|
+
// remove special characters
|
|
154
|
+
.replace(/[`!*()\-?;:'",.]/gi, "")
|
|
155
|
+
.toLowerCase()
|
|
156
|
+
)
|
|
157
|
+
}
|
|
158
|
+
function removeSpaces(text: string) {
|
|
159
|
+
return text.replace(/\s/g, "")
|
|
160
|
+
}
|
package/package.json
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "json-bible",
|
|
3
|
-
"description": "Universal JSON Bible Format",
|
|
4
|
-
"version": "1.
|
|
5
|
-
"main": "index.js",
|
|
6
|
-
"type": "module",
|
|
7
|
-
"scripts": {
|
|
8
|
-
"dev": "node dist/index.js",
|
|
9
|
-
"build": "tsc && node add-js-extension.js",
|
|
10
|
-
"test": "echo \"Error: no test specified\" && exit 1"
|
|
11
|
-
},
|
|
12
|
-
"author": "vassbo",
|
|
13
|
-
"license": "ISC",
|
|
14
|
-
"devDependencies": {
|
|
15
|
-
"@types/node": "^22.7.6",
|
|
16
|
-
"typescript": "^5.6.2"
|
|
17
|
-
}
|
|
18
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "json-bible",
|
|
3
|
+
"description": "Universal JSON Bible Format",
|
|
4
|
+
"version": "1.1.0",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"dev": "node dist/index.js",
|
|
9
|
+
"build": "tsc && node add-js-extension.js",
|
|
10
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
11
|
+
},
|
|
12
|
+
"author": "vassbo",
|
|
13
|
+
"license": "ISC",
|
|
14
|
+
"devDependencies": {
|
|
15
|
+
"@types/node": "^22.7.6",
|
|
16
|
+
"typescript": "^5.6.2"
|
|
17
|
+
}
|
|
18
|
+
}
|