MediaWiki:Common.js

From Sanatan Hindu Dharma
Revision as of 13:41, 20 January 2026 by Balaji (talk | contribs)

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.

mw.loader.using('mediawiki.util').done(function () {
    console.log("Common.js loaded safely");
});


/* Any JavaScript here will be loaded for all users on every page load. */

/*

document.addEventListener("DOMContentLoaded", function() {
  const btn = document.querySelector(".toggle-btn");
  const content = document.querySelector(".toggle-content");

  if (btn && content) {
    btn.addEventListener("click", function() {
      content.style.display = (content.style.display === "block") ? "none" : "block";
    });
  }
});


// Auto-add parent category when editing/creating a subpage
( function () {
    if ( mw.config.get('wgAction') !== 'edit' ) return;

    // wgTitle contains title without namespace, e.g. "Ancient-education/Subpage"
    var title = mw.config.get('wgTitle') || '';
    if ( title.indexOf('/') === -1 ) return; // not a subpage

    var parent = title.split('/')[0]; // "Ancient-education"

    // jQuery available
    $( function () {
        var textarea = $('#wpTextbox1');
        if ( !textarea.length ) return;

        // Only append if not present
        var current = textarea.val() || '';
        var catTag = '\n[[Category:' + parent + ']]';
        if ( current.indexOf(catTag.trim()) === -1 ) {
            // Insert the category at the end of the text area (preserve existing text)
            textarea.val(current + catTag);
        }
    } );
}() );

$(document).ready(function () {
  // Skip special pages
  if (mw.config.get('wgNamespaceNumber') < 0) return;

  var $content = $('#mw-content-text');

  // Fetch all page titles from the API (main namespace only)
  $.get(mw.util.wikiScript('api'), {
    action: 'query',
    list: 'allpages',
    aplimit: 'max',
    format: 'json'
  }).done(function (data) {
    var titles = data.query.allpages.map(function (p) { return p.title; });

    var html = $content.html();
    titles.forEach(function (title) {
      // Safe regex for whole words
      var safeTitle = title.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
      var regex = new RegExp('\\b(' + safeTitle + ')\\b', 'g');
      html = html.replace(regex, '<a href="/wiki/' + encodeURIComponent(title.replace(/ /g, '_')) + '">$1</a>');
    });

    $content.html(html);
  });
});






$(document).ready(function() {
    if (mw.config.get('wgNamespaceNumber') >= 0) { // Only show on normal pages
        var pageName = mw.config.get('wgPageName');
        var uploadUrl = mw.util.getUrl('Form:UploadVideo', { 'page': pageName });

        $('<div style="position:fixed; bottom:20px; right:20px; background:#007bff; color:white; padding:10px; border-radius:5px; cursor:pointer; font-weight:bold;">Upload a Video</div>')
        .click(function() {
            window.location.href = uploadUrl;
        }).appendTo('body');
    }
});
 */


/* Start Pushkar Code */

$('img').attr('loading', 'lazy');

mw.loader.using(['jquery'], function () {
  // Load Slick dynamically
  $.getScript('https://cdn.jsdelivr.net/npm/slick-carousel@1.8.1/slick/slick.min.js')
    .done(function () {
      $(function () {
        // Initialize Slick after content loads
       $('.adsBannerSliderHero ').not('.slick-initialized').slick({
          slidesToShow: 1,
          slidesToScroll: 1,
          autoplay: true,
          autoplaySpeed: 3000,
          dots: false,
          arrows: true,
          prevArrow: '<button class="prev-arrow"><i class="fas fa-chevron-left"></i></button>',
          nextArrow: '<button class="next-arrow"><i class="fas fa-chevron-right"></i></button>',
          //adaptiveHeight: true
        });

       $('.sanatanWisdomSlider').not('.slick-initialized').slick({
          slidesToShow: 3,
          slidesToScroll: 1,
          autoplay: true,
          autoplaySpeed: 3000,
          dots: false,
          arrows: true,
          prevArrow: '<button class="prev-arrow"><i class="fas fa-chevron-left"></i></button>',
          nextArrow: '<button class="next-arrow"><i class="fas fa-chevron-right"></i></button>',
          responsive: [
                       {breakpoint: 768, settings: {arrows: false, centerMode: true, slidesToShow: 2 }},
                       {breakpoint: 480, settings: { arrows: false,slidesToShow: 1}}
                      ],
          adaptiveHeight: true
        });

        $('.latestArticlesSlider').not('.slick-initialized').slick({
         slidesToShow: 3,
          slidesToScroll: 1,
          autoplay: true,
          autoplaySpeed: 3000,
          dots: false,
          arrows: false,
          variableWidth: true,
          prevArrow: '<button class="prev-arrow"><i class="fas fa-chevron-left"></i></button>',
          nextArrow: '<button class="next-arrow"><i class="fas fa-chevron-right"></i></button>',
          responsive: [
                       {breakpoint: 768, settings: {arrows: false, centerMode: true, slidesToShow: 2 }},
                       {breakpoint: 480, settings: { arrows: false,slidesToShow: 1}}
                      ],
          adaptiveHeight: true
        });


      });
    })
    .fail(function () {
      console.error('Failed to load Slick Slider JS');
    });
});

/* End Pushkar Code */





/* Functions of Slider One */

(function () {
  function initSliders() {
    var sliders = document.querySelectorAll('.mw-slider');
    sliders.forEach(function (slider) {
      // Avoid double-init
      if (slider._mwSliderInited) return;
      slider._mwSliderInited = true;

      var viewport = slider.querySelector('.mw-slider-viewport');
      var track = slider.querySelector('.mw-slider-track');
      var items = Array.from(slider.querySelectorAll('.mw-slider-item'));
      var btnPrev = slider.querySelector('.mw-slider-btn.prev');
      var btnNext = slider.querySelector('.mw-slider-btn.next');

      var currentIndex = 0;
      var itemsToShow = getItemsToShow();
      var gap = parseFloat(getComputedStyle(track).columnGap || getComputedStyle(track).gap || 16);

      function getItemsToShow() {
        var w = window.innerWidth;
        if (w <= 600) return 1;
        if (w <= 900) return 2;
        return 3;
      }

      function updateSizes() {
        itemsToShow = getItemsToShow();
        // compute single item width including gap
        if (!items[0]) return;
        var itemRect = items[0].getBoundingClientRect();
        gap = parseFloat(getComputedStyle(track).columnGap || getComputedStyle(track).gap || 16);
        var single = itemRect.width + gap;
        // ensure currentIndex in range
        var maxIndex = Math.max(0, items.length - itemsToShow);
        currentIndex = Math.min(currentIndex, maxIndex);
        // apply transform
        var translateX = -currentIndex * single;
        track.style.transform = 'translateX(' + translateX + 'px)';
        updateButtons();
      }

      function updateButtons() {
        var maxIndex = Math.max(0, items.length - itemsToShow);
        if (btnPrev) btnPrev.disabled = currentIndex <= 0;
        if (btnNext) btnNext.disabled = currentIndex >= maxIndex;
      }

      function gotoIndex(index) {
        var maxIndex = Math.max(0, items.length - itemsToShow);
        currentIndex = Math.max(0, Math.min(maxIndex, index));
        updateSizes();
      }

      if (btnPrev) btnPrev.addEventListener('click', function () {
        gotoIndex(currentIndex - 1);
      });
      if (btnNext) btnNext.addEventListener('click', function () {
        gotoIndex(currentIndex + 1);
      });

      // Keyboard support
      slider.addEventListener('keydown', function (e) {
        if (e.key === 'ArrowLeft') gotoIndex(currentIndex - 1);
        if (e.key === 'ArrowRight') gotoIndex(currentIndex + 1);
      });

      // Resize handling
      var resizeTimeout;
      window.addEventListener('resize', function () {
        clearTimeout(resizeTimeout);
        resizeTimeout = setTimeout(updateSizes, 120);
      });

      // Touch / drag support
      (function () {
        var startX = 0;
        var currentTranslate = 0;
        var dragging = false;
        var pointerId = null;

        function pointerDown(e) {
          if (e.pointerType === 'mouse' && e.button !== 0) return;
          dragging = true;
          pointerId = e.pointerId;
          startX = e.clientX;
          track.style.transition = 'none';
          track.setPointerCapture && track.setPointerCapture(pointerId);
        }

        function pointerMove(e) {
          if (!dragging || e.pointerId !== pointerId) return;
          var dx = e.clientX - startX;
          var itemRect = items[0] && items[0].getBoundingClientRect();
          var single = itemRect ? (itemRect.width + gap) : 0;
          var baseTranslate = -currentIndex * single;
          track.style.transform = 'translateX(' + (baseTranslate + dx) + 'px)';
        }

        function pointerUp(e) {
          if (!dragging || e.pointerId !== pointerId) return;
          dragging = false;
          track.style.transition = '';
          var dx = e.clientX - startX;
          var threshold = Math.max(40, (items[0] ? items[0].getBoundingClientRect().width : 200) * 0.15);
          if (dx > threshold) {
            gotoIndex(currentIndex - 1);
          } else if (dx < -threshold) {
            gotoIndex(currentIndex + 1);
          } else {
            updateSizes();
          }
          try { track.releasePointerCapture(pointerId); } catch (err) {}
          pointerId = null;
        }

        track.addEventListener('pointerdown', pointerDown);
        window.addEventListener('pointermove', pointerMove);
        window.addEventListener('pointerup', pointerUp);
        track.addEventListener('pointercancel', pointerUp);
      })();

      // Initial sizes
      setTimeout(updateSizes, 60);
    });
  }

  // init on DOM ready
  if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', initSliders);
  } else {
    initSliders();
  }

  // If new content is loaded via AJAX on the wiki, re-init
  if (window.mw && mw.hook) {
    mw.hook('wikipage.content').add(initSliders);
  }
})();

