Jump to content

MediaWiki:Common.js: Difference between revisions

From Doom Relics
No edit summary
Tag: Reverted
No edit summary
Tag: Reverted
Line 118: Line 118:
   if (sp !== 'FormEdit' && sp !== 'FormStart') return;
   if (sp !== 'FormEdit' && sp !== 'FormStart') return;


   let activeTargetName = null;
   let activeInputId = null;
   let snapshot = {};
   let snapshot = new Map();
   let guardTimer = null;
   let guardUntil = 0;


   function takeSnapshot($scope) {
  function isImgInput(el) {
     snapshot = {};
    const name = el.getAttribute('name') || '';
     $scope.find('input[name^="img"]').each(function () {
    return name.startsWith('Relic[img');
       snapshot[this.name] = $(this).val() || '';
  }
 
   function takeSnapshot() {
     snapshot.clear();
     $content.find('input[name^="Relic[img"]').each(function () {
       snapshot.set(this, this.value || '');
     });
     });
   }
   }


   function startGuard(targetName, $scope) {
   function startGuardFor(inputId) {
     activeTargetName = targetName;
     activeInputId = inputId || null;
     takeSnapshot($scope);
     takeSnapshot();
 
     guardUntil = Date.now() + 15000; // 15s window for upload popup to return
     if (guardTimer) clearTimeout(guardTimer);
    // Guard window: long enough to cover the upload popup returning
    guardTimer = setTimeout(function () {
      activeTargetName = null;
      guardTimer = null;
    }, 15000);
   }
   }


   // 1) When "Upload file" is clicked, figure out which img field it belongs to and start guarding.
   // 1) Capture which exact input an Upload link belongs to
   $content.on('mousedown click', 'a, button', function () {
   $content.on('mousedown click', 'a.pf_uploadable', function () {
     const $el = $(this);
     const inputId = this.getAttribute('data-inputid'); // e.g. "input_8"
    const text = ($el.text() || '').trim().toLowerCase();
    startGuardFor(inputId);
     const href = ($el.attr('href') || '');
     // Ensure the correct input is focused before the upload popup runs
    const cls = ($el.attr('class') || '').toLowerCase();
    if (inputId) {
      const el = document.getElementById(inputId);
      if (el) { el.focus(); el.click(); }
    }
  });


    const looksLikeUpload =
  // 2) Hard guard: revert changes to any img input except the active one
      text === 'upload file' ||
  // Use input event + a fallback MutationObserver for cases where events don't fire.
      href.includes('Special:Upload') ||
  function enforceGuard(changedEl) {
      href.includes('Special:UploadWindow') ||
    if (!activeInputId) return;
      cls.includes('upload');
    if (Date.now() > guardUntil) { activeInputId = null; return; }
    if (!changedEl || !isImgInput(changedEl)) return;


     if (!looksLikeUpload) return;
     const isTarget = changedEl.id === activeInputId;


     // Find the nearest row and the img input in that row (img1/img2/img3…)
     if (isTarget) {
    const $row = $el.closest('tr');
      snapshot.set(changedEl, changedEl.value || '');
     let $target = $row.find('input[name^="img"]').first();
      return;
     }


     // Fallback: closest previous img input
     // Revert non-target field to the snapshot value
     if (!$target.length) {
     const prev = snapshot.has(changedEl) ? snapshot.get(changedEl) : '';
      $target = $el.closest('td, .pfFormInput, div').find('input[name^="img"]').first();
    if ((changedEl.value || '') !== prev) {
      changedEl.value = prev;
     }
     }
  }


    if ($target.length) {
  // Input/change events (fast path)
      startGuard($target.attr('name'), $content);
  $content.on('input change', 'input[name^="Relic[img"]', function () {
      $target.trigger('focus').trigger('click');
    enforceGuard(this);
    }
   });
   });


   // 2) Guard: if PF tries to change multiple img fields, revert all except the active target.
   // MutationObserver (fallback path for silent value updates)
   $content.on('input change', 'input[name^="img"]', function () {
   const root = $content.get(0);
    if (!activeTargetName) return;
  if (!root) return;


    const name = this.name;
  const obs = new MutationObserver(function () {
     const $this = $(this);
    if (!activeInputId) return;
     if (Date.now() > guardUntil) { activeInputId = null; return; }


     // Allow the target field to change; update snapshot for the target.
     // Enforce guard across all image inputs
     if (name === activeTargetName) {
     root.querySelectorAll('input[name^="Relic[img"]').forEach(enforceGuard);
      snapshot[name] = $this.val() || '';
  });
      return;
    }


    // Revert non-target fields if they change
  obs.observe(root, { subtree: true, childList: true, attributes: true });
    const previous = snapshot[name] ?? '';
    if (($this.val() || '') !== previous) {
      $this.val(previous);
    }
  });
});
});

Revision as of 15:05, 25 December 2025

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

