Restore Right/Middle-Click New Tab Links for manko.fun / solji.kim

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

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name         Restore Right/Middle-Click New Tab Links for manko.fun / solji.kim
// @namespace    http://tampermonkey.net/
// @version      1.3.1
// @description  Restore right/middle-click new tab function on manko.fun / solji.kim.
//               Note: This script works properly in Tampermonkey when Developer Mode is enabled.
//
// @author       VanillaMilk
// @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 KEY_OPEN_BLANK='mf_linkifier_open_blank'
  const KEY_SIDE_NAV='mf_linkifier_side_nav'
  const Rt = {
    en: { popular:"Popular","most popular":"Most Popular","top rated":"Top Rated",top_rated:"Top Rated",new:"New",subtitle:"Subtitle",random:"Random",category:"Category",actor:"Actor",genre:"Genre",country:"Country",usecase:"Download app",censored:"Censored",uncensored:"Uncensored",fc2:"FC2" },
    zh: { popular:"热门作品","most popular":"热门作品","top rated":"最佳评分",top_rated:"最佳评分",new:"最新作品",subtitle:"字幕",random:"随机",category:"类别",actor:"演员",genre:"类型",country:"国家",usecase:"下载应用程序",censored:"马赛克",uncensored:"无马赛克",fc2:"FC2" },
    tw: { popular:"受歡迎的","most popular":"受歡迎的","top rated":"最高評分",top_rated:"最高評分",new:"新的",subtitle:"字幕",random:"隨機的",category:"類別",actor:"演員",genre:"類型",country:"國家",usecase:"下載應用程式",censored:"審查",uncensored:"未經審查",fc2:"FC2" },
    ja: { popular:"人気","most popular":"人気","top rated":"最高評価",top_rated:"最高評価",new:"新作",subtitle:"字幕",random:"ランダム",category:"カテゴリー",actor:"俳優",genre:"ジャンル",country:"国名",usecase:"アプリをダウンロード",censored:"修正",uncensored:"無修正",fc2:"FC2" },
    ko: { popular:"인기작","most popular":"인기작","top rated":"최고평점",top_rated:"최고평점",new:"신작",subtitle:"자막",random:"랜덤",category:"카테고리",actor:"배우",genre:"장르",country:"국가",usecase:"앱 다운로드",censored:"유모",uncensored:"노모",fc2:"FC2" },
    id: { popular:"Populer","most popular":"Populer","top rated":"Peringkat Teratas",top_rated:"Peringkat Teratas",new:"Baru",subtitle:"Subjudul",random:"Acak",category:"Kategori",actor:"Aktor",genre:"Genre",country:"Negara",usecase:"Unduh aplikasi",censored:"Disensor",uncensored:"Tanpa Sensor",fc2:"FC2" },
    ms: { popular:"Popular","most popular":"Popular","top rated":"Tertinggi",top_rated:"Tertinggi",new:"baru",subtitle:"Sari kata",random:"rawak",category:"kategori",actor:"pelakon",genre:"Genre",country:"Negara",usecase:"Muat turun aplikasi",censored:"ditapis",uncensored:"Tidak ditapis",fc2:"FC2" },
    th: { popular:"เป็นที่นิยม","most popular":"เป็นที่นิยม","top rated":"อันดับสูงสุด",top_rated:"อันดับสูงสุด",new:"ใหม่",subtitle:"คำบรรยาย",random:"สุ่ม",category:"หมวดหมู่",actor:"นักแสดงชาย",genre:"ประเภท",country:"ประเทศ",usecase:"ดาวน์โหลดแอป",censored:"เซ็นเซอร์",uncensored:"ไม่เซ็นเซอร์",fc2:"เอฟซี2" },
    vi: { popular:"Phổ biến","most popular":"Phổ biến","top rated":"Đánh giá cao nhất",top_rated:"Đánh giá cao nhất",new:"Mới",subtitle:"Phụ đề",random:"Ngẫu nhiên",category:"Loại",actor:"Diễn viên",genre:"Thể loại",country:"Quốc gia",usecase:"Tải ứng dụng",censored:"Bị kiểm duyệt",uncensored:"Không kiểm duyệt",fc2:"FC2" }
  }

  function englishParamForKey(key){
    if(!key) return null
    if(key==='popular') return Rt.en['most popular'] || 'Most Popular'
    return Rt.en[key] || key
  }
  function cleanText(t){
    return (t||'').replace(/[\p{Extended_Pictographic}\p{Emoji_Presentation}\uFE0F\u200D\u2600-\u27BF]/gu,'').trim()
  }
  function labelToKey(label){
    const t = cleanText(label).toLowerCase()
    for(const map of Object.values(Rt)){
      for(const [k,v] of Object.entries(map)){
        if(String(v).toLowerCase()===t) return k
      }
    }
    if(t==='top rated') return 'top rated'
    if(t==='most popular') return 'most popular'
    return null
  }

  async function gv(k,d='0'){try{return String(await GM_getValue(k,d))==='1'}catch{return false}}
  async function sv(k,v){try{await GM_setValue(k,v?'1':'0')}catch{}}

  let useBlank=await gv(KEY_OPEN_BLANK,'0')
  let sideEnable=await gv(KEY_SIDE_NAV,'0')

  if(typeof GM_registerMenuCommand==='function'){
    GM_registerMenuCommand((useBlank?'☑ ':'☐ ')+'Left-click opens in new tab',async()=>{useBlank=!useBlank;await sv(KEY_OPEN_BLANK,useBlank);location.reload()})
    GM_registerMenuCommand((sideEnable?'☑ ':'☐ ')+'Side buttons: page ±1',async()=>{sideEnable=!sideEnable;await sv(KEY_SIDE_NAV,sideEnable);location.reload()})
  }