/* Full width Slider */
(function () {
  function initFullSliders() {
    var sliders = document.querySelectorAll('.mw-fullslider');
    sliders.forEach(function (slider) {
      if (slider._fullSliderInit) return;
      slider._fullSliderInit = true;

      var track = slider.querySelector('.mw-fullslider-track');
      var slides = Array.from(track.children);
      var btnPrev = slider.querySelector('.mw-fullslider-btn.prev');
      var btnNext = slider.querySelector('.mw-fullslider-btn.next');
      var dots = Array.from(slider.querySelectorAll('.mw-fullslider-dots .dot'));

      var current = 0;
      var slideCount = slides.length;

      function goTo(index, animate) {
        current = (index % slideCount + slideCount) % slideCount;
        var x = -current * slider.clientWidth;
        if (animate === false) track.style.transition = 'none';
        else track.style.transition = '';
        track.style.transform = 'translateX(' + x + 'px)';
        updateControls();
        if (animate === false) {
          // force reflow then restore
          void track.offsetWidth;
          track.style.transition = '';
        }
      }

      function updateControls() {
        if (btnPrev) btnPrev.disabled = false;
        if (btnNext) btnNext.disabled = false;
        // update dots
        dots.forEach(function (d) {
          d.classList.toggle('active', +d.getAttribute('data-index') === current);
          d.setAttribute('aria-pressed', (+d.getAttribute('data-index') === current).toString());
        });
      }

      if (btnPrev) btnPrev.addEventListener('click', function () { goTo(current - 1); });
      if (btnNext) btnNext.addEventListener('click', function () { goTo(current + 1); });

      dots.forEach(function (dot) {
        dot.addEventListener('click', function () {
          var idx = parseInt(this.getAttribute('data-index'), 10);
          goTo(idx);
        });
      });

      // Resize handling: ensure slide width matches viewport
      var resizeTimer;
      function onResize() {
        clearTimeout(resizeTimer);
        resizeTimer = setTimeout(function () {
          // Recompute translate in case width changed
          goTo(current, false);
        }, 80);
      }
      window.addEventListener('resize', onResize);

      // Touch / drag support
      (function () {
        var startX = 0, startTranslate = 0, dragging = false, pointerId = null;
        function down(e) {
          if (e.pointerType === 'mouse' && e.button !== 0) return;
          dragging = true;
          pointerId = e.pointerId;
          startX = e.clientX;
          var style = window.getComputedStyle(track);
          var matrix = new WebKitCSSMatrix(style.transform);
          startTranslate = matrix.m41 || 0;
          track.style.transition = 'none';
          e.target.setPointerCapture && e.target.setPointerCapture(pointerId);
        }
        function move(e) {
          if (!dragging || e.pointerId !== pointerId) return;
          var dx = e.clientX - startX;
          track.style.transform = 'translateX(' + (startTranslate + dx) + 'px)';
        }
        function up(e) {
          if (!dragging || e.pointerId !== pointerId) return;
          dragging = false;
          track.style.transition = '';
          var dx = e.clientX - startX;
          var threshold = Math.max(40, slider.clientWidth * 0.12);
          if (dx > threshold) goTo(current - 1);
          else if (dx < -threshold) goTo(current + 1);
          else goTo(current);
          try { e.target.releasePointerCapture(pointerId); } catch (err) {}
          pointerId = null;
        }
        track.addEventListener('pointerdown', down);
        window.addEventListener('pointermove', move);
        window.addEventListener('pointerup', up);
        track.addEventListener('pointercancel', up);
      })();

      // Autoplay (optional): change interval or set autoplay = false
      var autoplay = true;
      var autoplayInterval = 4500; // ms
      var autoplayTimer = null;
      function startAutoplay() {
        if (!autoplay) return;
        stopAutoplay();
        autoplayTimer = setInterval(function () { goTo(current + 1); }, autoplayInterval);
      }
      function stopAutoplay() {
        if (autoplayTimer) { clearInterval(autoplayTimer); autoplayTimer = null; }
      }
      slider.addEventListener('mouseenter', stopAutoplay);
      slider.addEventListener('mouseleave', startAutoplay);
      slider.addEventListener('focusin', stopAutoplay);
      slider.addEventListener('focusout', startAutoplay);

      // Respect prefers-reduced-motion
      var rmq = window.matchMedia('(prefers-reduced-motion: reduce)');
      if (rmq.matches) autoplay = false;

      // Ensure initial sizing: make each slide exactly slider.clientWidth
      function layoutSlides() {
        var w = slider.clientWidth;
        slides.forEach(function (s) { s.style.minWidth = w + 'px'; s.style.maxWidth = w + 'px'; });
        goTo(current, false);
      }

      // Wait for images then layout
      function imgsReady(cb) {
        var imgs = Array.from(slider.querySelectorAll('img'));
        var rem = imgs.length;
        if (!rem) return cb();
        imgs.forEach(function (img) {
          if (img.complete) { rem--; if (!rem) cb(); }
          else {
            img.addEventListener('load', function () { rem--; if (!rem) cb(); });
            img.addEventListener('error', function () { rem--; if (!rem) cb(); });
          }
        });
      }

      imgsReady(function () {
        layoutSlides();
        startAutoplay();
        window.addEventListener('resize', onResize);
      });

      // expose for debug (optional)
      slider._goTo = goTo;
    });
  }

  if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', initFullSliders);
  else initFullSliders();

  if (window.mw && mw.hook) mw.hook('wikipage.content').add(initFullSliders);
})();





 
/* ============================================================
   Admin Tools visibility controller (Chameleon-safe)
   ============================================================ */

mw.loader.using(['mediawiki.user'], function () {

    mw.user.getGroups().then(function (groups) {

        // Your REAL admin groups (from screenshot)
        var adminGroups = [
            'administrator',
            'bureaucrat',
            'interface-admin',
            'pageownership-admin',
            'sysop'
        ];

        var isAdmin = adminGroups.some(function (g) {
            return groups.includes(g);
        });

        if (isAdmin) {
            document.body.classList.add('show-tools-admin');
        }

    });

});






 




// == Showing Template as default footer
// == Custom footer by language
$(function () {

    // Get clean path
    var path = window.location.pathname.replace(/^\/+|\/+$/g, '');
    var parts = path.split('/');

    // Language detection
    var lang = 'en'; // default
    if (parts.length > 0) {
        var first = parts[0].toLowerCase();
        if (first === 'hi') lang = 'hi';
        else if (first === 'ta') lang = 'ta';
    }

    // Footer template map
    var templateMap = {
        en: 'Custom-footer',
        hi: 'Custom-footer-Hindi',
        ta: 'Custom-footer-Tamil'
    };

    var template = templateMap[lang] || templateMap.en;

    const observer = new MutationObserver(function () {

        if (
            $('.footercontainer.container #footer-info').length &&
            $('.footercontainer.container #footer-places').length
        ) {

            // Remove default footer
            $('.footercontainer.container').remove();

            // Load footer template
            $.ajax({
                url: mw.util.getUrl('Template:' + template, { action: 'render' }),
                type: 'GET',
                success: function (html) {

                    if (!$('#mw-custom-footer').length) {
                        $('<div id="mw-custom-footer"></div>')
                            .html(html)
                            .appendTo('body');
                    }
                }
            });

            observer.disconnect();
        }
    });

    observer.observe(document.body, {
        childList: true,
        subtree: true
    });

});

















// Related_links button mobile

$(document).ready(function () {
    var box = $('.related-links-box');

    if (box.length) {
        $('body').on('click', '.related-links-box h2', function () {
            box.toggleClass('open');
        });
    }
});


//Category list show more button
$(document).ready(function () {
    $('.show-all-categories').on('click', function () {
        $('.category-full-list').slideDown(200);
        $(this).hide();
    });
});




// Latest article  Creation UI

