MediaWiki:Common.js

From Psalms: Layer by Layer
Jump to: navigation, search

Note: After publishing, you may have to bypass your browser's cache to see the changes.

  • Firefox / Safari: Hold Shift while clicking Reload, or press either Ctrl-F5 or Ctrl-R (⌘-R on a Mac)
  • Google Chrome: Press Ctrl-Shift-R (⌘-Shift-R on a Mac)
  • Internet Explorer / Edge: Hold Ctrl while clicking Refresh, or press Ctrl-F5
  • Opera: Press Ctrl-F5.
	/* Any JavaScript here will be loaded for all users on every page load. */
	
	// Function to check if all <pre class="mermaid"> elements are processed
	function checkMermaidProcessed() {
	    var mermaidPreElements = document.querySelectorAll('pre.mermaid');
	    var allProcessed = true;
	
	    mermaidPreElements.forEach(function (element) {
	        if (!element.hasAttribute('data-processed') || element.getAttribute('data-processed') !== 'true') {
	            allProcessed = false;
	        }
	    });
	
	    return allProcessed;
	}
	
	// Function to wait until all Mermaid diagrams are processed
	function waitForMermaidProcessing(callback) {
	    var interval = setInterval(function () {
	        if (checkMermaidProcessed()) {
	            clearInterval(interval);
	            callback(); // Once all elements are processed, run the callback
	        }
	    }, 100); // Check every 100ms
	}
	
	function toggleVisibility(containerId, className) {
	    //console.log("Toggling visibility for " + className + " in " + containerId);
	    var container = document.getElementById(containerId);
	    if (container) {
	    	if (className==="alternative"){
				
	
				// Match all elements with pinkish FILL or STROKE
				var elements = container.querySelectorAll('[fill^="#f4"], [stroke^="#f4"], [fill^="#f6"], [stroke^="#f6"]');
				
				elements.forEach(function(el) {
				    if (!el.hasAttribute('data-original-display')) {
				        el.setAttribute('data-original-display', el.style.display || "");
				    }
				
				    if (el.style.display === "none") {
				        el.style.display = el.getAttribute('data-original-display') || "";
				    } else {
				        el.style.display = "none";
				    }
				});
	
	    	}else{
		        var elements = container.querySelectorAll("g." + className);
		        for (var i = 0; i < elements.length; i++) {
		            var element = elements[i];
		            
		            if (element.style.display === "none") {
		                element.style.display = ""; // Show element
		            } else {
		                element.style.display = "none"; // Hide element
		            }
		        }
	    	}
	    } else {
	        console.warn("Container with ID \"" + containerId + "\" not found.");
	    }
	}
	
	function attachToggleListeners() {
	    //console.log("Attaching event listeners to toggle links.");
	    var toggleLinks = document.querySelectorAll("[data-container-id][data-class]");
	    //console.log("Found " + toggleLinks.length + " links.");
	
	    // If no toggle links are found, print a warning
	    if (toggleLinks.length === 0) {
	        console.warn("No toggle links found on the page.");
	    }
	    
	    for (var i = 0; i < toggleLinks.length; i++) {
	        (function (toggleLink) {
	            toggleLink.addEventListener("click", function (event) {
	                event.preventDefault(); // Prevent default link behavior
	                var containerId = toggleLink.getAttribute("data-container-id");
	                var className = toggleLink.getAttribute("data-class");
	                toggleVisibility(containerId, className);
	            });
	        })(toggleLinks[i]);
	    }
	}
	


	


// ===========================


