MediaWiki: Common.js: Difference between revisions
From Psalms: Layer by Layer
mNo edit summary |
mNo edit summary |
||
Line 558: | Line 558: | ||
var lineInput = latest.querySelector('input[name$="[LineID]"]'); | var lineInput = latest.querySelector('input[name$="[LineID]"]'); | ||
var indexInput = latest.querySelector('input[name$="[Index]"]'); | var indexInput = latest.querySelector('input[name$="[Index]"]'); | ||
Line 836: | Line 833: | ||
if (colorInput) colorInput.value = selectedColor; | if (colorInput) colorInput.value = selectedColor; | ||
} | } | ||
}, | }, 200); | ||
} | } | ||
} | } |
Revision as of 15:26, 8 May 2025
/* 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 document.getElementById("createSpans").onclick = function () { var rows = document.querySelectorAll("#buildOverlay tr"); for (var i = 0; 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 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(" "); } } }); } document.getElementById("saveSpans").onclick = function () { var allSpans = document.querySelectorAll('#buildOverlay .line span'); var formAdder = document.querySelector('.multipleTemplateAdder a'); // Remove all current span form entries var allInstances = document.querySelectorAll('.multipleTemplateInstance'); for (var i = 0; i < allInstances.length; i++) { allInstances[i].remove(); } var count = 0; for (var i = 0; i < allSpans.length; i++) { var span = allSpans[i]; var classes = span.className.split(' '); var idClass = null; for (var j = 0; j < classes.length; j++) { if (classes[j].indexOf('id-') === 0) { idClass = classes[j].substring(3); // Remove the 'id-' prefix break; } } if (!idClass) continue; var language = span.classList.contains('hebrew') ? 'Hebrew' : span.classList.contains('english') || span.classList.contains('gloss') ? 'English' : null; if (!language) continue; var lineDiv = span.closest('.line'); var lineID = lineDiv ? lineDiv.getAttribute('data-line') : ''; var word = span.textContent.trim(); var index = 1; var siblings = lineDiv ? lineDiv.querySelectorAll('span.' + (language === 'Hebrew' ? 'hebrew' : 'english')) : []; for (var k = 0; k < siblings.length; k++) { if (siblings[k] === span) { index = k + 1; break; } } if (formAdder) formAdder.click(); (function (word, idClass, lineID, language, index) { setTimeout(function () { var latestInstances = document.querySelectorAll('.multipleTemplateInstance'); var latest = latestInstances[latestInstances.length - 1]; if (!latest) return; var langSelect = latest.querySelector('select[name$="[Language]"]'); var wordIdInput = latest.querySelector('input[name$="[WordID]"]'); var wordInput = latest.querySelector('input[name$="[Word]"]'); var lineInput = latest.querySelector('input[name$="[LineID]"]'); var indexInput = latest.querySelector('input[name$="[Index]"]'); if (langSelect) langSelect.value = language; if (wordIdInput) wordIdInput.value = idClass; if (wordInput) wordInput.value = word; if (lineInput) lineInput.value = lineID; if (indexInput) indexInput.value = index; }, 100 * count); })(word, idClass, lineID, language, index); count++; } console.log("Saved " + count + " 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]); } });