$(document).ready(function () {

    // Only run on this page
    if (mw.config.get("wgPageName") !== "LatestArticlesUpdate") return;

    console.log("Latest Articles Editor Loaded (ES5)");

    // Load JSON from page
    new mw.Api().get({
        action: "query",
        titles: "LatestArticlesJSON",
        prop: "revisions",
        rvprop: "content",
        formatversion: "2"
    }).done(function (res) {

        var content = res.query.pages[0].revisions[0].content || "[]";
        var data = [];

        try { 
            data = JSON.parse(content); 
        } catch (e) {}

        // Prefill boxes
        for (var i = 0; i < data.length; i++) {
            $("#title" + (i + 1)).val(data[i].title);
            $("#link" + (i + 1)).val(data[i].link);
        }

    });


    // SAVE BUTTON CLICK
    $("#saveLatestArticlesBtn").click(function () {

        var items = [];
        var i, t, l;

        for (i = 1; i <= 10; i++) {

            t = $("#title" + i).val().trim();
            l = $("#link" + i).val().trim();

            if (t !== "" && l !== "") {
                items.push({ title: t, link: l });
            }
        }

        // Save JSON back to storage page
        new mw.Api().postWithEditToken({
            action: "edit",
            title: "LatestArticlesJSON",
            text: JSON.stringify(items, null, 2),
            summary: "Updated Latest Articles"
        }).done(function () {
            $("#saveStatus").html("<b style='color:green;'>✔ Saved successfully!</b>");
        });

    });

});



// To show updated articles in Frontpage

mw.hook("wikipage.content").add(function () {

    // Run only if div exists
    if (!document.getElementById("LatestArticlesOutput")) return;

    console.log("Homepage Latest Articles Loader Running");

    new mw.Api().get({
        action: "query",
        titles: "LatestArticlesJSON",
        prop: "revisions",
        rvprop: "content",
        formatversion: "2"
    }).done(function (res) {

        var content = res.query.pages[0].revisions[0].content || "[]";
        var data = [];

        try { data = JSON.parse(content); } catch(e){}

        var out = $("#LatestArticlesOutput");
        out.html("");

        for (var i = 0; i < data.length; i++) {
            var row = data[i];
            out.append(
                '<a class="latestArticleBtn" href="' + row.link + '">' +
                row.title +
                '</a>'
            );
        }
    });

});









