@youversion/platform-core 1.20.2 → 1.22.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.2 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.22.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
- CJS dist/index.cjs 56.19 KB
12
- CJS ⚡️ Build success in 59ms
13
- ESM dist/index.js 53.04 KB
14
- ESM ⚡️ Build success in 59ms
11
+ ESM dist/index.js 53.56 KB
12
+ ESM dist/chunk-2Z2S2WY3.js 7.74 KB
13
+ ESM dist/server.js 443.00 B
14
+ ESM dist/browser.js 135.00 B
15
+ ESM ⚡️ Build success in 58ms
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 58ms
15
20
  DTS Build start
16
- DTS ⚡️ Build success in 1992ms
17
- DTS dist/index.d.cts 33.09 KB
18
- DTS dist/index.d.ts 33.09 KB
21
+ DTS ⚡️ Build success in 2493ms
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
@@ -1,7 +1,7 @@
1
1
  # @youversion/platform-core
2
2
 
3
3
  ## OVERVIEW
4
- Foundation package providing pure TypeScript API clients for YouVersion services with zero React dependencies.
4
+ Foundation package providing pure TypeScript API clients for YouVersion services with zero React dependencies. Also provides framework-agnostic browser CSS (design tokens, preflight reset, Bible reader typography) so any web platform can render Bible content with proper styling.
5
5
 
6
6
  **Related packages:**
7
7
  - For React hooks wrapping these clients → see `packages/hooks/AGENTS.md`
@@ -10,6 +10,11 @@ Foundation package providing pure TypeScript API clients for YouVersion services
10
10
  ## STRUCTURE
11
11
  ```
12
12
  schemas/ # Zod schemas for all data types (schema-first design)
13
+ styles/ # Browser CSS (exported via ./browser/styles/*)
14
+ fonts.css # Google Fonts import (Inter, Source Serif 4)
15
+ theme.css # --yv-* design tokens on :root, dark mode, scoped preflight
16
+ bible-reader.css # USFM/Bible typography for [data-slot='yv-bible-renderer']
17
+ index.css # Barrel: imports fonts + theme + bible-reader
13
18
  client.ts # ApiClient - main HTTP client
14
19
  bible.ts # BibleClient - Bible data operations
15
20
  languages.ts # LanguagesClient - language data
@@ -17,27 +22,76 @@ highlights.ts # HighlightsClient - user highlights
17
22
  YouVersionAPI.ts # Base YouVersion API client
18
23
  SignInWithYouVersionPKCE.ts # PKCE auth implementation
19
24
  StorageStrategy.ts # Storage interface (SessionStorage, MemoryStorage)
25
+ bible-html-transformer.ts # Runtime-agnostic transformer (also contains browser convenience fn)
26
+ bible-html-transformer-server.ts # Server convenience wrapper (uses linkedom)
27
+ browser.ts # Browser entry point
28
+ server.ts # Server entry point
29
+ index.ts # Main entry point (runtime-agnostic)
20
30
  ```
21
31
 
22
32
  ## PUBLIC API
33
+
34
+ ### TypeScript (`@youversion/platform-core`)
23
35
  - `ApiClient`: Main HTTP client with auth handling
24
36
  - `BibleClient`: Fetch Bibles, chapters, verses, versions
25
37
  - `LanguagesClient`: Get available languages
26
38
  - `HighlightsClient`: Manage user highlights
27
39
  - `SignInWithYouVersionPKCE()`: PKCE auth flow function
28
40
  - `SessionStorage`, `MemoryStorage`: Storage strategies
41
+ - `transformBibleHtml`: Runtime-agnostic Bible HTML transformer (requires DOM adapters)
42
+ - `TransformBibleHtmlOptions`: Options for DOM parsing and serialization
43
+
44
+ ### Browser CSS (`@youversion/platform-core/browser/styles/*`)
45
+ - `index.css`: All-in-one import (fonts + theme + bible-reader)
46
+ - `theme.css`: `--yv-*` design tokens on `:root` + dark mode (`[data-yv-theme='dark']`) + scoped preflight
47
+ - `bible-reader.css`: USFM typography for `[data-slot='yv-bible-renderer']` or `[data-yv-sdk-bible-reader]`
48
+ - `fonts.css`: Google Fonts import (Inter, Source Serif 4)
29
49
 
30
50
  ## DOs / DON'Ts
31
51
 
32
- ✅ Do: Keep this package **framework-agnostic** (no React, no DOM, no browser-only APIs)
52
+ ✅ Do: Keep this package **framework-agnostic**, but if you must target server or browser, those files must export from `/server` or `/browser`
33
53
  ✅ Do: Define all input/output types in `schemas/` using Zod; schemas are the single source of truth
