@youversion/platform-core 1.20.1 → 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.
@@ -1,18 +1,29 @@
1
1
 
2
- > @youversion/platform-core@1.20.1 build /home/runner/work/platform-sdk-react/platform-sdk-react/packages/core
3
- > tsup src/index.ts --format cjs,esm --dts
2
+ > @youversion/platform-core@1.21.0 build /home/runner/work/platform-sdk-react/platform-sdk-react/packages/core
3
+ > tsup src/index.ts src/browser.ts src/server.ts --format cjs,esm --dts
4
4
 
5
- CLI Building entry: src/index.ts
5
+ CLI Building entry: src/browser.ts, src/index.ts, src/server.ts
6
6
  CLI Using tsconfig: tsconfig.json
7
7
  CLI tsup v8.5.0
8
8
  CLI Target: es2022
9
9
  CJS Build start
10
10
  ESM Build start
11
- ESM dist/index.js 53.00 KB
12
- ESM ⚡️ Build success in 42ms
13
- CJS dist/index.cjs 56.12 KB
14
- CJS ⚡️ Build success in 43ms
11
+ ESM dist/browser.js 135.00 B
12
+ ESM dist/server.js 443.00 B
13
+ ESM dist/index.js 53.56 KB
14
+ ESM dist/chunk-2Z2S2WY3.js 7.74 KB
15
+ ESM ⚡️ Build success in 65ms
16
+ CJS dist/browser.cjs 8.75 KB
17
+ CJS dist/index.cjs 64.03 KB
18
+ CJS dist/server.cjs 8.76 KB
19
+ CJS ⚡️ Build success in 66ms
15
20
  DTS Build start
16
- DTS ⚡️ Build success in 1883ms
17
- DTS dist/index.d.cts 34.06 KB
18
- DTS dist/index.d.ts 34.06 KB
21
+ DTS ⚡️ Build success in 3000ms
22
+ DTS dist/server.d.cts 863.00 B
23
+ DTS dist/index.d.cts 33.68 KB
24
+ DTS dist/browser.d.cts 93.00 B
25
+ DTS dist/browser-DzQ1yOHv.d.cts 2.67 KB
26
+ DTS dist/server.d.ts 862.00 B
27
+ DTS dist/index.d.ts 33.68 KB
28
+ DTS dist/browser.d.ts 92.00 B
29
+ DTS dist/browser-DzQ1yOHv.d.ts 2.67 KB
package/AGENTS.md CHANGED
@@ -17,6 +17,11 @@ highlights.ts # HighlightsClient - user highlights
17
17
  YouVersionAPI.ts # Base YouVersion API client
18
18
  SignInWithYouVersionPKCE.ts # PKCE auth implementation
19
19
  StorageStrategy.ts # Storage interface (SessionStorage, MemoryStorage)