// Paste this into MediaWiki:Common.js
mw.loader.using('ext.visualEditor.core').then(function () {

    // Small helper: detect domain-like text (gives true for "example.com", "sub.example.co", "example.com/path")
    function looksLikeDomain(text) {
        if (!text) return false;
        // allow www., allow path after TLD, basic heuristic
        return /\b([a-z0-9-]+\.)+[a-z]{2,}(\/\S*)?/i.test(text.trim());
    }

    // Normalize a domain-like text into an https:// URL
    function buildExternalFromText(text) {
        text = (text || '').trim();
        if (!text) return null;
        // If already has protocol, return as-is
        if (/^\w+:\/\//.test(text)) return text;
        // Remove leading slashes if VE added them (e.g. "/Writerpage1/gulfplacement.com")
        text = text.replace(/^\/+/, '');
        // If text contains ' ' (label + url), try to extract domain-like chunk
        var match = text.match(/([a-z0-9-]+\.)+[a-z]{2,}(\/\S*)?/i);
        if (match) {
            return 'https://' + match[0];
        }
        return 'https://' + text;
    }

    function ensureExternalAnchorsInSurface(surface) {
        if (!surface || !surface.$element) return function () {};
        var $el = surface.$element;
        var observer = null;
        var repeatTimer = null;
        var repeatCount = 0;
        var MAX_REPEAT = 30; // ~30 * 200ms = 6s of retries

        // fix anchors that VE rewrote into internal
        function fixAnchorsOnce() {
            try {
                $el.find('a').each(function () {
                    var a = this;
                    var href = a.getAttribute('href') || '';
                    var text = a.textContent || a.innerText || '';
                    // If href is relative or missing protocol AND the visible link text looks like a domain
                    if (href && !/^\w+:\/\//.test(href) && looksLikeDomain(text)) {
                        var newHref = buildExternalFromText(text);
                        if (newHref) {
                            a.setAttribute('href', newHref);
                            // mark so we don't process again
                            a.setAttribute('data-external-forced', '1');
                            // show as external in VE dialog as well by adding rel
                            a.setAttribute('rel', 'noopener noreferrer');
                        }
                    }
                    // Some VE-created anchors may have href like "/Writerpage1/gulfplacement.com" or "Writerpage1/gulfplacement.com"
                    // The above logic catches those since href lacks protocol and text contains domain-like string.
                });
            } catch (e) {
                // ignore
                console.log('fixAnchorsOnce error', e);
            }
        }

        // Start a short-lived repetition (overrides background rewrite that runs after a delay)
        function startRepeater() {
            repeatCount = 0;
            if (repeatTimer) {
                clearInterval(repeatTimer);
            }
            repeatTimer = setInterval(function () {
                fixAnchorsOnce();
                repeatCount++;
                if (repeatCount >= MAX_REPEAT) {
                    clearInterval(repeatTimer);
                    repeatTimer = null;
                }
            }, 200);
        }

        // MutationObserver to catch transform events quickly
        try {
            observer = new MutationObserver(function () {
                fixAnchorsOnce();
            });
            observer.observe($el[0], { childList: true, subtree: true, attributes: true });
        } catch (e) {
            // MutationObserver not available? fallback to simple interval already used above
            console.log('MutationObserver not available', e);
        }

        // return cleanup function (if needed)
        return function cleanup() {
            if (observer) {
                try { observer.disconnect(); } catch (e) {}
                observer = null;
            }
            if (repeatTimer) {
                clearInterval(repeatTimer);
                repeatTimer = null;
            }
        };
    }

    // Wait for VE to initialize and then attach paste handler and anchor-fixer
    ve.init.platform.addReadyCallback(function () {
        try {
            var surface = ve.init.target.getSurface();
            if (!surface || !surface.$element) {
                return;
            }

            // Disable as many auto-internal-link hooks as reasonable client-side (best-effort)
            try {
                if (ve.ui && ve.ui.MWInternalLinkAnnotation) {
                    if (ve.ui.MWInternalLinkAnnotation.static) {
                        ve.ui.MWInternalLinkAnnotation.static.matchFunction = function () { return false; };
                    }
                }
                if (ve.dm && ve.dm.MWInternalLinkAnnotation && ve.dm.MWInternalLinkAnnotation.static) {
                    ve.dm.MWInternalLinkAnnotation.static.matchRdfaTypes = [];
                    ve.dm.MWInternalLinkAnnotation.static.matchFunction = function () { return false; };
                }
                if (ve.ce && ve.ce.MWInternalLinkAnnotation && ve.ce.MWInternalLinkAnnotation.static) {
                    ve.ce.MWInternalLinkAnnotation.static.matchRdfaTypes = [];
                }
            } catch (err) {
                // ignore if internals differ
                console.log('VE annotator override error', err);
            }

            // Attach paste handler
            surface.$element.on('paste', function (e) {
                try {
                    var data = (e.originalEvent || e).clipboardData;
                    if (!data) return;
                    var text = data.getData('text/plain');
                    if (!text) return;

                    // Prevent default paste (we will insert cleaned text)
                    e.preventDefault();

                    // Convert any URLs to explicit external link syntax in the pasted text
                    // This helps VE treat them as external when inserted
                    text = text.replace(/https?:\/\/[^\s]+/g, function (url) {
                        return '[' + url + ']';
                    });

                    // Insert cleaned text into VE surface model (best-effort simple insertion)
                    try {
                        // Use VE model insertion if available
                        if (surface.model && surface.model.getFragment) {
                            surface.model.getFragment().insertContent(text);
                        } else {
                            // fallback to inserting into contenteditable DOM
                            document.execCommand('insertText', false, text);
                        }
                    } catch (insertErr) {
                        // fallback DOM insert
                        document.execCommand('insertText', false, text);
                    }

                    // Immediately run anchor fixer and start repeating to defeat delayed rewriter
                    var cleanup = ensureExternalAnchorsInSurface(surface);
                    // ensure cleanup after a while (we already limit repeats inside ensureExternalAnchorsInSurface)
                    setTimeout(function () {
                        if (typeof cleanup === 'function') {
                            try { cleanup(); } catch (e) {}
                        }
                    }, 8000); // give it up to 8s (safe upper bound) then cleanup
                    // also start immediate fight-back
                    // (the internal ensureExternalAnchorsInSurface already schedules repeats)
                } catch (outerE) {
                    console.log('paste handler error', outerE);
                }
            });

            // Extra safety: run an initial pass on load to correct any existing bad anchors
            try {
                setTimeout(function () {
                    ensureExternalAnchorsInSurface(surface);
                }, 500);
            } catch (e) {
                // ignore
            }

        } catch (e) {
            console.log('VE addReadyCallback error', e);
        }
    });

});












//Showing SEO button in Visual Editor
(function () {
mw.loader.using([
    'ext.visualEditor.desktopArticleTarget.init',
    'oojs-ui',
    'jquery',
    'mediawiki.api'
]).done(function () {

    /* --------------------------------------------------
       Add "SEO" button to VisualEditor toolbar
    -------------------------------------------------- */
    if (mw.config.get('wgIsArticle')) {
        mw.hook('ve.activationComplete').add(function () {
            try {
                var veTarget = ve.init && ve.init.target;
                var toolbar = veTarget && veTarget.toolbar;
                if (!toolbar) return;
                if (toolbar.$element.find('.ve-ui-toolbar-seoButton').length) return;

                var seoButton = new OO.ui.ButtonWidget({
                    label: 'SEO',
                    icon: 'settings',
                    classes: ['ve-ui-toolbar-seoButton']
                });

                seoButton.on('click', function () {
                    openSEODialog(veTarget);
                });

                toolbar.$element.find('.oo-ui-toolbar-actions').append(seoButton.$element);

            } catch (e) {
                console.error('[SEO] Toolbar init error:', e);
            }
        });
    }


    /* --------------------------------------------------
         SEO Dialog
    -------------------------------------------------- */
    function openSEODialog(veTarget) {

        var surface = veTarget.surface;
        var model = surface.getModel();
        var docText = '';

        try {
            docText = model.getDocument().data.getText(true);
        } catch (e) {
            var txt = document.getElementById('wpTextbox1');
            if (txt) docText = txt.value;
        }

        // Extract existing SEO comment
        var oldTitle = '', oldDesc = '', oldKeys = '';
        var match = /<!--\s*SEO\s+title=(.*?)\s+description=([\s\S]*?)\s+keywords=([\s\S]*?)\s*-->/i.exec(docText);

        if (match) {
            oldTitle = match[1].trim();
            oldDesc  = match[2].trim();
            oldKeys  = match[3].trim();
        }

        // Input fields
        var titleInput = new OO.ui.TextInputWidget({ placeholder: 'Meta Title', value: oldTitle });
        var descInput  = new OO.ui.MultilineTextInputWidget({ placeholder: 'Meta Description', rows: 3, value: oldDesc });
        var keysInput  = new OO.ui.TextInputWidget({ placeholder: 'Meta Keywords', value: oldKeys });


        // Create Dialog
        var SEODialog = function (cfg) { SEODialog.super.call(this, cfg); };
        OO.inheritClass(SEODialog, OO.ui.ProcessDialog);

        SEODialog.static.name = 'seoDialog';
        SEODialog.static.title = 'SEO Settings';
        SEODialog.static.size = 'larger';
        SEODialog.static.actions = [
            { action: 'save',   label: 'Save',   flags: ['primary', 'progressive'] },
            { action: 'cancel', label: 'Cancel', flags: ['safe'] }
        ];

        SEODialog.prototype.initialize = function () {
            SEODialog.super.prototype.initialize.apply(this, arguments);

            var panel = new OO.ui.PanelLayout({ padded: true, expanded: true });

            panel.$element.append(
                $('<label>').text('Meta Title'),
                titleInput.$element, $('<br><br>'),

                $('<label>').text('Meta Description'),
                descInput.$element, $('<br><br>'),

                $('<label>').text('Meta Keywords'),
                keysInput.$element
            );

            this.$body.append(panel.$element);
        };

        SEODialog.prototype.getBodyHeight = function () {
            return 420;
        };


        /* --------------------------------------------------
           FINAL FIX: SAFE SAVE USING API
           (DO NOT CHANGE VE CONTENT)
        -------------------------------------------------- */
        SEODialog.prototype.getActionProcess = function (action) {

            var dialog = this;

            if (action === 'save') {
                return new OO.ui.Process(function () {

                    var t = titleInput.getValue().trim();
                    var d = descInput.getValue().trim();
                    var k = keysInput.getValue().trim();

                    // New SEO block
                  var newSEO =
    "<!--SEO title=\"" + mw.html.escape(t) +
    "\" description=\"" + mw.html.escape(d) +
    "\" keywords=\"" + mw.html.escape(k) +
    "\" -->";

                    var api = new mw.Api();

                    // STEP 1: Load TRUE source wikitext (not VE text)
                    api.get({
                        action: "query",
                        prop: "revisions",
                        rvprop: "content",
                        formatversion: "2",
                        titles: mw.config.get("wgPageName")
                    }).done(function (data) {

                        var page = data.query.pages[0];
                        var source = page.revisions[0].content;

                        // STEP 2: Remove old SEO block
                        source = source.replace(/<!--SEO[\s\S]*?-->/i, "");

                        // STEP 3: Add new SEO at very top
                        source = newSEO + "\n" + source;

                        // STEP 4: Save back safely
                        api.postWithToken("csrf", {
                            action: "edit",
                            title: mw.config.get("wgPageName"),
                            text: source,
                            summary: "Updated SEO metadata"
                        }).done(function () {
                            mw.notify("✔ SEO updated successfully");
                            location.reload();
                        });
                    });

                    dialog.close();
                });
            }

            if (action === 'cancel') {
                return new OO.ui.Process(function () {
                    dialog.close();
                });
            }
        };


        // --------------------------------------------------
        // Open dialog
        // --------------------------------------------------
        var wm = window._seoWM || new OO.ui.WindowManager();
        if (!window._seoWM) {
            window._seoWM = wm;
            $(document.body).append(wm.$element);
        }

        var dlg = new SEODialog();
        wm.addWindows([dlg]);
        wm.openWindow(dlg);
    }

});
})();












/* ====================================================================
   VISUALEDITOR – Writer Tools (Common.js Edition)
   Word Count, Character Count, Reading Time
   With Close/Open Button
   ES5 Compatible | MediaWiki 1.39.x
   ==================================================================== */

(function () {

    if (window.WT_Loaded) return;
    window.WT_Loaded = true;

    var PANEL_ID = 'wt-ve-panel';
    var WPM = 200;  // words per minute reading speed
    var updateTimeout = null;
    var surfaceRef = null;

    /* --------------------------------------------------------------
       Create Writer Tools Panel + Open Button
    -------------------------------------------------------------- */
    function createPanel() {
        if (document.getElementById(PANEL_ID)) return;

        /* Main Panel */
        var panel = document.createElement('div');
        panel.id = PANEL_ID;
        panel.style.position = 'fixed';
        panel.style.left = '20px';
        panel.style.bottom = '20px';
        panel.style.padding = '12px 16px';
        panel.style.fontSize = '13px';
        panel.style.background = '#fff';
        panel.style.border = '1px solid #ccc';
        panel.style.borderRadius = '8px';
        panel.style.boxShadow = '0 2px 10px rgba(0,0,0,0.1)';
        panel.style.zIndex = '2000';
        panel.style.minWidth = '220px';

        /* Close Button (X) */
        var closeBtn = document.createElement('div');
        closeBtn.textContent = '✕';
        closeBtn.style.position = 'absolute';
        closeBtn.style.top = '4px';
        closeBtn.style.right = '8px';
        closeBtn.style.cursor = 'pointer';
        closeBtn.style.fontSize = '14px';
        closeBtn.style.color = '#666';
        closeBtn.onclick = function () {
            panel.style.display = 'none';
            document.getElementById('wt-open-btn').style.display = 'block';
        };
        panel.appendChild(closeBtn);

        /* Title */
        var title = document.createElement('div');
        title.textContent = 'Writer Tools';
        title.style.fontWeight = 'bold';
        title.style.marginBottom = '6px';
        panel.appendChild(title);

        /* Stats */
        var wordsDiv = document.createElement('div');
        wordsDiv.id = 'wt-words';
        wordsDiv.textContent = 'Words: 0';
        panel.appendChild(wordsDiv);

        var charsDiv = document.createElement('div');
        charsDiv.id = 'wt-chars';
        charsDiv.textContent = 'Characters: 0';
        panel.appendChild(charsDiv);

        var rtDiv = document.createElement('div');
        rtDiv.id = 'wt-rt';
        rtDiv.textContent = 'Reading time: 0 min';
        panel.appendChild(rtDiv);

        document.body.appendChild(panel);

        /* OPEN Button (Floating) */
        var openBtn = document.createElement('div');
        openBtn.id = 'wt-open-btn';
        openBtn.textContent = 'Writer Tools';
        openBtn.style.position = 'fixed';
        openBtn.style.left = '20px';
        openBtn.style.bottom = '20px';
        openBtn.style.padding = '6px 10px';
        openBtn.style.background = '#0366d6';
        openBtn.style.color = '#fff';
        openBtn.style.borderRadius = '6px';
        openBtn.style.cursor = 'pointer';
        openBtn.style.fontSize = '12px';
        openBtn.style.zIndex = '2001';
        openBtn.style.display = 'none';
        openBtn.onclick = function () {
            panel.style.display = 'block';
            openBtn.style.display = 'none';
        };

        document.body.appendChild(openBtn);
    }

    /* --------------------------------------------------------------
       Text Utilities
    -------------------------------------------------------------- */
    function cleanText(str) {
        if (!str) return '';
        str = str.replace(/\u00A0/g, ' '); // non-breaking spaces
        return str.replace(/\s+/g, ' ').trim();
    }

    function countWords(str) {
        if (!str) return 0;
        var arr = str.split(/\s+/);
        var c = 0;
        for (var i = 0; i < arr.length; i++) {
            if (/[A-Za-z0-9]/.test(arr[i])) c++;
        }
        return c;
    }

    function countChars(str) {
        return str ? str.length : 0;
    }

    /* --------------------------------------------------------------
       Update Panel Stats
    -------------------------------------------------------------- */
    function updateStats() {
        if (!surfaceRef) return;

        try {
            var view = surfaceRef.getView();
            if (!view) return;

            var text = view.$element.text() || '';
            text = cleanText(text);

            var words = countWords(text);
            var chars = countChars(text);
            var minutes = Math.ceil(words / WPM);
            if (minutes < 1) minutes = 1;

            document.getElementById('wt-words').textContent = 'Words: ' + words;
            document.getElementById('wt-chars').textContent = 'Characters: ' + chars;
            document.getElementById('wt-rt').textContent = 'Reading time: ' + minutes + ' min';
        } catch (err) {
            console.error('WriterTools update error:', err);
        }
    }

    /* --------------------------------------------------------------
       Attach to VisualEditor surface
    -------------------------------------------------------------- */
    function attachToVE() {
        try {
            surfaceRef = null;

            if (
                ve &&
                ve.init &&
                ve.init.target &&
                ve.init.target.getSurface
            ) {
                surfaceRef = ve.init.target.getSurface();
            }

            if (!surfaceRef) return;

            createPanel();
            updateStats();

            /* Update while typing (throttle 200ms) */
            surfaceRef.getModel().on('transact', function () {
                if (updateTimeout) clearTimeout(updateTimeout);
                updateTimeout = setTimeout(updateStats, 200);
            });

        } catch (e) {
            console.error('WriterTools attach error:', e);
        }
    }

    /* --------------------------------------------------------------
       Trigger when VE activates
    -------------------------------------------------------------- */
    mw.hook('ve.activationComplete').add(function () {
        setTimeout(attachToVE, 400);
    });

})();












// Start of Page Creation UI


(function () {

    // ===================================================================
    //  A) FLOATING CREATE BUTTON UI (unchanged, ES5)
    // ===================================================================
    function initCreateButton() {
        if (typeof mw === 'undefined') return;
        if (!mw.config.get('wgUserName')) return;

        var groups = mw.config.get('wgUserGroups') || [];
        var isEditor = groups.indexOf('editor') !== -1;
        var isAdmin  = groups.indexOf('sysop') !== -1;
        var isWriter = groups.indexOf('writer') !== -1;
        var isLead   = groups.indexOf('lead')   !== -1;

        // Only Writers, Leads, Editors, Admins can use UI
        if (!(isWriter || isLead || isEditor || isAdmin)) return;

        // Avoid duplicate button
        if (document.getElementById('floatingCreateBtn')) return;

        // Floating button
        var btn = document.createElement('button');
        btn.id = 'floatingCreateBtn';
        btn.appendChild(document.createTextNode('+'));

        btn.style.cssText =
            'position:fixed;bottom:30px;right:30px;width:60px;height:60px;' +
            'border-radius:50%;background:#0078D7;color:#fff;font-size:32px;' +
            'border:none;cursor:pointer;z-index:9999;box-shadow:0 4px 10px rgba(0,0,0,0.3);';

        btn.onmouseover = function () { btn.style.background = '#005fa3'; };
        btn.onmouseout  = function () { btn.style.background = '#0078D7'; };

        document.body.appendChild(btn);

        // Popup box
        var popup = document.createElement('div');
        popup.id = 'createPopupBox';
        popup.style.cssText =
            'position:fixed;bottom:100px;right:30px;background:#fff;padding:15px;' +
            'border:2px solid #ccc;border-radius:12px;width:300px;display:none;' +
            'box-shadow:0 6px 14px rgba(0,0,0,0.25);z-index:10000;';

        var html = '';

        // Language selector
        html += '<label style="font-weight:bold;">Select Language</label><br>';
        html += '<select id="pageLang" style="width:100%;padding:6px;margin:6px 0;border:1px solid #ccc;border-radius:5px;">';
        html += '<option value="en">English</option>';
        html += '<option value="hi">Hindi</option>';
        html += '<option value="ta">Tamil</option>';
        html += '</select>';

        // Page creation
        html += '<label style="font-weight:bold;">Create new page</label>';
        html += '<input type="text" id="newPageName" placeholder="Enter page name"' +
                'style="width:100%;padding:6px;margin-top:4px;border:1px solid #ccc;border-radius:5px;">';

        html += '<button id="createPageBtn" style="margin-top:8px;width:100%;background:#0078D7;' +
                'color:white;border:none;padding:6px;border-radius:6px;cursor:pointer;">Create Page</button>';

        // ---------- CATEGORY CREATION (Editor/Admin only) ----------
        if (isEditor || isAdmin) {
            html += '<hr style="margin:10px 0;">';
            html += '<label style="font-weight:bold;">Create new category</label>';
            html += '<input type="text" id="newCategoryName" placeholder="Enter category name"' +
                    'style="width:100%;padding:6px;margin-top:4px;border:1px solid #ccc;border-radius:5px;">';

            html += '<button id="createCategoryBtn" style="margin-top:8px;width:100%;background:#28a745;' +
                    'color:white;border:none;padding:6px;border-radius:6px;cursor:pointer;">Create Category</button>';
        }

        // Dashboard + Mapping
        html += '<hr style="margin:10px 0;">';
        html += '<button id="openDashboardBtn" style="width:100%;background:#6f42c1;color:white;' +
                'border:none;padding:6px;border-radius:6px;cursor:pointer;">Dashboard</button>';

        html += '<button id="openMappingBtn" style="width:100%;background:#ff5733;color:white;' +
                'border:none;padding:6px;border-radius:6px;margin-top:6px;cursor:pointer;">Mapping</button>';

        popup.innerHTML = html;
        document.body.appendChild(popup);

        // Toggle popup
        btn.onclick = function () {
            popup.style.display = (popup.style.display === 'none') ? 'block' : 'none';
        };

        // Popup click handler
        popup.onclick = function (ev) {
            var target = ev.target;

            // Create normal page
            if (target.id === 'createPageBtn') {
                var lang = document.getElementById('pageLang').value;
                var name = document.getElementById('newPageName').value.trim();

                if (!name) return alert("Please enter a page name");

                var t = name.replace(/\s+/g, '_');
                var url = (lang === 'hi') ? '/Hi/' + t :
                          (lang === 'ta') ? '/Ta/' + t :
                                            '/' + t;

                // No categories needed here; creating new page directly
                window.location.href = url + '?action=edit&veaction=edit';
            }

            // Create category
            if (target.id === 'createCategoryBtn') {
                var cat = document.getElementById('newCategoryName').value.trim();
                if (!cat) return alert("Please enter a category name");

                var cTitle = 'Category:' + cat.replace(/\s+/g, '-');
                window.location.href = '/' + cTitle + '?action=edit&veaction=edit';
            }

            if (target.id === 'openDashboardBtn')
                window.location.href = '/Special:TeamDashboard';

            if (target.id === 'openMappingBtn')
                window.location.href = '/Special:TeamMapping';
        };
    }

    if (document.readyState === 'complete' || document.readyState === 'interactive')
        initCreateButton();
    else
        document.addEventListener('DOMContentLoaded', initCreateButton);


    // ===================================================================
    //  B) CATEGORY + PARENT PAGE LOGIC (Original + Multilingual Enhancements)
    //     including category inheritance helpers
    // ===================================================================
    mw.loader.using(['mediawiki.util', 'mediawiki.api']).then(function () {

        var api   = new mw.Api();
        var user  = mw.config.get('wgUserName');
        if (!user) return;

        var ns     = mw.config.get('wgNamespaceNumber');
        var title  = mw.config.get('wgTitle')    || '';
        var pagenm = mw.config.get('wgPageName') || '';
        var action = mw.config.get('wgAction')   || '';
        var revId  = mw.config.get('wgCurRevisionId') || 0;

        var urlParams   = new URLSearchParams(location.search);
        var autoCat     = urlParams.get('autocategory');
        var skipParent  = urlParams.get('skipparent');

        // ---------------------------------------------------------------
        //  CATEGORY PROMPT (UNCHANGED)
        // ---------------------------------------------------------------
        if (ns === 14) {   // Category namespace
            var key = "category_prompt_done_" + title;

            function askCategory() {
                if (sessionStorage.getItem(key)) return;
                sessionStorage.setItem(key, 'done');

                var ask = confirm(
                    'You created category "' + title + '".\n\nCreate a page with this name?'
                );
                if (ask) {
                    window.location.href = mw.util.getUrl(title, {
                        action:'edit', veaction:'edit',
                        autocategory:title,
                        skipparent:1
                    });
                }
            }

            mw.hook('postEdit').add(askCategory);

            $(window).on('popstate', function () {
                if (action !== 'view') return;
                if (sessionStorage.getItem(key)) return;

                api.get({
                    action:'query',
                    titles:'Category:' + title,
                    prop:'info'
                }).done(function (res) {
                    var page = res.query.pages[Object.keys(res.query.pages)[0]];
                    if (page && !page.missing) askCategory();
                });
            });
        }

        // ---------------------------------------------------------------
        //  INJECT AUTOCATEGORY
        // ---------------------------------------------------------------
        function injectCategory(cat) {
            var box = $('#wpTextbox1');
            if (!box.length) return;

            var tag = '[[Category:' + cat + ']]';
            if (box.val().indexOf(tag) === -1)
                box.val(tag + '\n\n' + box.val());
        }

        if (autoCat) {
            $(function () { injectCategory(autoCat); });
            mw.hook('ve.activationComplete').add(function () { injectCategory(autoCat); });
        }

        // ---------------------------------------------------------------
        //  LANGUAGE DETECTION
        // ---------------------------------------------------------------
        var isHindi = pagenm.indexOf("Hi/") === 0;
        var isTamil = pagenm.indexOf("Ta/") === 0;
        var langPrefix = isHindi ? "Hi/" : (isTamil ? "Ta/" : "");

        // Remove prefix from title
        function stripPrefix(t) {
            return t.replace(/^Hi\//, "").replace(/^Ta\//, "");
        }

        // Build clean title
        function buildTitle(parent, child) {
            parent = stripPrefix(parent).replace(/^\/+|\/+$/g, "");
            child  = stripPrefix(child ).replace(/^\/+|\/+$/g, "");

            if (!parent)
                return langPrefix + child;

            return langPrefix + parent + "/" + child;
        }

        // ---------------------------------------------------------------
        //  HELPER: Get parent categories (exposed globally)
        // ---------------------------------------------------------------
        window.getParentCategories = function (parentTitle, callback) {
            if (!parentTitle) {
                callback([]);
                return;
            }
            api.get({
                action: "query",
                prop: "categories",
                titles: parentTitle,
                cllimit: "max"
            }).done(function (res) {
                try {
                    var page = res.query.pages[Object.keys(res.query.pages)[0]];
                    var cats = [];
                    if (page && page.categories) {
                        for (var i = 0; i < page.categories.length; i++) {
                            var c = page.categories[i].title.replace(/^Category:/, '');
                            cats.push(c);
                        }
                    }
                    callback(cats);
                } catch (ee) {
                    callback([]);
                }
            }).fail(function () {
                callback([]);
            });
        };

        // ---------------------------------------------------------------
        //  HELPER: Insert categories into editor (VE or Source)
        //  Runs once per stored inheritCategories session value
        // ---------------------------------------------------------------
        function applyInheritedCategories() {
            var data = sessionStorage.getItem("inheritCategories");
            if (!data) return;
            try {
                var cats = JSON.parse(data);
            } catch (e) {
                sessionStorage.removeItem("inheritCategories");
                return;
            }
            if (!cats || !cats.length) {
                sessionStorage.removeItem("inheritCategories");
                return;
            }

            // Remove to avoid double-insert
            sessionStorage.removeItem("inheritCategories");

            // Build wikitext fragment
            var wikitext = "";
            for (var i = 0; i < cats.length; i++) {
                wikitext += "[[Category:" + cats[i] + "]]\n";
            }

            // 1) If source editor present, insert there
            var box = document.getElementById("wpTextbox1");
            if (box) {
                box.value = wikitext + "\n" + box.value;
                return;
            }

            // 2) VisualEditor - try to insert using VE model
            try {
                if (window.ve && ve.init && ve.init.target) {
                    var surface = ve.init.target.getSurface();
                    if (!surface || !surface.getModel) {
                        // VE not ready yet: retry shortly
                        setTimeout(applyInheritedCategories, 200);
                        return;
                    }
                    var model = surface.getModel();
                    // Attempt insertion; wrap in try/catch to avoid breaking if APIs differ
                    try {
                        model.getDocument().transact(model.insertContent(wikitext + "\n"));
                    } catch (e) {
                        // As a fallback, try to insert by collapsing selection and inserting text
                        try {
                            var frag = model.getDocument().getFragment();
                            if (frag && frag.collapseToEnd) {
                                frag.collapseToEnd();
                                frag.insertContent("\n" + wikitext);
                            }
                        } catch (e2) {
                            // give up silently - categories already removed from session
                        }
                    }
                }
            } catch (ee) {
                // nothing
            }
        }

        // Run at DOM ready (source editor case)
        $(applyInheritedCategories);
        // Run after VE activation (VE case)
        mw.hook('ve.activationComplete').add(applyInheritedCategories);


        // ---------------------------------------------------------------
        //  PARENT PAGE MODAL (made global so VE can call it: window.askParent)
        //  + Cancel and click-outside behavior
        // ---------------------------------------------------------------
        window.askParent = function (child, done) {
            if (window.__parentModalShown) return;
            window.__parentModalShown = true;

            var o = $('<div>').css({
                position:'fixed',top:0,left:0,width:'100%',height:'100%',
                background:'rgba(0,0,0,0.5)',display:'flex',
                justifyContent:'center',alignItems:'center',
                zIndex:999999
            });

            var box = $('<div>').css({
                background:'#fff',padding:'20px',width:'420px',
                borderRadius:'10px',boxShadow:'0 4px 12px rgba(0,0,0,0.3)',
                position:'relative'
            });

            var input = $('<input type="text" placeholder="Search parent...">').css({
                width:'100%',padding:'8px',border:'1px solid #ccc',borderRadius:'6px'
            });

            var list = $('<ul>').css({
                listStyle:'none',padding:0,margin:'10px 0 0',
                maxHeight:'200px',overflowY:'auto',border:'1px solid #ddd',
                borderRadius:'6px',display:'none'
            });

            var btns = $('<div>').css({ marginTop:'10px', textAlign:'right' });

            var ok = $('<button>Confirm</button>').css({
                background:'#007bff',color:'#fff',padding:'8px 14px',
                border:'none',borderRadius:'6px',cursor:'pointer'
            });

            var skip = $('<button>No Parent</button>').css({
                background:'#6c757d',color:'#fff',padding:'8px 14px',
                border:'none',borderRadius:'6px',cursor:'pointer',
                marginLeft:'8px'
            });

            var cancel = $('<button>Cancel</button>').css({
                background:'#dc3545',color:'#fff',padding:'8px 14px',
                border:'none',borderRadius:'6px',cursor:'pointer',
                marginLeft:'8px'
            });

            btns.append(ok, skip, cancel);
            box.append('<h3>Select Parent Page</h3>', input, list, btns);
            o.append(box);
            $('body').append(o);

            var selected = '';

            input.on('input', function () {
                var q = $.trim(input.val());
                list.empty();
                if (!q) return list.hide();

                api.get({
                    action:'opensearch',
                    search: langPrefix + q,
                    limit: 12,
                    namespace:0
                }).done(function (res) {
                    var results = res[1] || [];
                    list.empty();
                    if (!results.length) return list.hide();
                    list.show();

                    for (var i = 0; i < results.length; i++) {
                        (function(pg){
                            var li = $('<li>').text(pg).css({
                                padding:'8px',cursor:'pointer'
                            }).hover(
                                function(){ $(this).css('background','#eee'); },
                                function(){ $(this).css('background',''); }
                            ).click(function(){
                                selected = pg;
                                input.val(stripPrefix(pg));
                                list.hide();
                            });
                            list.append(li);
                        })(results[i]);
                    }
                });
            });

            // Confirm
            ok.on('click', function () {
                var parent = selected || input.val().trim();
                if (!parent) return alert("Select parent or click No Parent");
                o.remove();
                window.__parentModalShown = false;
                done(parent);
            });

            // No Parent (explicitly remove parent)
            skip.on('click', function () {
                o.remove();
                window.__parentModalShown = false;
                done("");
            });

            // Cancel (do nothing)
            cancel.on('click', function () {
                o.remove();
                window.__parentModalShown = false;
                done(null);
            });

            // Click outside to close (acts like Cancel)
            o.on('click', function (e) {
                if (e.target === o[0]) {
                    o.remove();
                    window.__parentModalShown = false;
                    done(null);
                }
            });
        };

        // ---------------------------------------------------------------
        //  NEW PAGE HANDLING (when creating a new page via edit)
        //  — now stores parent categories into sessionStorage before redirect
        // ---------------------------------------------------------------
        function handleNewPage() {
            if (ns !== 0) return;
            if (action !== 'edit') return;
            if (revId !== 0) return;

            if (skipParent && autoCat) return;

            askParent(pagenm, function (parent) {
                // If user cancelled modal, parent === null
                if (parent === null) return;

                var finalTitle = buildTitle(parent, pagenm);

                // If parent given, fetch its categories and store for the new page
                if (parent) {
                    window.getParentCategories(parent, function(cats) {
                        try {
                            sessionStorage.setItem("inheritCategories", JSON.stringify(cats || []));
                        } catch (e) {}
                        // Redirect to edit (VE)
                        var url = mw.util.getUrl(finalTitle, { veaction:'edit' });
                        if (location.href !== url) location.href = url;
                    });
                } else {
                    // Explicit "No Parent" — clear parent, but still open editor
                    try { sessionStorage.removeItem("inheritCategories"); } catch (e) {}
                    var url2 = mw.util.getUrl(finalTitle, { veaction:'edit' });
                    if (location.href !== url2) location.href = url2;
                }
            });
        }

        if (action === 'edit') handleNewPage();
        mw.hook('ve.activationComplete').add(handleNewPage);

    }); // end mw.loader.using for Section B


    // ===================================================================
    //  C) CHANGE PARENT PAGE (VE PAGE SETTINGS ONLY) — CLEAN FINAL VERSION
    // ===================================================================
    mw.loader.using(['mediawiki.util', 'mediawiki.api']).then(function () {

        if (window.__changeParentInjected) return;
        window.__changeParentInjected = true;

        var ns      = mw.config.get('wgNamespaceNumber');
        var action  = mw.config.get('wgAction') || "";
        var revId   = mw.config.get('wgCurRevisionId') || 0;

        // only main namespace & editing
        if (ns !== 0) return;
        var isVE  = location.search.indexOf("veaction=edit") !== -1;
        var isEdit = action === "edit";
        if (!isVE && !isEdit) return;
        if (!revId) return;

        var groups = mw.config.get("wgUserGroups") || [];
        var allowed =
            groups.indexOf("writer") !== -1 ||
            groups.indexOf("lead")   !== -1 ||
            groups.indexOf("editor") !== -1 ||
            groups.indexOf("sysop")  !== -1;

        if (!allowed) return;


        // Use a robust wait for VE toolbar + settings popup
        function waitAndInject() {
            var $settingsTool = $('span.oo-ui-tool-name-settings');
            if (!$settingsTool.length) {
                return setTimeout(waitAndInject, 200);
            }

            var $toolGroup = $settingsTool.closest(".oo-ui-toolGroup-tools");
            if (!$toolGroup.length) {
                return setTimeout(waitAndInject, 200);
            }

            if ($("#ve-change-parent-tool").length) return;

            var $tool = $('<span>')
                .attr("id", "ve-change-parent-tool")
                .addClass("oo-ui-widget oo-ui-widget-enabled oo-ui-tool oo-ui-tool-link")
                .css({ display: "block" });

            var $link = $('<a>')
                .addClass("oo-ui-tool-link")
                .attr("tabindex", "0")
                .css({
                    display: "flex",
                    alignItems: "center",
                    padding: "6px"
                });

            // Use hierarchy icon to avoid overlap problems
            var $icon = $('<span>')
                .addClass("oo-ui-iconElement-icon oo-ui-icon-hierarchy")
                .css({ marginRight: "6px" });

            var $label = $('<span>')
                .addClass("oo-ui-tool-title")
                .text("Change Parent");

            $link.append($icon).append($label);
            $tool.append($link);
            $toolGroup.append($tool);

            // OOUI-safe click
            $link.on("click", function (e) {
                e.preventDefault();
                e.stopPropagation();

                if (typeof window.askParent !== "function") {
                    alert("Parent selector not ready. Reload editor.");
                    return;
                }

                var fullTitle = mw.config.get("wgPageName") || "";

                // Detect language prefix
                var prefix = "";
                if (fullTitle.indexOf("Hi/") === 0) prefix = "Hi/";
                if (fullTitle.indexOf("Ta/") === 0) prefix = "Ta/";

                var noPrefix = fullTitle.replace(/^Hi\//, "").replace(/^Ta\//, "");
                var parts = noPrefix.split("/");
                var child = parts[parts.length - 1];

                window.askParent(fullTitle, function (parent) {

                    // parent === null means cancel
                    if (parent === null) return;

                    parent = parent.replace(/^Hi\//, "")
                                   .replace(/^Ta\//, "")
                                   .trim();

                    var newTitle = parent ?
                        prefix + parent + "/" + child :
                        prefix + child;

                    newTitle = newTitle.replace(/\/+/g, "/");

                    if (newTitle === fullTitle) {
                        alert("Parent not changed.");
                        return;
                    }

                    // Before moving, fetch parent categories for the new parent path
                    var parentForCats = parent || "";
                    if (parentForCats) {
                        // store categories so new page gets them after move+redirect
                        window.getParentCategories(parentForCats, function (cats) {
                            try {
                                sessionStorage.setItem("inheritCategories", JSON.stringify(cats || []));
                            } catch (e) {}
                            // perform move
                            movePage(fullTitle, newTitle);
                        });
                    } else {
                        // No parent selected (move to top-level)
                        try { sessionStorage.removeItem("inheritCategories"); } catch (e) {}
                        movePage(fullTitle, newTitle);
                    }
                });
            });

            console.log("✔ Change Parent added to VE Page Settings");
        }

        // Wait for VE activation and then try injecting (more robust)
        mw.hook('ve.activationComplete').add(function () {
            waitAndInject();
        });
        // Also attempt immediately (in case VE already activated)
        setTimeout(waitAndInject, 300);


        // ===================================================================
        // MOVE PAGE (unchanged except parent-cats handling is done before calling movePage)
        // ===================================================================
        function movePage(oldTitle, newTitle) {

            var api = new mw.Api();

            api.postWithToken("csrf", {
                action: "move",
                from: oldTitle,
                to: newTitle,
                reason: "Updated parent structure",
                movetalk: 1,
                noredirect: 1
            })
            .done(function () {

                api.postWithToken("csrf", {
                    action: "delete",
                    title: oldTitle,
                    reason: "Old parent path cleaned after move"
                })
                .done(function () {
                    alert("Parent updated and old page deleted.");
                    // After move & delete we redirect to new title.
                    // applyInheritedCategories() will run on load because we pre-saved inheritCategories.
                    window.location.href = mw.util.getUrl(newTitle);
                })
                .fail(function (err) {
                    alert("Page moved, but deletion failed: " +
                        (err && err.error && err.error.info ? err.error.info : "Unknown"));
                    window.location.href = mw.util.getUrl(newTitle);
                });

            })
            .fail(function (err) {
                var msg = (err && err.error && err.error.info) ? err.error.info : "Unknown error";
                alert("Move failed: " + msg);
            });
        }

    }); // end Section C

})();

//End of Create page UI






// Start Team Dashboard Settings 

mw.loader.using(['mediawiki.api', 'mediawiki.util']).then(function () {
  (function ($) {
    /** ---------- Utility: escape HTML ---------- **/
    function escapeHtml(str) {
      return String(str)
        .replace(/&/g, '&amp;')
        .replace(/</g, '&lt;')
        .replace(/>/g, '&gt;')
        .replace(/"/g, '&quot;')
        .replace(/'/g, '&#39;');
    }

    /** ---------- Detect TeamDashboard ---------- **/
    var pageName = mw.config.get('wgCanonicalSpecialPageName') || '';
    if (String(pageName).toLowerCase() !== 'teamdashboard') return;

    /** ---------- Accordion UI ---------- **/
    function initAccordionOnce() {
      $('.accordion-toggle').each(function () {
        var $el = $(this);
        var targetSel = $el.attr('data-target');
        var $target = $(targetSel);
        if (!$target.length) return;

        if ($el.find('.pa-arrow').length === 0)
          

        var saved = localStorage.getItem('pa:' + targetSel);
        if (saved === 'open') {
          $target.show();
          $el.find('.pa-arrow').text('▼ ');
        }

        if (!$el.data('pa-bound')) {
          $el.on('click', function () {
            var open = $target.is(':visible');
            $target.toggle(!open);
            $el.find('.pa-arrow').text(open ? '▶ ' : '▼ ');
            localStorage.setItem('pa:' + targetSel, open ? 'closed' : 'open');
          });
          $el.data('pa-bound', true);
        }
      });
    }

    /** ---------- MediaWiki API ---------- **/
    var api = new mw.Api();
    function getFreshToken() {
      return api
        .get({
          action: 'query',
          meta: 'tokens',
          type: 'csrf',
          format: 'json'
        })
        .then(function (d) {
          return d.query.tokens.csrftoken || '';
        })
        .catch(function () {
          return '';
        });
    }

    /** ---------- POST helper ---------- **/
    function postAction(fd) {
      return $.ajax({
        url: mw.util.getUrl('Special:TeamDashboard'),
        type: 'POST',
        data: fd,
        processData: false,
        contentType: false,
        headers: { 'X-Requested-With': 'XMLHttpRequest' }
      });
    }

    /** ---------- Approve / Reject / Forward ---------- **/
    $(document).on('click', 'button[data-action]', function (e) {
      e.preventDefault();
      var $btn = $(this);
      if ($btn.prop('disabled')) return;

      var action = $btn.data('action'),
        pageId = $btn.data('page-id');

      $btn.prop('disabled', true);

      getFreshToken().then(function (token) {
        if (!token) {
          alert('Session expired, reload.');
          $btn.prop('disabled', false);
          return;
        }

        var fd = new FormData();
        fd.append('pa_action', action);
        fd.append('page_id', pageId);
        fd.append('token', token);

        if (action === 'forward') {
          var txt = $.trim($btn.closest('td').find('input.comment-input').val() || '');
          if (txt !== '') fd.append('comment', txt);
        }

        postAction(fd)
          .done(function () {
            if (action === 'approve') {
              $btn.text('Approved ✓').prop('disabled', true);
              $btn.closest('td').prev().prev().text('approved');
            } else if (action === 'reject') {
              $btn.text('Rejected ✗').prop('disabled', true);
              $btn.closest('td').prev().prev().text('rejected');
            } else if (action === 'forward') {
              $btn.text('Forwarded ➜').prop('disabled', true);
            }
          })
          .fail(function () {
            alert('Action failed. Please try again.');
            $btn.prop('disabled', false);
          });
      });
    });

    /** ---------- Comment submit ---------- **/
    $(document).on('click', '.comment-submit', function (e) {
      e.preventDefault();
      var $btn = $(this);
      if ($btn.prop('disabled')) return;

      var $wrap = $btn.closest('.ajax-comment-wrap'),
        txt = $.trim($wrap.find('input.comment-input').val() || ''),
        pageId = $btn.data('page-id');

      if (txt === '') return;
      $btn.prop('disabled', true);

      getFreshToken().then(function (token) {
        if (!token) {
          alert('Session expired, reload.');
          $btn.prop('disabled', false);
          return;
        }

        var fd = new FormData();
        fd.append('pa_action', 'comment');
        fd.append('page_id', pageId);
        fd.append('token', token);
        fd.append('comment', txt);

        postAction(fd)
          .done(function (resp) {
            var $td = $btn.closest('td');
            var $list = $td.find('.comment-list');
            if (!$list.length) {
              var box = $('<div class="comment-box"><b>Comments</b><ul class="comment-list"></ul></div>');
              $td.append(box);
              $list = box.find('.comment-list');
            }

            var username = mw.config.get('wgUserName') || 'You';
            var now = new Date();
            var istOffset = 5.5 * 60 * 60 * 1000; // UTC+5:30
            var ist = new Date(now.getTime() + istOffset);
            var timestamp =
              ist.getFullYear() +
              '-' +
              String(ist.getMonth() + 1).padStart(2, '0') +
              '-' +
              String(ist.getDate()).padStart(2, '0') +
              ' ' +
              String(ist.getHours()).padStart(2, '0') +
              ':' +
              String(ist.getMinutes()).padStart(2, '0') +
              ':' +
              String(ist.getSeconds()).padStart(2, '0');

            var li = $('<li/>').html(
              '<b>' +
                escapeHtml(username) +
                '</b> <span class="date">(' +
                timestamp +
                ' IST)</span><br>' +
                escapeHtml(txt) +
                ' <button class="edit-comment-btn">✏️</button>'
            );
            $list.prepend(li);
            $wrap.find('input.comment-input').val('');
          })
          .fail(function () {
            alert('Could not add comment. Try again.');
          })
          .always(function () {
            $btn.prop('disabled', false);
          });
      });
    });

    /** ---------- Enter key submits comment ---------- **/
    $(document).on('keypress', 'input.comment-input', function (e) {
      if (e.which === 13) {
        e.preventDefault();
        $(this).closest('.ajax-comment-wrap').find('.comment-submit').trigger('click');
      }
    });

    /** ---------- Delete comment ---------- **/
    $(document).on('click', '.delete-comment-btn', function (e) {
      e.preventDefault();
      var $btn = $(this);
      if ($btn.prop('disabled')) return;
      if (!confirm('Delete this comment?')) return;

      var commentId = $btn.data('comment-id'),
        pageId = $btn.data('page-id');

      $btn.prop('disabled', true);

      getFreshToken().then(function (token) {
        if (!token) {
          alert('Session expired.');
          $btn.prop('disabled', false);
          return;
        }

        var fd = new FormData();
        fd.append('pa_action', 'delete_comment');
        fd.append('comment_id', commentId);
        fd.append('page_id', pageId);
        fd.append('token', token);

        postAction(fd)
          .done(function () {
            $btn.closest('li').fadeOut(200, function () {
              $(this).remove();
            });
          })
          .fail(function () {
            alert('Delete failed.');
          })
          .always(function () {
            $btn.prop('disabled', false);
          });
      });
    });

    /** ---------- ✏️ Edit comment (new feature) ---------- **/
    $(document).on('click', '.edit-comment-btn', function () {
      var $btn = $(this);
      var $li = $btn.closest('li');
      var oldText = $li
        .clone()
        .children()
        .remove()
        .end()
        .text()
        .trim();

      var pageId = $li.closest('td').find('.comment-submit').data('page-id');
      var commentId = $btn.data('comment-id');

      if ($li.find('.edit-comment-area').length) return;

      var $area = $('<textarea class="edit-comment-area"></textarea>').val(oldText);
      var $save = $('<button class="pa-btn save-edit">Save</button>');
      var $cancel = $('<button class="pa-btn danger cancel-edit">Cancel</button>');
      $li.append($area, $save, $cancel);

      $save.on('click', function () {
        var newText = $.trim($area.val());
        if (!newText) return alert('Comment cannot be empty.');

        getFreshToken().then(function (token) {
          var fd = new FormData();
          fd.append('pa_action', 'edit_comment');
          fd.append('comment_id', commentId);
          fd.append('page_id', pageId);
          fd.append('comment', newText);
          fd.append('token', token);

          postAction(fd)
            .done(function (resp) {
              if (resp.ok) {
                $area.remove();
                $save.remove();
                $cancel.remove();
                $btn.before(' ' + escapeHtml(newText) + ' <em>(edited)</em> ');
              } else {
                alert(resp.error || 'Edit failed.');
              }
            })
            .fail(function () {
              alert('Edit failed.');
            });
        });
      });

      $cancel.on('click', function () {
        $area.remove();
        $save.remove();
        $cancel.remove();
      });
    });

    /** ---------- Search Filter ---------- **/
    $(document).on('input', '.search-input', function () {
      var q = $(this).val().toLowerCase();
      $('.dashboard-table tbody tr').each(function () {
        var t = $(this).text().toLowerCase();
        $(this).toggle(t.indexOf(q) !== -1);
      });
    });

    /** ---------- Init ---------- **/
    initAccordionOnce();
    setTimeout(initAccordionOnce, 700);
  })(jQuery);
});

/** ---------- Article Header Sort (Pending Pages) ---------- **/
$(document).on('change', '.pa-article-sort', function () {
    var mode = $(this).val();

    var $table = $(this).closest('table');
    var $tbody = $table.find('tbody');
    if (!$tbody.length) return;

    var rows = $tbody.find('tr').get();

    rows.sort(function (a, b) {
        var ta = $(a).data('title');
        var tb = $(b).data('title');

        var da = new Date($(a).data('time'));
        var db = new Date($(b).data('time'));

        if (mode === 'az') return ta.localeCompare(tb);
        if (mode === 'za') return tb.localeCompare(ta);
        if (mode === 'oldest') return da - db;
        if (mode === 'newest') return db - da;

        return 0;
    });

    $.each(rows, function (_, row) {
        $tbody.append(row);
    });
});

/* ============================================================
   TeamDashboard — Safe Pagination + Continuous Numbering
   ============================================================ */
mw.loader.using(['jquery'], function () {
  jQuery(function ($) {

    if (mw.config.get('wgCanonicalSpecialPageName') !== 'TeamDashboard') return;

    var PAGE_SIZE = 10;

    function buildPagination($table) {
      var $tbody = $table.find('tbody');
      var $rows  = $tbody.find('tr');

      if ($rows.length <= PAGE_SIZE) return;

      var totalPages = Math.ceil($rows.length / PAGE_SIZE);
      var currentPage = 1;

      $table.next('.pa-pager').remove();
      var $pager = $('<div class="pa-pager"></div>');

      function renderPage() {
        var start = (currentPage - 1) * PAGE_SIZE;
        var end   = start + PAGE_SIZE;

        $rows.hide().slice(start, end).show();

        // ✅ ONLY affects numbering
        $tbody.css('counter-reset', 'pa-row ' + start);

        renderPager();
      }

      function renderPager() {
        $pager.empty();

        var $prev = $('<button class="pa-btn">◀</button>');
        if (currentPage === 1) $prev.prop('disabled', true);
        $prev.on('click', function () {
          if (currentPage > 1) { currentPage--; renderPage(); }
        });
        $pager.append($prev);

        for (var i = 1; i <= totalPages; i++) {
          (function (p) {
            var $btn = $('<button class="pa-btn"></button>').text(p);
            if (p === currentPage) $btn.addClass('active');
            $btn.on('click', function () {
              currentPage = p;
              renderPage();
            });
            $pager.append($btn);
          })(i);
        }

        var $next = $('<button class="pa-btn">▶</button>');
        if (currentPage === totalPages) $next.prop('disabled', true);
        $next.on('click', function () {
          if (currentPage < totalPages) { currentPage++; renderPage(); }
        });
        $pager.append($next);
      }

      renderPage();
      $table.after($pager);
    }

    $('.dashboard-table').each(function () {
      buildPagination($(this));
    });

    $(document).on('click', '.accordion-toggle', function () {
      var target = $(this).data('target');
      if (!target) return;
      setTimeout(function () {
        $(target).find('.dashboard-table').each(function () {
          buildPagination($(this));
        });
      }, 120);
    });

  });
});



// End of Team Dashboard

//Opening Links in Newtab Only

mw.loader.using('mediawiki.util', function () {

    function findAnchor(el) {
        while (el && el !== document) {
            if (el.tagName && el.tagName.toLowerCase() === 'a') {
                return el;
            }
            el = el.parentNode;
        }
        return null;
    }

    document.addEventListener('click', function (e) {

        e = e || window.event;
        var target = e.target || e.srcElement;
        var link = findAnchor(target);

        if (!link) {
            return;
        }

        var href = link.getAttribute('href');

        // Ignore empty, anchors, javascript links
        if (!href || href.indexOf('#') === 0 || href.indexOf('javascript:') === 0) {
            return;
        }

        // Force new tab
        link.setAttribute('target', '_blank');
        link.setAttribute('rel', 'noopener noreferrer');

    }, false);

});

// End of Link Opening code