// ==UserScript==
// @name Pixeldrain Bypass Player (Modal Overlay)
// @namespace pixeldrain-direct-player-modal
// @version 3.1.2
// @description Adds a button to open a direct, hotlinked video player in a modal, bypassing Pixeldrain limits. No need for a new tab. Also works with my Pixeldrain SRT Subtitle Injector.
// @author medy (logic inspired by Sak32009)
// @license MIT
// @match *://pixeldrain.com/u/*
// @match *://pixeldrain.com/l/*
// @require https://cdn.jsdelivr.net/npm/[email protected]/dist/jquery.min.js
// @require https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.min.js
// @grant GM_addStyle
// @grant unsafeWindow
// @run-at document-end
// @noframes
// ==/UserScript==
/* global bootstrap, jQuery */
// Inject the entire minified Bootstrap 5.3 CSS, scoped to our container class.
(t=> {
if(typeof GM_addStyle=="function") {
GM_addStyle(t);
return
}
const r=document.createElement("style");
r.textContent=t,document.head.append(r)
})
(` .pd-player-scope {
all:initial
}
.pd-player-scope * {
all:revert
}
.pd-player-scope,.pd-player-scope[data-bs-theme=light] {
--bs-blue:#0d6efd;
--bs-indigo:#6610f2;
--bs-purple:#6f42c1;
--bs-pink:#d63384;
--bs-red:#dc3545;
--bs-orange:#fd7e14;
--bs-yellow:#ffc107;
--bs-green:#198754;
--bs-teal:#20c997;
--bs-cyan:#0dcaf0;
--bs-black:#000;
--bs-white:#fff;
--bs-gray:#6c757d;
--bs-gray-dark:#343a40;
--bs-gray-100:#f8f9fa;
--bs-gray-200:#e9ecef;
--bs-gray-300:#dee2e6;
--bs-gray-400:#ced4da;
--bs-gray-500:#adb5bd;
--bs-gray-600:#6c757d;
--bs-gray-700:#495057;
--bs-gray-800:#343a40;
--bs-gray-900:#212529;
--bs-primary:#0d6efd;
--bs-secondary:#6c757d;
--bs-success:#198754;
--bs-info:#0dcaf0;
--bs-warning:#ffc107;
--bs-danger:#dc3545;
--bs-light:#f8f9fa;
--bs-dark:#212529;
--bs-primary-rgb:13,110,253;
--bs-secondary-rgb:108,117,125;
--bs-success-rgb:25,135,84;
--bs-info-rgb:13,202,240;
--bs-warning-rgb:255,193,7;
--bs-danger-rgb:220,53,69;
--bs-light-rgb:248,249,250;
--bs-dark-rgb:33,37,41;
--bs-primary-text-emphasis:#052c65;
--bs-secondary-text-emphasis:#2b2f32;
--bs-success-text-emphasis:#0a3622;
--bs-info-text-emphasis:#055160;
--bs-warning-text-emphasis:#664d03;
--bs-danger-text-emphasis:#58151c;
--bs-light-text-emphasis:#495057;
--bs-dark-text-emphasis:#495057;
--bs-primary-bg-subtle:#cfe2ff;
--bs-secondary-bg-subtle:#e2e3e5;
--bs-success-bg-subtle:#d1e7dd;
--bs-info-bg-subtle:#cff4fc;
--bs-warning-bg-subtle:#fff3cd;
--bs-danger-bg-subtle:#f8d7da;
--bs-light-bg-subtle:#fcfcfd;
--bs-dark-bg-subtle:#ced4da;
--bs-primary-border-subtle:#9ec5fe;
--bs-secondary-border-subtle:#c4c8cb;
--bs-success-border-subtle:#a3cfbb;
--bs-info-border-subtle:#9eeaf9;
--bs-warning-border-subtle:#ffe69c;
--bs-danger-border-subtle:#f1aeb5;
--bs-light-border-subtle:#e9ecef;
--bs-dark-border-subtle:#adb5bd;
--bs-white-rgb:255,255,255;
--bs-black-rgb:0,0,0;
--bs-font-sans-serif:system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue","Noto Sans","Liberation Sans",Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";
--bs-font-monospace:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;
--bs-gradient:linear-gradient(180deg, rgba(255, 255, 255, .15), rgba(255, 255, 255, 0));
--bs-body-font-family:var(--bs-font-sans-serif);
--bs-body-font-size:1rem;
--bs-body-font-weight:400;
--bs-body-line-height:1.5;
--bs-body-color:#212529;
--bs-body-color-rgb:33,37,41;
--bs-body-bg:#fff;
--bs-body-bg-rgb:255,255,255;
--bs-emphasis-color:#000;
--bs-emphasis-color-rgb:0,0,0;
--bs-secondary-color:rgba(33,37,41,.75);
--bs-secondary-color-rgb:33,37,41;
--bs-secondary-bg:#e9ecef;
--bs-secondary-bg-rgb:233,236,239;
--bs-tertiary-color:rgba(33,37,41,.5);
--bs-tertiary-color-rgb:33,37,41;
--bs-tertiary-bg:#f8f9fa;
--bs-tertiary-bg-rgb:248,249,250;
--bs-heading-color:inherit;
--bs-link-color:#0d6efd;
--bs-link-color-rgb:13,110,253;
--bs-link-decoration:underline;
--bs-link-hover-color:#0a58ca;
--bs-link-hover-color-rgb:10,88,202;
--bs-code-color:#d63384;
--bs-highlight-color:#212529;
--bs-highlight-bg:#fff3cd;
--bs-border-width:1px;
--bs-border-style:solid;
--bs-border-color:#dee2e6;
--bs-border-color-translucent:rgba(0,0,0,.175);
--bs-border-radius:.375rem;
--bs-border-radius-sm:.25rem;
--bs-border-radius-lg:.5rem;
--bs-border-radius-xl:1rem;
--bs-border-radius-xxl:2rem;
--bs-border-radius-2xl:var(--bs-border-radius-xxl);
--bs-border-radius-pill:50rem;
--bs-box-shadow:0 .5rem 1rem rgba(0,0,0,.15);
--bs-box-shadow-sm:0 .125rem .25rem rgba(0,0,0,.075);
--bs-box-shadow-lg:0 1rem 3rem rgba(0,0,0,.175);
--bs-box-shadow-inset:inset 0 1px 2px rgba(0,0,0,.075);
--bs-focus-ring-width:.25rem;
--bs-focus-ring-opacity:.25;
--bs-focus-ring-color:rgba(13,110,253,.25);
--bs-form-valid-color:#198754;
--bs-form-valid-border-color:#198754;
--bs-form-invalid-color:#dc3545;
--bs-form-invalid-border-color:#dc3545
}
.pd-player-scope[data-bs-theme=dark] {
color-scheme:dark;
--bs-body-color:#dee2e6;
--bs-body-color-rgb:222,226,230;
--bs-body-bg:#212529;
--bs-body-bg-rgb:33,37,41;
--bs-emphasis-color:#fff;
--bs-emphasis-color-rgb:255,255,255;
--bs-secondary-color:rgba(222,226,230,.75);
--bs-secondary-color-rgb:222,226,230;
--bs-secondary-bg:#343a40;
--bs-secondary-bg-rgb:52,58,64;
--bs-tertiary-color:rgba(222,226,230,.5);
--bs-tertiary-color-rgb:222,226,230;
--bs-tertiary-bg:#2b3035;
--bs-tertiary-bg-rgb:43,48,53;
--bs-primary-text-emphasis:#6ea8fe;
--bs-secondary-text-emphasis:#a7acb1;
--bs-success-text-emphasis:#75b798;
--bs-info-text-emphasis:#6edff6;
--bs-warning-text-emphasis:#ffda6a;
--bs-danger-text-emphasis:#ea868f;
--bs-light-text-emphasis:#f8f9fa;
--bs-dark-text-emphasis:#dee2e6;
--bs-primary-bg-subtle:#031633;
--bs-secondary-bg-subtle:#161719;
--bs-success-bg-subtle:#051b11;
--bs-info-bg-subtle:#032830;
--bs-warning-bg-subtle:#332701;
--bs-danger-bg-subtle:#2c0b0e;
--bs-light-bg-subtle:#343a40;
--bs-dark-bg-subtle:#1a1d20;
--bs-primary-border-subtle:#084298;
--bs-secondary-border-subtle:#41464b;
--bs-success-border-subtle:#0f5132;
--bs-info-border-subtle:#087990;
--bs-warning-border-subtle:#997404;
--bs-danger-border-subtle:#842029;
--bs-light-border-subtle:#495057;
--bs-dark-border-subtle:#343a40;
--bs-heading-color:inherit;
--bs-link-color:#6ea8fe;
--bs-link-hover-color:#8bb9fe;
--bs-link-color-rgb:110,168,254;
--bs-link-hover-color-rgb:139,185,254;
--bs-code-color:#e685b5;
--bs-highlight-color:#dee2e6;
--bs-highlight-bg:#664d03;
--bs-border-color:#495057;
--bs-border-color-translucent:rgba(255,255,255,.15);
--bs-form-valid-color:#75b798;
--bs-form-valid-border-color:#75b798;
--bs-form-invalid-color:#ea868f;
--bs-form-invalid-border-color:#ea868f
}
.pd-player-scope *,.pd-player-scope :before,.pd-player-scope :after {
box-sizing:border-box
}
.pd-player-scope h1,.pd-player-scope h2,.pd-player-scope h3,.pd-player-scope h4,.pd-player-scope h5,.pd-player-scope h6 {
margin-top:0;
margin-bottom:.5rem;
font-weight:500;
line-height:1.2;
color:var(--bs-heading-color)
}
.pd-player-scope .btn-close {
--bs-btn-close-color:#000;
--bs-btn-close-bg:url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23000'%3e%3cpath d='M.293.293a1 1 0 0 1 1.414 0L8 6.586 14.293.293a1 1 0 1 1 1.414 1.414L9.414 8l6.293 6.293a1 1 0 0 1-1.414 1.414L8 9.414l-6.293 6.293a1 1 0 0 1-1.414-1.414L6.586 8 .293 1.707a1 1 0 0 1 0-1.414z'/%3e%3c/svg%3e\");
--bs-btn-close-opacity:.5;
--bs-btn-close-hover-opacity:.75;
--bs-btn-close-focus-shadow:0 0 0 .25rem rgba(13,110,253,.25);
--bs-btn-close-focus-opacity:1;
--bs-btn-close-disabled-opacity:.25;
--bs-btn-close-white-filter:invert(1) grayscale(100%) brightness(200%);
box-sizing:content-box;
width:1em;
height:1em;
padding:.25em .25em;
color:var(--bs-btn-close-color);
background:transparent var(--bs-btn-close-bg) center/1em auto no-repeat;
border:0;
border-radius:.375rem;
opacity:var(--bs-btn-close-opacity)
}
.pd-player-scope .btn-close:hover {
color:var(--bs-btn-close-color);
text-decoration:none;
opacity:var(--bs-btn-close-hover-opacity)
}
.pd-player-scope .btn-close:focus {
outline:0;
box-shadow:var(--bs-btn-close-focus-shadow);
opacity:var(--bs-btn-close-focus-opacity)
}
.pd-player-scope .btn-close:disabled,.pd-player-scope .btn-close.disabled {
pointer-events:none;
-webkit-user-select:none;
-moz-user-select:none;
user-select:none;
opacity:var(--bs-btn-close-disabled-opacity)
}
.pd-player-scope .btn-close-white,.pd-player-scope[data-bs-theme=dark] .btn-close {
filter:var(--bs-btn-close-white-filter)
}
.pd-player-scope .modal {
--bs-modal-zindex:1055;
--bs-modal-width:500px;
--bs-modal-padding:1rem;
--bs-modal-margin:.5rem;
--bs-modal-color:;
--bs-modal-bg:var(--bs-body-bg);
--bs-modal-border-color:var(--bs-border-color-translucent);
--bs-modal-border-width:var(--bs-border-width);
--bs-modal-border-radius:var(--bs-border-radius-lg);
--bs-modal-box-shadow:var(--bs-box-shadow-sm);
--bs-modal-inner-border-radius:calc(var(--bs-border-radius-lg) - var(--bs-border-width));
--bs-modal-header-padding-x:1rem;
--bs-modal-header-padding-y:1rem;
--bs-modal-header-padding:1rem 1rem;
--bs-modal-header-border-color:var(--bs-border-color);
--bs-modal-header-border-width:var(--bs-border-width);
--bs-modal-title-line-height:1.5;
--bs-modal-footer-gap:.5rem;
--bs-modal-footer-bg:;
--bs-modal-footer-border-color:var(--bs-border-color);
--bs-modal-footer-border-width:var(--bs-border-width);
position:fixed;
top:0;
left:0;
z-index:var(--bs-modal-zindex);
display:none;
width:100%;
height:100%;
overflow-x:hidden;
overflow-y:auto;
outline:0
}
.pd-player-scope .modal-dialog {
position:relative;
width:auto;
margin:var(--bs-modal-margin);
pointer-events:none
}
.pd-player-scope .modal.fade .modal-dialog {
transition:transform .3s ease-out;
transform:translate(0,-50px)
}
@media (prefers-reduced-motion:reduce) {
.pd-player-scope .modal.fade .modal-dialog {
transition:none
}
}
.pd-player-scope .modal.show .modal-dialog {
transform:none
}
.pd-player-scope .modal.modal-static .modal-dialog {
transform:scale(1.02)
}
.pd-player-scope .modal-dialog-scrollable {
height:calc(100% - var(--bs-modal-margin)*2)
}
.pd-player-scope .modal-dialog-scrollable .modal-content {
max-height:100%;
overflow:hidden
}
.pd-player-scope .modal-dialog-scrollable .modal-body {
overflow-y:auto
}
.pd-player-scope .modal-dialog-centered {
display:flex;
align-items:center;
min-height:calc(100% - var(--bs-modal-margin)*2)
}
.pd-player-scope .modal-content {
position:relative;
display:flex;
flex-direction:column;
width:100%;
color:var(--bs-modal-color);
pointer-events:auto;
background-color:var(--bs-modal-bg);
background-clip:padding-box;
border:var(--bs-modal-border-width) solid var(--bs-modal-border-color);
border-radius:var(--bs-modal-border-radius);
outline:0
}
.modal-backdrop {
--bs-backdrop-zindex:1050;
--bs-backdrop-bg:#000;
--bs-backdrop-opacity:.5;
position:fixed;
top:0;
left:0;
z-index:var(--bs-backdrop-zindex);
width:100vw;
height:100vh;
background-color:var(--bs-backdrop-bg)
}
.modal-backdrop.fade {
opacity:0
}
.modal-backdrop.show {
opacity:var(--bs-backdrop-opacity)
}
.pd-player-scope .modal-header {
display:flex;
flex-shrink:0;
align-items:center;
justify-content:space-between;
padding:var(--bs-modal-header-padding);
border-bottom:var(--bs-modal-header-border-width) solid var(--bs-modal-header-border-color);
border-top-left-radius:var(--bs-modal-inner-border-radius);
border-top-right-radius:var(--bs-modal-inner-border-radius)
}
.pd-player-scope .modal-header .btn-close {
padding:calc(var(--bs-modal-header-padding-y)*.5) calc(var(--bs-modal-header-padding-x)*.5);
margin:calc(var(--bs-modal-header-padding-y)*-.5) calc(var(--bs-modal-header-padding-x)*-.5) calc(var(--bs-modal-header-padding-y)*-.5) auto
}
.pd-player-scope .modal-title {
margin-bottom:0;
line-height:var(--bs-modal-title-line-height)
}
.pd-player-scope .modal-body {
position:relative;
flex:1 1 auto;
padding:var(--bs-modal-padding)
}
.pd-player-scope .modal-footer {
display:flex;
flex-shrink:0;
flex-wrap:wrap;
align-items:center;
justify-content:flex-end;
padding:calc(var(--bs-modal-padding) - var(--bs-modal-footer-gap)*.5);
background-color:var(--bs-modal-footer-bg);
border-top:var(--bs-modal-footer-border-width) solid var(--bs-modal-footer-border-color);
border-bottom-right-radius:var(--bs-modal-inner-border-radius);
border-bottom-left-radius:var(--bs-modal-inner-border-radius)
}
.pd-player-scope .modal-footer>* {
margin:calc(var(--bs-modal-footer-gap)*.5)
}
@media (min-width:576px) {
.pd-player-scope .modal {
--bs-modal-margin:1.75rem;
--bs-modal-box-shadow:var(--bs-box-shadow)
}
.pd-player-scope .modal-dialog {
max-width:var(--bs-modal-width);
margin-right:auto;
margin-left:auto
}
.pd-player-scope .modal-sm {
--bs-modal-width:300px
}
}
@media (min-width:992px) {
.pd-player-scope .modal-lg,.pd-player-scope .modal-xl {
--bs-modal-width:800px
}
}
@media (min-width:1200px) {
.pd-player-scope .modal-xl {
--bs-modal-width:1140px
}
}
.pd-player-scope .fade {
transition:opacity .15s linear
}
@media (prefers-reduced-motion:reduce) {
.pd-player-scope .fade {
transition:none
}
}
.pd-player-scope .fade:not(.show) {
opacity:0
}
.pd-player-scope .p-0 {
padding:0!important
}
.pd-player-scope .fw-bold {
font-weight:700!important
}
.pd-player-scope .modal-body video {
width:100%;
max-height:calc(100vh - 100px);
display:block;
outline:none;
background-color:#000;
}
`);
(function($, bootstrap) {
'use strict';
// Use .noConflict to avoid clashes with the site's own jQuery if it has one.
$ = $.noConflict(true);
const SCRIPT_NAME = 'Pixeldrain Direct Player';
const BYPASS_URL_BASE = 'https://pd.cybar.xyz';
const CONTAINER_CLASS = 'pd-player-scope';
const MODAL_ID = 'pd-direct-player-modal';
let uiInitialized = false;
// --- HTML Templates ---
const MODAL_HTML = `
<div class="modal fade" id="$ {
MODAL_ID
}
" tabindex="-1">
<div class="modal-dialog modal-xl modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title fw-bold">Direct Player</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body p-0">
<!-- Video tag will be injected here -->
</div>
</div>
</div>
</div>
`;
/**
* Finds the current file ID and checks if it's a video.
* @returns {
{
id: string, name: string
}
|null
}
*/
function getCurrentFileInfo() {
// Primary Method: Read from the global viewer_data object. It's more comprehensive.
if (typeof unsafeWindow.viewer_data?.api_response !== 'undefined') {
const data = unsafeWindow.viewer_data;
const api = data.api_response;
// Handle list pages (/l/...)
if (data.type === 'list' && data.file_id && api.files) {
const currentFile = api.files.find(f => f.id === data.file_id);
if (currentFile && currentFile.mime_type?.startsWith('video/')) {
return {
id: currentFile.id, name: currentFile.name
};
}
}
// Handle single file pages (/u/...)
else if (data.type === 'file' && api.id && api.mime_type?.startsWith('video/')) {
return {
id: api.id, name: api.name
};
}
}
// Fallback Method: For initial load before viewer_data might be populated.
const videoMetaTag = document.querySelector('meta[property="og:video"]');
if (videoMetaTag?.content) {
const urlParts = videoMetaTag.content.split('/');
const fileId = urlParts.pop();
const fileName = document.title.split('~')[0].trim();
if (fileId) return {
id: fileId, name: fileName
};
}
return null;
}
/**
* Creates and injects the toolbar button.
* @param {
{
id: string, name: string
}
}
fileInfo
*/
function createOrUpdateButton(fileInfo) {
const toolbar = document.querySelector('.toolbar');
const templateButton = document.querySelector('.toolbar_button');
const separatorTemplate = document.querySelector('.toolbar .separator');
if (!toolbar || !templateButton || !separatorTemplate) {
console.log(`[$ {
SCRIPT_NAME
}
] Toolbar elements not found yet`);
return false;
}
// Check if button already exists
let directButton = document.querySelector('#pd-direct-player-btn');
if (!directButton) {
// Create the button using Pixeldrain's template
directButton = templateButton.cloneNode(true);
directButton.id = 'pd-direct-player-btn';
directButton.removeAttribute('href');
directButton.removeAttribute('title');
directButton.querySelector('i').textContent = 'play_arrow';
directButton.querySelector('span').textContent = 'Play Direct';
directButton.addEventListener('click', (e) => {
e.preventDefault();
e.stopPropagation();
const currentId = directButton.getAttribute('data-file-id');
const currentName = directButton.getAttribute('data-file-name');
if (currentId) {
createAndShowModal(currentId, currentName);
}
});
// Insert into toolbar (after the last separator)
const newSeparator = separatorTemplate.cloneNode(true);
separatorTemplate.parentNode.insertBefore(newSeparator, separatorTemplate.nextSibling);
newSeparator.parentNode.insertBefore(directButton, newSeparator.nextSibling);
console.log(`[$ {
SCRIPT_NAME
}
] Direct player button created`);
}
// Update button data
directButton.setAttribute('data-file-id', fileInfo.id);
directButton.setAttribute('data-file-name', fileInfo.name);
return true;
}
/**
* Removes the toolbar button if it exists.
*/
function removeButton() {
const directButton = document.querySelector('#pd-direct-player-btn');
if (directButton) {
// Also remove the separator that was added with it
const prevSeparator = directButton.previousElementSibling;
if (prevSeparator && prevSeparator.classList.contains('separator')) {
prevSeparator.remove();
}
directButton.remove();
console.log(`[$ {
SCRIPT_NAME
}
] Direct player button removed`);
}
}
/**
* Creates the Bootstrap modal, injects the video player, and shows it.
* @param {
string
}
fileId
* @param {
string
}
fileName
*/
function createAndShowModal(fileId, fileName) {
// Ensure we have our container
let $container = $(`.$ {
CONTAINER_CLASS
}
`);
if (!$container.length) {
$container = $('<div>').addClass(CONTAINER_CLASS).attr('data-bs-theme', 'dark').appendTo('body');
}
// Remove any old modal instance first
$(`#$ {
MODAL_ID
}
`).remove();
const videoUrl = `$ {
BYPASS_URL_BASE
}
/$ {
fileId
}
`;
const $modal = $(MODAL_HTML);
$modal.find('.modal-title').text(fileName);
const $video = $('<video>').attr( {
src: videoUrl,
controls: true,
autoplay: true
});
$modal.find('.modal-body').append($video);
$modal.appendTo($container);
const modalInstance = new bootstrap.Modal($modal[0]);
// Add event listener for when the modal is closed, to stop the video and clean up.
$modal[0].addEventListener('hidden.bs.modal', event => {
const videoEl = $modal.find('video')[0];
if (videoEl) {
videoEl.pause();
videoEl.src = '';
// Detach the source
}
modalInstance.dispose();
$modal.remove();
// Remove from DOM
});
modalInstance.show();
console.log(`[$ {
SCRIPT_NAME
}
] Modal opened for $ {
fileName
}
`);
}
/**
* Main execution logic.
*/
function run() {
const fileInfo = getCurrentFileInfo();
if (fileInfo) {
console.log(`[$ {
SCRIPT_NAME
}
] Video detected: $ {
fileInfo.name
}
($ {
fileInfo.id
})
`);
const buttonCreated = createOrUpdateButton(fileInfo);
if (!buttonCreated) {
// If we couldn't create the button, try again later
setTimeout(run, 1000);
}
}
else {
console.log(`[$ {
SCRIPT_NAME
}
] No video detected on this page/view.`);
removeButton();
}
}
/**
* Sets up UI when toolbar is available.
*/
function setupUI() {
if (uiInitialized) return;
const toolbar = document.querySelector('.toolbar');
const templateButton = document.querySelector('.toolbar_button');
const separatorTemplate = document.querySelector('.toolbar .separator');
if (!toolbar || !templateButton || !separatorTemplate) {
console.log(`[$ {
SCRIPT_NAME
}
] Toolbar not ready yet, retrying...`);
return false;
}
uiInitialized = true;
console.log(`[$ {
SCRIPT_NAME
}
] UI Initialized`);
// Run initial setup
run();
return true;
}
// --- Script Entry Point ---
function initializeScript() {
// Wait for document body to be available
if (!document.body) {
setTimeout(initializeScript, 100);
return;
}
// Try to setup UI immediately
if (setupUI()) {
return;
// Success
}
// If immediate setup failed, use observer
const observer = new MutationObserver((mutations, obs) => {
if (setupUI()) {
obs.disconnect();
}
});
observer.observe(document.body, {
childList: true,
subtree: true
});
// Fallback timeout
setTimeout(() => {
if (!uiInitialized) {
console.log(`[$ {
SCRIPT_NAME
}
] Fallback initialization`);
setupUI();
}
},
3000);
}
// Start initialization
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initializeScript);
}
else {
initializeScript();
}
// Listen for hash changes on list pages to update the button for the new file.
$(window).on('hashchange', function() {
// Give the site's JS time to update viewer_data
setTimeout(run, 200);
});
})
(jQuery, bootstrap);