$(document).ready(function () {

	
	// ===========================
	// Begin placeholder auto-loading
	// ===========================



	var observer = new IntersectionObserver(function(entries) {
	    entries.forEach(function(entry) {
	        if (entry.isIntersecting) {
	            var target = entry.target;
	            if (!target.dataset.loaded) { // Don't double-load
	                loadChunk(target);
	            }
	        }
	    });
	}, {
	    rootMargin: '200px'
	});
	
	document.querySelectorAll('.placeholder').forEach(function(placeholder) {
	    observer.observe(placeholder);
	});
	
	function loadChunk(placeholder) {
	    var id = placeholder.id; // e.g., v-1

		var basePage = mw.config.get('wgPageName').replace(/Test/g, "");
		// Remove prefix (v, vv, verse) followed by any number of ".", "_", "-", or space
		var cleanedId = id.replace(/^(vv|v|verse)[\.\_\-\s]*/i, '');
		var nextPage = basePage + cleanedId;
		console.log('Seeking to load ' + nextPage + ' (from original ' + id + ')');
	
	    var xhr = new XMLHttpRequest();
	    xhr.open('GET', '/w/' + nextPage + '?action=render', true);
	    xhr.onreadystatechange = function () {
			if (xhr.readyState === 4) {
	            if (xhr.status === 200) {
	                placeholder.innerHTML = xhr.responseText;
	                placeholder.dataset.loaded = 'true';

					addHeadingsFromChunkToTOC(placeholder);

	            } else if (xhr.status === 404) {
	                placeholder.innerHTML = '<div class="missing-chunk">This section is not yet available.</div>';
	                placeholder.dataset.loaded = 'true'; // Prevent reloading attempts
	            } else {
	                placeholder.innerHTML = '<div class="load-error">Error loading section (status ' + xhr.status + ').</div>';
	                placeholder.dataset.loaded = 'true';
	            }
	        }

	    };
	    xhr.send();
	}

	function addHeadingsFromChunkToTOC(chunkDiv) {
	    if (!chunkDiv) return;
	
	    var targetText = chunkDiv.id.replace(/_/,' ') + ' (loading...)';
	    var allSpans = document.querySelectorAll('.toctext');
	
	    var parentTocSpan = null;
	    for (var i = 0; i < allSpans.length; i++) {
	        if (allSpans[i].textContent === targetText) {
	            allSpans[i].textContent = chunkDiv.id.replace(/_/,' '); // Remove (loading...)
	            parentTocSpan = allSpans[i];
	            break;
	        }
	    }
	
	    if (!parentTocSpan) {
	        console.warn('Parent TOC span not found for chunk:', chunkDiv.id);
	        return;
	    }


		// Clean up the corresponding H1 heading (remove " (loading...)" from text and ID)
		var originalHeadingId = chunkDiv.id + '_(loading...)';
		var h1span = document.getElementById(originalHeadingId);
		
		if (h1span && h1span.classList.contains('mw-headline')) {
		    var cleanedId = chunkDiv.id;
		
		    // Update ID
		    h1span.id = cleanedId;
		
		    // Update display text
		    h1span.textContent = cleanedId.replace(/_/g, ' ');
		}
		

	
	    var parentTocLi = parentTocSpan.closest('li');
	    if (!parentTocLi) {
	        console.warn('Parent TOC li not found for chunk:', chunkDiv.id);
	        return;
	    }
	
	    // Find or create a sub-UL under this parent LI
	    var subUl = parentTocLi.querySelector('ul');
	    if (!subUl) {
	        subUl = document.createElement('ul');
	        subUl.className = 'toc-sublist'; // optional styling class
	        parentTocLi.appendChild(subUl);
	    }
	
	    // Now find all new headings inside the chunk
	    var newHeadings = chunkDiv.querySelectorAll('h2, h3');
	
	    for (var j = 0; j < newHeadings.length; j++) {
	        var heading = newHeadings[j];
	
	        // Ensure the heading has an id
	        if (!heading.id) {
	            heading.id = 'heading-' + Math.random().toString(36).substr(2, 9);
	        }
	
	        // Create new TOC <li>
	        var li = document.createElement('li');
	        li.className = 'toclevel-' + (heading.tagName === 'H2' ? '1' : '2');
	
	        var a = document.createElement('a');
	        a.href = '#' + heading.id;
	        a.className = 'nav-link';
	
	        var spanNumber = document.createElement('span');
	        spanNumber.className = 'tocnumber';
	        spanNumber.textContent = ''; // optional numbering if needed
	
	        var spanText = document.createElement('span');
	        spanText.className = 'toctext';
	        spanText.textContent = heading.textContent;
	
	        a.appendChild(spanNumber);
	        a.appendChild(spanText);
	        li.appendChild(a);
	        subUl.appendChild(li);
	    }
	}


	
	// ===========================
	// End placeholder auto-loading
	// ===========================

	

	// ===========================
	// Code to build overlays in the first place
	// ===========================
	

	
	// split first column of Hebrew into spans per word and attaches handlers
	if (document.getElementById("createSpans")) {
		document.getElementById("createSpans").onclick = function () {
	
		  var rows = document.querySelectorAll("#buildOverlay tr");
		
		  for (var i = 1; i < rows.length; i++) {
		    var row = rows[i];
		    if (!row.cells || row.cells.length < 3) continue;
		
		    var hebrewCell = row.cells[0];
		    var verseCell = row.cells[1];
		    var englishCell = row.cells[2];
		
		    var verse = verseCell
		      ? verseCell.textContent.replace(/\s+/g, '').trim()
		      : "v" + i;
		
		    if (hebrewCell.getAttribute("data-wrapped") !== "true") {
		      wrapCellWords(hebrewCell, verse, true);  // true = Hebrew
		      hebrewCell.setAttribute("data-wrapped", "true");
		    }
		
		    if (englishCell.getAttribute("data-wrapped") !== "true") {
		      wrapCellWords(englishCell, verse, false); // false = English
		      englishCell.setAttribute("data-wrapped", "true");
		    }
		  }
		};
	}

	function wrapCellWords(cell, verse, isHebrew) {
	  var text = cell.textContent || cell.innerText;
	  var words = text.replace(/\s+/g, ' ').trim().split(' ');
	
	  var lineDiv = document.createElement("div");
	  lineDiv.className = "line";
	  lineDiv.setAttribute("data-line", verse);
	
	  for (var i = 0; i < words.length; i++) {
	    var span = document.createElement("span");
	    var word = words[i];
	
	    if (isHebrew) {
	      var wordID = verse + "-" + (i + 1) + "-1";
	      span.className = "hebrew id-" + wordID;
	      //span.id = wordID;
	      span.textContent = word;
	      attachHebrewSplitHandler(span);
	    } else {
	      span.className = "english";
	      span.textContent = word;
	      span.addEventListener("click", function () {
	        var mode = document.querySelector('input[name="Overlay/Build[Mode]"]:checked');
	        if (!mode || mode.value !== "Align English") return;
	        if (!selectedHebrewID) return;
	
	        // Remove previous Hebrew links (id-*) from this span
	        var classes = this.className.split(" ");
	        var newClasses = [];
	        for (var j = 0; j < classes.length; j++) {
	          if (classes[j].indexOf("id-") !== 0) {
	            newClasses.push(classes[j]);
	          }
	        }
	        newClasses.push("id-" + selectedHebrewID);
	        this.className = newClasses.join(" ");
	      });
	    }
	
	    lineDiv.appendChild(span);
	    lineDiv.appendChild(document.createTextNode(" "));
	  }
	
	  cell.innerHTML = "";
	  cell.appendChild(lineDiv);
	}


	// permit different CSS for different modes
	function updateOverlayModeClass() {
	  var modeInput = document.querySelector('input[name="Overlay/Build[Mode]"]:checked');
	  var overlay = document.getElementById("buildOverlay");
	  if (!modeInput || !overlay) return;
	
	  // Remove existing mode classes
	  overlay.classList.remove("mode-split", "mode-align");
	
	  // Add new mode class
	  if (modeInput.value === "Split Hebrew") {
	    overlay.classList.add("mode-split");
	  } else if (modeInput.value === "Align English") {
	    overlay.classList.add("mode-align");
	  }
	}
	
	document.querySelectorAll('input[name="Overlay/Build[Mode]"]').forEach(function (radio) {
	  radio.addEventListener("change", updateOverlayModeClass);
	});


	// split-span-into-multiple-spans (to be used both at startup and when new spans are created)
	
	var selectedHebrewID = null;
	var collectedSpans = []; // Array of {language, word, wordID, line, index}

	
	function attachHebrewSplitHandler(span) {

	  span.addEventListener("click", function(event) {
	    event.stopPropagation();

		var span = event.target;
	    if (span.tagName !== "SPAN") return;
	
		// Find the class starting with "id-"
		var classes = span.className.split(" ");
		var idClass = null;
		
		for (var i = 0; i < classes.length; i++) {
		  if (classes[i].indexOf("id-") === 0) {
		    idClass = classes[i];
		    break;
		  }
		}
		
		if (!idClass) return; // No ID found, stop processing
		
		// If you still want to extract parts like "2a", "3", etc.:
		var idParts = idClass.slice(3).split("-"); // remove "id-" and split
		var verse = idParts[0]; // e.g., "2a"
		var wordIndex = idParts[1]; // e.g., "3"
		var baseID = verse + "-" + wordIndex;
	

		  // Check if "Split Hebrew" mode is selected
		var selectedMode = document.querySelector('input[name="Overlay/Build[Mode]"]:checked');
		if (selectedMode.value === "Split Hebrew") {
		  // run split logic


 if (event.shiftKey) {
        var baseMatch = idClass.match(/^id-([\w]+-[\w]+)-\d+$/);
        if (baseMatch) {
          mergeSplitSpans(baseMatch[1]);  // e.g., "2a-3"
          return;
        }
      }

		  
		    var range;
		    if (document.caretRangeFromPoint) {
		      range = document.caretRangeFromPoint(event.clientX, event.clientY);
		    } else if (document.caretPositionFromPoint) {
		      var pos = document.caretPositionFromPoint(event.clientX, event.clientY);
		      if (!pos) return;
		      range = document.createRange();
		      range.setStart(pos.offsetNode, pos.offset);
		    } else {
		      return;
		    }
		
		    if (!range || !span.textContent) return;
		
			var classMatch = span.className.match(/id-(\d+[a-z]?)-(\d+)-(\d+)/);
			if (!classMatch) return;
			
			var verse = classMatch[1];      // e.g., "3" or "3a"
			var wordIndex = classMatch[2];  // e.g., "1"
			var letterIndex = parseInt(classMatch[3], 10); // e.g., 1
			
			var baseID = verse + "-" + wordIndex;
			var offset = range.startOffset;
			var text = span.textContent;
			
			if (offset <= 0 || offset >= text.length) return;
			
			var before = text.slice(0, offset);
			var after = text.slice(offset);
			
			// First span keeps same class
			var span1 = document.createElement("span");
			span1.className = "hebrew id-" + baseID + "-" + letterIndex;
			span1.textContent = before;
			span1.style.cursor = "pointer";
			attachHebrewSplitHandler(span1);
			
			// Second span gets incremented letter index
			var newLetterIndex = letterIndex + before.length;
			var span2 = document.createElement("span");
			span2.className = "hebrew id-" + baseID + "-" + newLetterIndex;
			span2.textContent = after;
			span2.style.cursor = "pointer";
			attachHebrewSplitHandler(span2);
			
			// Insert into DOM
			var parent = span.parentNode;
			var spacer = document.createTextNode(" ");
			parent.insertBefore(span1, span);
			parent.insertBefore(spacer, span);
			parent.insertBefore(span2, span);
			parent.removeChild(span);
			

	  

		} else if (selectedMode.value === "Align English") {
		  // Determine if the clicked span is Hebrew or English
		  if (span.classList.contains("hebrew")) {
		    // Hebrew word clicked → highlight it and clear all prior alignments
		
		    // Remove previous Hebrew highlights
		    var allHebrew = document.querySelectorAll("span.hebrew");
		    for (var i = 0; i < allHebrew.length; i++) {
		      allHebrew[i].classList.remove("hebrew-selected");
		    }
		
		    // Hide all previous alignments from English
		    var allEnglish = document.querySelectorAll("span.gloss, span.english");
		    for (var i = 0; i < allEnglish.length; i++) {
		      allEnglish[i].classList.remove("aligned-english");
		
		      /* Remove any id-XXX class
		      var classes = allEnglish[i].className.split(" ");
		      var newClasses = [];
		      for (var j = 0; j < classes.length; j++) {
		        if (classes[j].indexOf("id-") !== 0) {
		          newClasses.push(classes[j]);
		        }
		      }
		      allEnglish[i].className = newClasses.join(" "); */
		    }
		
		    // Set and highlight the selected Hebrew word
		    span.classList.add("hebrew-selected");
		    var idMatch = span.className.match(/id-[\w-]+/);
		    selectedHebrewID = idMatch ? idMatch[0].substring(3) : null;
		
		  } else if (span.classList.contains("gloss") || span.classList.contains("english")) {
		    // English word clicked → align it to selected Hebrew (if any)
		
		    if (!selectedHebrewID) return;
		
		    // Remove existing id-XXX classes on this span
		    var classes = span.className.split(" ");
		    var newClasses = [];
		    for (var j = 0; j < classes.length; j++) {
		      if (classes[j].indexOf("id-") !== 0) {
		        newClasses.push(classes[j]);
		      }
		    }
		
		    // Add the selected Hebrew ID and highlight class
		    newClasses.push("id-" + selectedHebrewID);
		    newClasses.push("aligned-english");
		    span.className = newClasses.join(" ");
		  }
		}
	
	  });
	}
	
	
function mergeSplitSpans(baseID) {
  var all = document.querySelectorAll('span[class*="id-' + baseID + '-"]');
  if (all.length <= 1) return;

  var mergedText = '';
  for (var i = 0; i < all.length; i++) {
    mergedText += all[i].textContent;
  }

  var parent = all[0].parentNode;
  var firstSpan = all[0];

  // Remove all spans except the first
  for (var i = 1; i < all.length; i++) {
    parent.removeChild(all[i]);
  }

  // Rebuild the class name to reflect id-baseID-1
  var newClassName = firstSpan.className
    .split(" ")
    .map(function (cls) {
      return cls.startsWith("id-") ? "id-" + baseID + "-1" : cls;
    })
    .join(" ");

  firstSpan.className = newClassName;
  firstSpan.textContent = mergedText;

  attachHebrewSplitHandler(firstSpan);
}



	document.getElementById("saveSpans").onclick = function () {
	  var allSpans = document.querySelectorAll('#buildOverlay .line span');
	  var output = [];
	
	  for (var i = 0; i < allSpans.length; i++) {
	    var span = allSpans[i];
	    var classes = span.className.split(/\s+/);
	    var idMatch = null;
	    var lang = null;
	
	    for (var j = 0; j < classes.length; j++) {
	      if (classes[j].indexOf('id-') === 0) {
	        idMatch = classes[j].substring(3); // strip off 'id-'
	      }
	      if (classes[j] === 'hebrew') {
	        lang = 'Hebrew';
	      } else if (classes[j] === 'gloss' || classes[j] === 'english') {
	        lang = 'English';
	      }
	    }
	
	    if (!lang || !idMatch) continue;
	
	    var word = span.textContent.replace(/^\s+|\s+$/g, ''); // .trim() in ES5
	    var lineDiv = span.closest('.line');
	    var line = lineDiv ? lineDiv.getAttribute('data-line') : '';
	
	    var siblings = lineDiv ? lineDiv.querySelectorAll('span.' + lang.toLowerCase()) : [];
	    var index = 1;
	    for (var k = 0; k < siblings.length; k++) {
	      if (siblings[k] === span) {
	        index = k + 1;
	        break;
	      }
	    }
	
	    output.push('{{Overlay/Span|Language=' + lang + '|WordID=' + idMatch + '|Word=' + word + '|LineID=' + line + '|Index=' + index + '}}');
	  }
	
	  var textarea = document.querySelector('textarea[name$="[SpanText]"]');
	  if (textarea) {
	    textarea.value = output.join("\n");
	  }
	
	  console.log("Saved " + output.length + " spans.");
	};

	
	
	if (document.getElementById("buildOverlay")) {
	  var spans = document.querySelectorAll('span.hebrew');
	  for (var i = 0; i < spans.length; i++) {
	    attachHebrewSplitHandler(spans[i]);
	  }
	}
	
		
	
	
	// ===========================
	// Auto-create links for notes such as "v. 2 preferred diagram"
	// ===========================

	
    var headingLinks = {};


	if (document.getElementById("createDiagramLinks")) {
	    // 1. Build a mapping from "v. 2 preferred" ➔ "Preferred_2"
	    var currentVerseRange = "";
	
	    var headings = document.querySelectorAll('h1, h2');
	    for (var i = 0; i < headings.length; i++) {
	        var heading = headings[i];
	        if (heading.tagName === 'H1') {
	            currentVerseRange = heading.innerText.trim().toLowerCase(); // e.g., "v. 2"
	        }
	        if (heading.tagName === 'H2') {
	            var description = heading.innerText.trim();
	            var combinedKey = (currentVerseRange + " " + description).toLowerCase(); // e.g., "v. 2 preferred"
	
	            // Ensure heading has a usable ID
	            if (!heading.id) {
	                heading.id = description.replace(/\s+/g, "_") + "_" + currentVerseRange.replace(/[^0-9]/g, "");
	            }
	
	            headingLinks[combinedKey] = heading.id;
	        }
	    }
	
	    // 3. Apply to whole document
	    linkifyTextNodes(document.body);
	}
	

    // 2. Search and replace text nodes
    function linkifyTextNodes(node) {
        if (node.nodeType === Node.TEXT_NODE) {
            var text = node.nodeValue;
            var parent = node.parentNode;

            for (var phrase in headingLinks) {
                var regex = new RegExp("\\b(" + phrase + ") diagram\\b", "i");
                var match = regex.exec(text);
                if (match) {
                    var before = text.slice(0, match.index);
                    var after = text.slice(match.index + match[0].length);

                    var anchor = document.createElement('a');
                    anchor.href = "#" + headingLinks[phrase];
                    anchor.textContent = match[0];

                    parent.insertBefore(document.createTextNode(before), node);
                    parent.insertBefore(anchor, node);
                    if (after) parent.insertBefore(document.createTextNode(after), node);
                    parent.removeChild(node);
                    break; // Stop after first match in this node
                }
            }
        } else if (node.nodeType === Node.ELEMENT_NODE && node.tagName !== 'A') {
            for (var j = 0; j < node.childNodes.length; j++) {
                linkifyTextNodes(node.childNodes[j]);
            }
        }
    }

	// ===========================
	// End links for notes such as "v. 2 preferred diagram"
	// ===========================


	// BEGIN TEXT OVERLAY CODE

	// === TEXT OVERLAY COLOR PICKER LOGIC ===
	// var selectedColor = 'red';
	var selectedParticipant = 'psalmist';
	var coloredWords = [];
	
	/*
	document.querySelectorAll('input[name="Text Overlay[Color]"]').forEach(function (radio) {
	  radio.addEventListener('change', function (e) {
	    selectedColor = e.target.value;
	    console.warn("Selected color:", selectedColor);
	  });
	});*/
	
	// 2. Set default color based on default participant
	
	// set up color map
	    var divColorMap = document.getElementById("color-map");
		var colorMap = {};
		var colorPicker = document.querySelector("div.color-picker");
		if (divColorMap) {
		    var encoded = divColorMap.textContent;
		    if (encoded) {
				try {
				    var decoded = decodeURIComponent(encoded);
				    colorMap = JSON.parse(decoded);
				    console.log("Color map successfully decoded.");
				} catch (e) {
				    console.error("Error processing color map:", e);
				}
		
		    }
		}
		else{
			console.log("No color map found");	
		}
	selectedColor = '';
	
	
	// 3a. Setup listener for radio buttons (when using radio buttons)
	document.querySelectorAll('input[name="Text Overlay[Participant]"]').forEach(function (radio) {
	    radio.addEventListener('change', function (e) {
	        selectedParticipant = e.target.value;
	        selectedColor = colorMap[selectedParticipant] || '';
	        console.warn("Selected:", selectedParticipant, "→", selectedColor);
	    });
	});
	
	
	// 3b. Setup listener for color grid (when using that)
		
	// Color selection via grid
	document.querySelectorAll('.color-cell').forEach(function(cell) {
	  cell.addEventListener('click', function () {
	    // Remove highlight from all cells on the color grid
	    document.querySelectorAll('.color-cell').forEach(function(c) {
	      c.style.outline = 'none';
	    });
	
	    // Highlight this one
	    cell.style.outline = '4px solid black';
	
	    // Set selectedColor
	    selectedColor = cell.dataset.color || '';
        selectedParticipant = cell.dataset.participant || 'unknown';
	    console.log("Selected color from grid: " + selectedColor + " for participant: " + selectedParticipant);
	  });
	});
	
	
	
	
	// on click - individual words
	document.querySelectorAll('span.hebrew').forEach(function (span) {
	  span.style.cursor = 'pointer';
//	  span.addEventListener('click', function () {
	  	
	  	
	  span.addEventListener('click', function (event) {
		event.stopPropagation(); // prevent the line-level click from firing

	  	
	    var idMatch = span.className.match(/id-[\w-]+/);
	    if (!idMatch) return;
	
	    var id = idMatch[0];
	    var word = span.textContent;
	    var glossEl = document.querySelector('.gloss.' + id);
	    var gloss = glossEl ? glossEl.textContent : '';
	
	    if (!coloredWords.some(function (w) { return w.id === id; })) {
	      coloredWords.push({ id: id, hebrew: word, gloss: gloss, participant:selectedParticipant, color: selectedColor });
	      
			// Add a new form instance by clicking the "Add another" button
			var addButton = document.querySelector('.multipleTemplateAdder a');
			if (addButton) {
			  addButton.click();
			
			  // Slight delay to allow the DOM to update
			  setTimeout(function () {
			    // Get the list of all current instances
			    var allInstances = document.querySelectorAll('.multipleTemplateInstance');
			    var latest = allInstances[allInstances.length - 1];
			
			    if (latest) {
			      // Fill in WordID and Color
			      var hebrewInput = latest.querySelector('input[name$="[Hebrew]"]');
			      var wordIdInput = latest.querySelector('input[name$="[WordID]"]');
			      var participantInput = latest.querySelector('input[name$="[Participant]"]');
			      var colorInput = latest.querySelector('input[name$="[Color]"]');
			
			      if (hebrewInput) hebrewInput.value = word;
			      if (wordIdInput) wordIdInput.value = id;
			      if (participantInput) participantInput.value = selectedParticipant;
			      if (colorInput) colorInput.value = selectedColor;
			    }
			  }, 100); // Adjust delay if needed
			}
	     
	    }
	console.log("Setting to " + selectedColor);
	    span.style.backgroundColor = selectedColor;
	    if (glossEl) glossEl.style.backgroundColor = selectedColor;
	  });
	});
	

	
	// Line-level click handling
	document.querySelectorAll('div.line').forEach(function (lineDiv) {
	  lineDiv.style.cursor = 'pointer';
	  lineDiv.addEventListener('click', function () {
	    if (!selectedColor || !selectedParticipant) {
	      console.warn("No color or participant selected.");
	      return;
	    }
	
	    var lineId = lineDiv.dataset.line;
	    if (!lineId) return;
	
	    // Find all lines with the same data-line attribute
	    var matchingLines = document.querySelectorAll('div.line[data-line="' + CSS.escape(lineId) + '"]');
	
	    // Get representative text from the first match
	    var lineText = matchingLines[0].textContent.trim();
	
	    if (!coloredWords.some(function (w) { return w.id === lineId; })) {
	      coloredWords.push({
	        id: lineId,
	        hebrew: lineText,
	        gloss: '',
	        participant: selectedParticipant,
	        color: selectedColor
	      });
	
	      var addButton = document.querySelector('.multipleTemplateAdder a');
	      if (addButton) {
	        addButton.click();
	
	        setTimeout(function () {
	          var allInstances = document.querySelectorAll('.multipleTemplateInstance');
	          var latest = allInstances[allInstances.length - 1];
	          if (latest) {
	            var hebrewInput = latest.querySelector('input[name$="[Hebrew]"]');
	            var wordIdInput = latest.querySelector('input[name$="[WordID]"]');
	            var participantInput = latest.querySelector('input[name$="[Participant]"]');
	            var colorInput = latest.querySelector('input[name$="[Color]"]');
	            if (hebrewInput) hebrewInput.value = lineText;
	            if (wordIdInput) wordIdInput.value = lineId;
	            if (participantInput) participantInput.value = selectedParticipant;
	            if (colorInput) colorInput.value = selectedColor;
	          }
	        }, 200);
	      }
	    }
	
	    // Apply background color to all matching line divs
	    matchingLines.forEach(function (div) {
	      div.style.backgroundColor = selectedColor;
	    });
	
	    console.log("Highlighted all lines with data-line:", lineId);
	  });
	});




	// export a list
	window.exportAnnotations = function () {
	  var output = document.getElementById('annotation-output');
	  if (output) {
	    output.textContent = coloredWords.map(function (w) {
	      return w.id + ": " + w.hebrew + " / " + w.gloss + " [" + w.color + "]";
	    }).join('\n');
	  } else {
	    console.warn("No #annotation-output element found.");
	  }
	};
	
	
	
	
	
	function applyAllOverlayAnnotations() {
	    var overlays = document.querySelectorAll("div.overlay");
	
	    // If no overlays are found, print a warning
	    if (overlays.length === 0) {
	        console.warn("No overlays found on the page.");
	    }
	    for (var i = 0; i < overlays.length; i++) {
	        var overlay = overlays[i];
	        var containerId = overlay.id;
	        var encoded = overlay.getAttribute("data-annotations");
	
	        if (!encoded) continue;
	        
	        try {
	            var decoded = decodeURIComponent(encoded);
	            var annotations = JSON.parse(decoded);
	            applyOverlayColors(containerId, annotations);
	        } catch (e) {
	            console.error("Error processing annotations for", containerId, e);
	        }
	    }
	}

    function applyOverlayColors(containerId, annotations) {
        var container = document.getElementById(containerId);
        if (!container) return;

        for (var wordID in annotations) {
		    if (annotations.hasOwnProperty(wordID) && wordID.trim() !== "") {
	            console.log("Seeking color for " + wordID + " and " + annotations[wordID]);
		        var color = colorMap[annotations[wordID]];
		        var elements = container.querySelectorAll("." + CSS.escape(wordID));
	            console.log("Processing color " + color + " for " + elements + " elements");
		        for (var j = 0; j < elements.length; j++) {
		            elements[j].style.backgroundColor = color;
		        }
		    }
		}


    }
    
    

	
	
	
	// END TEXT OVERLAY CODE


    //console.log("Document ready. Attaching event listeners to toggle links.");

    // Now attach event listeners for toggling visibility
    attachToggleListeners();
    
    // handle any overlay colors
    applyAllOverlayAnnotations();

    // Wait until all Mermaid diagrams are processed
    waitForMermaidProcessing(function () {
        //console.log("Mermaid diagrams are fully processed.");

        $('div[id^="verse-"]').each(function () {
            var parentDiv = $(this);
            var svg = parentDiv.find('svg');

            if (svg.length > 0) {
                var preElement = parentDiv.find('pre.mermaid');  // The <pre> element containing the SVG
                var preWidth = preElement.width();
                var preHeight = preElement.height();
                var viewBox = svg[0].getAttribute('viewBox');

                if (viewBox) {
                    var viewBoxValues = viewBox.split(' ');
                    var viewBoxWidth = parseFloat(viewBoxValues[2]);
                    var viewBoxHeight = parseFloat(viewBoxValues[3]);
                    var scaleX = preWidth / viewBoxWidth;
                    var scaleY = preHeight / viewBoxHeight;
                    var scale = Math.min(scaleX, scaleY);

                    svg.css({
                        'width': (preWidth) + 'px',
                        'max-width': (preWidth) + 'px',
                        'height': (viewBoxHeight * scale) + 'px',
                        'position': 'relative',  // Ensure the SVG has a positioning context
                        'left': '-10px'  // Offset the SVG to the left, because firefox and others misalign it to the right. This removes the horizontal scrollbar
                    });

					// Initialize panzoom
					var panZoomInstance = Panzoom(svg[0], { 
						contain: 'outside',
						minScale: 1,  // default is 0.125
						maxScale: 10,  // default is 4
						panOnlyWhenZoomed: true, //default is false
						zoomSpeed: 0.040, // default is 6.5% per mouse wheel event
						pinchSpeed: 1.5 // default is zoom two times faster than the distance between fingers
					});
                    parentDiv[0].addEventListener('wheel', function (e) {
						e.preventDefault();
    					panZoomInstance.zoomWithWheel(e, { step: 0.04 }); // custom override per event
					});
                    parentDiv[0].addEventListener('dblclick', function (event) {
	        	    var rect = parentDiv[0].getBoundingClientRect();
				    var offsetX = event.clientX - rect.left;
				    var offsetY = event.clientY - rect.top;
				    if (event.shiftKey) {
				        // Shift + Double-click → Zoom Out
				        panZoomInstance.zoomOut({ focal: { x: offsetX, y: offsetY } });
				    } else {
				        // Regular Double-click → Zoom In
				        panZoomInstance.zoomIn({ focal: { x: offsetX, y: offsetY } });
				    }
	            });

                    // Resize handler to keep SVG scaled on window resize
                    var resizeHandler = function () {
                        var newWidth = preElement.width();

                        svg.css({
                            'width': (newWidth) + 'px',
                            'max-width': (newWidth) + 'px'
                            // Do not change the height to avoid reflowing the html page
                        });
                    };
                    
                    // Listen for resize events
                    $(window).on('resize', resizeHandler);
                }
            }
        
            
        });

        // Initially hide elements with the "highlight-phrase" class
        document.querySelectorAll(".highlight-phrase").forEach(function (element) {
            element.style.display = "none"; // Hide elements initially
        });



        // Bind lightbox functionality
        $('.lightbox-button').on('click', function () {
            // Get the target <div> ID from the button's data-target attribute
            var targetDivId = $(this).data('target'); // e.g., '#verse-1'
            var parentDiv = $(targetDivId); // Find the corresponding <div> by ID
            var associatedSvg = parentDiv.find('svg'); // Find the SVG inside the <pre>

            if (associatedSvg.length > 0) {
                openLightbox(associatedSvg[0]);
            }
        });

        // Open the lightbox and display the SVG in full-screen
        function openLightbox(svgElement) {
            // Create lightbox container if it doesn't exist            
            var lightbox = $('<div id="lightbox-overlay" class="lightbox-overlay">')
                .appendTo('body')
                .css({
                    position: 'fixed',
                    top: 0,
                    left: 0,
                    width: '100%',
                    height: '100%',
                    backgroundColor: 'rgba(128, 128, 128, 0.8)',
                    display: 'flex',
                    justifyContent: 'center',
                    alignItems: 'center',
                    zIndex: 9999,
                });

            // Create the SVG container in the lightbox
                var lightboxSvgContainer = $('<div class="lightbox-svg-container">')
                .appendTo(lightbox)
                .css({
                    width: '95%',
                    height: '95%',
                    overflow: 'hidden',
                    backgroundColor: 'rgba(255, 255, 255, 1.0)',
                });

            var lightboxSvg = $(svgElement).clone().appendTo(lightboxSvgContainer);  // Clone the SVG
            // resize the svg to the available space
            lightboxSvg.css({
                'width': '100%',
                'max-width': '100%',
                'height': '100%'
            });

            // Apply panzoom to the cloned SVG in the lightbox
			var panZoomInstanceLightbox = Panzoom(lightboxSvg[0], { 
				contain: 'outside',
				minScale: 1,  // default is 0.125
				maxScale: 10,  // default is 4
				panOnlyWhenZoomed: true, //default is false
				zoomSpeed: 0.040, // default is 6.5% per mouse wheel event
				pinchSpeed: 1.5 // default is zoom two times faster than the distance between fingers
			});
            lightboxSvg[0].addEventListener('wheel', function (e) {
				e.preventDefault();
				panZoomInstanceLightbox.zoomWithWheel(e, { step: 0.04 }); // custom override per event
			});
            lightboxSvg[0].addEventListener('dblclick', function (event) {
        	    var rect = lightboxSvg[0].getBoundingClientRect();
			    var offsetX = event.clientX - rect.left;
			    var offsetY = event.clientY - rect.top;
			    if (event.shiftKey) {
			        // Shift + Double-click → Zoom Out
			        panZoomInstanceLightbox.zoomOut({ focal: { x: offsetX, y: offsetY } });
			    } else {
			        // Regular Double-click → Zoom In
			        panZoomInstanceLightbox.zoomIn({ focal: { x: offsetX, y: offsetY } });
			    }
            });

            var closeButton = $('<button class="lightbox-close-button">Close</button>')
                .appendTo(lightbox)
                .css({
                    position: 'absolute',
                    top: '10px',
                    right: '10px',
                    backgroundColor: '#fff',
                    color: '#000',
                    border: '1px solid #bbb',
                    borderRadius: '1rem',
                    padding: '10px 20px',
                    cursor: 'pointer',
                    zIndex: 10000
                })
                .on('click', function () {
                    // Close the lightbox when the close button is clicked
                    lightbox.remove();
                });

            lightbox.on('click', function (event) {
                // Close the lightbox when clicking outside the SVG
                if ($(event.target).is(lightbox)) {
                    lightbox.remove();
                }
            });

            // Close the lightbox with the Escape key
            $(document).on('keydown', function (event) {
                if (event.key === "Escape" || event.keyCode === 27) {
                    lightbox.remove();
                    $(document).off('keydown');  // Remove the keydown listener to prevent multiple bindings
                }
            });
        }
    });

	// Bidirectional hover for Hebrew and Gloss (ES5-compatible)
	var hoverElements = document.querySelectorAll(".hebrew, .gloss");

	// If no toggle links are found, print a warning
	    if (hoverElements.length === 0) {
	        console.warn("No hover elements found on the page.");
	    }
    	
	for (var i = 0; i < hoverElements.length; i++) {
	    (function (el) {
	        el.addEventListener("mouseenter", function () {
	            //var classList = el.className.split(" ");
	            
	            var className = (typeof el.className === 'object' && el.className.baseVal) ? el.className.baseVal : el.className;
				var classList = className.split(" ");

	            for (var j = 0; j < classList.length; j++) {
	                var cls = classList[j];
	                if (cls.indexOf("id-") === 0) {
	                    var matches = document.getElementsByClassName(cls);
	                    for (var k = 0; k < matches.length; k++) {
	                        matches[k].classList.add("highlighted");
	                    }
	                }
	            }
	        });
	
	        el.addEventListener("mouseleave", function () {
	            //var classList = el.className.split(" ");
	            
	            var className = (typeof el.className === 'object' && el.className.baseVal) ? el.className.baseVal : el.className;
				var classList = className.split(" ");
	            for (var j = 0; j < classList.length; j++) {
	                var cls = classList[j];
	                if (cls.indexOf("id-") === 0) {
	                    var matches = document.getElementsByClassName(cls);
	                    for (var k = 0; k < matches.length; k++) {
	                        matches[k].classList.remove("highlighted");
	                    }
	                }
	            }
	        });
	    })(hoverElements[i]);
	}
	

});