@youversion/platform-core 1.20.2 → 1.21.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/.turbo/turbo-build.log +21 -10
- package/AGENTS.md +47 -2
- package/CHANGELOG.md +16 -0
- package/dist/browser-DzQ1yOHv.d.cts +69 -0
- package/dist/browser-DzQ1yOHv.d.ts +69 -0
- package/dist/browser.cjs +270 -0
- package/dist/browser.d.cts +1 -0
- package/dist/browser.d.ts +1 -0
- package/dist/browser.js +6 -0
- package/dist/chunk-2Z2S2WY3.js +245 -0
- package/dist/index.cjs +246 -2
- package/dist/index.d.cts +11 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +16 -1
- package/dist/server.cjs +275 -0
- package/dist/server.d.cts +25 -0
- package/dist/server.d.ts +25 -0
- package/dist/server.js +18 -0
- package/package.json +23 -5
- package/src/bible-html-transformer-server.ts +37 -0
- package/src/bible-html-transformer.server.test.ts +151 -0
- package/src/bible-html-transformer.test.ts +403 -0
- package/src/bible-html-transformer.ts +378 -0
- package/src/bible.ts +10 -0
- package/src/browser.ts +4 -0
- package/src/index.ts +5 -0
- package/src/server.ts +2 -0
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
// src/bible-html-transformer.ts
|
|
2
|
+
var NON_BREAKING_SPACE = "\xA0";
|
|
3
|
+
var FOOTNOTE_KEY_ATTR = "data-footnote-key";
|
|
4
|
+
var NEEDS_SPACE_BEFORE = /^[^\s.,;:!?)}\]'"'»›]/;
|
|
5
|
+
var ALLOWED_TAGS = /* @__PURE__ */ new Set([
|
|
6
|
+
"DIV",
|
|
7
|
+
"P",
|
|
8
|
+
"SPAN",
|
|
9
|
+
"SUP",
|
|
10
|
+
"SUB",
|
|
11
|
+
"EM",
|
|
12
|
+
"STRONG",
|
|
13
|
+
"I",
|
|
14
|
+
"B",
|
|
15
|
+
"SMALL",
|
|
16
|
+
"BR",
|
|
17
|
+
"SECTION",
|
|
18
|
+
"TABLE",
|
|
19
|
+
"THEAD",
|
|
20
|
+
"TBODY",
|
|
21
|
+
"TR",
|
|
22
|
+
"TD",
|
|
23
|
+
"TH"
|
|
24
|
+
]);
|
|
25
|
+
var DROP_ENTIRELY_TAGS = /* @__PURE__ */ new Set([
|
|
26
|
+
"SCRIPT",
|
|
27
|
+
"STYLE",
|
|
28
|
+
"IFRAME",
|
|
29
|
+
"OBJECT",
|
|
30
|
+
"EMBED",
|
|
31
|
+
"SVG",
|
|
32
|
+
"MATH",
|
|
33
|
+
"FORM",
|
|
34
|
+
"INPUT",
|
|
35
|
+
"BUTTON",
|
|
36
|
+
"TEXTAREA",
|
|
37
|
+
"SELECT",
|
|
38
|
+
"TEMPLATE",
|
|
39
|
+
"LINK",
|
|
40
|
+
"META",
|
|
41
|
+
"BASE",
|
|
42
|
+
"NOSCRIPT"
|
|
43
|
+
]);
|
|
44
|
+
var ALLOWED_ATTRS = /* @__PURE__ */ new Set(["class", "v", "colspan", "rowspan", "dir", "usfm"]);
|
|
45
|
+
function sanitizeBibleHtmlDocument(doc) {
|
|
46
|
+
const root = doc.body ?? doc.documentElement;
|
|
47
|
+
for (const el of Array.from(root.querySelectorAll("*"))) {
|
|
48
|
+
const tag = el.tagName;
|
|
49
|
+
if (DROP_ENTIRELY_TAGS.has(tag)) {
|
|
50
|
+
el.remove();
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
if (!ALLOWED_TAGS.has(tag)) {
|
|
54
|
+
el.replaceWith(...Array.from(el.childNodes));
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
for (const attr of Array.from(el.attributes)) {
|
|
58
|
+
const name = attr.name.toLowerCase();
|
|
59
|
+
if (name.startsWith("on")) {
|
|
60
|
+
el.removeAttribute(attr.name);
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
if (!ALLOWED_ATTRS.has(name) && !name.startsWith("data-")) {
|
|
64
|
+
el.removeAttribute(attr.name);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
function wrapVerseContent(doc) {
|
|
70
|
+
function wrapParagraphContent(doc2, paragraph, verseNum) {
|
|
71
|
+
const children = Array.from(paragraph.childNodes);
|
|
72
|
+
if (children.length === 0) return;
|
|
73
|
+
const wrapper = doc2.createElement("span");
|
|
74
|
+
wrapper.className = "yv-v";
|
|
75
|
+
wrapper.setAttribute("v", verseNum);
|
|
76
|
+
const firstChild = children[0];
|
|
77
|
+
if (firstChild) {
|
|
78
|
+
paragraph.insertBefore(wrapper, firstChild);
|
|
79
|
+
}
|
|
80
|
+
children.forEach((child) => {
|
|
81
|
+
wrapper.appendChild(child);
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
function wrapParagraphsUntilBoundary(doc2, verseNum, startParagraph, endParagraph) {
|
|
85
|
+
if (!startParagraph) return;
|
|
86
|
+
let currentParagraph = startParagraph.nextElementSibling;
|
|
87
|
+
while (currentParagraph && currentParagraph !== endParagraph) {
|
|
88
|
+
const isHeading = currentParagraph.classList.contains("yv-h") || currentParagraph.matches(
|
|
89
|
+
".s1, .s2, .s3, .s4, .ms, .ms1, .ms2, .ms3, .ms4, .mr, .sp, .sr, .qa, .r"
|
|
90
|
+
);
|
|
91
|
+
if (isHeading) {
|
|
92
|
+
currentParagraph = currentParagraph.nextElementSibling;
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
if (currentParagraph.querySelector(".yv-v[v]")) break;
|
|
96
|
+
if (currentParagraph.classList.contains("p") || currentParagraph.tagName === "P") {
|
|
97
|
+
wrapParagraphContent(doc2, currentParagraph, verseNum);
|
|
98
|
+
}
|
|
99
|
+
currentParagraph = currentParagraph.nextElementSibling;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
function handleParagraphWrapping(doc2, currentParagraph, nextParagraph, verseNum) {
|
|
103
|
+
if (!currentParagraph) return;
|
|
104
|
+
if (!nextParagraph) {
|
|
105
|
+
wrapParagraphsUntilBoundary(doc2, verseNum, currentParagraph);
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
if (currentParagraph !== nextParagraph) {
|
|
109
|
+
wrapParagraphsUntilBoundary(doc2, verseNum, currentParagraph, nextParagraph);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
function processVerseMarker(marker, index, markers) {
|
|
113
|
+
const verseNum = marker.getAttribute("v");
|
|
114
|
+
if (!verseNum) return;
|
|
115
|
+
const nextMarker = markers[index + 1];
|
|
116
|
+
const nodesToWrap = collectNodesBetweenMarkers(marker, nextMarker);
|
|
117
|
+
if (nodesToWrap.length === 0) return;
|
|
118
|
+
const currentParagraph = marker.closest(".p, p, div.p");
|
|
119
|
+
const nextParagraph = nextMarker?.closest(".p, p, div.p") || null;
|
|
120
|
+
const doc2 = marker.ownerDocument;
|
|
121
|
+
wrapNodesInVerse(marker, verseNum, nodesToWrap);
|
|
122
|
+
handleParagraphWrapping(doc2, currentParagraph, nextParagraph, verseNum);
|
|
123
|
+
}
|
|
124
|
+
function wrapNodesInVerse(marker, verseNum, nodes) {
|
|
125
|
+
const wrapper = marker.ownerDocument.createElement("span");
|
|
126
|
+
wrapper.className = "yv-v";
|
|
127
|
+
wrapper.setAttribute("v", verseNum);
|
|
128
|
+
const firstNode = nodes[0];
|
|
129
|
+
if (firstNode) {
|
|
130
|
+
marker.parentNode?.insertBefore(wrapper, firstNode);
|
|
131
|
+
}
|
|
132
|
+
nodes.forEach((node) => {
|
|
133
|
+
wrapper.appendChild(node);
|
|
134
|
+
});
|
|
135
|
+
marker.remove();
|
|
136
|
+
}
|
|
137
|
+
function shouldStopCollecting(node, endMarker) {
|
|
138
|
+
if (node === endMarker) return true;
|
|
139
|
+
if (endMarker && node.nodeType === 1 && node.contains(endMarker)) return true;
|
|
140
|
+
return false;
|
|
141
|
+
}
|
|
142
|
+
function shouldSkipNode(node) {
|
|
143
|
+
return node.nodeType === 1 && node.classList.contains("yv-h");
|
|
144
|
+
}
|
|
145
|
+
function collectNodesBetweenMarkers(startMarker, endMarker) {
|
|
146
|
+
const nodes = [];
|
|
147
|
+
let current = startMarker.nextSibling;
|
|
148
|
+
while (current && !shouldStopCollecting(current, endMarker)) {
|
|
149
|
+
if (shouldSkipNode(current)) {
|
|
150
|
+
current = current.nextSibling;
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
nodes.push(current);
|
|
154
|
+
current = current.nextSibling;
|
|
155
|
+
}
|
|
156
|
+
return nodes;
|
|
157
|
+
}
|
|
158
|
+
const verseMarkers = Array.from(doc.querySelectorAll(".yv-v[v]"));
|
|
159
|
+
verseMarkers.forEach(processVerseMarker);
|
|
160
|
+
}
|
|
161
|
+
function assignFootnoteKeys(doc) {
|
|
162
|
+
let introIdx = 0;
|
|
163
|
+
doc.querySelectorAll(".yv-n.f").forEach((fn) => {
|
|
164
|
+
const verseNum = fn.closest(".yv-v[v]")?.getAttribute("v");
|
|
165
|
+
fn.setAttribute(FOOTNOTE_KEY_ATTR, verseNum ?? `intro-${introIdx++}`);
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
function replaceFootnotesWithAnchors(doc, footnotes) {
|
|
169
|
+
for (const fn of footnotes) {
|
|
170
|
+
const key = fn.getAttribute(FOOTNOTE_KEY_ATTR);
|
|
171
|
+
if (!key) continue;
|
|
172
|
+
const prev = fn.previousSibling;
|
|
173
|
+
const next = fn.nextSibling;
|
|
174
|
+
const prevText = prev?.textContent ?? "";
|
|
175
|
+
const nextText = next?.textContent ?? "";
|
|
176
|
+
const prevNeedsSpace = prevText.length > 0 && !/\s$/.test(prevText);
|
|
177
|
+
const nextNeedsSpace = nextText.length > 0 && NEEDS_SPACE_BEFORE.test(nextText);
|
|
178
|
+
if (prevNeedsSpace && nextNeedsSpace && fn.parentNode) {
|
|
179
|
+
fn.parentNode.insertBefore(doc.createTextNode(" "), fn);
|
|
180
|
+
}
|
|
181
|
+
const anchor = doc.createElement("span");
|
|
182
|
+
anchor.setAttribute("data-verse-footnote", key);
|
|
183
|
+
anchor.setAttribute("data-verse-footnote-content", fn.innerHTML);
|
|
184
|
+
fn.replaceWith(anchor);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
function addNbspToVerseLabels(doc) {
|
|
188
|
+
doc.querySelectorAll(".yv-vlbl").forEach((label) => {
|
|
189
|
+
const text = label.textContent || "";
|
|
190
|
+
if (!text.endsWith(NON_BREAKING_SPACE)) {
|
|
191
|
+
label.textContent = text + NON_BREAKING_SPACE;
|
|
192
|
+
}
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
function fixIrregularTables(doc) {
|
|
196
|
+
doc.querySelectorAll("table").forEach((table) => {
|
|
197
|
+
const rows = table.querySelectorAll("tr");
|
|
198
|
+
if (rows.length === 0) return;
|
|
199
|
+
let maxColumns = 0;
|
|
200
|
+
rows.forEach((row) => {
|
|
201
|
+
let count = 0;
|
|
202
|
+
row.querySelectorAll("td, th").forEach((cell) => {
|
|
203
|
+
count += parseInt(cell.getAttribute("colspan") || "1", 10);
|
|
204
|
+
});
|
|
205
|
+
maxColumns = Math.max(maxColumns, count);
|
|
206
|
+
});
|
|
207
|
+
if (maxColumns > 1) {
|
|
208
|
+
rows.forEach((row) => {
|
|
209
|
+
const cells = row.querySelectorAll("td, th");
|
|
210
|
+
if (cells.length === 1) {
|
|
211
|
+
const existing = parseInt(cells[0].getAttribute("colspan") || "1", 10);
|
|
212
|
+
if (existing < maxColumns) {
|
|
213
|
+
cells[0].setAttribute("colspan", maxColumns.toString());
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
function transformBibleHtml(html, options) {
|
|
221
|
+
const doc = options.parseHtml(html);
|
|
222
|
+
sanitizeBibleHtmlDocument(doc);
|
|
223
|
+
wrapVerseContent(doc);
|
|
224
|
+
assignFootnoteKeys(doc);
|
|
225
|
+
const footnotes = Array.from(doc.querySelectorAll(".yv-n.f"));
|
|
226
|
+
replaceFootnotesWithAnchors(doc, footnotes);
|
|
227
|
+
addNbspToVerseLabels(doc);
|
|
228
|
+
fixIrregularTables(doc);
|
|
229
|
+
const transformedHtml = options.serializeHtml(doc);
|
|
230
|
+
return { html: transformedHtml };
|
|
231
|
+
}
|
|
232
|
+
function transformBibleHtmlForBrowser(html) {
|
|
233
|
+
if (typeof globalThis.DOMParser === "undefined") {
|
|
234
|
+
throw new Error("DOMParser is required to transform Bible HTML in browser environments");
|
|
235
|
+
}
|
|
236
|
+
return transformBibleHtml(html, {
|
|
237
|
+
parseHtml: (h) => new DOMParser().parseFromString(h, "text/html"),
|
|
238
|
+
serializeHtml: (doc) => doc.body.innerHTML
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
export {
|
|
243
|
+
transformBibleHtml,
|
|
244
|
+
transformBibleHtmlForBrowser
|
|
245
|
+
};
|
package/dist/index.cjs
CHANGED
|
@@ -36,7 +36,8 @@ __export(index_exports, {
|
|
|
36
36
|
YouVersionAPIUsers: () => YouVersionAPIUsers,
|
|
37
37
|
YouVersionPlatformConfiguration: () => YouVersionPlatformConfiguration,
|
|
38
38
|
YouVersionUserInfo: () => YouVersionUserInfo,
|
|
39
|
-
getAdjacentChapter: () => getAdjacentChapter
|
|
39
|
+
getAdjacentChapter: () => getAdjacentChapter,
|
|
40
|
+
transformBibleHtml: () => transformBibleHtml
|
|
40
41
|
});
|
|
41
42
|
module.exports = __toCommonJS(index_exports);
|
|
42
43
|
|
|
@@ -789,12 +790,18 @@ var BibleClient = class _BibleClient {
|
|
|
789
790
|
/**
|
|
790
791
|
* Fetches a passage (range of verses) from the Bible using the passages endpoint.
|
|
791
792
|
* This is the new API format that returns HTML-formatted content.
|
|
793
|
+
*
|
|
794
|
+
* Note: The HTML returned from the API contains inline footnote content that should
|
|
795
|
+
* be transformed before rendering. Use `transformBibleHtml()` or
|
|
796
|
+
* `transformBibleHtmlForBrowser()` to clean up the HTML and extract footnotes.
|
|
797
|
+
*
|
|
792
798
|
* @param versionId The version ID.
|
|
793
799
|
* @param usfm The USFM reference (e.g., "JHN.3.1-2", "GEN.1", "JHN.3.16").
|
|
794
800
|
* @param format The format to return ("html" or "text", default: "html").
|
|
795
801
|
* @param include_headings Whether to include headings in the content.
|
|
796
802
|
* @param include_notes Whether to include notes in the content.
|
|
797
803
|
* @returns The requested BiblePassage object with HTML content.
|
|
804
|
+
*
|
|
798
805
|
* @example
|
|
799
806
|
* ```ts
|
|
800
807
|
* // Get a single verse
|
|
@@ -805,6 +812,10 @@ var BibleClient = class _BibleClient {
|
|
|
805
812
|
*
|
|
806
813
|
* // Get an entire chapter
|
|
807
814
|
* const chapter = await bibleClient.getPassage(3034, "GEN.1");
|
|
815
|
+
*
|
|
816
|
+
* // Transform HTML before rendering
|
|
817
|
+
* const passage = await bibleClient.getPassage(3034, "JHN.3.16", "html", true, true);
|
|
818
|
+
* const transformed = transformBibleHtmlForBrowser(passage.content);
|
|
808
819
|
* ```
|
|
809
820
|
*/
|
|
810
821
|
async getPassage(versionId, usfm, format = "html", include_headings, include_notes) {
|
|
@@ -1732,6 +1743,238 @@ function lastCanonicalOfPreviousBook(books, bookIndex) {
|
|
|
1732
1743
|
}
|
|
1733
1744
|
return null;
|
|
1734
1745
|
}
|
|
1746
|
+
|
|
1747
|
+
// src/bible-html-transformer.ts
|
|
1748
|
+
var NON_BREAKING_SPACE = "\xA0";
|
|
1749
|
+
var FOOTNOTE_KEY_ATTR = "data-footnote-key";
|
|
1750
|
+
var NEEDS_SPACE_BEFORE = /^[^\s.,;:!?)}\]'"'»›]/;
|
|
1751
|
+
var ALLOWED_TAGS = /* @__PURE__ */ new Set([
|
|
1752
|
+
"DIV",
|
|
1753
|
+
"P",
|
|
1754
|
+
"SPAN",
|
|
1755
|
+
"SUP",
|
|
1756
|
+
"SUB",
|
|
1757
|
+
"EM",
|
|
1758
|
+
"STRONG",
|
|
1759
|
+
"I",
|
|
1760
|
+
"B",
|
|
1761
|
+
"SMALL",
|
|
1762
|
+
"BR",
|
|
1763
|
+
"SECTION",
|
|
1764
|
+
"TABLE",
|
|
1765
|
+
"THEAD",
|
|
1766
|
+
"TBODY",
|
|
1767
|
+
"TR",
|
|
1768
|
+
"TD",
|
|
1769
|
+
"TH"
|
|
1770
|
+
]);
|
|
1771
|
+
var DROP_ENTIRELY_TAGS = /* @__PURE__ */ new Set([
|
|
1772
|
+
"SCRIPT",
|
|
1773
|
+
"STYLE",
|
|
1774
|
+
"IFRAME",
|
|
1775
|
+
"OBJECT",
|
|
1776
|
+
"EMBED",
|
|
1777
|
+
"SVG",
|
|
1778
|
+
"MATH",
|
|
1779
|
+
"FORM",
|
|
1780
|
+
"INPUT",
|
|
1781
|
+
"BUTTON",
|
|
1782
|
+
"TEXTAREA",
|
|
1783
|
+
"SELECT",
|
|
1784
|
+
"TEMPLATE",
|
|
1785
|
+
"LINK",
|
|
1786
|
+
"META",
|
|
1787
|
+
"BASE",
|
|
1788
|
+
"NOSCRIPT"
|
|
1789
|
+
]);
|
|
1790
|
+
var ALLOWED_ATTRS = /* @__PURE__ */ new Set(["class", "v", "colspan", "rowspan", "dir", "usfm"]);
|
|
1791
|
+
function sanitizeBibleHtmlDocument(doc) {
|
|
1792
|
+
const root = doc.body ?? doc.documentElement;
|
|
1793
|
+
for (const el of Array.from(root.querySelectorAll("*"))) {
|
|
1794
|
+
const tag = el.tagName;
|
|
1795
|
+
if (DROP_ENTIRELY_TAGS.has(tag)) {
|
|
1796
|
+
el.remove();
|
|
1797
|
+
continue;
|
|
1798
|
+
}
|
|
1799
|
+
if (!ALLOWED_TAGS.has(tag)) {
|
|
1800
|
+
el.replaceWith(...Array.from(el.childNodes));
|
|
1801
|
+
continue;
|
|
1802
|
+
}
|
|
1803
|
+
for (const attr of Array.from(el.attributes)) {
|
|
1804
|
+
const name = attr.name.toLowerCase();
|
|
1805
|
+
if (name.startsWith("on")) {
|
|
1806
|
+
el.removeAttribute(attr.name);
|
|
1807
|
+
continue;
|
|
1808
|
+
}
|
|
1809
|
+
if (!ALLOWED_ATTRS.has(name) && !name.startsWith("data-")) {
|
|
1810
|
+
el.removeAttribute(attr.name);
|
|
1811
|
+
}
|
|
1812
|
+
}
|
|
1813
|
+
}
|
|
1814
|
+
}
|
|
1815
|
+
function wrapVerseContent(doc) {
|
|
1816
|
+
function wrapParagraphContent(doc2, paragraph, verseNum) {
|
|
1817
|
+
const children = Array.from(paragraph.childNodes);
|
|
1818
|
+
if (children.length === 0) return;
|
|
1819
|
+
const wrapper = doc2.createElement("span");
|
|
1820
|
+
wrapper.className = "yv-v";
|
|
1821
|
+
wrapper.setAttribute("v", verseNum);
|
|
1822
|
+
const firstChild = children[0];
|
|
1823
|
+
if (firstChild) {
|
|
1824
|
+
paragraph.insertBefore(wrapper, firstChild);
|
|
1825
|
+
}
|
|
1826
|
+
children.forEach((child) => {
|
|
1827
|
+
wrapper.appendChild(child);
|
|
1828
|
+
});
|
|
1829
|
+
}
|
|
1830
|
+
function wrapParagraphsUntilBoundary(doc2, verseNum, startParagraph, endParagraph) {
|
|
1831
|
+
if (!startParagraph) return;
|
|
1832
|
+
let currentParagraph = startParagraph.nextElementSibling;
|
|
1833
|
+
while (currentParagraph && currentParagraph !== endParagraph) {
|
|
1834
|
+
const isHeading = currentParagraph.classList.contains("yv-h") || currentParagraph.matches(
|
|
1835
|
+
".s1, .s2, .s3, .s4, .ms, .ms1, .ms2, .ms3, .ms4, .mr, .sp, .sr, .qa, .r"
|
|
1836
|
+
);
|
|
1837
|
+
if (isHeading) {
|
|
1838
|
+
currentParagraph = currentParagraph.nextElementSibling;
|
|
1839
|
+
continue;
|
|
1840
|
+
}
|
|
1841
|
+
if (currentParagraph.querySelector(".yv-v[v]")) break;
|
|
1842
|
+
if (currentParagraph.classList.contains("p") || currentParagraph.tagName === "P") {
|
|
1843
|
+
wrapParagraphContent(doc2, currentParagraph, verseNum);
|
|
1844
|
+
}
|
|
1845
|
+
currentParagraph = currentParagraph.nextElementSibling;
|
|
1846
|
+
}
|
|
1847
|
+
}
|
|
1848
|
+
function handleParagraphWrapping(doc2, currentParagraph, nextParagraph, verseNum) {
|
|
1849
|
+
if (!currentParagraph) return;
|
|
1850
|
+
if (!nextParagraph) {
|
|
1851
|
+
wrapParagraphsUntilBoundary(doc2, verseNum, currentParagraph);
|
|
1852
|
+
return;
|
|
1853
|
+
}
|
|
1854
|
+
if (currentParagraph !== nextParagraph) {
|
|
1855
|
+
wrapParagraphsUntilBoundary(doc2, verseNum, currentParagraph, nextParagraph);
|
|
1856
|
+
}
|
|
1857
|
+
}
|
|
1858
|
+
function processVerseMarker(marker, index, markers) {
|
|
1859
|
+
const verseNum = marker.getAttribute("v");
|
|
1860
|
+
if (!verseNum) return;
|
|
1861
|
+
const nextMarker = markers[index + 1];
|
|
1862
|
+
const nodesToWrap = collectNodesBetweenMarkers(marker, nextMarker);
|
|
1863
|
+
if (nodesToWrap.length === 0) return;
|
|
1864
|
+
const currentParagraph = marker.closest(".p, p, div.p");
|
|
1865
|
+
const nextParagraph = nextMarker?.closest(".p, p, div.p") || null;
|
|
1866
|
+
const doc2 = marker.ownerDocument;
|
|
1867
|
+
wrapNodesInVerse(marker, verseNum, nodesToWrap);
|
|
1868
|
+
handleParagraphWrapping(doc2, currentParagraph, nextParagraph, verseNum);
|
|
1869
|
+
}
|
|
1870
|
+
function wrapNodesInVerse(marker, verseNum, nodes) {
|
|
1871
|
+
const wrapper = marker.ownerDocument.createElement("span");
|
|
1872
|
+
wrapper.className = "yv-v";
|
|
1873
|
+
wrapper.setAttribute("v", verseNum);
|
|
1874
|
+
const firstNode = nodes[0];
|
|
1875
|
+
if (firstNode) {
|
|
1876
|
+
marker.parentNode?.insertBefore(wrapper, firstNode);
|
|
1877
|
+
}
|
|
1878
|
+
nodes.forEach((node) => {
|
|
1879
|
+
wrapper.appendChild(node);
|
|
1880
|
+
});
|
|
1881
|
+
marker.remove();
|
|
1882
|
+
}
|
|
1883
|
+
function shouldStopCollecting(node, endMarker) {
|
|
1884
|
+
if (node === endMarker) return true;
|
|
1885
|
+
if (endMarker && node.nodeType === 1 && node.contains(endMarker)) return true;
|
|
1886
|
+
return false;
|
|
1887
|
+
}
|
|
1888
|
+
function shouldSkipNode(node) {
|
|
1889
|
+
return node.nodeType === 1 && node.classList.contains("yv-h");
|
|
1890
|
+
}
|
|
1891
|
+
function collectNodesBetweenMarkers(startMarker, endMarker) {
|
|
1892
|
+
const nodes = [];
|
|
1893
|
+
let current = startMarker.nextSibling;
|
|
1894
|
+
while (current && !shouldStopCollecting(current, endMarker)) {
|
|
1895
|
+
if (shouldSkipNode(current)) {
|
|
1896
|
+
current = current.nextSibling;
|
|
1897
|
+
continue;
|
|
1898
|
+
}
|
|
1899
|
+
nodes.push(current);
|
|
1900
|
+
current = current.nextSibling;
|
|
1901
|
+
}
|
|
1902
|
+
return nodes;
|
|
1903
|
+
}
|
|
1904
|
+
const verseMarkers = Array.from(doc.querySelectorAll(".yv-v[v]"));
|
|
1905
|
+
verseMarkers.forEach(processVerseMarker);
|
|
1906
|
+
}
|
|
1907
|
+
function assignFootnoteKeys(doc) {
|
|
1908
|
+
let introIdx = 0;
|
|
1909
|
+
doc.querySelectorAll(".yv-n.f").forEach((fn) => {
|
|
1910
|
+
const verseNum = fn.closest(".yv-v[v]")?.getAttribute("v");
|
|
1911
|
+
fn.setAttribute(FOOTNOTE_KEY_ATTR, verseNum ?? `intro-${introIdx++}`);
|
|
1912
|
+
});
|
|
1913
|
+
}
|
|
1914
|
+
function replaceFootnotesWithAnchors(doc, footnotes) {
|
|
1915
|
+
for (const fn of footnotes) {
|
|
1916
|
+
const key = fn.getAttribute(FOOTNOTE_KEY_ATTR);
|
|
1917
|
+
if (!key) continue;
|
|
1918
|
+
const prev = fn.previousSibling;
|
|
1919
|
+
const next = fn.nextSibling;
|
|
1920
|
+
const prevText = prev?.textContent ?? "";
|
|
1921
|
+
const nextText = next?.textContent ?? "";
|
|
1922
|
+
const prevNeedsSpace = prevText.length > 0 && !/\s$/.test(prevText);
|
|
1923
|
+
const nextNeedsSpace = nextText.length > 0 && NEEDS_SPACE_BEFORE.test(nextText);
|
|
1924
|
+
if (prevNeedsSpace && nextNeedsSpace && fn.parentNode) {
|
|
1925
|
+
fn.parentNode.insertBefore(doc.createTextNode(" "), fn);
|
|
1926
|
+
}
|
|
1927
|
+
const anchor = doc.createElement("span");
|
|
1928
|
+
anchor.setAttribute("data-verse-footnote", key);
|
|
1929
|
+
anchor.setAttribute("data-verse-footnote-content", fn.innerHTML);
|
|
1930
|
+
fn.replaceWith(anchor);
|
|
1931
|
+
}
|
|
1932
|
+
}
|
|
1933
|
+
function addNbspToVerseLabels(doc) {
|
|
1934
|
+
doc.querySelectorAll(".yv-vlbl").forEach((label) => {
|
|
1935
|
+
const text = label.textContent || "";
|
|
1936
|
+
if (!text.endsWith(NON_BREAKING_SPACE)) {
|
|
1937
|
+
label.textContent = text + NON_BREAKING_SPACE;
|
|
1938
|
+
}
|
|
1939
|
+
});
|
|
1940
|
+
}
|
|
1941
|
+
function fixIrregularTables(doc) {
|
|
1942
|
+
doc.querySelectorAll("table").forEach((table) => {
|
|
1943
|
+
const rows = table.querySelectorAll("tr");
|
|
1944
|
+
if (rows.length === 0) return;
|
|
1945
|
+
let maxColumns = 0;
|
|
1946
|
+
rows.forEach((row) => {
|
|
1947
|
+
let count = 0;
|
|
1948
|
+
row.querySelectorAll("td, th").forEach((cell) => {
|
|
1949
|
+
count += parseInt(cell.getAttribute("colspan") || "1", 10);
|
|
1950
|
+
});
|
|
1951
|
+
maxColumns = Math.max(maxColumns, count);
|
|
1952
|
+
});
|
|
1953
|
+
if (maxColumns > 1) {
|
|
1954
|
+
rows.forEach((row) => {
|
|
1955
|
+
const cells = row.querySelectorAll("td, th");
|
|
1956
|
+
if (cells.length === 1) {
|
|
1957
|
+
const existing = parseInt(cells[0].getAttribute("colspan") || "1", 10);
|
|
1958
|
+
if (existing < maxColumns) {
|
|
1959
|
+
cells[0].setAttribute("colspan", maxColumns.toString());
|
|
1960
|
+
}
|
|
1961
|
+
}
|
|
1962
|
+
});
|
|
1963
|
+
}
|
|
1964
|
+
});
|
|
1965
|
+
}
|
|
1966
|
+
function transformBibleHtml(html, options) {
|
|
1967
|
+
const doc = options.parseHtml(html);
|
|
1968
|
+
sanitizeBibleHtmlDocument(doc);
|
|
1969
|
+
wrapVerseContent(doc);
|
|
1970
|
+
assignFootnoteKeys(doc);
|
|
1971
|
+
const footnotes = Array.from(doc.querySelectorAll(".yv-n.f"));
|
|
1972
|
+
replaceFootnotesWithAnchors(doc, footnotes);
|
|
1973
|
+
addNbspToVerseLabels(doc);
|
|
1974
|
+
fixIrregularTables(doc);
|
|
1975
|
+
const transformedHtml = options.serializeHtml(doc);
|
|
1976
|
+
return { html: transformedHtml };
|
|
1977
|
+
}
|
|
1735
1978
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1736
1979
|
0 && (module.exports = {
|
|
1737
1980
|
ApiClient,
|
|
@@ -1750,5 +1993,6 @@ function lastCanonicalOfPreviousBook(books, bookIndex) {
|
|
|
1750
1993
|
YouVersionAPIUsers,
|
|
1751
1994
|
YouVersionPlatformConfiguration,
|
|
1752
1995
|
YouVersionUserInfo,
|
|
1753
|
-
getAdjacentChapter
|
|
1996
|
+
getAdjacentChapter,
|
|
1997
|
+
transformBibleHtml
|
|
1754
1998
|
});
|
package/dist/index.d.cts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
+
export { a as TransformBibleHtmlOptions, T as TransformedBibleHtml, t as transformBibleHtml } from './browser-DzQ1yOHv.cjs';
|
|
2
3
|
|
|
3
4
|
declare const SignInWithYouVersionPermission: {
|
|
4
5
|
readonly bibles: "bibles";
|
|
@@ -390,12 +391,18 @@ declare class BibleClient {
|
|
|
390
391
|
/**
|
|
391
392
|
* Fetches a passage (range of verses) from the Bible using the passages endpoint.
|
|
392
393
|
* This is the new API format that returns HTML-formatted content.
|
|
394
|
+
*
|
|
395
|
+
* Note: The HTML returned from the API contains inline footnote content that should
|
|
396
|
+
* be transformed before rendering. Use `transformBibleHtml()` or
|
|
397
|
+
* `transformBibleHtmlForBrowser()` to clean up the HTML and extract footnotes.
|
|
398
|
+
*
|
|
393
399
|
* @param versionId The version ID.
|
|
394
400
|
* @param usfm The USFM reference (e.g., "JHN.3.1-2", "GEN.1", "JHN.3.16").
|
|
395
401
|
* @param format The format to return ("html" or "text", default: "html").
|
|
396
402
|
* @param include_headings Whether to include headings in the content.
|
|
397
403
|
* @param include_notes Whether to include notes in the content.
|
|
398
404
|
* @returns The requested BiblePassage object with HTML content.
|
|
405
|
+
*
|
|
399
406
|
* @example
|
|
400
407
|
* ```ts
|
|
401
408
|
* // Get a single verse
|
|
@@ -406,6 +413,10 @@ declare class BibleClient {
|
|
|
406
413
|
*
|
|
407
414
|
* // Get an entire chapter
|
|
408
415
|
* const chapter = await bibleClient.getPassage(3034, "GEN.1");
|
|
416
|
+
*
|
|
417
|
+
* // Transform HTML before rendering
|
|
418
|
+
* const passage = await bibleClient.getPassage(3034, "JHN.3.16", "html", true, true);
|
|
419
|
+
* const transformed = transformBibleHtmlForBrowser(passage.content);
|
|
409
420
|
* ```
|
|
410
421
|
*/
|
|
411
422
|
getPassage(versionId: number, usfm: string, format?: 'html' | 'text', include_headings?: boolean, include_notes?: boolean): Promise<BiblePassage>;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
+
export { a as TransformBibleHtmlOptions, T as TransformedBibleHtml, t as transformBibleHtml } from './browser-DzQ1yOHv.js';
|
|
2
3
|
|
|
3
4
|
declare const SignInWithYouVersionPermission: {
|
|
4
5
|
readonly bibles: "bibles";
|
|
@@ -390,12 +391,18 @@ declare class BibleClient {
|
|
|
390
391
|
/**
|
|
391
392
|
* Fetches a passage (range of verses) from the Bible using the passages endpoint.
|
|
392
393
|
* This is the new API format that returns HTML-formatted content.
|
|
394
|
+
*
|
|
395
|
+
* Note: The HTML returned from the API contains inline footnote content that should
|
|
396
|
+
* be transformed before rendering. Use `transformBibleHtml()` or
|
|
397
|
+
* `transformBibleHtmlForBrowser()` to clean up the HTML and extract footnotes.
|
|
398
|
+
*
|
|
393
399
|
* @param versionId The version ID.
|
|
394
400
|
* @param usfm The USFM reference (e.g., "JHN.3.1-2", "GEN.1", "JHN.3.16").
|
|
395
401
|
* @param format The format to return ("html" or "text", default: "html").
|
|
396
402
|
* @param include_headings Whether to include headings in the content.
|
|
397
403
|
* @param include_notes Whether to include notes in the content.
|
|
398
404
|
* @returns The requested BiblePassage object with HTML content.
|
|
405
|
+
*
|
|
399
406
|
* @example
|
|
400
407
|
* ```ts
|
|
401
408
|
* // Get a single verse
|
|
@@ -406,6 +413,10 @@ declare class BibleClient {
|
|
|
406
413
|
*
|
|
407
414
|
* // Get an entire chapter
|
|
408
415
|
* const chapter = await bibleClient.getPassage(3034, "GEN.1");
|
|
416
|
+
*
|
|
417
|
+
* // Transform HTML before rendering
|
|
418
|
+
* const passage = await bibleClient.getPassage(3034, "JHN.3.16", "html", true, true);
|
|
419
|
+
* const transformed = transformBibleHtmlForBrowser(passage.content);
|
|
409
420
|
* ```
|
|
410
421
|
*/
|
|
411
422
|
getPassage(versionId: number, usfm: string, format?: 'html' | 'text', include_headings?: boolean, include_notes?: boolean): Promise<BiblePassage>;
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import {
|
|
2
|
+
transformBibleHtml
|
|
3
|
+
} from "./chunk-2Z2S2WY3.js";
|
|
4
|
+
|
|
1
5
|
// src/client.ts
|
|
2
6
|
var ApiClient = class {
|
|
3
7
|
baseURL;
|
|
@@ -747,12 +751,18 @@ var BibleClient = class _BibleClient {
|
|
|
747
751
|
/**
|
|
748
752
|
* Fetches a passage (range of verses) from the Bible using the passages endpoint.
|
|
749
753
|
* This is the new API format that returns HTML-formatted content.
|
|
754
|
+
*
|
|
755
|
+
* Note: The HTML returned from the API contains inline footnote content that should
|
|
756
|
+
* be transformed before rendering. Use `transformBibleHtml()` or
|
|
757
|
+
* `transformBibleHtmlForBrowser()` to clean up the HTML and extract footnotes.
|
|
758
|
+
*
|
|
750
759
|
* @param versionId The version ID.
|
|
751
760
|
* @param usfm The USFM reference (e.g., "JHN.3.1-2", "GEN.1", "JHN.3.16").
|
|
752
761
|
* @param format The format to return ("html" or "text", default: "html").
|
|
753
762
|
* @param include_headings Whether to include headings in the content.
|
|
754
763
|
* @param include_notes Whether to include notes in the content.
|
|
755
764
|
* @returns The requested BiblePassage object with HTML content.
|
|
765
|
+
*
|
|
756
766
|
* @example
|
|
757
767
|
* ```ts
|
|
758
768
|
* // Get a single verse
|
|
@@ -763,6 +773,10 @@ var BibleClient = class _BibleClient {
|
|
|
763
773
|
*
|
|
764
774
|
* // Get an entire chapter
|
|
765
775
|
* const chapter = await bibleClient.getPassage(3034, "GEN.1");
|
|
776
|
+
*
|
|
777
|
+
* // Transform HTML before rendering
|
|
778
|
+
* const passage = await bibleClient.getPassage(3034, "JHN.3.16", "html", true, true);
|
|
779
|
+
* const transformed = transformBibleHtmlForBrowser(passage.content);
|
|
766
780
|
* ```
|
|
767
781
|
*/
|
|
768
782
|
async getPassage(versionId, usfm, format = "html", include_headings, include_notes) {
|
|
@@ -1707,5 +1721,6 @@ export {
|
|
|
1707
1721
|
YouVersionAPIUsers,
|
|
1708
1722
|
YouVersionPlatformConfiguration,
|
|
1709
1723
|
YouVersionUserInfo,
|
|
1710
|
-
getAdjacentChapter
|
|
1724
|
+
getAdjacentChapter,
|
|
1725
|
+
transformBibleHtml
|
|
1711
1726
|
};
|