34
54
  ✅ Do: Reuse `YouVersionAPI` base client for new service clients
35
55
  ✅ Do: Parse API responses with Zod schemas for validation
36
56
 
37
- ❌ Don't: Import React, `window`, `document`, or browser storage APIs directly
57
+ ❌ Don't: Import React, `window`, `document`, or browser storage APIs, but if you must target the browser, those files must export from `/browser`
38
58
  ❌ Don't: Bypass Zod validation for API responses
39
59
  ❌ Don't: Implement UI, hooks, or React state here
40
60
 
61
+ ## ENVIRONMENT-SPECIFIC EXPORTS
62
+
63
+ The Bible HTML transformer provides both a runtime-agnostic core and environment-specific convenience wrappers:
64
+
65
+ - `@youversion/platform-core` → Runtime-agnostic `transformBibleHtml` (requires DOM adapters)
66
+ - `@youversion/platform-core/browser` → Browser convenience wrapper (uses native DOMParser)
67
+ - `@youversion/platform-core/server` → Server convenience wrapper (uses linkedom)
68
+
69
+ **Examples:**
70
+
71
+ ```ts
72
+ // Runtime-agnostic (works anywhere with custom adapters)
73
+ import { transformBibleHtml } from '@youversion/platform-core';
74
+
75
+ const result = transformBibleHtml(html, {
76
+ parseHtml: (h) => new DOMParser().parseFromString(h, 'text/html'),
77
+ serializeHtml: (doc) => doc.body.innerHTML,
78
+ });
79
+
80
+ // Browser convenience (uses native DOMParser)
81
+ import { transformBibleHtml } from '@youversion/platform-core/browser';
82
+
83
+ const result = transformBibleHtml(html);
84
+
85
+ // Server convenience (uses linkedom, requires: npm install linkedom)
86
+ import { transformBibleHtml } from '@youversion/platform-core/server';
87
+
88
+ const result = transformBibleHtml(html);
89
+ ```
90
+
91
+ **Why separate entry points?**
92
+
93
+ 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.
94
+
41
95
  ## ADDING A NEW ENDPOINT OR CLIENT
42
96
 
43
97
  1. **Define types** in `schemas/` using Zod:
@@ -67,6 +121,8 @@ StorageStrategy.ts # Storage interface (SessionStorage, MemoryStorage)
67
121
  - Storage: Abstract via StorageStrategy interface
68
122
  - Auth: PKCE flow with pluggable storage backends
69
123
  - Error handling: Zod validation for all API responses
124
+ - Browser CSS: Plain CSS only (no Tailwind, no preprocessors), served from `src/styles/` without a build step
125
+ - Two export namespaces: `"."` for TS (framework-agnostic), `"./browser"` for browser environments, and `"./server` for server environments
70
126
 
71
127
  ## TESTING
72
128
 
package/CHANGELOG.md CHANGED
@@ -1,5 +1,27 @@
1
1
  # @youversion/platform-core
2
2
 
3
+ ## 1.22.0
4
+
5
+ ### Minor Changes
6
+
7
+ - ff14f28: We've moved our theme, fonts, and Bible CSS from the React package into our core JS package to make it more framework agnostic so that consumers using any web framework can include our CSS without the React peer dependency.
8
+
9
+ ## 1.21.0
10
+
11
+ ### Minor Changes
12
+
13
+ - 87ad436: **`@youversion/platform-core`**: Add `transformBibleHtml` — a runtime-agnostic Bible HTML transformer with new `/browser` and `/server` subpath exports.
14
+ - `@youversion/platform-core` — runtime-agnostic core; accepts `parseHtml`/`serializeHtml` adapters so it works with any DOM implementation
15
+ - `@youversion/platform-core/browser` — zero-config convenience wrapper using the native `DOMParser`
16
+ - `@youversion/platform-core/server` — zero-config convenience wrapper using `linkedom` (optional peer dependency)
17
+
18
+ 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.
19
+
20
+ **`@youversion/platform-react-ui`**: Migrate Bible HTML transformation from the UI package to `@youversion/platform-core/browser`.
21
+ - Removed `isomorphic-dompurify` dependency (lighter bundle)
22
+ - Footnote popover data is now read from DOM attributes at render time instead of a separate data structure
23
+ - Added SSR safety guard — `Verse.Html` returns raw HTML during server rendering and transforms on the client after hydration
24
+
3
25
  ## 1.20.2
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
+ };