|
|
| Line 1: |
Line 1: |
| /* ========================= | | /* Grantha.io — chapter nav scroll-spy */ |
| VERSE ACTIONS
| | (function () { |
| ========================= */
| | 'use strict'; |
| ( function () { | |
| 'use strict';
| |
|
| |
|
| /* ── Safe delegated listener ─────────────────────────────────────────── */
| | function initScrollSpy() { |
| function onDocClick( e ) {
| | var nav = document.querySelector('.gr-chapter-nav'); |
| var target = e.target;
| | if (!nav) return; |
|
| |
|
| // Walk up to find a matching action button
| | var links = Array.from(nav.querySelectorAll('a')).filter(function (a) { |
| function closest( el, cls ) {
| | return a.href && a.href.indexOf('#') !== -1; |
| while ( el && el !== document ) {
| | }); |
| if ( el.classList && el.classList.contains( cls ) ) return el;
| | if (!links.length) return; |
| el = el.parentNode; | |
| }
| |
| return null;
| |
| }
| |
| | |
| /* ── Commentary toggle ── */
| |
| var commentBtn = closest( target, 'verse-action-commentary' ); | |
| if ( commentBtn ) {
| |
| e.preventDefault();
| |
| var verseId = commentBtn.getAttribute( 'data-verse' );
| |
| if ( !verseId ) return;
| |
| | |
| // Match all commentary bodies for this verse by id prefix
| |
| var allBodies = document.querySelectorAll( '[id^="commentary-body-' + verseId + '"]' );
| |
| var allBtns = document.querySelectorAll( '.verse-action-commentary[data-verse="' + verseId + '"]' );
| |
| | |
| var isOpen = allBodies.length && allBodies[0].classList.contains( 'open' );
| |
| | |
| // Close all open commentaries on the page first
| |
| document.querySelectorAll( '.commentary-body.open' ).forEach( function ( el ) {
| |
| el.classList.remove( 'open' );
| |
| } );
| |
| document.querySelectorAll( '.verse-action-commentary.active' ).forEach( function ( el ) {
| |
| el.classList.remove( 'active' );
| |
| } );
| |
| | |
| // If it was closed, open it
| |
| if ( !isOpen ) {
| |
| allBodies.forEach( function ( el ) { el.classList.add( 'open' ); } );
| |
| allBtns.forEach( function ( el ) { el.classList.add( 'active' ); } );
| |
| }
| |
| return;
| |
| }
| |
| | |
| /* ── Copy verse — copies verse ID for crosslinking ── */
| |
| var copyBtn = closest( target, 'verse-action-copy' );
| |
| if ( copyBtn ) {
| |
| e.preventDefault();
| |
| var verseId = copyBtn.getAttribute( 'data-verse' );
| |
| if ( !verseId ) return;
| |
| copyText( verseId, copyBtn );
| |
| return;
| |
| }
| |
| /* ── Copy ID ── */
| |
| var idBtn = closest( target, 'copy-id-btn' );
| |
| if ( idBtn ) {
| |
| e.preventDefault();
| |
| var id = idBtn.getAttribute( 'data-copyid' );
| |
| if ( !id ) return;
| |
| copyText( id, idBtn );
| |
| return;
| |
| }
| |
| }
| |
| | |
| document.addEventListener( 'click', onDocClick );
| |
| | |
| /* ── Copy helper + tooltip ───────────────────────────────────────────── */
| |
| function copyText( text, btn ) {
| |
| function showTooltip() {
| |
| // Remove stale tooltip if double-clicked
| |
| var old = btn.querySelector( '.copy-tooltip' );
| |
| if ( old ) old.remove();
| |
| | |
| btn.style.position = 'relative';
| |
| var tip = document.createElement( 'span' );
| |
| tip.className = 'copy-tooltip';
| |
| tip.textContent = 'Copied ✓';
| |
| btn.appendChild( tip );
| |
| | |
| requestAnimationFrame( function () {
| |
| requestAnimationFrame( function () {
| |
| tip.classList.add( 'copy-tooltip-visible' );
| |
| } ); | |
| } );
| |
| | |
| setTimeout( function () {
| |
| tip.classList.remove( 'copy-tooltip-visible' ); | |
| setTimeout( function () { tip.remove(); }, 400 );
| |
| }, 1400 );
| |
| }
| |
| | |
| if ( navigator.clipboard && window.isSecureContext ) {
| |
| navigator.clipboard.writeText( text ).then( showTooltip );
| |
| } else {
| |
| var ta = document.createElement( 'textarea' );
| |
| ta.value = text;
| |
| ta.style.cssText = 'position:fixed;opacity:0;pointer-events:none;';
| |
| document.body.appendChild( ta );
| |
| ta.select();
| |
| document.execCommand( 'copy' );
| |
| document.body.removeChild( ta );
| |
| showTooltip();
| |
| }
| |
| }
| |
| | |
| }() );
| |
| function openCreateDialog() {
| |
| | |
| var overlay = document.createElement('div');
| |
| overlay.className = 'grantha-modal';
| |
| | |
| overlay.innerHTML = `
| |
| <div class="grantha-modal-box">
| |
|
| |
| <div class="gm-header">
| |
| <div class="gm-title">New document</div>
| |
| <div class="gm-sub">Create a new text</div>
| |
| </div>
| |
| | |
| <input
| |
| type="text"
| |
| id="gm-input"
| |
| placeholder="Untitled document"
| |
| autofocus
| |
| />
| |
| | |
| <div class="gm-actions">
| |
| <button class="gm-btn gm-cancel">Cancel</button>
| |
| <button class="gm-btn gm-create">Create</button>
| |
| </div>
| |
| | |
| </div>
| |
| `;
| |
| | |
| document.body.appendChild(overlay);
| |
| | |
| var input = overlay.querySelector('#gm-input');
| |
| input.focus();
| |
| | |
| // Cancel
| |
| overlay.querySelector('.gm-cancel').onclick = function () {
| |
| overlay.remove();
| |
| };
| |
|
| |
|
| // Create
| | var targets = links.map(function (a) { |
| overlay.querySelector('.gm-create').onclick = function () {
| | var id = a.href.split('#')[1]; |
| var name = input.value.trim();
| | return id ? document.getElementById(id) : null; |
| if (!name) return;
| | }); |
|
| |
|
| name = name.replace(/\s+/g, '_');
| | function update() { |
| | /* Try all possible scroll containers — window, html, body, |
| | and MW-specific wrappers */ |
| | var scrollY = window.scrollY |
| | || document.documentElement.scrollTop |
| | || document.body.scrollTop |
| | || 0; |
| | var threshold = scrollY + 130; |
| | var active = 0; |
| | for (var i = 0; i < targets.length; i++) { |
| | if (targets[i]) { |
| | /* offsetTop walks up to get absolute position */ |
| | var el = targets[i]; |
| | var top = 0; |
| | while (el) { top += el.offsetTop; el = el.offsetParent; } |
| | if (top <= threshold) active = i; |
| | } |
| | } |
| | links.forEach(function (a, i) { |
| | a.style.color = (i === active) ? '#f57c00' : ''; |
| | a.style.fontWeight = (i === active) ? '600' : ''; |
| | }); |
| | } |
|
| |
|
| window.location.href = mw.util.getUrl(name, { action: 'edit' });
| | /* Attach to every possible scroll source */ |
| };
| | window.addEventListener('scroll', update, { passive: true }); |
| | document.addEventListener('scroll', update, { passive: true }); |
| | var mwContent = document.getElementById('mw-content-text') |
| | || document.getElementById('content') |
| | || document.getElementById('bodyContent'); |
| | if (mwContent) { |
| | mwContent.addEventListener('scroll', update, { passive: true }); |
| | } |
|
| |
|
| // Enter key support
| | update(); |
| input.addEventListener('keydown', function (e) {
| |
| if (e.key === 'Enter') {
| |
| overlay.querySelector('.gm-create').click();
| |
| } | | } |
| });
| |
|
| |
|
| // Click outside to close
| | mw.hook('wikipage.content').add(function () { |
| overlay.onclick = function (e) {
| | /* Small delay to let MW finish rendering large pages */ |
| if (e.target === overlay) overlay.remove();
| | setTimeout(initScrollSpy, 300); |
| };
| | }); |
| } | | }()); |