MediaWiki:Gadget-floatingYouTubePlayer.js

From Angelina Jordan Wiki
Revision as of 19:10, 28 September 2025 by Most2dot0 (talk | contribs) (New version supposed to fix the bug)

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)
  • Edge: Hold Ctrl while clicking Refresh, or press Ctrl-F5.
```javascript
// Unified YouTube player gadget with safe SPA-style navigation
// Fixes section jump bug by ignoring hash-only navigation

function insertYouTubePlayers() {
    var placeholders = document.querySelectorAll('.youtube-player-placeholder');

    // Load the YouTube IFrame API if not already loaded
    if (!document.getElementById('youtube-iframe-api')) {
        var tag = document.createElement('script');
        tag.src = "https://www.youtube.com/iframe_api";
        tag.id = 'youtube-iframe-api';
        var firstScriptTag = document.getElementsByTagName('script')[0];
        firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
    }

    var videoLists = [];
    var currentVideoIndices = [];
    var checkIntervals = [];

    function parseVideosFromPlaceholder(placeholder) {
        var videoDataList = [];
        var links = placeholder.querySelectorAll('a');
        var includeRefs = placeholder.getAttribute('data-include-refs') === 'true';

        function parseUrl(href) {
            var match = href && href.match(/\/\/(www\.)?youtube\.com\/watch\?v=([a-zA-Z0-9_-]+)([^\s]*)/);
            if (match) {
                var videoId = match[2];
                var urlParams = match[3];
                var params = new URLSearchParams(urlParams);
                var start = params.get('t') ? parseInt(params.get('t')) : 0;
                var end = params.get('end') ? parseInt(params.get('end')) : null;
                videoDataList.push({ videoId: videoId, start: start, end: end });
            }
        }

        links.forEach(function(link) {
            parseUrl(link.getAttribute('href'));
        });

        if (includeRefs) {
            var refSupTags = placeholder.querySelectorAll('sup a[href^="#"]');
            refSupTags.forEach(function(supTag) {
                var refId = supTag.getAttribute('href').substring(1);
                var refListItem = document.getElementById(refId);
                if (refListItem) {
                    var refLinks = refListItem.querySelectorAll('a');
                    refLinks.forEach(function(link) {
                        parseUrl(link.getAttribute('href'));
                    });
                }
            });
        }

        if (videoDataList.length === 0) {
            var dataVideos = (placeholder.getAttribute('data-videos') || '').split(',');
            dataVideos.forEach(function(videoData) {
                if (!videoData) return;
                var parts = videoData.split('&');
                var videoId = parts[0];
                var start = 0;
                var end = null;
                parts.forEach(function(part) {
                    if (part.startsWith('t=')) {
                        start = parseInt(part.substring(2)) || 0;
                    } else if (part.startsWith('end=')) {
                        end = parseInt(part.substring(4)) || null;
                    }
                });
                videoDataList.push({ videoId: videoId, start: start, end: end });
            });
        }

        return videoDataList;
    }

    function onYouTubeIframeAPIReady() {
        var observer = new IntersectionObserver(function(entries) {
            entries.forEach(function(entry) {
                if (entry.isIntersecting) {
                    var index = entry.target.getAttribute('data-index');
                    if (!entry.target.getAttribute('data-loaded')) {
                        entry.target.setAttribute('data-loaded', 'true');
                        loadYouTubePlayer(entry.target, index);
                    }
                }
            });
        });

        placeholders.forEach(function(placeholder, index) {
            placeholder.setAttribute('data-index', index);
            observer.observe(placeholder);
        });
    }

    window.onYouTubeIframeAPIReady = onYouTubeIframeAPIReady;

    function loadYouTubePlayer(placeholder, index) {
        var videoDataList = parseVideosFromPlaceholder(placeholder);
        videoLists[index] = videoDataList;
        currentVideoIndices[index] = 0;
        checkIntervals[index] = null;

        var playerDiv = document.createElement('div');
        playerDiv.id = 'youtube-player-' + index;
        placeholder.appendChild(playerDiv);

        var controlsDiv = document.createElement('div');
        controlsDiv.className = 'youtube-player-controls';
        controlsDiv.innerHTML = '<button class="prev-button" data-index="' + index + '">Previous</button>' +
                                '<button class="next-button" data-index="' + index + '">Next</button>';
        placeholder.appendChild(controlsDiv);

        window['youtube-player-' + index] = new YT.Player('youtube-player-' + index, {
            height: '270',
            width: '480',
            videoId: videoLists[index][currentVideoIndices[index]].videoId,
            playerVars: {
                start: videoLists[index][currentVideoIndices[index]].start
            },
            events: {
                'onStateChange': onPlayerStateChange(index),
                'onReady': onPlayerReady(index)
            }
        });

        var links = placeholder.querySelectorAll('a');
        links.forEach(function(link) {
            var href = link.getAttribute('href');
            var match = href && href.match(/\/\/(www\.)?youtube\.com\/watch\?v=([a-zA-Z0-9_-]+)([^\s]*)/);
            if (match) {
                var videoId = match[2];
                var urlParams = match[3];
                var params = new URLSearchParams(urlParams);
                var start = params.get('t') ? parseInt(params.get('t')) : 0;
                var end = params.get('end') ? parseInt(params.get('end')) : null;

                link.addEventListener('click', function(event) {
                    event.preventDefault();
                    var videoIndex = videoLists[index].findIndex(function(video) {
                        return video.videoId === videoId && video.start === start && video.end === end;
                    });
                    if (videoIndex !== -1) {
                        currentVideoIndices[index] = videoIndex;
                        window['youtube-player-' + index].loadVideoById({
                            videoId: videoLists[index][currentVideoIndices[index]].videoId,
                            startSeconds: videoLists[index][currentVideoIndices[index]].start
                        });
                    }
                });
            }
        });
    }

    function onPlayerReady(index) {
        return function() {
            var placeholder = placeholders[index];
            var originalHTML = placeholder.querySelector('.youtube-placeholder-text');
            if (originalHTML) {
                originalHTML.style.display = '';
            }
        };
    }

    function onPlayerStateChange(index) {
        return function(event) {
            clearInterval(checkIntervals[index]);
            if (event.data == YT.PlayerState.PLAYING) {
                for (var i = 0; i < placeholders.length; i++) {
                    if (i !== parseInt(index) && window['youtube-player-' + i] &&
                        window['youtube-player-' + i].getPlayerState() === YT.PlayerState.PLAYING) {
                        window['youtube-player-' + i].pauseVideo();
                    }
                }
                if (videoLists[index][currentVideoIndices[index]].end) {
                    checkIntervals[index] = setInterval(function() {
                        var currentTime = window['youtube-player-' + index].getCurrentTime();
                        if (currentTime >= videoLists[index][currentVideoIndices[index]].end) {
                            playVideo(index, 1);
                        }
                    }, 1000);
                }
            } else if (event.data == YT.PlayerState.ENDED) {
                playVideo(index, 1);
            }
        };
    }

    function playVideo(index, direction) {
        clearInterval(checkIntervals[index]);
        currentVideoIndices[index] += direction;
        if (currentVideoIndices[index] >= videoLists[index].length) {
            currentVideoIndices[index] = 0;
        } else if (currentVideoIndices[index] < 0) {
            currentVideoIndices[index] = videoLists[index].length - 1;
        }
        window['youtube-player-' + index].loadVideoById({
            videoId: videoLists[index][currentVideoIndices[index]].videoId,
            startSeconds: videoLists[index][currentVideoIndices[index]].start
        });
    }

    document.addEventListener('click', function(event) {
        if (event.target.classList.contains('next-button')) {
            var index = event.target.getAttribute('data-index');
            playVideo(index, 1);
        } else if (event.target.classList.contains('prev-button')) {
            var index = event.target.getAttribute('data-index');
            playVideo(index, -1);
        }
    });
}

// ---- Safe SPA navigation wrapper ----

(function setupNavigation() {
    // Normalize initial history state without hash
    if (!history.state || !history.state.url) {
        history.replaceState({ url: location.href.split('#')[0] }, '', location.href);
    }

    window.addEventListener('popstate', function() {
        var baseNow = location.href.split('#')[0];
        var baseState = history.state && history.state.url ? history.state.url : null;
        if (baseState === baseNow) {
            return; // hash-only change
        }
        loadArticleByUrl(location.href, { replaceState: true });
    });

    // Example shouldInterceptLink, skipping section jumps
    function shouldInterceptLink(a) {
        if (a.target === '_blank') return false;
        if (a.hostname !== location.hostname) return false;
        if (a.pathname === location.pathname && a.hash) return false;
        return true;
    }

    document.addEventListener('click', function(ev) {
        var a = ev.target.closest('a');
        if (!a) return;
        if (!shouldInterceptLink(a)) return;
        ev.preventDefault();
        loadArticleByUrl(a.href, { pushState: true });
    });

    function loadArticleByUrl(href, opts) {
        var baseNow = location.href.split('#')[0];
        var baseTarget = href.split('#')[0];
        if (baseNow === baseTarget) {
            return; // prevent reload on hash-only navigation
        }

        fetch(href, { headers: { 'X-Requested-With': 'XMLHttpRequest' } })
            .then(function(resp) { return resp.text(); })
            .then(function(html) {
                var doc = new DOMParser().parseFromString(html, 'text/html');
                var content = doc.querySelector('#content');
                if (content) {
                    document.querySelector('#content').innerHTML = content.innerHTML;
                }
                if (opts.pushState) {
                    history.pushState({ url: href.split('#')[0] }, '', href);
                } else if (opts.replaceState) {
                    history.replaceState({ url: href.split('#')[0] }, '', href);
                }
                insertYouTubePlayers();
            });
    }
})();

insertYouTubePlayers();
```