MediaWiki:Common.js: Difference between revisions

From PRS
Jump to navigation Jump to search
No edit summary
No edit summary
Line 12: Line 12:
         }
         }


         /* Block setAttribute from removing controls or adding disabled */
         /* Block setAttribute */
         var originalSetAttribute = video.setAttribute.bind(video);
         var originalSetAttribute = video.setAttribute.bind(video);
         var originalRemoveAttribute = video.removeAttribute.bind(video);
         var originalRemoveAttribute = video.removeAttribute.bind(video);
Line 18: Line 18:
         video.setAttribute = function (name, value) {
         video.setAttribute = function (name, value) {
             if (name === 'disabled') return;
             if (name === 'disabled') return;
             if (name === 'controls') {
             if (name === 'controls') return originalSetAttribute('controls', '');
                return originalSetAttribute('controls', '');
            }
             return originalSetAttribute(name, value);
             return originalSetAttribute(name, value);
         };
         };
Line 29: Line 27:
         };
         };


         /* Block direct property assignment: video.disabled = true */
         /* Block direct property assignment */
         Object.defineProperty(video, 'disabled', {
         Object.defineProperty(video, 'disabled', {
             get: function () { return false; },
             get: function () { return false; },
             set: function () { /* block all attempts to set disabled */ },
             set: function () { /* blocked */ },
             configurable: true
             configurable: true
        });
        /* MutationObserver as a second layer —
          if disabled attribute sneaks in via another path, remove it immediately */
        var observer = new MutationObserver(function (mutations) {
            mutations.forEach(function (mutation) {
                if (mutation.attributeName === 'disabled') {
                    originalRemoveAttribute('disabled');
                }
                if (mutation.attributeName === 'controls') {
                    if (!video.hasAttribute('controls')) {
                        originalSetAttribute('controls', '');
                    }
                }
            });
        });
        observer.observe(video, {
            attributes: true,
            attributeFilter: ['controls', 'disabled']
         });
         });



Revision as of 08:31, 22 April 2026

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

/* ── Intercept and block TMH attribute changes ── */
$(function () {
    if ($('.training-video-wrap').length === 0) return;

    function lockControls() {
        var video = document.querySelector('.mw-file-element');
        if (!video) {
            setTimeout(lockControls, 100);
            return;
        }

        /* Block setAttribute */
        var originalSetAttribute = video.setAttribute.bind(video);
        var originalRemoveAttribute = video.removeAttribute.bind(video);

        video.setAttribute = function (name, value) {
            if (name === 'disabled') return;
            if (name === 'controls') return originalSetAttribute('controls', '');
            return originalSetAttribute(name, value);
        };

        video.removeAttribute = function (name) {
            if (name === 'controls') return;
            return originalRemoveAttribute(name);
        };

        /* Block direct property assignment */
        Object.defineProperty(video, 'disabled', {
            get: function () { return false; },
            set: function () { /* blocked */ },
            configurable: true
        });

        /* MutationObserver as a second layer —
           if disabled attribute sneaks in via another path, remove it immediately */
        var observer = new MutationObserver(function (mutations) {
            mutations.forEach(function (mutation) {
                if (mutation.attributeName === 'disabled') {
                    originalRemoveAttribute('disabled');
                }
                if (mutation.attributeName === 'controls') {
                    if (!video.hasAttribute('controls')) {
                        originalSetAttribute('controls', '');
                    }
                }
            });
        });

        observer.observe(video, {
            attributes: true,
            attributeFilter: ['controls', 'disabled']
        });

        /* Set initial state */
        originalSetAttribute('controls', '');
        originalRemoveAttribute('disabled');
    }

    lockControls();
});

/* ============================================================
   Training Video – Synced Transcript Sidebar
   ============================================================ */
$(function () {
    if ($('.training-video-wrap').length === 0) return;

    var $video      = $('.training-video-wrap video');
    var $entries    = $('.ts-entry');
    var $body       = $('.ts-scroll-body');
    var $markers    = $('.ts-progress-marker');
    var $fill       = $('.ts-progress-fill');
    var $timeDisp   = $('.ts-time-display');

    if ($video.length === 0) return;

    var videoEl = $video[0];

    function fmt(s) {
        s = Math.floor(s);
        return Math.floor(s / 60) + ':' + ('0' + (s % 60)).slice(-2);
    }

    var timestamps = [];
    $entries.each(function () {
        timestamps.push(parseInt($(this).data('time'), 10));
    });

    function highlightActive() {
        var cur = videoEl.currentTime;
        var activeIdx = 0;
        for (var i = 0; i < timestamps.length; i++) {
            if (cur >= timestamps[i]) activeIdx = i;
        }
        $entries.each(function (i) {
            $(this).toggleClass('ts-active', i === activeIdx);
        });
        var $active = $entries.eq(activeIdx);
        if ($active.length && $body.length) {
            var entryTop      = $active.position().top;
            var bodyScrollTop = $body.scrollTop();
            var bodyHeight    = $body.height();
            if (entryTop < 0 || entryTop > bodyHeight - 60) {
                $body.animate({ scrollTop: bodyScrollTop + entryTop - 60 }, 200);
            }
        }
    }

    function updateProgress() {
        if (!videoEl.duration) return;
        var pct = (videoEl.currentTime / videoEl.duration) * 100;
        $fill.css('width', pct + '%');
        $timeDisp.text(fmt(videoEl.currentTime) + ' / ' + fmt(videoEl.duration));
    }

    $video.on('timeupdate', function () {
        highlightActive();
        updateProgress();
    });

    $video.on('loadedmetadata', function () {
        $timeDisp.text('0:00 / ' + fmt(videoEl.duration));
        $markers.each(function () {
            var t = parseInt($(this).data('time'), 10);
            $(this).css('left', ((t / videoEl.duration) * 100) + '%');
        });
    });

    $entries.on('click', function () {
        var t = parseInt($(this).data('time'), 10);
        videoEl.currentTime = t;
        if (videoEl.paused) videoEl.play();
        highlightActive();
    });

    $('.ts-progress-track').on('click', function (e) {
        var rect  = this.getBoundingClientRect();
        var ratio = Math.max(0, Math.min(1, (e.clientX - rect.left) / rect.width));
        videoEl.currentTime = ratio * videoEl.duration;
    });

    $markers.on('click', function (e) {
        e.stopPropagation();
        var t = parseInt($(this).data('time'), 10);
        videoEl.currentTime = t;
        if (videoEl.paused) videoEl.play();
    });

});
/* ============================================================ */