@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.
- package/.turbo/turbo-build.log +21 -10
- package/AGENTS.md +59 -3
- package/CHANGELOG.md +22 -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 +24 -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
- package/src/styles/bible-reader.css +1515 -0
- package/src/styles/fonts.css +5 -0
- package/src/styles/index.css +3 -0
- package/src/styles/theme.css +300 -0
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,18 +1,29 @@
|
|
|
1
1
|
|
|
2
|
-
> @youversion/platform-core@1.
|
|
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
|
-
[34mCLI[39m Building entry: src/index.ts
|
|
5
|
+
[34mCLI[39m Building entry: src/browser.ts, src/index.ts, src/server.ts
|
|
6
6
|
[34mCLI[39m Using tsconfig: tsconfig.json
|
|
7
7
|
[34mCLI[39m tsup v8.5.0
|
|
8
8
|
[34mCLI[39m Target: es2022
|
|
9
9
|
[34mCJS[39m Build start
|
|
10
10
|
[34mESM[39m Build start
|
|
11
|
-
[
|
|
12
|
-
[
|
|
13
|
-
[32mESM[39m [1mdist/
|
|
14
|
-
[32mESM[39m
|
|
11
|
+
[32mESM[39m [1mdist/index.js [22m[32m53.56 KB[39m
|
|
12
|
+
[32mESM[39m [1mdist/chunk-2Z2S2WY3.js [22m[32m7.74 KB[39m
|
|
13
|
+
[32mESM[39m [1mdist/server.js [22m[32m443.00 B[39m
|
|
14
|
+
[32mESM[39m [1mdist/browser.js [22m[32m135.00 B[39m
|
|
15
|
+
[32mESM[39m ⚡️ Build success in 58ms
|
|
16
|
+
[32mCJS[39m [1mdist/browser.cjs [22m[32m8.75 KB[39m
|
|
17
|
+
[32mCJS[39m [1mdist/index.cjs [22m[32m64.03 KB[39m
|
|
18
|
+
[32mCJS[39m [1mdist/server.cjs [22m[32m8.76 KB[39m
|
|
19
|
+
[32mCJS[39m ⚡️ Build success in 58ms
|
|
15
20
|
[34mDTS[39m Build start
|
|
16
|
-
[32mDTS[39m ⚡️ Build success in
|
|
17
|
-
[32mDTS[39m [1mdist/
|
|
18
|
-
[32mDTS[39m [1mdist/index.d.
|
|
21
|
+
[32mDTS[39m ⚡️ Build success in 2493ms
|
|
22
|
+
[32mDTS[39m [1mdist/server.d.cts [22m[32m863.00 B[39m
|
|
23
|
+
[32mDTS[39m [1mdist/index.d.cts [22m[32m33.68 KB[39m
|
|
24
|
+
[32mDTS[39m [1mdist/browser.d.cts [22m[32m93.00 B[39m
|
|
25
|
+
[32mDTS[39m [1mdist/browser-DzQ1yOHv.d.cts [22m[32m2.67 KB[39m
|
|
26
|
+
[32mDTS[39m [1mdist/server.d.ts [22m[32m862.00 B[39m
|
|
27
|
+
[32mDTS[39m [1mdist/index.d.ts [22m[32m33.68 KB[39m
|
|
28
|
+
[32mDTS[39m [1mdist/browser.d.ts [22m[32m92.00 B[39m
|
|
29
|
+
[32mDTS[39m [1mdist/browser-DzQ1yOHv.d.ts [22m[32m2.67 KB[39m
|
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
|
|
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
|
|
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 };
|
package/dist/browser.cjs
ADDED
|
@@ -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';
|