// Vector 2022: pin main menu for logged-out users on desktop
if (!mw.config.get('wgUserName') && window.matchMedia('(min-width: 1120px)').matches) {
  const observer = new MutationObserver((_, obs) => {
    if (document.documentElement.classList.contains('vector-animations-ready')) {
      const pin = document.querySelector('[data-event-name="pinnable-header.vector-main-menu.pin"]');
      if (pin) pin.click();
      obs.disconnect();
    }
  });
  observer.observe(document.documentElement, { attributes: true, attributeFilter: ['class'] });
}
// Remove "Main menu" headings/labels injected by Vector/Vector 2022 (works across variants)
(function () {
  function killMainMenuText(root = document) {
    // Look for common heading/label elements
    const candidates = root.querySelectorAll(
      '#vector-main-menu, #vector-main-menu-pinned-container, #mw-panel, nav.vector-main-menu, #vector-main-menu-dropdown'
    );

    candidates.forEach(container => {
      // Remove elements whose visible text is exactly "Main menu"
      container.querySelectorAll('*').forEach(el => {
        const txt = (el.textContent || '').trim();
        if (txt === 'Main menu') {
          el.style.display = 'none';
        }
        // Also catch the longer hint text variants
        if (txt.toLowerCase().includes('main menu has moved here')) {
          el.style.display = 'none';
        }
      });
    });
  }

  // Run now, on load, and after Vector finishes UI init
  killMainMenuText();
  window.addEventListener('load', () => killMainMenuText());

  const obs = new MutationObserver(() => killMainMenuText());
  obs.observe(document.documentElement, { childList: true, subtree: true });
})();
// DOOM ARCHIVES header: inject above your Doom Titles links (guaranteed)
(function () {
  const HEADER_CLASS = 'doom-archives-header';

  function styleHeader(header) {
    header.style.display = 'block';
    header.style.width = '100%';
    header.style.boxSizing = 'border-box';
    header.style.margin = '10px 0 10px';
    header.style.padding = '12px 14px';
    header.style.background = 'rgba(0,0,0,0.75)';
    header.style.border = '1px solid #3a0000';
    header.style.borderRadius = '12px';
    header.style.boxShadow = 'inset 0 0 20px rgba(180,0,0,0.30), 0 0 8px rgba(0,0,0,0.8)';
    header.style.color = '#f5c542';
    header.style.fontWeight = '900';
    header.style.letterSpacing = '0.22em';
    header.style.textTransform = 'uppercase';
    header.style.textAlign = 'center';
    header.style.fontFamily = "'Bebas Neue', Arial, sans-serif";
    header.style.textShadow = '0 0 3px rgba(245,197,66,0.35)';
    header.style.fontSize = '22px';
  }

  function findAnchor() {
    // Use a link you KNOW exists in your sidebar
    return (
      document.querySelector('a[href*="Category:Doom_(1993)"]') ||
      document.querySelector('a[href*="Category:Doom_II"]') ||
      document.querySelector('a[href*="Category:Doom_3"]') ||
      document.querySelector('a[href*="Category:Doom_Eternal"]')
    );
  }

  function ensureHeader() {
    const a = findAnchor();
    if (!a) return;

    // Find the menu content container that holds the link list
    const menuContent =
      a.closest('.vector-menu-content') ||
      a.closest('ul') ||
      a.parentElement;

    if (!menuContent) return;

    // Avoid duplicates
    if (menuContent.querySelector('.' + HEADER_CLASS)) return;

    const header = document.createElement('div');
    header.className = HEADER_CLASS;
    header.textContent = 'DOOM RELICS';
    styleHeader(header);

    // Insert header before the first list item / first link area
    menuContent.insertBefore(header, menuContent.firstChild);
  }

  function run() {
    ensureHeader();
    setTimeout(ensureHeader, 150);
    setTimeout(ensureHeader, 600);
    setTimeout(ensureHeader, 1200);
  }

  // Run now + keep alive if Vector re-renders
  run();
  window.addEventListener('load', run);

  const obs = new MutationObserver(() => ensureHeader());
  obs.observe(document.documentElement, { childList: true, subtree: true });
})();
mw.hook('wikipage.content').add(function ($content) {
  const sp = mw.config.get('wgCanonicalSpecialPageName');
  if (sp !== 'FormEdit' && sp !== 'FormStart') return;

  let activeInputId = null;
  let snapshot = new Map();
  let guardUntil = 0;

  function isImgInput(el) {
    const name = el.getAttribute('name') || '';
    return name.startsWith('Relic[img');
  }

  function takeSnapshot() {
    snapshot.clear();
    $content.find('input[name^="Relic[img"]').each(function () {
      snapshot.set(this, this.value || '');
    });
  }

  function startGuardFor(inputId) {
    activeInputId = inputId || null;
    takeSnapshot();
    guardUntil = Date.now() + 15000; // 15s window for upload popup to return
  }

  // 1) Capture which exact input an Upload link belongs to
  $content.on('mousedown click', 'a.pf_uploadable', function () {
    const inputId = this.getAttribute('data-inputid'); // e.g. "input_8"
    startGuardFor(inputId);
    // Ensure the correct input is focused before the upload popup runs
    if (inputId) {
      const el = document.getElementById(inputId);
      if (el) { el.focus(); el.click(); }
    }
  });

  // 2) Hard guard: revert changes to any img input except the active one
  // Use input event + a fallback MutationObserver for cases where events don't fire.
  function enforceGuard(changedEl) {
    if (!activeInputId) return;
    if (Date.now() > guardUntil) { activeInputId = null; return; }
    if (!changedEl || !isImgInput(changedEl)) return;

    const isTarget = changedEl.id === activeInputId;

    if (isTarget) {
      snapshot.set(changedEl, changedEl.value || '');
      return;
    }

    // Revert non-target field to the snapshot value
    const prev = snapshot.has(changedEl) ? snapshot.get(changedEl) : '';
    if ((changedEl.value || '') !== prev) {
      changedEl.value = prev;
    }
  }

  // Input/change events (fast path)
  $content.on('input change', 'input[name^="Relic[img"]', function () {
    enforceGuard(this);
  });

  // MutationObserver (fallback path for silent value updates)
  const root = $content.get(0);
  if (!root) return;

  const obs = new MutationObserver(function () {
    if (!activeInputId) return;
    if (Date.now() > guardUntil) { activeInputId = null; return; }

    // Enforce guard across all image inputs
    root.querySelectorAll('input[name^="Relic[img"]').forEach(enforceGuard);
  });

  obs.observe(root, { subtree: true, childList: true, attributes: true });
});