@@ -29,6 +29,10 @@ type ExtractedNotes = {
2929 notes : Record < string , VerseNotes > ;
3030} ;
3131
32+ /**
33+ * Checks if a node should be excluded from verse text reconstruction.
34+ * Excludes: verse markers (.yv-v), verse labels (.yv-vlbl), headers (.yv-h), and footnotes (.yv-n).
35+ */
3236function isExcludedNode ( node : Node ) : boolean {
3337 if ( ! ( node instanceof Element ) ) return false ;
3438 if ( node . classList . contains ( 'yv-v' ) || node . classList . contains ( 'yv-vlbl' ) ) return true ;
@@ -37,6 +41,20 @@ function isExcludedNode(node: Node): boolean {
3741 return false ;
3842}
3943
44+ /**
45+ * Extracts footnotes from Bible HTML and prepares data for footnote popovers.
46+ *
47+ * This function does three things:
48+ * 1. Identifies verse boundaries using `.yv-v[v]` markers (verses can span multiple paragraphs)
49+ * 2. For each verse with footnotes, builds a plain-text version with A/B/C markers for the popover
50+ * 3. Inserts placeholder spans at the end of each verse (where the footnote icon will render)
51+ *
52+ * The challenge: verses don't respect paragraph boundaries. A verse starts at `.yv-v[v="X"]`
53+ * and ends at the next `.yv-v[v]` marker, potentially spanning multiple `<div class="p">` elements.
54+ * We use a TreeWalker to flatten the DOM into document order, then use index ranges to define verses.
55+ *
56+ * @returns Modified HTML with footnotes removed and placeholders inserted, plus notes data for popovers
57+ */
4058function extractNotesFromHtml ( html : string ) : ExtractedNotes {
4159 if ( typeof window === 'undefined' ) return { html, notes : { } } ;
4260
@@ -47,6 +65,7 @@ function extractNotesFromHtml(html: string): ExtractedNotes {
4765 const verseMarkers = Array . from ( doc . querySelectorAll ( '.yv-v[v]' ) ) ;
4866 if ( ! verseMarkers . length ) return { html : doc . body . innerHTML , notes : { } } ;
4967
68+ // Flatten DOM into document order so we can define verse boundaries by index ranges
5069 const walker = doc . createTreeWalker ( doc . body , NodeFilter . SHOW_ELEMENT | NodeFilter . SHOW_TEXT ) ;
5170 const allNodes : Node [ ] = [ ] ;
5271 do {
@@ -56,6 +75,7 @@ function extractNotesFromHtml(html: string): ExtractedNotes {
5675 const nodeIndex = new Map ( allNodes . map ( ( n , i ) => [ n , i ] ) ) ;
5776 const footnotes = doc . querySelectorAll ( '.yv-n.f' ) ;
5877
78+ // Define verse boundaries: each verse spans from its marker to the next marker (or end of content)
5979 const verses = verseMarkers . map ( ( marker , i ) => {
6080 const nextMarker = verseMarkers [ i + 1 ] ;
6181 return {
@@ -66,6 +86,7 @@ function extractNotesFromHtml(html: string): ExtractedNotes {
6686 } ;
6787 } ) ;
6888
89+ // Assign each footnote to its containing verse (find verse whose range contains the footnote)
6990 footnotes . forEach ( ( fn ) => {
7091 const idx = nodeIndex . get ( fn ) ;
7192 if ( idx !== undefined ) {
@@ -78,6 +99,7 @@ function extractNotesFromHtml(html: string): ExtractedNotes {
7899
79100 const notes : Record < string , VerseNotes > = { } ;
80101 withNotes . forEach ( ( verse ) => {
102+ // Build plain-text verse content for popover, replacing footnotes with A/B/C markers
81103 let text = '' ;
82104 let noteIdx = 0 ;
83105 let lastP : Element | null = null ;
@@ -101,6 +123,7 @@ function extractNotesFromHtml(html: string): ExtractedNotes {
101123 } else if ( node . nodeType === Node . TEXT_NODE && parent ) {
102124 if ( parent . closest ( '.yv-h' ) || parent . closest ( '.yv-n.f' ) ) continue ;
103125 if ( parent . classList . contains ( 'yv-v' ) || parent . classList . contains ( 'yv-vlbl' ) ) continue ;
126+ // Add space when transitioning between paragraphs (verses can span multiple <p> elements)
104127 const curP = parent . closest ( '.p, p, div.p' ) ;
105128 if ( lastP && curP && lastP !== curP ) text += ' ' ;
106129 text += node . textContent || '' ;
@@ -110,6 +133,7 @@ function extractNotesFromHtml(html: string): ExtractedNotes {
110133
111134 notes [ verse . num ] = { verseHtml : text , notes : verse . fns . map ( ( fn ) => fn . innerHTML ) } ;
112135
136+ // Insert placeholder at end of verse content (walk backwards to find last text node)
113137 for ( let i = verse . end - 1 ; i > verse . start ; i -- ) {
114138 const node = allNodes [ i ] ;
115139 if ( ! node ) continue ;
0 commit comments