20
+ bible-html-transformer.ts # Runtime-agnostic transformer (also contains browser convenience fn)
21
+ bible-html-transformer-server.ts # Server convenience wrapper (uses linkedom)
22
+ browser.ts # Browser entry point
23
+ server.ts # Server entry point
24
+ index.ts # Main entry point (runtime-agnostic)
20
25
  ```
21
26
 
22
27
  ## PUBLIC API
@@ -26,18 +31,58 @@ StorageStrategy.ts # Storage interface (SessionStorage, MemoryStorage)
26
31
  - `HighlightsClient`: Manage user highlights
27
32
  - `SignInWithYouVersionPKCE()`: PKCE auth flow function
28
33
  - `SessionStorage`, `MemoryStorage`: Storage strategies
34
+ - `transformBibleHtml`: Runtime-agnostic Bible HTML transformer (requires DOM adapters)
35
+ - `TransformBibleHtmlOptions`: Options for DOM parsing and serialization
29
36
 
30
37
  ## DOs / DON'Ts
31
38
 
32
- ✅ Do: Keep this package **framework-agnostic** (no React, no DOM, no browser-only APIs)
39
+ ✅ Do: Keep this package **framework-agnostic** (no React, no environment-specific dependencies in main export)
40
+ ✅ Do: Provide pure, runtime-agnostic functions as the main export
41
+ ✅ Do: Use environment-specific entry points (`/browser`, `/server`) for convenience wrappers
33
42
  ✅ Do: Define all input/output types in `schemas/` using Zod; schemas are the single source of truth
34
43
  ✅ Do: Reuse `YouVersionAPI` base client for new service clients
35
44
  ✅ Do: Parse API responses with Zod schemas for validation
36
45
 
37
- ❌ Don't: Import React, `window`, `document`, or browser storage APIs directly
46
+ ❌ Don't: Import React or React-specific APIs
47
+ ❌ Don't: Bundle linkedom in browser builds (use `/server` entry point for server-side code)
48
+ ❌ Don't: Include environment-specific code in the default entry point
38
49
  ❌ Don't: Bypass Zod validation for API responses
39
50
  ❌ Don't: Implement UI, hooks, or React state here
40
51
 
52
+ ## ENVIRONMENT-SPECIFIC EXPORTS
53
+
54
+ The Bible HTML transformer provides both a runtime-agnostic core and environment-specific convenience wrappers:
55
+
56
+ - `@youversion/platform-core` → Runtime-agnostic `transformBibleHtml` (requires DOM adapters)
57
+ - `@youversion/platform-core/browser` → Browser convenience wrapper (uses native DOMParser)
58
+ - `@youversion/platform-core/server` → Server convenience wrapper (uses linkedom)
59
+
60
+ **Examples:**
61
+
62
+ ```ts
63
+ // Runtime-agnostic (works anywhere with custom adapters)
64
+ import { transformBibleHtml } from '@youversion/platform-core';
65
+
66
+ const result = transformBibleHtml(html, {
67
+ parseHtml: (h) => new DOMParser().parseFromString(h, 'text/html'),
68
+ serializeHtml: (doc) => doc.body.innerHTML,
69
+ });
70
+
71
+ // Browser convenience (uses native DOMParser)
72
+ import { transformBibleHtml } from '@youversion/platform-core/browser';
73
+
74
+ const result = transformBibleHtml(html);
75
+
76
+ // Server convenience (uses linkedom, requires: npm install linkedom)
77
+ import { transformBibleHtml } from '@youversion/platform-core/server';
78
+
79
+ const result = transformBibleHtml(html);
80
+ ```
81
+
82
+ **Why separate entry points?**
83
+
84
+ This architecture keeps the main export truly runtime-agnostic while providing ergonomic convenience wrappers for common environments. The separate `/browser` and `/server` entry points ensure optimal bundle sizes - linkedom won't be bundled in browser builds.
85
+
41
86
  ## ADDING A NEW ENDPOINT OR CLIENT
42
87
 
43
88
  1. **Define types** in `schemas/` using Zod:
package/CHANGELOG.md CHANGED
@@ -1,5 +1,27 @@
1
1
  # @youversion/platform-core
2
2
 
3
+ ## 1.21.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 87ad436: **`@youversion/platform-core`**: Add `transformBibleHtml` — a runtime-agnostic Bible HTML transformer with new `/browser` and `/server` subpath exports.
8
+ - `@youversion/platform-core` — runtime-agnostic core; accepts `parseHtml`/`serializeHtml` adapters so it works with any DOM implementation
9
+ - `@youversion/platform-core/browser` — zero-config convenience wrapper using the native `DOMParser`
10
+ - `@youversion/platform-core/server` — zero-config convenience wrapper using `linkedom` (optional peer dependency)
11
+
12
+ The transformer sanitizes API HTML (custom allowlist-based sanitizer, no DOMPurify dependency), wraps verse content for CSS targeting, and embeds footnote data as `data-verse-footnote` / `data-verse-footnote-content` attributes directly in the HTML.
13
+
14
+ **`@youversion/platform-react-ui`**: Migrate Bible HTML transformation from the UI package to `@youversion/platform-core/browser`.
15
+ - Removed `isomorphic-dompurify` dependency (lighter bundle)
16
+ - Footnote popover data is now read from DOM attributes at render time instead of a separate data structure
17
+ - Added SSR safety guard — `Verse.Html` returns raw HTML during server rendering and transforms on the client after hydration
18
+
19
+ ## 1.20.2
20
+
21
+ ### Patch Changes
22
+
23
+ - dd52fbe: fix: use spinner icon instead of "Loading..." text in Bible version button
24
+
3
25
  ## 1.20.1
4
26
 
5
27
  ### Patch Changes
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Options for transforming Bible HTML. Requires DOM adapter functions
3
+ * to parse and serialize HTML, making the transformer runtime-agnostic.
4
+ */
5
+ type TransformBibleHtmlOptions = {
6
+ /** Parses an HTML string into a DOM Document */
7
+ parseHtml: (html: string) => Document;
8
+ /** Serializes a Document back to an HTML string */
9
+ serializeHtml: (doc: Document) => string;
10
+ };
11
+ /**
12
+ * The result of transforming Bible HTML.
13
+ *
14
+ * The returned HTML is self-contained — footnote data is embedded as attributes:
15
+ * - `data-verse-footnote="KEY"` marks the footnote position
16
+ * - `data-verse-footnote-content="HTML"` contains the footnote's inner HTML
17
+ *
18
+ * Consumers can access verse context by walking up from a footnote anchor
19
+ * to `.closest('.yv-v[v]')`.
20
+ */
21
+ type TransformedBibleHtml = {
22
+ /** The transformed HTML with footnotes replaced by marker elements */
23
+ html: string;
24
+ };
25
+ /**
26
+ * Transforms Bible HTML by cleaning up verse structure, extracting footnotes,
27
+ * and replacing them with self-contained anchor elements.
28
+ *
29
+ * Footnote data is embedded directly in the HTML via attributes:
30
+ * - `data-verse-footnote="KEY"` — the footnote key (verse number or `intro-N`)
31
+ * - `data-verse-footnote-content="HTML"` — the footnote's inner HTML content
32
+ *
33
+ * Verse context is available by walking up from a footnote anchor:
34
+ * `anchor.closest('.yv-v[v]')` returns the verse wrapper (null for intro footnotes).
35
+ *
36
+ * @param html - The raw Bible HTML from the YouVersion API
37
+ * @param options - DOM adapter options for parsing and serializing HTML
38
+ * @returns The transformed HTML
39
+ *
40
+ * @example
41
+ * ```ts
42
+ * import { transformBibleHtml } from '@youversion/platform-core';
43
+ *
44
+ * const result = transformBibleHtml(rawHtml, {
45
+ * parseHtml: (html) => new DOMParser().parseFromString(html, 'text/html'),
46
+ * serializeHtml: (doc) => doc.body.innerHTML,
47
+ * });
48
+ *
49
+ * console.log(result.html); // Clean HTML with self-contained footnote anchors
50
+ * ```
51
+ */
52
+ declare function transformBibleHtml(html: string, options: TransformBibleHtmlOptions): TransformedBibleHtml;
53
+ /**
54
+ * Transforms Bible HTML for browser environments using the native DOMParser API.
55
+ *
56
+ * @param html - The raw Bible HTML from the YouVersion API
57
+ * @returns The transformed HTML
58
+ *
59
+ * @example
60
+ * ```ts
61
+ * import { transformBibleHtmlForBrowser } from '@youversion/platform-core/browser';
62
+ *
63
+ * const result = transformBibleHtmlForBrowser(rawHtml);
64
+ * console.log(result.html); // Clean HTML with self-contained footnote anchors
65
+ * ```
66
+ */
67
+ declare function transformBibleHtmlForBrowser(html: string): TransformedBibleHtml;
68
+
69
+ export { type TransformedBibleHtml as T, type TransformBibleHtmlOptions as a, transformBibleHtmlForBrowser as b, transformBibleHtml as t };
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Options for transforming Bible HTML. Requires DOM adapter functions
3
+ * to parse and serialize HTML, making the transformer runtime-agnostic.
4
+ */
5
+ type TransformBibleHtmlOptions = {
6
+ /** Parses an HTML string into a DOM Document */
7
+ parseHtml: (html: string) => Document;
8
+ /** Serializes a Document back to an HTML string */
9
+ serializeHtml: (doc: Document) => string;
10
+ };
11
+ /**
12
+ * The result of transforming Bible HTML.
13
+ *
14
+ * The returned HTML is self-contained — footnote data is embedded as attributes:
15
+ * - `data-verse-footnote="KEY"` marks the footnote position
16
+ * - `data-verse-footnote-content="HTML"` contains the footnote's inner HTML
17
+ *
18
+ * Consumers can access verse context by walking up from a footnote anchor
19
+ * to `.closest('.yv-v[v]')`.
20
+ */
21
+ type TransformedBibleHtml = {
22
+ /** The transformed HTML with footnotes replaced by marker elements */
23
+ html: string;
24
+ };
25
+ /**
26
+ * Transforms Bible HTML by cleaning up verse structure, extracting footnotes,
27
+ * and replacing them with self-contained anchor elements.
28
+ *
29
+ * Footnote data is embedded directly in the HTML via attributes:
30
+ * - `data-verse-footnote="KEY"` — the footnote key (verse number or `intro-N`)
31
+ * - `data-verse-footnote-content="HTML"` — the footnote's inner HTML content
32
+ *
33
+ * Verse context is available by walking up from a footnote anchor:
34
+ * `anchor.closest('.yv-v[v]')` returns the verse wrapper (null for intro footnotes).
35
+ *
36
+ * @param html - The raw Bible HTML from the YouVersion API
37
+ * @param options - DOM adapter options for parsing and serializing HTML
38
+ * @returns The transformed HTML
39
+ *
40
+ * @example
41
+ * ```ts
42
+ * import { transformBibleHtml } from '@youversion/platform-core';
43
+ *
44
+ * const result = transformBibleHtml(rawHtml, {
45
+ * parseHtml: (html) => new DOMParser().parseFromString(html, 'text/html'),
46
+ * serializeHtml: (doc) => doc.body.innerHTML,
47
+ * });
48
+ *
49
+ * console.log(result.html); // Clean HTML with self-contained footnote anchors
50
+ * ```
51
+ */
52
+ declare function transformBibleHtml(html: string, options: TransformBibleHtmlOptions): TransformedBibleHtml;
53
+ /**
54
+ * Transforms Bible HTML for browser environments using the native DOMParser API.
55
+ *
56
+ * @param html - The raw Bible HTML from the YouVersion API
57
+ * @returns The transformed HTML
58
+ *
59
+ * @example
60
+ * ```ts
61
+ * import { transformBibleHtmlForBrowser } from '@youversion/platform-core/browser';
62
+ *
63
+ * const result = transformBibleHtmlForBrowser(rawHtml);
64
+ * console.log(result.html); // Clean HTML with self-contained footnote anchors
65
+ * ```
66
+ */
67
+ declare function transformBibleHtmlForBrowser(html: string): TransformedBibleHtml;
68
+
69
+ export { type TransformedBibleHtml as T, type TransformBibleHtmlOptions as a, transformBibleHtmlForBrowser as b, transformBibleHtml as t };
@@ -0,0 +1,270 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/browser.ts
21
+ var browser_exports = {};
22
+ __export(browser_exports, {
23
+ transformBibleHtml: () => transformBibleHtmlForBrowser
24
+ });
25
+ module.exports = __toCommonJS(browser_exports);
26
+
27
+ // src/bible-html-transformer.ts
28
+ var NON_BREAKING_SPACE = "\xA0";
29
+ var FOOTNOTE_KEY_ATTR = "data-footnote-key";
30
+ var NEEDS_SPACE_BEFORE = /^[^\s.,;:!?)}\]'"'»›]/;
31
+ var ALLOWED_TAGS = /* @__PURE__ */ new Set([
32
+ "DIV",
33
+ "P",
34
+ "SPAN",
35
+ "SUP",
36
+ "SUB",
37
+ "EM",
38
+ "STRONG",
39
+ "I",
40
+ "B",
41
+ "SMALL",
42
+ "BR",
43
+ "SECTION",
44
+ "TABLE",
45
+ "THEAD",
46
+ "TBODY",
47
+ "TR",
48
+ "TD",
49
+ "TH"
50
+ ]);
51
+ var DROP_ENTIRELY_TAGS = /* @__PURE__ */ new Set([
52
+ "SCRIPT",
53
+ "STYLE",
54
+ "IFRAME",
55
+ "OBJECT",
56
+ "EMBED",
57
+ "SVG",
58
+ "MATH",
59
+ "FORM",
60
+ "INPUT",
61
+ "BUTTON",
62
+ "TEXTAREA",
63
+ "SELECT",
64
+ "TEMPLATE",
65
+ "LINK",
66
+ "META",
67
+ "BASE",
68
+ "NOSCRIPT"
69
+ ]);
70
+ var ALLOWED_ATTRS = /* @__PURE__ */ new Set(["class", "v", "colspan", "rowspan", "dir", "usfm"]);
71
+ function sanitizeBibleHtmlDocument(doc) {
72
+ const root = doc.body ?? doc.documentElement;
73
+ for (const el of Array.from(root.querySelectorAll("*"))) {
74
+ const tag = el.tagName;
75
+ if (DROP_ENTIRELY_TAGS.has(tag)) {
76
+ el.remove();
77
+ continue;
78
+ }
79
+ if (!ALLOWED_TAGS.has(tag)) {
80
+ el.replaceWith(...Array.from(el.childNodes));
81
+ continue;
82
+ }
83
+ for (const attr of Array.from(el.attributes)) {
84
+ const name = attr.name.toLowerCase();
85
+ if (name.startsWith("on")) {
86
+ el.removeAttribute(attr.name);
87
+ continue;
88
+ }
89
+ if (!ALLOWED_ATTRS.has(name) && !name.startsWith("data-")) {
90
+ el.removeAttribute(attr.name);
91
+ }
92
+ }
93
+ }
94
+ }
95
+ function wrapVerseContent(doc) {
96
+ function wrapParagraphContent(doc2, paragraph, verseNum) {
97
+ const children = Array.from(paragraph.childNodes);
98
+ if (children.length === 0) return;
99
+ const wrapper = doc2.createElement("span");
100
+ wrapper.className = "yv-v";
101
+ wrapper.setAttribute("v", verseNum);
102
+ const firstChild = children[0];
103
+ if (firstChild) {
104
+ paragraph.insertBefore(wrapper, firstChild);
105
+ }
106
+ children.forEach((child) => {
107
+ wrapper.appendChild(child);
108
+ });
109
+ }
110
+ function wrapParagraphsUntilBoundary(doc2, verseNum, startParagraph, endParagraph) {
111
+ if (!startParagraph) return;
112
+ let currentParagraph = startParagraph.nextElementSibling;
113
+ while (currentParagraph && currentParagraph !== endParagraph) {
114
+ const isHeading = currentParagraph.classList.contains("yv-h") || currentParagraph.matches(
115
+ ".s1, .s2, .s3, .s4, .ms, .ms1, .ms2, .ms3, .ms4, .mr, .sp, .sr, .qa, .r"
116
+ );
117
+ if (isHeading) {
118
+ currentParagraph = currentParagraph.nextElementSibling;
119
+ continue;
120
+ }
121
+ if (currentParagraph.querySelector(".yv-v[v]")) break;
122
+ if (currentParagraph.classList.contains("p") || currentParagraph.tagName === "P") {
123
+ wrapParagraphContent(doc2, currentParagraph, verseNum);
124
+ }
125
+ currentParagraph = currentParagraph.nextElementSibling;
126
+ }
127
+ }
128
+ function handleParagraphWrapping(doc2, currentParagraph, nextParagraph, verseNum) {
129
+ if (!currentParagraph) return;
130
+ if (!nextParagraph) {
131
+ wrapParagraphsUntilBoundary(doc2, verseNum, currentParagraph);
132
+ return;
133
+ }
134
+ if (currentParagraph !== nextParagraph) {
135
+ wrapParagraphsUntilBoundary(doc2, verseNum, currentParagraph, nextParagraph);
136
+ }
137
+ }
138
+ function processVerseMarker(marker, index, markers) {
139
+ const verseNum = marker.getAttribute("v");
140
+ if (!verseNum) return;
141
+ const nextMarker = markers[index + 1];
142
+ const nodesToWrap = collectNodesBetweenMarkers(marker, nextMarker);
143
+ if (nodesToWrap.length === 0) return;
144
+ const currentParagraph = marker.closest(".p, p, div.p");
145
+ const nextParagraph = nextMarker?.closest(".p, p, div.p") || null;
146
+ const doc2 = marker.ownerDocument;
147
+ wrapNodesInVerse(marker, verseNum, nodesToWrap);
148
+ handleParagraphWrapping(doc2, currentParagraph, nextParagraph, verseNum);
149
+ }
150
+ function wrapNodesInVerse(marker, verseNum, nodes) {
151
+ const wrapper = marker.ownerDocument.createElement("span");
152
+ wrapper.className = "yv-v";
153
+ wrapper.setAttribute("v", verseNum);
154
+ const firstNode = nodes[0];
155
+ if (firstNode) {
156
+ marker.parentNode?.insertBefore(wrapper, firstNode);
157
+ }
158
+ nodes.forEach((node) => {
159
+ wrapper.appendChild(node);
160
+ });
161
+ marker.remove();
162
+ }
163
+ function shouldStopCollecting(node, endMarker) {
164
+ if (node === endMarker) return true;
165
+ if (endMarker && node.nodeType === 1 && node.contains(endMarker)) return true;
166
+ return false;
167
+ }
168
+ function shouldSkipNode(node) {
169
+ return node.nodeType === 1 && node.classList.contains("yv-h");
170
+ }
171
+ function collectNodesBetweenMarkers(startMarker, endMarker) {
172
+ const nodes = [];
173
+ let current = startMarker.nextSibling;
174
+ while (current && !shouldStopCollecting(current, endMarker)) {
175
+ if (shouldSkipNode(current)) {
176
+ current = current.nextSibling;
177
+ continue;
178
+ }
179
+ nodes.push(current);
180
+ current = current.nextSibling;
181
+ }
182
+ return nodes;
183
+ }
184
+ const verseMarkers = Array.from(doc.querySelectorAll(".yv-v[v]"));
185
+ verseMarkers.forEach(processVerseMarker);
186
+ }
187
+ function assignFootnoteKeys(doc) {
188
+ let introIdx = 0;
189
+ doc.querySelectorAll(".yv-n.f").forEach((fn) => {
190
+ const verseNum = fn.closest(".yv-v[v]")?.getAttribute("v");
191
+ fn.setAttribute(FOOTNOTE_KEY_ATTR, verseNum ?? `intro-${introIdx++}`);
192
+ });
193
+ }
194
+ function replaceFootnotesWithAnchors(doc, footnotes) {
195
+ for (const fn of footnotes) {
196
+ const key = fn.getAttribute(FOOTNOTE_KEY_ATTR);
197
+ if (!key) continue;
198
+ const prev = fn.previousSibling;
199
+ const next = fn.nextSibling;
200
+ const prevText = prev?.textContent ?? "";
201
+ const nextText = next?.textContent ?? "";
202
+ const prevNeedsSpace = prevText.length > 0 && !/\s$/.test(prevText);
203
+ const nextNeedsSpace = nextText.length > 0 && NEEDS_SPACE_BEFORE.test(nextText);
204
+ if (prevNeedsSpace && nextNeedsSpace && fn.parentNode) {
205
+ fn.parentNode.insertBefore(doc.createTextNode(" "), fn);
206
+ }
207
+ const anchor = doc.createElement("span");
208
+ anchor.setAttribute("data-verse-footnote", key);
209
+ anchor.setAttribute("data-verse-footnote-content", fn.innerHTML);
210
+ fn.replaceWith(anchor);
211
+ }
212
+ }
213
+ function addNbspToVerseLabels(doc) {
214
+ doc.querySelectorAll(".yv-vlbl").forEach((label) => {
215
+ const text = label.textContent || "";
216
+ if (!text.endsWith(NON_BREAKING_SPACE)) {
217
+ label.textContent = text + NON_BREAKING_SPACE;
218
+ }
219
+ });
220
+ }
221
+ function fixIrregularTables(doc) {
222
+ doc.querySelectorAll("table").forEach((table) => {
223
+ const rows = table.querySelectorAll("tr");
224
+ if (rows.length === 0) return;
225
+ let maxColumns = 0;
226
+ rows.forEach((row) => {
227
+ let count = 0;
228
+ row.querySelectorAll("td, th").forEach((cell) => {
229
+ count += parseInt(cell.getAttribute("colspan") || "1", 10);
230
+ });
231
+ maxColumns = Math.max(maxColumns, count);
232
+ });
233
+ if (maxColumns > 1) {
234
+ rows.forEach((row) => {
235
+ const cells = row.querySelectorAll("td, th");
236
+ if (cells.length === 1) {
237
+ const existing = parseInt(cells[0].getAttribute("colspan") || "1", 10);
238
+ if (existing < maxColumns) {
239
+ cells[0].setAttribute("colspan", maxColumns.toString());
240
+ }
241
+ }
242
+ });
243
+ }
244
+ });
245
+ }
246
+ function transformBibleHtml(html, options) {
247
+ const doc = options.parseHtml(html);
248
+ sanitizeBibleHtmlDocument(doc);
249
+ wrapVerseContent(doc);
250
+ assignFootnoteKeys(doc);
251
+ const footnotes = Array.from(doc.querySelectorAll(".yv-n.f"));
252
+ replaceFootnotesWithAnchors(doc, footnotes);
253
+ addNbspToVerseLabels(doc);
254
+ fixIrregularTables(doc);
255
+ const transformedHtml = options.serializeHtml(doc);
256
+ return { html: transformedHtml };
257
+ }
258
+ function transformBibleHtmlForBrowser(html) {
259
+ if (typeof globalThis.DOMParser === "undefined") {
260
+ throw new Error("DOMParser is required to transform Bible HTML in browser environments");
261
+ }
262
+ return transformBibleHtml(html, {
263
+ parseHtml: (h) => new DOMParser().parseFromString(h, "text/html"),
264
+ serializeHtml: (doc) => doc.body.innerHTML
265
+ });
266
+ }
267
+ // Annotate the CommonJS export names for ESM import in node:
268
+ 0 && (module.exports = {
269
+ transformBibleHtml
270
+ });
@@ -0,0 +1 @@
1
+ export { T as TransformedBibleHtml, b as transformBibleHtml } from './browser-DzQ1yOHv.cjs';
@@ -0,0 +1 @@
1
+ export { T as TransformedBibleHtml, b as transformBibleHtml } from './browser-DzQ1yOHv.js';
@@ -0,0 +1,6 @@
1
+ import {
2
+ transformBibleHtmlForBrowser
3
+ } from "./chunk-2Z2S2WY3.js";
4
+ export {
5
+ transformBibleHtmlForBrowser as transformBibleHtml
6
+ };