@youversion/platform-react-ui 1.14.3 → 1.14.4
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/dist/components/verse.d.ts.map +1 -1
- package/dist/index.cjs +159 -170
- package/dist/index.js +167 -178
- package/dist/lib/verse-html-utils.d.ts +20 -20
- package/dist/lib/verse-html-utils.d.ts.map +1 -1
- package/package.json +5 -5
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"verse.d.ts","sourceRoot":"","sources":["../../src/components/verse.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"verse.d.ts","sourceRoot":"","sources":["../../src/components/verse.tsx"],"names":[],"mappings":"AAcA,OAAO,EAGL,KAAK,UAAU,EAEhB,MAAM,wBAAwB,CAAC;AA6JhC;;GAEG;AACH,KAAK,UAAU,GAAG;IAChB;;OAEG;IACH,MAAM,EAAE,MAAM,CAAC;IACf;;OAEG;IACH,IAAI,EAAE,MAAM,CAAC;IACb;;OAEG;IACH,IAAI,CAAC,EAAE,SAAS,GAAG,IAAI,CAAC;CACzB,CAAC;AAEF,KAAK,cAAc,GAAG;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;IACzB,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,aAAa,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;IAC3C,iBAAiB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC7C,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,KAAK;IAChB;;;;;;;;OAQG;mCACwC,UAAU,KAAG,KAAK,CAAC,YAAY;;CAwE3E,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,KAAK,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;IACzB,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,aAAa,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;IAC3C,iBAAiB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC7C,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,aAAa,GAAI,qJAY3B,kBAAkB,KAAG,KAAK,CAAC,YA2D7B,CAAC"}
|
package/dist/index.cjs
CHANGED
|
@@ -15569,13 +15569,24 @@ function PersonIcon(props) {
|
|
|
15569
15569
|
|
|
15570
15570
|
// src/components/verse.tsx
|
|
15571
15571
|
var import_platform_react_hooks3 = require("@youversion/platform-react-hooks");
|
|
15572
|
-
var import_isomorphic_dompurify = __toESM(require("isomorphic-dompurify"), 1);
|
|
15573
15572
|
var import_react3 = require("react");
|
|
15574
15573
|
var import_react_dom = require("react-dom");
|
|
15575
15574
|
|
|
15576
15575
|
// src/lib/verse-html-utils.ts
|
|
15576
|
+
var import_isomorphic_dompurify = __toESM(require("isomorphic-dompurify"), 1);
|
|
15577
15577
|
var NON_BREAKING_SPACE = "\xA0";
|
|
15578
15578
|
var LETTERS = "abcdefghijklmnopqrstuvwxyz";
|
|
15579
|
+
function getFootnoteMarker(index) {
|
|
15580
|
+
const base = LETTERS.length;
|
|
15581
|
+
if (base === 0) return String(index + 1);
|
|
15582
|
+
let value = index;
|
|
15583
|
+
let marker = "";
|
|
15584
|
+
do {
|
|
15585
|
+
marker = LETTERS[value % base] + marker;
|
|
15586
|
+
value = Math.floor(value / base) - 1;
|
|
15587
|
+
} while (value >= 0);
|
|
15588
|
+
return marker;
|
|
15589
|
+
}
|
|
15579
15590
|
var INTER_FONT = '"Inter", sans-serif';
|
|
15580
15591
|
var SOURCE_SERIF_FONT = '"Source Serif 4", serif';
|
|
15581
15592
|
function wrapVerseContent(doc) {
|
|
@@ -15668,73 +15679,125 @@ function wrapVerseContent(doc) {
|
|
|
15668
15679
|
const verseMarkers = Array.from(doc.querySelectorAll(".yv-v[v]"));
|
|
15669
15680
|
verseMarkers.forEach(processVerseMarker);
|
|
15670
15681
|
}
|
|
15682
|
+
var NEEDS_SPACE_BEFORE = /^[^\s.,;:!?)}\]'"»›]/;
|
|
15683
|
+
function buildVerseHtml(wrappers) {
|
|
15684
|
+
const parts = [];
|
|
15685
|
+
let noteIdx = 0;
|
|
15686
|
+
for (let i = 0; i < wrappers.length; i++) {
|
|
15687
|
+
if (i > 0) parts.push(" ");
|
|
15688
|
+
const clone2 = wrappers[i].cloneNode(true);
|
|
15689
|
+
const ownerDoc = wrappers[i].ownerDocument;
|
|
15690
|
+
clone2.querySelectorAll(".yv-h, .yv-vlbl").forEach((el) => el.remove());
|
|
15691
|
+
clone2.querySelectorAll(".yv-n.f").forEach((fn) => {
|
|
15692
|
+
const marker = ownerDoc.createElement("sup");
|
|
15693
|
+
marker.className = "yv:text-muted-foreground";
|
|
15694
|
+
marker.textContent = getFootnoteMarker(noteIdx++);
|
|
15695
|
+
fn.replaceWith(marker);
|
|
15696
|
+
});
|
|
15697
|
+
parts.push(clone2.innerHTML);
|
|
15698
|
+
}
|
|
15699
|
+
return parts.join("");
|
|
15700
|
+
}
|
|
15701
|
+
function replaceFootnotesWithAnchors(doc, footnotes) {
|
|
15702
|
+
for (const fn of footnotes) {
|
|
15703
|
+
const verseNum = fn.closest(".yv-v[v]")?.getAttribute("v");
|
|
15704
|
+
if (!verseNum) continue;
|
|
15705
|
+
const prev = fn.previousSibling;
|
|
15706
|
+
const next = fn.nextSibling;
|
|
15707
|
+
const prevText = prev?.textContent ?? "";
|
|
15708
|
+
const nextText = next?.textContent ?? "";
|
|
15709
|
+
const prevNeedsSpace = prevText.length > 0 && !/\s$/.test(prevText);
|
|
15710
|
+
const nextNeedsSpace = nextText.length > 0 && NEEDS_SPACE_BEFORE.test(nextText);
|
|
15711
|
+
if (prevNeedsSpace && nextNeedsSpace && fn.parentNode) {
|
|
15712
|
+
fn.parentNode.insertBefore(doc.createTextNode(" "), fn);
|
|
15713
|
+
}
|
|
15714
|
+
const anchor = doc.createElement("span");
|
|
15715
|
+
anchor.setAttribute("data-verse-footnote", verseNum);
|
|
15716
|
+
fn.replaceWith(anchor);
|
|
15717
|
+
}
|
|
15718
|
+
}
|
|
15671
15719
|
function extractNotesFromWrappedHtml(doc) {
|
|
15672
15720
|
const footnotes = Array.from(doc.querySelectorAll(".yv-n.f"));
|
|
15673
15721
|
if (!footnotes.length) return {};
|
|
15674
15722
|
const footnotesByVerse = /* @__PURE__ */ new Map();
|
|
15675
|
-
|
|
15723
|
+
for (const fn of footnotes) {
|
|
15676
15724
|
const verseNum = fn.closest(".yv-v[v]")?.getAttribute("v");
|
|
15677
|
-
if (verseNum)
|
|
15678
|
-
|
|
15679
|
-
|
|
15680
|
-
|
|
15681
|
-
|
|
15682
|
-
}
|
|
15683
|
-
arr.push(fn);
|
|
15725
|
+
if (!verseNum) continue;
|
|
15726
|
+
let arr = footnotesByVerse.get(verseNum);
|
|
15727
|
+
if (!arr) {
|
|
15728
|
+
arr = [];
|
|
15729
|
+
footnotesByVerse.set(verseNum, arr);
|
|
15684
15730
|
}
|
|
15731
|
+
arr.push(fn);
|
|
15732
|
+
}
|
|
15733
|
+
const wrappersByVerse = /* @__PURE__ */ new Map();
|
|
15734
|
+
doc.querySelectorAll(".yv-v[v]").forEach((el) => {
|
|
15735
|
+
const verseNum = el.getAttribute("v");
|
|
15736
|
+
if (!verseNum) return;
|
|
15737
|
+
const arr = wrappersByVerse.get(verseNum);
|
|
15738
|
+
if (arr) arr.push(el);
|
|
15739
|
+
else wrappersByVerse.set(verseNum, [el]);
|
|
15685
15740
|
});
|
|
15686
15741
|
const notes = {};
|
|
15687
|
-
const
|
|
15688
|
-
footnotesByVerse.forEach((fns, verseNum) => {
|
|
15689
|
-
const verseWrappers = Array.from(doc.querySelectorAll(`.yv-v[v="${verseNum}"]`));
|
|
15690
|
-
let verseHtml = "";
|
|
15691
|
-
let noteIdx = 0;
|
|
15692
|
-
verseWrappers.forEach((wrapper, wrapperIdx) => {
|
|
15693
|
-
if (wrapperIdx > 0) verseHtml += " ";
|
|
15694
|
-
const walker = doc.createTreeWalker(wrapper, NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_TEXT);
|
|
15695
|
-
let lastWasFootnote = false;
|
|
15696
|
-
while (walker.nextNode()) {
|
|
15697
|
-
const node = walker.currentNode;
|
|
15698
|
-
if (node instanceof Element) {
|
|
15699
|
-
if (node.classList.contains("yv-n") && node.classList.contains("f")) {
|
|
15700
|
-
verseHtml += `<sup class="yv:text-muted-foreground">${LETTERS[noteIdx++] || noteIdx}</sup>`;
|
|
15701
|
-
lastWasFootnote = true;
|
|
15702
|
-
}
|
|
15703
|
-
} else if (node.nodeType === Node.TEXT_NODE) {
|
|
15704
|
-
const parent = node.parentElement;
|
|
15705
|
-
if (parent?.closest(".yv-n.f") || parent?.closest(".yv-h")) continue;
|
|
15706
|
-
if (parent?.classList.contains("yv-vlbl")) continue;
|
|
15707
|
-
let text = node.textContent || "";
|
|
15708
|
-
if (lastWasFootnote && text && NEEDS_SPACE_BEFORE.test(text)) {
|
|
15709
|
-
text = " " + text;
|
|
15710
|
-
}
|
|
15711
|
-
verseHtml += text;
|
|
15712
|
-
lastWasFootnote = false;
|
|
15713
|
-
}
|
|
15714
|
-
}
|
|
15715
|
-
});
|
|
15742
|
+
for (const [verseNum, fns] of footnotesByVerse) {
|
|
15716
15743
|
notes[verseNum] = {
|
|
15717
|
-
verseHtml,
|
|
15744
|
+
verseHtml: buildVerseHtml(wrappersByVerse.get(verseNum) ?? []),
|
|
15718
15745
|
notes: fns.map((fn) => fn.innerHTML)
|
|
15719
15746
|
};
|
|
15720
|
-
|
|
15721
|
-
|
|
15722
|
-
|
|
15723
|
-
|
|
15724
|
-
|
|
15747
|
+
}
|
|
15748
|
+
replaceFootnotesWithAnchors(doc, footnotes);
|
|
15749
|
+
return notes;
|
|
15750
|
+
}
|
|
15751
|
+
function addNbspToVerseLabels(doc) {
|
|
15752
|
+
doc.querySelectorAll(".yv-vlbl").forEach((label) => {
|
|
15753
|
+
const text = label.textContent || "";
|
|
15754
|
+
if (!text.endsWith(NON_BREAKING_SPACE)) {
|
|
15755
|
+
label.textContent = text + NON_BREAKING_SPACE;
|
|
15725
15756
|
}
|
|
15726
15757
|
});
|
|
15727
|
-
|
|
15728
|
-
|
|
15729
|
-
|
|
15730
|
-
const
|
|
15731
|
-
|
|
15732
|
-
|
|
15733
|
-
|
|
15758
|
+
}
|
|
15759
|
+
function fixIrregularTables(doc) {
|
|
15760
|
+
doc.querySelectorAll("table").forEach((table) => {
|
|
15761
|
+
const rows = table.querySelectorAll("tr");
|
|
15762
|
+
if (rows.length === 0) return;
|
|
15763
|
+
let maxColumns = 0;
|
|
15764
|
+
rows.forEach((row) => {
|
|
15765
|
+
let count = 0;
|
|
15766
|
+
row.querySelectorAll("td, th").forEach((cell) => {
|
|
15767
|
+
count += cell instanceof HTMLTableCellElement ? parseInt(cell.getAttribute("colspan") || "1", 10) : 1;
|
|
15768
|
+
});
|
|
15769
|
+
maxColumns = Math.max(maxColumns, count);
|
|
15770
|
+
});
|
|
15771
|
+
if (maxColumns > 1) {
|
|
15772
|
+
rows.forEach((row) => {
|
|
15773
|
+
const cells = row.querySelectorAll("td, th");
|
|
15774
|
+
if (cells.length === 1 && cells[0] instanceof HTMLTableCellElement) {
|
|
15775
|
+
const existing = parseInt(cells[0].getAttribute("colspan") || "1", 10);
|
|
15776
|
+
if (existing < maxColumns) {
|
|
15777
|
+
cells[0].setAttribute("colspan", maxColumns.toString());
|
|
15778
|
+
}
|
|
15779
|
+
}
|
|
15780
|
+
});
|
|
15734
15781
|
}
|
|
15735
|
-
fn.remove();
|
|
15736
15782
|
});
|
|
15737
|
-
|
|
15783
|
+
}
|
|
15784
|
+
var DOMPURIFY_CONFIG = {
|
|
15785
|
+
ALLOWED_ATTR: ["class", "style", "id", "v", "usfm"],
|
|
15786
|
+
ALLOW_DATA_ATTR: true
|
|
15787
|
+
};
|
|
15788
|
+
function transformBibleHtml(html) {
|
|
15789
|
+
if (typeof window === "undefined" || !("DOMParser" in window)) {
|
|
15790
|
+
return { html, notes: {} };
|
|
15791
|
+
}
|
|
15792
|
+
const doc = new DOMParser().parseFromString(
|
|
15793
|
+
import_isomorphic_dompurify.default.sanitize(html, DOMPURIFY_CONFIG),
|
|
15794
|
+
"text/html"
|
|
15795
|
+
);
|
|
15796
|
+
wrapVerseContent(doc);
|
|
15797
|
+
const notes = extractNotesFromWrappedHtml(doc);
|
|
15798
|
+
addNbspToVerseLabels(doc);
|
|
15799
|
+
fixIrregularTables(doc);
|
|
15800
|
+
return { html: doc.body.innerHTML, notes };
|
|
15738
15801
|
}
|
|
15739
15802
|
|
|
15740
15803
|
// src/components/icons/footnote.tsx
|
|
@@ -15795,20 +15858,23 @@ var VerseFootnoteButton = (0, import_react3.memo)(function VerseFootnoteButton2(
|
|
|
15795
15858
|
dangerouslySetInnerHTML: { __html: verseNotes.verseHtml }
|
|
15796
15859
|
}
|
|
15797
15860
|
),
|
|
15798
|
-
/* @__PURE__ */ (0, import_jsx_runtime20.jsx)("ul", { className: "yv:list-none yv:p-0 yv:m-0 yv:space-y-1", children: verseNotes.notes.map((note, index) =>
|
|
15799
|
-
|
|
15800
|
-
|
|
15801
|
-
|
|
15802
|
-
|
|
15803
|
-
|
|
15804
|
-
|
|
15805
|
-
|
|
15806
|
-
|
|
15807
|
-
|
|
15808
|
-
|
|
15809
|
-
|
|
15810
|
-
|
|
15811
|
-
|
|
15861
|
+
/* @__PURE__ */ (0, import_jsx_runtime20.jsx)("ul", { className: "yv:list-none yv:p-0 yv:m-0 yv:space-y-1", children: verseNotes.notes.map((note, index) => {
|
|
15862
|
+
const marker = getFootnoteMarker(index);
|
|
15863
|
+
return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(
|
|
15864
|
+
"li",
|
|
15865
|
+
{
|
|
15866
|
+
className: "yv:flex yv:gap-2 yv:text-xs yv:border-b yv:border-border yv:py-2",
|
|
15867
|
+
children: [
|
|
15868
|
+
/* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("span", { className: "", children: [
|
|
15869
|
+
marker,
|
|
15870
|
+
"."
|
|
15871
|
+
] }),
|
|
15872
|
+
/* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { dangerouslySetInnerHTML: { __html: note } })
|
|
15873
|
+
]
|
|
15874
|
+
},
|
|
15875
|
+
marker
|
|
15876
|
+
);
|
|
15877
|
+
}) })
|
|
15812
15878
|
] })
|
|
15813
15879
|
}
|
|
15814
15880
|
)
|
|
@@ -15825,57 +15891,39 @@ function BibleTextHtml({
|
|
|
15825
15891
|
highlightedVerses = {}
|
|
15826
15892
|
}) {
|
|
15827
15893
|
const contentRef = (0, import_react3.useRef)(null);
|
|
15828
|
-
const [placeholders, setPlaceholders] = (0, import_react3.useState)(
|
|
15894
|
+
const [placeholders, setPlaceholders] = (0, import_react3.useState)([]);
|
|
15829
15895
|
const providerTheme = (0, import_platform_react_hooks3.useTheme)();
|
|
15830
15896
|
const currentTheme = theme || providerTheme;
|
|
15831
15897
|
(0, import_react3.useLayoutEffect)(() => {
|
|
15832
15898
|
if (!contentRef.current) return;
|
|
15833
15899
|
contentRef.current.innerHTML = html;
|
|
15834
|
-
const
|
|
15835
|
-
|
|
15836
|
-
|
|
15837
|
-
|
|
15900
|
+
const anchors = contentRef.current.querySelectorAll("[data-verse-footnote]");
|
|
15901
|
+
const result = [];
|
|
15902
|
+
anchors.forEach((el) => {
|
|
15903
|
+
const verseNum = el.getAttribute("data-verse-footnote");
|
|
15904
|
+
if (verseNum) result.push({ verseNum, el });
|
|
15838
15905
|
});
|
|
15839
|
-
setPlaceholders(
|
|
15840
|
-
}, [html
|
|
15906
|
+
setPlaceholders(result);
|
|
15907
|
+
}, [html]);
|
|
15841
15908
|
(0, import_react3.useLayoutEffect)(() => {
|
|
15842
15909
|
if (!contentRef.current) return;
|
|
15843
|
-
|
|
15844
|
-
verseElements.forEach((el) => {
|
|
15910
|
+
contentRef.current.querySelectorAll(".yv-v[v]").forEach((el) => {
|
|
15845
15911
|
const verseNum = parseInt(el.getAttribute("v") || "0", 10);
|
|
15846
|
-
|
|
15847
|
-
|
|
15848
|
-
} else {
|
|
15849
|
-
el.classList.remove("yv-v-selected");
|
|
15850
|
-
}
|
|
15851
|
-
if (highlightedVerses[verseNum]) {
|
|
15852
|
-
el.classList.add("yv-v-highlighted");
|
|
15853
|
-
} else {
|
|
15854
|
-
el.classList.remove("yv-v-highlighted");
|
|
15855
|
-
}
|
|
15912
|
+
el.classList.toggle("yv-v-selected", selectedVerses.includes(verseNum));
|
|
15913
|
+
el.classList.toggle("yv-v-highlighted", !!highlightedVerses[verseNum]);
|
|
15856
15914
|
});
|
|
15857
15915
|
}, [html, selectedVerses, highlightedVerses]);
|
|
15858
|
-
const
|
|
15859
|
-
|
|
15860
|
-
|
|
15861
|
-
const
|
|
15862
|
-
if (!
|
|
15863
|
-
const
|
|
15864
|
-
|
|
15865
|
-
|
|
15866
|
-
if (!verseEl) return;
|
|
15867
|
-
const verseNum = parseInt(verseEl.getAttribute("v") || "0", 10);
|
|
15868
|
-
if (verseNum === 0) return;
|
|
15869
|
-
const current = selectedVersesRef.current;
|
|
15870
|
-
const newSelected = current.includes(verseNum) ? current.filter((v) => v !== verseNum) : [...current, verseNum].sort((a, b) => a - b);
|
|
15871
|
-
onVerseSelect(newSelected);
|
|
15872
|
-
};
|
|
15873
|
-
element.addEventListener("click", handleClick);
|
|
15874
|
-
return () => element.removeEventListener("click", handleClick);
|
|
15875
|
-
}, [onVerseSelect]);
|
|
15916
|
+
const handleClick = onVerseSelect ? (e) => {
|
|
15917
|
+
const verseEl = e.target.closest(".yv-v[v]");
|
|
15918
|
+
if (!verseEl) return;
|
|
15919
|
+
const verseNum = parseInt(verseEl.getAttribute("v") || "0", 10);
|
|
15920
|
+
if (!verseNum) return;
|
|
15921
|
+
const newSelected = selectedVerses.includes(verseNum) ? selectedVerses.filter((v) => v !== verseNum) : [...selectedVerses, verseNum].sort((a, b) => a - b);
|
|
15922
|
+
onVerseSelect(newSelected);
|
|
15923
|
+
} : void 0;
|
|
15876
15924
|
return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(import_jsx_runtime20.Fragment, { children: [
|
|
15877
|
-
/* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { ref: contentRef }),
|
|
15878
|
-
|
|
15925
|
+
/* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { ref: contentRef, onClick: handleClick }),
|
|
15926
|
+
placeholders.map(({ verseNum, el }, index) => {
|
|
15879
15927
|
const verseNotes = notes[verseNum];
|
|
15880
15928
|
if (!verseNotes) return null;
|
|
15881
15929
|
return (0, import_react_dom.createPortal)(
|
|
@@ -15889,68 +15937,12 @@ function BibleTextHtml({
|
|
|
15889
15937
|
theme: currentTheme
|
|
15890
15938
|
}
|
|
15891
15939
|
),
|
|
15892
|
-
el
|
|
15940
|
+
el,
|
|
15941
|
+
`${verseNum}-${index}`
|
|
15893
15942
|
);
|
|
15894
15943
|
})
|
|
15895
15944
|
] });
|
|
15896
15945
|
}
|
|
15897
|
-
var DOMPURIFY_CONFIG = {
|
|
15898
|
-
ALLOWED_ATTR: ["class", "style", "id", "v", "usfm"],
|
|
15899
|
-
ALLOW_DATA_ATTR: true
|
|
15900
|
-
};
|
|
15901
|
-
function yvDomTransformer(html, extractNotes = false) {
|
|
15902
|
-
if (typeof window === "undefined" || !("DOMParser" in window)) {
|
|
15903
|
-
return { html, notes: {} };
|
|
15904
|
-
}
|
|
15905
|
-
const doc = new DOMParser().parseFromString(
|
|
15906
|
-
import_isomorphic_dompurify.default.sanitize(html, DOMPURIFY_CONFIG),
|
|
15907
|
-
"text/html"
|
|
15908
|
-
);
|
|
15909
|
-
wrapVerseContent(doc);
|
|
15910
|
-
const extractedNotes = extractNotes ? extractNotesFromWrappedHtml(doc) : {};
|
|
15911
|
-
const verseLabels = doc.querySelectorAll(".yv-vlbl");
|
|
15912
|
-
verseLabels.forEach((label) => {
|
|
15913
|
-
const text = label.textContent || "";
|
|
15914
|
-
if (!text.endsWith(NON_BREAKING_SPACE)) {
|
|
15915
|
-
label.textContent = text + NON_BREAKING_SPACE;
|
|
15916
|
-
}
|
|
15917
|
-
});
|
|
15918
|
-
const tables = doc.querySelectorAll("table");
|
|
15919
|
-
tables.forEach((table) => {
|
|
15920
|
-
const rows = table.querySelectorAll("tr");
|
|
15921
|
-
if (rows.length === 0) return;
|
|
15922
|
-
let maxColumns = 0;
|
|
15923
|
-
rows.forEach((row) => {
|
|
15924
|
-
const cells = row.querySelectorAll("td, th");
|
|
15925
|
-
let rowColumnCount = 0;
|
|
15926
|
-
cells.forEach((cell) => {
|
|
15927
|
-
if (cell instanceof HTMLTableCellElement) {
|
|
15928
|
-
const colspan = parseInt(cell.getAttribute("colspan") || "1", 10);
|
|
15929
|
-
rowColumnCount += colspan;
|
|
15930
|
-
} else {
|
|
15931
|
-
rowColumnCount += 1;
|
|
15932
|
-
}
|
|
15933
|
-
});
|
|
15934
|
-
maxColumns = Math.max(maxColumns, rowColumnCount);
|
|
15935
|
-
});
|
|
15936
|
-
if (maxColumns > 1) {
|
|
15937
|
-
rows.forEach((row) => {
|
|
15938
|
-
const cells = row.querySelectorAll("td, th");
|
|
15939
|
-
if (cells.length === 1) {
|
|
15940
|
-
const cell = cells[0];
|
|
15941
|
-
if (cell instanceof HTMLTableCellElement) {
|
|
15942
|
-
const existingColspan = parseInt(cell.getAttribute("colspan") || "1", 10);
|
|
15943
|
-
if (existingColspan < maxColumns) {
|
|
15944
|
-
cell.setAttribute("colspan", maxColumns.toString());
|
|
15945
|
-
}
|
|
15946
|
-
}
|
|
15947
|
-
}
|
|
15948
|
-
});
|
|
15949
|
-
}
|
|
15950
|
-
});
|
|
15951
|
-
const modifiedHtml = doc.body.innerHTML;
|
|
15952
|
-
return { html: modifiedHtml, notes: extractedNotes };
|
|
15953
|
-
}
|
|
15954
15946
|
var Verse = {
|
|
15955
15947
|
/**
|
|
15956
15948
|
* Renders a single verse with superscript number and text.
|
|
@@ -15991,12 +15983,9 @@ var Verse = {
|
|
|
15991
15983
|
onVerseSelect,
|
|
15992
15984
|
highlightedVerses
|
|
15993
15985
|
}, ref) => {
|
|
15994
|
-
const
|
|
15986
|
+
const transformedData = (0, import_react3.useMemo)(() => transformBibleHtml(html), [html]);
|
|
15995
15987
|
const providerTheme = (0, import_platform_react_hooks3.useTheme)();
|
|
15996
15988
|
const currentTheme = theme || providerTheme;
|
|
15997
|
-
(0, import_react3.useEffect)(() => {
|
|
15998
|
-
setTransformedData(yvDomTransformer(html, true));
|
|
15999
|
-
}, [html]);
|
|
16000
15989
|
return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
|
|
16001
15990
|
"section",
|
|
16002
15991
|
{
|
|
@@ -16260,7 +16249,7 @@ function Content5() {
|
|
|
16260
16249
|
function UserMenu() {
|
|
16261
16250
|
const { auth, signIn, signOut, userInfo } = (0, import_platform_react_hooks4.useYVAuth)();
|
|
16262
16251
|
return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(Popover, { children: [
|
|
16263
|
-
/* @__PURE__ */ (0, import_jsx_runtime21.jsx)(PopoverTrigger, { "data-testid": "user-menu-trigger", children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(Button, { size: "icon", variant: "secondary", children: auth.isAuthenticated && userInfo?.avatarUrlFormat ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
|
|
16252
|
+
/* @__PURE__ */ (0, import_jsx_runtime21.jsx)(PopoverTrigger, { asChild: true, "data-testid": "user-menu-trigger", children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(Button, { size: "icon", variant: "secondary", children: auth.isAuthenticated && userInfo?.avatarUrlFormat ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
|
|
16264
16253
|
"img",
|
|
16265
16254
|
{
|
|
16266
16255
|
src: userInfo.getAvatarUrl(32, 32)?.toString(),
|
|
@@ -16353,7 +16342,7 @@ function Toolbar({ border = "top" }) {
|
|
|
16353
16342
|
)
|
|
16354
16343
|
] }),
|
|
16355
16344
|
/* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(Popover, { children: [
|
|
16356
|
-
/* @__PURE__ */ (0, import_jsx_runtime21.jsx)(PopoverTrigger, { "aria-label": "Settings", children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(Button, { size: "icon", variant: "secondary", children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(GearIcon, { className: "yv:text-foreground" }) }) }),
|
|
16345
|
+
/* @__PURE__ */ (0, import_jsx_runtime21.jsx)(PopoverTrigger, { asChild: true, "aria-label": "Settings", children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(Button, { size: "icon", variant: "secondary", children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(GearIcon, { className: "yv:text-foreground" }) }) }),
|
|
16357
16346
|
/* @__PURE__ */ (0, import_jsx_runtime21.jsx)(PopoverContent, { sideOffset: 16, heading: "Reader Settings", theme: background, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { className: "yv:flex yv:flex-col yv:gap-4 yv:p-4", children: [
|
|
16358
16347
|
/* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { className: "yv:grid yv:grid-cols-2", children: [
|
|
16359
16348
|
/* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
|
package/dist/index.js
CHANGED
|
@@ -14726,9 +14726,9 @@ import {
|
|
|
14726
14726
|
import {
|
|
14727
14727
|
createContext as createContext3,
|
|
14728
14728
|
useContext as useContext3,
|
|
14729
|
-
useEffect as
|
|
14729
|
+
useEffect as useEffect3,
|
|
14730
14730
|
useLayoutEffect as useLayoutEffect2,
|
|
14731
|
-
useMemo as
|
|
14731
|
+
useMemo as useMemo4,
|
|
14732
14732
|
useState as useState4
|
|
14733
14733
|
} from "react";
|
|
14734
14734
|
|
|
@@ -15551,11 +15551,10 @@ function PersonIcon(props) {
|
|
|
15551
15551
|
|
|
15552
15552
|
// src/components/verse.tsx
|
|
15553
15553
|
import { usePassage, useTheme as useTheme3 } from "@youversion/platform-react-hooks";
|
|
15554
|
-
import DOMPurify from "isomorphic-dompurify";
|
|
15555
15554
|
import {
|
|
15556
15555
|
forwardRef as forwardRef2,
|
|
15557
15556
|
memo,
|
|
15558
|
-
|
|
15557
|
+
useMemo as useMemo3,
|
|
15559
15558
|
useLayoutEffect,
|
|
15560
15559
|
useRef as useRef3,
|
|
15561
15560
|
useState as useState3
|
|
@@ -15563,8 +15562,20 @@ import {
|
|
|
15563
15562
|
import { createPortal } from "react-dom";
|
|
15564
15563
|
|
|
15565
15564
|
// src/lib/verse-html-utils.ts
|
|
15565
|
+
import DOMPurify from "isomorphic-dompurify";
|
|
15566
15566
|
var NON_BREAKING_SPACE = "\xA0";
|
|
15567
15567
|
var LETTERS = "abcdefghijklmnopqrstuvwxyz";
|
|
15568
|
+
function getFootnoteMarker(index) {
|
|
15569
|
+
const base = LETTERS.length;
|
|
15570
|
+
if (base === 0) return String(index + 1);
|
|
15571
|
+
let value = index;
|
|
15572
|
+
let marker = "";
|
|
15573
|
+
do {
|
|
15574
|
+
marker = LETTERS[value % base] + marker;
|
|
15575
|
+
value = Math.floor(value / base) - 1;
|
|
15576
|
+
} while (value >= 0);
|
|
15577
|
+
return marker;
|
|
15578
|
+
}
|
|
15568
15579
|
var INTER_FONT = '"Inter", sans-serif';
|
|
15569
15580
|
var SOURCE_SERIF_FONT = '"Source Serif 4", serif';
|
|
15570
15581
|
function wrapVerseContent(doc) {
|
|
@@ -15657,73 +15668,125 @@ function wrapVerseContent(doc) {
|
|
|
15657
15668
|
const verseMarkers = Array.from(doc.querySelectorAll(".yv-v[v]"));
|
|
15658
15669
|
verseMarkers.forEach(processVerseMarker);
|
|
15659
15670
|
}
|
|
15671
|
+
var NEEDS_SPACE_BEFORE = /^[^\s.,;:!?)}\]'"»›]/;
|
|
15672
|
+
function buildVerseHtml(wrappers) {
|
|
15673
|
+
const parts = [];
|
|
15674
|
+
let noteIdx = 0;
|
|
15675
|
+
for (let i = 0; i < wrappers.length; i++) {
|
|
15676
|
+
if (i > 0) parts.push(" ");
|
|
15677
|
+
const clone2 = wrappers[i].cloneNode(true);
|
|
15678
|
+
const ownerDoc = wrappers[i].ownerDocument;
|
|
15679
|
+
clone2.querySelectorAll(".yv-h, .yv-vlbl").forEach((el) => el.remove());
|
|
15680
|
+
clone2.querySelectorAll(".yv-n.f").forEach((fn) => {
|
|
15681
|
+
const marker = ownerDoc.createElement("sup");
|
|
15682
|
+
marker.className = "yv:text-muted-foreground";
|
|
15683
|
+
marker.textContent = getFootnoteMarker(noteIdx++);
|
|
15684
|
+
fn.replaceWith(marker);
|
|
15685
|
+
});
|
|
15686
|
+
parts.push(clone2.innerHTML);
|
|
15687
|
+
}
|
|
15688
|
+
return parts.join("");
|
|
15689
|
+
}
|
|
15690
|
+
function replaceFootnotesWithAnchors(doc, footnotes) {
|
|
15691
|
+
for (const fn of footnotes) {
|
|
15692
|
+
const verseNum = fn.closest(".yv-v[v]")?.getAttribute("v");
|
|
15693
|
+
if (!verseNum) continue;
|
|
15694
|
+
const prev = fn.previousSibling;
|
|
15695
|
+
const next = fn.nextSibling;
|
|
15696
|
+
const prevText = prev?.textContent ?? "";
|
|
15697
|
+
const nextText = next?.textContent ?? "";
|
|
15698
|
+
const prevNeedsSpace = prevText.length > 0 && !/\s$/.test(prevText);
|
|
15699
|
+
const nextNeedsSpace = nextText.length > 0 && NEEDS_SPACE_BEFORE.test(nextText);
|
|
15700
|
+
if (prevNeedsSpace && nextNeedsSpace && fn.parentNode) {
|
|
15701
|
+
fn.parentNode.insertBefore(doc.createTextNode(" "), fn);
|
|
15702
|
+
}
|
|
15703
|
+
const anchor = doc.createElement("span");
|
|
15704
|
+
anchor.setAttribute("data-verse-footnote", verseNum);
|
|
15705
|
+
fn.replaceWith(anchor);
|
|
15706
|
+
}
|
|
15707
|
+
}
|
|
15660
15708
|
function extractNotesFromWrappedHtml(doc) {
|
|
15661
15709
|
const footnotes = Array.from(doc.querySelectorAll(".yv-n.f"));
|
|
15662
15710
|
if (!footnotes.length) return {};
|
|
15663
15711
|
const footnotesByVerse = /* @__PURE__ */ new Map();
|
|
15664
|
-
|
|
15712
|
+
for (const fn of footnotes) {
|
|
15665
15713
|
const verseNum = fn.closest(".yv-v[v]")?.getAttribute("v");
|
|
15666
|
-
if (verseNum)
|
|
15667
|
-
|
|
15668
|
-
|
|
15669
|
-
|
|
15670
|
-
|
|
15671
|
-
}
|
|
15672
|
-
arr.push(fn);
|
|
15714
|
+
if (!verseNum) continue;
|
|
15715
|
+
let arr = footnotesByVerse.get(verseNum);
|
|
15716
|
+
if (!arr) {
|
|
15717
|
+
arr = [];
|
|
15718
|
+
footnotesByVerse.set(verseNum, arr);
|
|
15673
15719
|
}
|
|
15720
|
+
arr.push(fn);
|
|
15721
|
+
}
|
|
15722
|
+
const wrappersByVerse = /* @__PURE__ */ new Map();
|
|
15723
|
+
doc.querySelectorAll(".yv-v[v]").forEach((el) => {
|
|
15724
|
+
const verseNum = el.getAttribute("v");
|
|
15725
|
+
if (!verseNum) return;
|
|
15726
|
+
const arr = wrappersByVerse.get(verseNum);
|
|
15727
|
+
if (arr) arr.push(el);
|
|
15728
|
+
else wrappersByVerse.set(verseNum, [el]);
|
|
15674
15729
|
});
|
|
15675
15730
|
const notes = {};
|
|
15676
|
-
const
|
|
15677
|
-
footnotesByVerse.forEach((fns, verseNum) => {
|
|
15678
|
-
const verseWrappers = Array.from(doc.querySelectorAll(`.yv-v[v="${verseNum}"]`));
|
|
15679
|
-
let verseHtml = "";
|
|
15680
|
-
let noteIdx = 0;
|
|
15681
|
-
verseWrappers.forEach((wrapper, wrapperIdx) => {
|
|
15682
|
-
if (wrapperIdx > 0) verseHtml += " ";
|
|
15683
|
-
const walker = doc.createTreeWalker(wrapper, NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_TEXT);
|
|
15684
|
-
let lastWasFootnote = false;
|
|
15685
|
-
while (walker.nextNode()) {
|
|
15686
|
-
const node = walker.currentNode;
|
|
15687
|
-
if (node instanceof Element) {
|
|
15688
|
-
if (node.classList.contains("yv-n") && node.classList.contains("f")) {
|
|
15689
|
-
verseHtml += `<sup class="yv:text-muted-foreground">${LETTERS[noteIdx++] || noteIdx}</sup>`;
|
|
15690
|
-
lastWasFootnote = true;
|
|
15691
|
-
}
|
|
15692
|
-
} else if (node.nodeType === Node.TEXT_NODE) {
|
|
15693
|
-
const parent = node.parentElement;
|
|
15694
|
-
if (parent?.closest(".yv-n.f") || parent?.closest(".yv-h")) continue;
|
|
15695
|
-
if (parent?.classList.contains("yv-vlbl")) continue;
|
|
15696
|
-
let text = node.textContent || "";
|
|
15697
|
-
if (lastWasFootnote && text && NEEDS_SPACE_BEFORE.test(text)) {
|
|
15698
|
-
text = " " + text;
|
|
15699
|
-
}
|
|
15700
|
-
verseHtml += text;
|
|
15701
|
-
lastWasFootnote = false;
|
|
15702
|
-
}
|
|
15703
|
-
}
|
|
15704
|
-
});
|
|
15731
|
+
for (const [verseNum, fns] of footnotesByVerse) {
|
|
15705
15732
|
notes[verseNum] = {
|
|
15706
|
-
verseHtml,
|
|
15733
|
+
verseHtml: buildVerseHtml(wrappersByVerse.get(verseNum) ?? []),
|
|
15707
15734
|
notes: fns.map((fn) => fn.innerHTML)
|
|
15708
15735
|
};
|
|
15709
|
-
|
|
15710
|
-
|
|
15711
|
-
|
|
15712
|
-
|
|
15713
|
-
|
|
15736
|
+
}
|
|
15737
|
+
replaceFootnotesWithAnchors(doc, footnotes);
|
|
15738
|
+
return notes;
|
|
15739
|
+
}
|
|
15740
|
+
function addNbspToVerseLabels(doc) {
|
|
15741
|
+
doc.querySelectorAll(".yv-vlbl").forEach((label) => {
|
|
15742
|
+
const text = label.textContent || "";
|
|
15743
|
+
if (!text.endsWith(NON_BREAKING_SPACE)) {
|
|
15744
|
+
label.textContent = text + NON_BREAKING_SPACE;
|
|
15714
15745
|
}
|
|
15715
15746
|
});
|
|
15716
|
-
|
|
15717
|
-
|
|
15718
|
-
|
|
15719
|
-
const
|
|
15720
|
-
|
|
15721
|
-
|
|
15722
|
-
|
|
15747
|
+
}
|
|
15748
|
+
function fixIrregularTables(doc) {
|
|
15749
|
+
doc.querySelectorAll("table").forEach((table) => {
|
|
15750
|
+
const rows = table.querySelectorAll("tr");
|
|
15751
|
+
if (rows.length === 0) return;
|
|
15752
|
+
let maxColumns = 0;
|
|
15753
|
+
rows.forEach((row) => {
|
|
15754
|
+
let count = 0;
|
|
15755
|
+
row.querySelectorAll("td, th").forEach((cell) => {
|
|
15756
|
+
count += cell instanceof HTMLTableCellElement ? parseInt(cell.getAttribute("colspan") || "1", 10) : 1;
|
|
15757
|
+
});
|
|
15758
|
+
maxColumns = Math.max(maxColumns, count);
|
|
15759
|
+
});
|
|
15760
|
+
if (maxColumns > 1) {
|
|
15761
|
+
rows.forEach((row) => {
|
|
15762
|
+
const cells = row.querySelectorAll("td, th");
|
|
15763
|
+
if (cells.length === 1 && cells[0] instanceof HTMLTableCellElement) {
|
|
15764
|
+
const existing = parseInt(cells[0].getAttribute("colspan") || "1", 10);
|
|
15765
|
+
if (existing < maxColumns) {
|
|
15766
|
+
cells[0].setAttribute("colspan", maxColumns.toString());
|
|
15767
|
+
}
|
|
15768
|
+
}
|
|
15769
|
+
});
|
|
15723
15770
|
}
|
|
15724
|
-
fn.remove();
|
|
15725
15771
|
});
|
|
15726
|
-
|
|
15772
|
+
}
|
|
15773
|
+
var DOMPURIFY_CONFIG = {
|
|
15774
|
+
ALLOWED_ATTR: ["class", "style", "id", "v", "usfm"],
|
|
15775
|
+
ALLOW_DATA_ATTR: true
|
|
15776
|
+
};
|
|
15777
|
+
function transformBibleHtml(html) {
|
|
15778
|
+
if (typeof window === "undefined" || !("DOMParser" in window)) {
|
|
15779
|
+
return { html, notes: {} };
|
|
15780
|
+
}
|
|
15781
|
+
const doc = new DOMParser().parseFromString(
|
|
15782
|
+
DOMPurify.sanitize(html, DOMPURIFY_CONFIG),
|
|
15783
|
+
"text/html"
|
|
15784
|
+
);
|
|
15785
|
+
wrapVerseContent(doc);
|
|
15786
|
+
const notes = extractNotesFromWrappedHtml(doc);
|
|
15787
|
+
addNbspToVerseLabels(doc);
|
|
15788
|
+
fixIrregularTables(doc);
|
|
15789
|
+
return { html: doc.body.innerHTML, notes };
|
|
15727
15790
|
}
|
|
15728
15791
|
|
|
15729
15792
|
// src/components/icons/footnote.tsx
|
|
@@ -15784,20 +15847,23 @@ var VerseFootnoteButton = memo(function VerseFootnoteButton2({
|
|
|
15784
15847
|
dangerouslySetInnerHTML: { __html: verseNotes.verseHtml }
|
|
15785
15848
|
}
|
|
15786
15849
|
),
|
|
15787
|
-
/* @__PURE__ */ jsx20("ul", { className: "yv:list-none yv:p-0 yv:m-0 yv:space-y-1", children: verseNotes.notes.map((note, index) =>
|
|
15788
|
-
|
|
15789
|
-
|
|
15790
|
-
|
|
15791
|
-
|
|
15792
|
-
|
|
15793
|
-
|
|
15794
|
-
"
|
|
15795
|
-
|
|
15796
|
-
|
|
15797
|
-
|
|
15798
|
-
|
|
15799
|
-
|
|
15800
|
-
|
|
15850
|
+
/* @__PURE__ */ jsx20("ul", { className: "yv:list-none yv:p-0 yv:m-0 yv:space-y-1", children: verseNotes.notes.map((note, index) => {
|
|
15851
|
+
const marker = getFootnoteMarker(index);
|
|
15852
|
+
return /* @__PURE__ */ jsxs6(
|
|
15853
|
+
"li",
|
|
15854
|
+
{
|
|
15855
|
+
className: "yv:flex yv:gap-2 yv:text-xs yv:border-b yv:border-border yv:py-2",
|
|
15856
|
+
children: [
|
|
15857
|
+
/* @__PURE__ */ jsxs6("span", { className: "", children: [
|
|
15858
|
+
marker,
|
|
15859
|
+
"."
|
|
15860
|
+
] }),
|
|
15861
|
+
/* @__PURE__ */ jsx20("span", { dangerouslySetInnerHTML: { __html: note } })
|
|
15862
|
+
]
|
|
15863
|
+
},
|
|
15864
|
+
marker
|
|
15865
|
+
);
|
|
15866
|
+
}) })
|
|
15801
15867
|
] })
|
|
15802
15868
|
}
|
|
15803
15869
|
)
|
|
@@ -15814,57 +15880,39 @@ function BibleTextHtml({
|
|
|
15814
15880
|
highlightedVerses = {}
|
|
15815
15881
|
}) {
|
|
15816
15882
|
const contentRef = useRef3(null);
|
|
15817
|
-
const [placeholders, setPlaceholders] = useState3(
|
|
15883
|
+
const [placeholders, setPlaceholders] = useState3([]);
|
|
15818
15884
|
const providerTheme = useTheme3();
|
|
15819
15885
|
const currentTheme = theme || providerTheme;
|
|
15820
15886
|
useLayoutEffect(() => {
|
|
15821
15887
|
if (!contentRef.current) return;
|
|
15822
15888
|
contentRef.current.innerHTML = html;
|
|
15823
|
-
const
|
|
15824
|
-
|
|
15825
|
-
|
|
15826
|
-
|
|
15889
|
+
const anchors = contentRef.current.querySelectorAll("[data-verse-footnote]");
|
|
15890
|
+
const result = [];
|
|
15891
|
+
anchors.forEach((el) => {
|
|
15892
|
+
const verseNum = el.getAttribute("data-verse-footnote");
|
|
15893
|
+
if (verseNum) result.push({ verseNum, el });
|
|
15827
15894
|
});
|
|
15828
|
-
setPlaceholders(
|
|
15829
|
-
}, [html
|
|
15895
|
+
setPlaceholders(result);
|
|
15896
|
+
}, [html]);
|
|
15830
15897
|
useLayoutEffect(() => {
|
|
15831
15898
|
if (!contentRef.current) return;
|
|
15832
|
-
|
|
15833
|
-
verseElements.forEach((el) => {
|
|
15899
|
+
contentRef.current.querySelectorAll(".yv-v[v]").forEach((el) => {
|
|
15834
15900
|
const verseNum = parseInt(el.getAttribute("v") || "0", 10);
|
|
15835
|
-
|
|
15836
|
-
|
|
15837
|
-
} else {
|
|
15838
|
-
el.classList.remove("yv-v-selected");
|
|
15839
|
-
}
|
|
15840
|
-
if (highlightedVerses[verseNum]) {
|
|
15841
|
-
el.classList.add("yv-v-highlighted");
|
|
15842
|
-
} else {
|
|
15843
|
-
el.classList.remove("yv-v-highlighted");
|
|
15844
|
-
}
|
|
15901
|
+
el.classList.toggle("yv-v-selected", selectedVerses.includes(verseNum));
|
|
15902
|
+
el.classList.toggle("yv-v-highlighted", !!highlightedVerses[verseNum]);
|
|
15845
15903
|
});
|
|
15846
15904
|
}, [html, selectedVerses, highlightedVerses]);
|
|
15847
|
-
const
|
|
15848
|
-
|
|
15849
|
-
|
|
15850
|
-
const
|
|
15851
|
-
if (!
|
|
15852
|
-
const
|
|
15853
|
-
|
|
15854
|
-
|
|
15855
|
-
if (!verseEl) return;
|
|
15856
|
-
const verseNum = parseInt(verseEl.getAttribute("v") || "0", 10);
|
|
15857
|
-
if (verseNum === 0) return;
|
|
15858
|
-
const current = selectedVersesRef.current;
|
|
15859
|
-
const newSelected = current.includes(verseNum) ? current.filter((v) => v !== verseNum) : [...current, verseNum].sort((a, b) => a - b);
|
|
15860
|
-
onVerseSelect(newSelected);
|
|
15861
|
-
};
|
|
15862
|
-
element.addEventListener("click", handleClick);
|
|
15863
|
-
return () => element.removeEventListener("click", handleClick);
|
|
15864
|
-
}, [onVerseSelect]);
|
|
15905
|
+
const handleClick = onVerseSelect ? (e) => {
|
|
15906
|
+
const verseEl = e.target.closest(".yv-v[v]");
|
|
15907
|
+
if (!verseEl) return;
|
|
15908
|
+
const verseNum = parseInt(verseEl.getAttribute("v") || "0", 10);
|
|
15909
|
+
if (!verseNum) return;
|
|
15910
|
+
const newSelected = selectedVerses.includes(verseNum) ? selectedVerses.filter((v) => v !== verseNum) : [...selectedVerses, verseNum].sort((a, b) => a - b);
|
|
15911
|
+
onVerseSelect(newSelected);
|
|
15912
|
+
} : void 0;
|
|
15865
15913
|
return /* @__PURE__ */ jsxs6(Fragment2, { children: [
|
|
15866
|
-
/* @__PURE__ */ jsx20("div", { ref: contentRef }),
|
|
15867
|
-
|
|
15914
|
+
/* @__PURE__ */ jsx20("div", { ref: contentRef, onClick: handleClick }),
|
|
15915
|
+
placeholders.map(({ verseNum, el }, index) => {
|
|
15868
15916
|
const verseNotes = notes[verseNum];
|
|
15869
15917
|
if (!verseNotes) return null;
|
|
15870
15918
|
return createPortal(
|
|
@@ -15878,68 +15926,12 @@ function BibleTextHtml({
|
|
|
15878
15926
|
theme: currentTheme
|
|
15879
15927
|
}
|
|
15880
15928
|
),
|
|
15881
|
-
el
|
|
15929
|
+
el,
|
|
15930
|
+
`${verseNum}-${index}`
|
|
15882
15931
|
);
|
|
15883
15932
|
})
|
|
15884
15933
|
] });
|
|
15885
15934
|
}
|
|
15886
|
-
var DOMPURIFY_CONFIG = {
|
|
15887
|
-
ALLOWED_ATTR: ["class", "style", "id", "v", "usfm"],
|
|
15888
|
-
ALLOW_DATA_ATTR: true
|
|
15889
|
-
};
|
|
15890
|
-
function yvDomTransformer(html, extractNotes = false) {
|
|
15891
|
-
if (typeof window === "undefined" || !("DOMParser" in window)) {
|
|
15892
|
-
return { html, notes: {} };
|
|
15893
|
-
}
|
|
15894
|
-
const doc = new DOMParser().parseFromString(
|
|
15895
|
-
DOMPurify.sanitize(html, DOMPURIFY_CONFIG),
|
|
15896
|
-
"text/html"
|
|
15897
|
-
);
|
|
15898
|
-
wrapVerseContent(doc);
|
|
15899
|
-
const extractedNotes = extractNotes ? extractNotesFromWrappedHtml(doc) : {};
|
|
15900
|
-
const verseLabels = doc.querySelectorAll(".yv-vlbl");
|
|
15901
|
-
verseLabels.forEach((label) => {
|
|
15902
|
-
const text = label.textContent || "";
|
|
15903
|
-
if (!text.endsWith(NON_BREAKING_SPACE)) {
|
|
15904
|
-
label.textContent = text + NON_BREAKING_SPACE;
|
|
15905
|
-
}
|
|
15906
|
-
});
|
|
15907
|
-
const tables = doc.querySelectorAll("table");
|
|
15908
|
-
tables.forEach((table) => {
|
|
15909
|
-
const rows = table.querySelectorAll("tr");
|
|
15910
|
-
if (rows.length === 0) return;
|
|
15911
|
-
let maxColumns = 0;
|
|
15912
|
-
rows.forEach((row) => {
|
|
15913
|
-
const cells = row.querySelectorAll("td, th");
|
|
15914
|
-
let rowColumnCount = 0;
|
|
15915
|
-
cells.forEach((cell) => {
|
|
15916
|
-
if (cell instanceof HTMLTableCellElement) {
|
|
15917
|
-
const colspan = parseInt(cell.getAttribute("colspan") || "1", 10);
|
|
15918
|
-
rowColumnCount += colspan;
|
|
15919
|
-
} else {
|
|
15920
|
-
rowColumnCount += 1;
|
|
15921
|
-
}
|
|
15922
|
-
});
|
|
15923
|
-
maxColumns = Math.max(maxColumns, rowColumnCount);
|
|
15924
|
-
});
|
|
15925
|
-
if (maxColumns > 1) {
|
|
15926
|
-
rows.forEach((row) => {
|
|
15927
|
-
const cells = row.querySelectorAll("td, th");
|
|
15928
|
-
if (cells.length === 1) {
|
|
15929
|
-
const cell = cells[0];
|
|
15930
|
-
if (cell instanceof HTMLTableCellElement) {
|
|
15931
|
-
const existingColspan = parseInt(cell.getAttribute("colspan") || "1", 10);
|
|
15932
|
-
if (existingColspan < maxColumns) {
|
|
15933
|
-
cell.setAttribute("colspan", maxColumns.toString());
|
|
15934
|
-
}
|
|
15935
|
-
}
|
|
15936
|
-
}
|
|
15937
|
-
});
|
|
15938
|
-
}
|
|
15939
|
-
});
|
|
15940
|
-
const modifiedHtml = doc.body.innerHTML;
|
|
15941
|
-
return { html: modifiedHtml, notes: extractedNotes };
|
|
15942
|
-
}
|
|
15943
15935
|
var Verse = {
|
|
15944
15936
|
/**
|
|
15945
15937
|
* Renders a single verse with superscript number and text.
|
|
@@ -15980,12 +15972,9 @@ var Verse = {
|
|
|
15980
15972
|
onVerseSelect,
|
|
15981
15973
|
highlightedVerses
|
|
15982
15974
|
}, ref) => {
|
|
15983
|
-
const
|
|
15975
|
+
const transformedData = useMemo3(() => transformBibleHtml(html), [html]);
|
|
15984
15976
|
const providerTheme = useTheme3();
|
|
15985
15977
|
const currentTheme = theme || providerTheme;
|
|
15986
|
-
useEffect3(() => {
|
|
15987
|
-
setTransformedData(yvDomTransformer(html, true));
|
|
15988
|
-
}, [html]);
|
|
15989
15978
|
return /* @__PURE__ */ jsx20(
|
|
15990
15979
|
"section",
|
|
15991
15980
|
{
|
|
@@ -16145,10 +16134,10 @@ function Root6({
|
|
|
16145
16134
|
setCurrentFontFamily(savedFontFamily);
|
|
16146
16135
|
}
|
|
16147
16136
|
}, []);
|
|
16148
|
-
|
|
16137
|
+
useEffect3(() => {
|
|
16149
16138
|
localStorage.setItem("youversion-platform:reader:font-size", currentFontSize.toString());
|
|
16150
16139
|
}, [currentFontSize]);
|
|
16151
|
-
|
|
16140
|
+
useEffect3(() => {
|
|
16152
16141
|
localStorage.setItem("youversion-platform:reader:font-family", currentFontFamily);
|
|
16153
16142
|
}, [currentFontFamily]);
|
|
16154
16143
|
const providerTheme = useTheme4();
|
|
@@ -16191,7 +16180,7 @@ function Content5() {
|
|
|
16191
16180
|
} = useBibleReaderContext();
|
|
16192
16181
|
const { books } = useBooks2(versionId);
|
|
16193
16182
|
const { version: version2 } = useVersion2(versionId);
|
|
16194
|
-
const bookData =
|
|
16183
|
+
const bookData = useMemo4(() => {
|
|
16195
16184
|
return books?.data?.find((b) => b.id === book);
|
|
16196
16185
|
}, [books?.data, book]);
|
|
16197
16186
|
const usfmReference = `${book}.${chapter}`;
|
|
@@ -16249,7 +16238,7 @@ function Content5() {
|
|
|
16249
16238
|
function UserMenu() {
|
|
16250
16239
|
const { auth, signIn, signOut, userInfo } = useYVAuth();
|
|
16251
16240
|
return /* @__PURE__ */ jsxs7(Popover, { children: [
|
|
16252
|
-
/* @__PURE__ */ jsx21(PopoverTrigger, { "data-testid": "user-menu-trigger", children: /* @__PURE__ */ jsx21(Button, { size: "icon", variant: "secondary", children: auth.isAuthenticated && userInfo?.avatarUrlFormat ? /* @__PURE__ */ jsx21(
|
|
16241
|
+
/* @__PURE__ */ jsx21(PopoverTrigger, { asChild: true, "data-testid": "user-menu-trigger", children: /* @__PURE__ */ jsx21(Button, { size: "icon", variant: "secondary", children: auth.isAuthenticated && userInfo?.avatarUrlFormat ? /* @__PURE__ */ jsx21(
|
|
16253
16242
|
"img",
|
|
16254
16243
|
{
|
|
16255
16244
|
src: userInfo.getAvatarUrl(32, 32)?.toString(),
|
|
@@ -16342,7 +16331,7 @@ function Toolbar({ border = "top" }) {
|
|
|
16342
16331
|
)
|
|
16343
16332
|
] }),
|
|
16344
16333
|
/* @__PURE__ */ jsxs7(Popover, { children: [
|
|
16345
|
-
/* @__PURE__ */ jsx21(PopoverTrigger, { "aria-label": "Settings", children: /* @__PURE__ */ jsx21(Button, { size: "icon", variant: "secondary", children: /* @__PURE__ */ jsx21(GearIcon, { className: "yv:text-foreground" }) }) }),
|
|
16334
|
+
/* @__PURE__ */ jsx21(PopoverTrigger, { asChild: true, "aria-label": "Settings", children: /* @__PURE__ */ jsx21(Button, { size: "icon", variant: "secondary", children: /* @__PURE__ */ jsx21(GearIcon, { className: "yv:text-foreground" }) }) }),
|
|
16346
16335
|
/* @__PURE__ */ jsx21(PopoverContent, { sideOffset: 16, heading: "Reader Settings", theme: background, children: /* @__PURE__ */ jsxs7("div", { className: "yv:flex yv:flex-col yv:gap-4 yv:p-4", children: [
|
|
16347
16336
|
/* @__PURE__ */ jsxs7("div", { className: "yv:grid yv:grid-cols-2", children: [
|
|
16348
16337
|
/* @__PURE__ */ jsx21(
|
|
@@ -16433,7 +16422,7 @@ function Toolbar({ border = "top" }) {
|
|
|
16433
16422
|
var BibleReader = Object.assign({}, { Root: Root6, Content: Content5, Toolbar });
|
|
16434
16423
|
|
|
16435
16424
|
// src/components/YouVersionAuthButton.tsx
|
|
16436
|
-
import React10, { useMemo as
|
|
16425
|
+
import React10, { useMemo as useMemo5 } from "react";
|
|
16437
16426
|
|
|
16438
16427
|
// src/components/icons/loader.tsx
|
|
16439
16428
|
import { jsx as jsx22 } from "react/jsx-runtime";
|
|
@@ -16595,7 +16584,7 @@ var YouVersionAuthButton = React10.forwardRef(
|
|
|
16595
16584
|
}
|
|
16596
16585
|
};
|
|
16597
16586
|
const buttonLoading = auth.isLoading;
|
|
16598
|
-
const buttonText =
|
|
16587
|
+
const buttonText = useMemo5(() => {
|
|
16599
16588
|
if (text) return text;
|
|
16600
16589
|
const isSignOut = mode === "signOut" || mode === "auto" && auth.isAuthenticated;
|
|
16601
16590
|
if (size === "short") {
|
|
@@ -1,5 +1,13 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Converts a 0-based footnote index into an alphabetic marker.
|
|
3
|
+
*
|
|
4
|
+
* Examples with LETTERS = "abcdefghijklmnopqrstuvwxyz":
|
|
5
|
+
* 0 -> "a", 25 -> "z", 26 -> "aa", 27 -> "ab"
|
|
6
|
+
*
|
|
7
|
+
* This uses spreadsheet-style indexing and derives its base from
|
|
8
|
+
* LETTERS.length so there are no hardcoded numeric assumptions.
|
|
9
|
+
*/
|
|
10
|
+
export declare function getFootnoteMarker(index: number): string;
|
|
3
11
|
export type VerseNotes = {
|
|
4
12
|
verseHtml: string;
|
|
5
13
|
notes: string[];
|
|
@@ -8,24 +16,16 @@ export declare const INTER_FONT: "\"Inter\", sans-serif";
|
|
|
8
16
|
export declare const SOURCE_SERIF_FONT: "\"Source Serif 4\", serif";
|
|
9
17
|
export type FontFamily = typeof INTER_FONT | typeof SOURCE_SERIF_FONT | (string & {});
|
|
10
18
|
/**
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* Transforms empty verse markers into wrapping containers. When a verse spans
|
|
14
|
-
* multiple paragraphs, creates duplicate wrappers in each paragraph (Bible.com pattern).
|
|
15
|
-
*
|
|
16
|
-
* Before: <span class="yv-v" v="1"></span><span class="yv-vlbl">1</span>Text...
|
|
17
|
-
* After: <span class="yv-v" v="1"><span class="yv-vlbl">1</span>Text...</span>
|
|
19
|
+
* Full transformation pipeline for Bible HTML from the API.
|
|
18
20
|
*
|
|
19
|
-
*
|
|
21
|
+
* 1. Sanitize (DOMPurify)
|
|
22
|
+
* 2. Wrap verse content in selectable spans
|
|
23
|
+
* 3. Extract footnotes and replace with portal anchors
|
|
24
|
+
* 4. Add non-breaking spaces to verse labels
|
|
25
|
+
* 5. Fix irregular table layouts
|
|
20
26
|
*/
|
|
21
|
-
export declare function
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
* This function assumes verses are already wrapped in `.yv-v[v]` elements (by wrapVerseContent).
|
|
26
|
-
* It uses `.closest('.yv-v[v]')` to find which verse each footnote belongs to.
|
|
27
|
-
*
|
|
28
|
-
* @returns Notes data for popovers, keyed by verse number
|
|
29
|
-
*/
|
|
30
|
-
export declare function extractNotesFromWrappedHtml(doc: Document): Record<string, VerseNotes>;
|
|
27
|
+
export declare function transformBibleHtml(html: string): {
|
|
28
|
+
html: string;
|
|
29
|
+
notes: Record<string, VerseNotes>;
|
|
30
|
+
};
|
|
31
31
|
//# sourceMappingURL=verse-html-utils.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"verse-html-utils.d.ts","sourceRoot":"","sources":["../../src/lib/verse-html-utils.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"verse-html-utils.d.ts","sourceRoot":"","sources":["../../src/lib/verse-html-utils.ts"],"names":[],"mappings":"AAMA;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAavD;AAED,MAAM,MAAM,UAAU,GAAG;IACvB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB,CAAC;AAEF,eAAO,MAAM,UAAU,EAAG,uBAA8B,CAAC;AACzD,eAAO,MAAM,iBAAiB,EAAG,2BAAkC,CAAC;AACpE,MAAM,MAAM,UAAU,GAAG,OAAO,UAAU,GAAG,OAAO,iBAAiB,GAAG,CAAC,MAAM,GAAG,EAAE,CAAC,CAAC;AA0UtF;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAA;CAAE,CAgBpG"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@youversion/platform-react-ui",
|
|
3
|
-
"version": "1.14.
|
|
3
|
+
"version": "1.14.4",
|
|
4
4
|
"description": "React SDK for YouVersion Platform",
|
|
5
5
|
"license": "TBD",
|
|
6
6
|
"type": "module",
|
|
@@ -38,8 +38,8 @@
|
|
|
38
38
|
"isomorphic-dompurify": "2.23.0",
|
|
39
39
|
"tailwind-merge": "3.3.1",
|
|
40
40
|
"tw-animate-css": "1.4.0",
|
|
41
|
-
"@youversion/platform-core": "1.14.
|
|
42
|
-
"@youversion/platform-react-hooks": "1.14.
|
|
41
|
+
"@youversion/platform-core": "1.14.4",
|
|
42
|
+
"@youversion/platform-react-hooks": "1.14.4"
|
|
43
43
|
},
|
|
44
44
|
"peerDependencies": {
|
|
45
45
|
"react": ">=19.1.0 <20.0.0",
|
|
@@ -76,8 +76,8 @@
|
|
|
76
76
|
"vite": "7.1.11",
|
|
77
77
|
"vitest": "4.0.4",
|
|
78
78
|
"vitest-browser-react": "2.0.2",
|
|
79
|
-
"@internal/
|
|
80
|
-
"@internal/
|
|
79
|
+
"@internal/tsconfig": "0.0.0",
|
|
80
|
+
"@internal/eslint-config": "0.0.0"
|
|
81
81
|
},
|
|
82
82
|
"publishConfig": {
|
|
83
83
|
"access": "public",
|