(function normalizePageForPagination() {
  try {
    const u = new URL(location.href);
    const path = u.pathname;
    const isEligible = /^\/(movie-list|cate-list|actor-list|home)/.test(path);
    const isExcluded = /^\/(genre|maker|usecase)/.test(path);

    if (isEligible && !isExcluded && !u.searchParams.has('page')) {
      u.searchParams.set('page', '1');
      history.replaceState(null, '', u.toString());
    }
  } catch {}
})();

if (sideEnable) {
  let last = 0;
  const side = (e) => {
    if (e.button !== 3 && e.button !== 4) return;

    const u = new URL(location.href);
    const path = u.pathname;
    const isEligible = /^\/(movie-list|cate-list|actor-list)/.test(path);
    const isExcluded = /^\/(genre|maker|usecase)/.test(path);

    const hasQ = u.searchParams.has('page');
    const hs = u.hash.startsWith('#') ? u.hash.slice(1) : u.hash;
    const sp = new URLSearchParams(hs);
    const hasH = sp.has('page');

    if (isEligible && !isExcluded && !hasQ && !hasH) {
      u.searchParams.set('page','1');
      e.stopImmediatePropagation?.(); e.stopPropagation?.(); e.preventDefault?.();
      location.assign(u.toString());
      return;
    }

    if (!hasQ && !hasH) return;

    const now = Date.now(); if (now - last < 120) return; last = now;
    const delta = (e.button === 3) ? -1 : 1;

    let p = hasQ ? parseInt(u.searchParams.get('page') || '1', 10)
                 : parseInt(sp.get('page') || '1', 10);
    if (!Number.isFinite(p) || p < 1) p = 1;

    let n = p + delta; if (n < 1) n = 1;

    if (hasQ) {
      u.searchParams.set('page', String(n));
    } else {
      sp.set('page', String(n));
      u.hash = '#' + sp.toString();
    }

    e.stopImmediatePropagation?.(); e.stopPropagation?.(); e.preventDefault?.();
    location.assign(u.toString());
  };

  addEventListener('mouseup', side, true);
  addEventListener('pointerup', side, true);
}


  const style=document.createElement('style')
  style.textContent=`
    .tm-wrap{position:relative!important;}
    .tm-ol{position:absolute!important;inset:0!important;width:100%!important;height:100%!important;display:block!important;background:transparent!important;text-decoration:none!important;outline:none!important;pointer-events:auto!important;}
  `
  document.documentElement.appendChild(style)

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

  function setOverlay(el,href,forceNew){
    const w=wrap(el,forceNew)
    let a=w.querySelector(':scope > a.tm-ol')
    if(!a){a=document.createElement('a');a.className='tm-ol';a.rel='noopener noreferrer';w.appendChild(a)}
    a.href=href
    a.target=useBlank?'_blank':'_self'
    return a
  }

  function linkifyCard(card){
    if(!(card instanceof Element))return
    if(!card.hasAttribute(ID_ATTR))return
    if(card.__tm_done__)return
    const raw=card.getAttribute(ID_ATTR)||''
    const id=raw.split('?')[0].trim()
    if(!id)return
    setOverlay(card,DEST_BASE+encodeURIComponent(id),true)
    card.__tm_done__=true
  }

  function scanCards(root){
    root.querySelectorAll(`[${ID_ATTR}]`).forEach(linkifyCard)
    linkifyCard(root)
  }
  scanCards(document.body||document.documentElement)

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

  function hrefForTopLabel(txt){
    const key = labelToKey(txt)
    if(!key) return null
    if(key==='category' || key==='actor') return null
    if(key==='genre') return '/genre'
    if(key==='country') return '/maker'
    if(key==='usecase') return '/usecase'
    const cat = englishParamForKey(key) || cleanText(txt)
    return `/movie-list?category=${encodeURIComponent(cat)}`
  }

  function linkifyTopNav(root){
    root.querySelectorAll('nav button').forEach(btn=>{
      if(btn.__tm_top__)return
      const txt=cleanText(btn.textContent||'');if(!txt)return
      const href=hrefForTopLabel(txt)
      if(!href) return
      setOverlay(btn,href,true)
      btn.__tm_top__=true
    })
  }

  const CATEGORY_FIXED_KEYS=new Set(['censored','uncensored','popular','top rated','top_rated','fc2'])
  const ACTOR_FIXED_KEYS =new Set(['censored','uncensored'])

  function resolveDropdownMode(pop){
    let n=pop.previousElementSibling||pop.parentElement?.previousElementSibling
    if(n){
      const k = labelToKey(cleanText(n.textContent||''))
      if(k==='actor') return 'actor'
      if(k==='category') return 'category'
    }
    return 'category'
  }

  function linkifyDropdown(pop){
    const mode=resolveDropdownMode(pop)
    pop.querySelectorAll('button').forEach(btn=>{
      if(btn.__tm_dd__)return
      const label=cleanText(btn.textContent||'');if(!label)return

      const key = labelToKey(label)
      let href
      if(mode==='actor'){
        if(key && ACTOR_FIXED_KEYS.has(key)){
          const cat = englishParamForKey(key) || key
          href = `/actor-list?category=${encodeURIComponent(cat)}&page=1`
        }else{
          href = `/actor-list?category=${encodeURIComponent(label)}&page=1`
        }
      }else{
        if(key && CATEGORY_FIXED_KEYS.has(key)){
          const cat = englishParamForKey(key) || key
          href = `/cate-list?category=${encodeURIComponent(cat)}&page=1`
        }else{
          href = `/cate-list?category=${encodeURIComponent(label)}&page=1`
        }
      }

      setOverlay(btn,href,true)
      btn.__tm_dd__=true
    })
  }

  function fullScan(root){
    linkifyTopNav(root)
    root.querySelectorAll('div.absolute').forEach(div=>{
      if(div.querySelector('button'))linkifyDropdown(div)
    })
  }
  fullScan(document.body||document.documentElement)

  const moMenus=new MutationObserver(muts=>{
    for(const m of muts){
      if(m.type==='childList'){
        m.addedNodes.forEach(n=>{if(n.nodeType===1)fullScan(n)})
      }else if(m.type==='attributes'&&(m.attributeName==='class'||m.attributeName==='style'||m.attributeName==='aria-expanded')){
        if(m.target instanceof Element)fullScan(m.target)
      }
    }
  })
  moMenus.observe(document.documentElement,{childList:true,subtree:true,attributes:true,attributeFilter:['class','style','aria-expanded']})
})()