manko.fun Card Linkifier

Restore right/middle-click new tab function on manko.fun / solji.kim

// ==UserScript==
// @name         manko.fun Card Linkifier
// @namespace    http://tampermonkey.net/
// @version      v1.1
// @description  Restore right/middle-click new tab function on manko.fun / solji.kim
// @author       VanillaMilk
// @match        https://manko.fun/*
// @match        https://solji.kim/*
// @run-at       document-end
// @license      MIT
// @match        https://manko.fun/*
// @match        https://solji.kim/*
// @run-at       document-end
// @grant        GM_registerMenuCommand
// @grant        GM_setValue
// @grant        GM_getValue
// ==/UserScript==

(async function () {
  'use strict';

  const ID_ATTR = 'data-id';
  const DEST_BASE = 'https://solji.kim/movie-info/';
  const STORE_KEY = 'mf_linkifier_open_blank'; // '1' -> _blank, '0' -> _self

  async function loadMode() {
    try { return String(await GM_getValue(STORE_KEY, '0')) === '1'; } catch { return false; }
  }
  async function saveMode(v) {
    try { await GM_setValue(STORE_KEY, v ? '1' : '0'); } catch {}
  }

  let useBlank = await loadMode();
  const currentTarget = () => (useBlank ? '_blank' : '_self');

  GM_registerMenuCommand?.(
    (useBlank ? '☑ ' : '☐ ') + 'Left-click opens in new tab',
    async () => {
      useBlank = !useBlank;
      await saveMode(useBlank);
      location.reload();
    }
  );

  const style = document.createElement('style');
  style.textContent = `
    .tm-link-wrap { position: relative !important; }
    .tm-link-overlay {
      position: absolute !important; inset: 0 !important;
      width: 100% !important; height: 100% !important;
      z-index: 2147483646 !important; display: block !important;
      background: transparent !important; text-decoration: none !important;
      outline: none !important;
    }
    .tm-link-overlay:focus { outline: none !important; }
  `;
  document.documentElement.appendChild(style);

  function linkifyCard(card) {
    if (!(card instanceof Element)) return;
    if (!card.hasAttribute(ID_ATTR)) return;
    if (card.closest('a[href]:not([href^="#"]):not([href^="javascript:"])')) return;
    if (card.__tm_linkified__) return;

    const raw = card.getAttribute(ID_ATTR) || '';
    const id = raw.split('?')[0].trim();
    if (!id) return;

    card.__tm_linkified__ = true;

    const wrapper = ensureWrapper(card);
    let overlay = wrapper.querySelector(':scope > a.tm-link-overlay');
    if (!overlay) {
      overlay = document.createElement('a');
      overlay.className = 'tm-link-overlay';
      overlay.rel = 'noopener noreferrer';
      wrapper.appendChild(overlay);
    }
    overlay.target = currentTarget();
    overlay.href = DEST_BASE + encodeURIComponent(id);
  }

  function ensureWrapper(el) {
    const parent = el.parentElement;
    if (parent) {
      const cs = getComputedStyle(parent);
      if (cs.position !== 'static') return parent;
    }
    const wrapper = document.createElement('span');
    wrapper.className = 'tm-link-wrap';
    wrapper.style.position = 'relative';
    el.parentNode?.insertBefore(wrapper, el);
    wrapper.appendChild(el);
    return wrapper;
  }

  function scan(root) {
    if (!root || !root.querySelectorAll) return;
    root.querySelectorAll(`[${ID_ATTR}]`).forEach(linkifyCard);
    linkifyCard(root);
  }

  scan(document.body || document.documentElement);

  const mo = new MutationObserver(muts => {
    for (const m of muts) {
      if (m.type === 'childList') {
        m.addedNodes.forEach(n => n.nodeType === 1 && scan(n));
      } else if (m.type === 'attributes' && m.attributeName === ID_ATTR) {
        linkifyCard(m.target);
      }
    }
  });
  mo.observe(document.documentElement, {
    childList: true, subtree: true, attributes: true, attributeFilter: [ID_ATTR]
  });
})();