Greasy Fork

Greasy Fork is available in English.

5chサムネイル表示他

r:ホバー中のレスへのアンカーを記入 c(文字列非選択時):ホバー中のレスを引用 c(文字列選択時):選択文字列を引用 .:そのレス以降を表示 Esc:書き込み欄にスクロール

当前为 2020-12-16 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         5chサムネイル表示他
// @description  r:ホバー中のレスへのアンカーを記入 c(文字列非選択時):ホバー中のレスを引用 c(文字列選択時):選択文字列を引用 .:そのレス以降を表示 Esc:書き込み欄にスクロール
// @version      0.1.3
// @run-at       document-idle
// @match        *://*.5ch.net/test/read.cgi/*
// @grant        GM_addStyle
// @grant        none
// @require https://code.jquery.com/jquery-3.4.1.min.js
// @require https://code.jquery.com/ui/1.12.1/jquery-ui.min.js
// @namespace http://greasyfork.icu/users/181558
// ==/UserScript==

(function() {
  const enableHoverZoom = 1; // 1:画像サムネイルのホバーズームを有効
  const DEFAULT_MAIL_ADDRESS = ""; // メールアドレス初期値 sage等に変更可
  const inlineImageThumbnailHeight = 65; // 画像サムネイルの縦サイズ(px)
  const inlineImageThumbnailBokashi = 0; // 画像サムネイルのぼかしの強さ(0~10くらい)
  const WAIT = performance.now() * 0.5; // ページ開始後のウエイト 不安定なときは大きくする
  const WAIT_IMAGE_EMBED_INTERVAL = 500; // 画像埋め込みの頻度(ミリ秒)
  const WAIT_VIDEO_EMBED_INTERVAL = 500; // 動画埋め込みの頻度(ミリ秒)
  const NUMBER_IMAGE_EMBED_AT_ONCE = 3; // 画像埋め込みの速度(枚数)
  const debugTimer = 0; // 1ならかかった時間を計測
  const inlineImageThumbnailPreloadRadius = Math.max(window.innerHeight, 1080); // 画面外の上下何ピクセルまでを「画面内」とするか
  const ALTERNATIVE_THUMBNAIL = 0; // Math.random() > 0.5 ? 1 : 0; // 1:別方式のサムネイル

  // httpならhttpsに
  if (location.href.indexOf("http://") != -1) {
    location.href = location.href.replace(/^http:\/\//, "https://");
    return;
  }
  if (location.href.indexOf("subback.html") != -1) { return; }
  if (location.href.match(/\/\/.*\.5ch\.net\/\w*\/$/)) { return; }

  // レスにホバーしてキー入力
  $(document).on("keypress", e => {
    if (/input|textarea/i.test(e.target.tagName)) return;
    if ($('form>p>textarea').length) {
      if (e.key == "r") { // r::レスにアンカー
        let num = getNearest('//span[@class="number"]');
        if (num.innerText >= 2 || (num.innerText == 1 && $(window).scrollTop() < 500)) {
          $(getNearest('//div[@class="message"]/..')).effect("highlight", 500);
          floatKakikomi(">>" + num.innerText + "\n");
          $('form>p>textarea').effect("highlight", 500);
        }
        return false;
      }
      if (e.key == "c") { // c::レスを引用
        let num = getNearest('//span[@class="number"]');
        let res = getNearest('//div[@class="message"]');
        if (num.innerText >= 2 || (num.innerText == 1 && $(window).scrollTop() < 500)) {
          var selectedStr = window.getSelection().toString() ? ">" + window.getSelection().toString().replace(/^\r\n/, "").replace(/\r\n/g, "\r\n>").replace(/^\r\n|\r\n$/, "\r\n") : null;
          $(getNearest('//div[@class="message"]/..')).effect("highlight", 500);
          floatKakikomi(">>" + num.innerText + "\r\n" +
            (selectedStr ? selectedStr : (res.innerText.replace(/^>.*$\n/gm, "").replace(/^(.+)$/gm, ">$1"))) + "\r\n");
          $('form>p>textarea').effect("highlight", 500);
        }
        return false;
      }
    }
    if (e.key == ".") { // .::そのレス以降を表示
      let num = getNearest('//span[@class="number"]');
      if (num) {
        $(getNearest('//div[@class="message"]/..')).effect("highlight");
        location.href = location.href.replace(/\/l\d+$/, "").replace(/\/[0-9\-]{0,4}-?[0-9n]{0,4}?$/gm, "/").replace(/(\d)$/, "$1/") + num.innerText + "-";
      }
      return false;
    }
  });
  $(document).on("keydown", e => {
    if (/input|textarea/i.test(e.target.tagName)) return;
    if ($('form>p>textarea').length) {
      if (e.key == "Escape") { // Esc::書き込み欄にスクロール
        scrollKakikomi(false);
        return false;
      }
    }
  });

  var mousex = 0;
  var mousey = 0;
  document.addEventListener("mousemove", function(e) {
    mousex = e.clientX;
    mousey = e.clientY;
  }, false);

  function getNearest(xpath) {
    let ele = document.elementFromPoint(mousex, mousey);
    return getEleFromParent(ele, xpath);
  }

  function getEleFromParent(ele, xpath) { // ele要素の親をさかのぼりxpathを持つ要素
    for (let i = 0; i < 9; i++) {
      var ele2 = elegeta(xpath, ele);
      if (ele2.length) return ele2[0];
      if (ele === document) return;
      ele = ele.parentNode;
    }
    return;
  }

  var resFloat = false;

  // 書き込み欄までスクロール
  function scrollKakikomi(loop = true) {
    if (resFloat) return;
    var ele = eleget0('//div[@class="formbody"]/form/p/textarea');
    if (ele) {
      var $target = $('p>input.submitbtn'),
        offset = $target.offset() || { top: 0, left: 0 },
        outerHeight = $target.outerHeight();
      $("html,body").animate({ scrollTop: (offset.top - window.innerHeight + outerHeight) });
    } else if (loop) setTimeout(scrollKakikomi, 500);
  }

  setTimeout(hNukiURLHokan, WAIT);
  scrollKakikomi();

  setTimeout(() => {
    setInterval(videoUmekomi, WAIT_VIDEO_EMBED_INTERVAL);
    setInterval(imageUmekomi, WAIT_IMAGE_EMBED_INTERVAL);

    $(eleget0('//p/input[@name="mail"]')).css("ime-mode", "inactive");
    $(xa('//input[@placeholder="メールアドレス(省略可)"]')).dblclick(() => {
      $('input[placeholder="メールアドレス(省略可)"]').val("sage");
      floatKakikomi();
    });

    // 細かい調整
    $(".mascot").attr("style", "");
    $("div.formbox").css("margin", "0");
    $('input[placeholder="メールアドレス(省略可)"]').val(DEFAULT_MAIL_ADDRESS);
    $(eleget0('//p/input[@name="mail"]')).css("ime-mode", "inactive");
    $("form>p>textarea").click(() => {
      floatKakikomi();
    });
  }, WAIT);

  // h抜きのURLをリンクにする
  function hNukiURLHokan() {
    document.querySelectorAll("div.post").forEach(function(obj) {
      var html = obj.innerHTML;
      if (obj.innerText.match(/^ttps?:\/\//gm)) {
        var newhtml = html.replace(/[^h](ttps?:\/\/\S+)/gm, "<a referrerpolicy='no-referrer' rel='nofollow external noopener noreferrer' href=\"h$1\">$1</a>");
        obj.innerHTML = newhtml;
      }
    });
  }

  // 書き込み欄調整、クリックでフロート化
  function floatKakikomi(str = "") {
    resFloat = true;
    $(eleget0('//form/p/textarea[@name="MESSAGE"]')).css({ "z-index": "999", "position": "fixed", "right": "0em", "bottom": "3em" });
    $(eleget0('//input[@class="submitbtn btn"]')).css({ "z-index": "9999999", "position": "fixed", "right": "0em", "bottom": "0em" });
    str ? $("form>p>textarea").val(str) : 0;
    $(eleget0('//p/textarea[@name="MESSAGE" and @wrap="off"]')).css({ "width": "70%", "min-width": "900px" }).keyup(() => {
      kakikomiStretch();
    });
    $(eleget0('//form/p/textarea[@name="MESSAGE"]')).focus();
    kakikomiStretch();
  }

  function kakikomiStretch() {
    let target = eleget0('//p/textarea[@name="MESSAGE"]');
    let lineHeight = Number(target.getAttribute("rows"));
    //document.title=target.scrollHeight+" / "+ target.offsetHeight
    while (target.scrollHeight >= target.offsetHeight) {
      lineHeight++;
      target.setAttribute("rows", lineHeight);
    }
  }

  // 画像と動画をインライン埋め込み
  function imageUmekomi() {
    sw("umegazo")

    // 画像埋め込み
    var i = 0;

    hoverZoomHttp();
    //if (!ALTERNATIVE_THUMBNAIL) hoverZoomHttp();

    //if (ALTERNATIVE_THUMBNAIL) { for (let a of elegeta('//div[@div="thumb5ch"]')) a.remove(); }
    //for (let ele of elegeta(ALTERNATIVE_THUMBNAIL ? '//a[not(@imge)]' : '//a[not(@imge) and not(contains(text(),"http"))]').sort((a, b) => {
    if (ALTERNATIVE_THUMBNAIL) { for (let a of elegeta('//a[not(contains(@href,"twimg"))]/div[@div="thumb5ch"]')) a.remove(); }

    for (let ele of elegeta(ALTERNATIVE_THUMBNAIL ? '//a[not(@imge)][not(contains(@href,"twimg"))]' : '//a[not(@imge) and not(contains(text(),"http"))][not(contains(@href,"twimg"))]').sort((a, b) => {
        return Math.abs(a.getBoundingClientRect().top - document.documentElement.clientHeight / 2) > Math.abs(b.getBoundingClientRect().top - document.documentElement.clientHeight / 2) ? 1 : -1
      })) {

      if ((!isinscreen(ele))) continue; // 画面内に無い
      var url = $(ele).text();
      url = url.replace(/^(ttps?:\/\/)/m, "h$1");
      if (url.match(/.jpg$|.png$|.gif$|.bmp$/)) {
        //        $(ele).after($('<br><a referrerpolicy="no-referrer" rel="nofollow external noopener noreferrer" href=' + url + ' target="_blank"><img referrerpolicy="no-referrer" src=' + url + ' height="' + inlineImageThumbnailHeight + '" ' + (inlineImageThumbnailBokashi ? 'style="filter: blur(' + inlineImageThumbnailBokashi + 'px);"' : '') + '/></a><br>'));
        $(ele).after($('<br><a referrerpolicy="no-referrer" rel="nofollow external noopener noreferrer" href=' + url + ' target="_blank"><img referrerpolicy="no-referrer" src=' + url + ' height="' + inlineImageThumbnailHeight + '" ' + (inlineImageThumbnailBokashi ? 'style="filter: blur(' + inlineImageThumbnailBokashi + 'px);"' : '') + '/></a>'));
        ele.setAttribute("imge", "imge");
        if (enableHoverZoom) {
          setTimeout(() => {
            for (let a of elegeta('//a/img[not(@hz)]')) {
              if (a.parentNode) {
                a.parentNode.addEventListener("mouseover", function() { popupEle(a, a.parentNode) }, false);
                a.setAttribute("hz", "hz")
              }
            }
          }, 100)
        }
        if (++i >= NUMBER_IMAGE_EMBED_AT_ONCE) break; // 一度に設定枚数ずつしかやらない
      }
    }
    se("umegazo")
  };

  function hoverZoomHttp() {
    if (enableHoverZoom) {
      setTimeout(() => {
        for (let a of elegeta('//img[@class="thumb_i"][not(@hz)]')) {
          if (a.parentNode) {
            a.parentNode.addEventListener("mouseover", function() { popupEle(a, a.parentNode, a.parentNode.parentNode.innerText); }, false);
            a.setAttribute("hz", "hz")
          }
        }
      }, 100)
    }
  }

  function videoUmekomi() {
    sw("umedouga")
    // ニコ動埋め込み(PrivacyBadger等は要Disable)
    for (let ele of elegeta('//a[contains(@href,"nicovideo")][not(@nde)]')) {
      if ((!isinscreen(ele))) continue; // 画面内に無い
      let url = ele.innerText.replace(/^ttp/i, "http");
      ele.setAttribute("nde", "nde");
      let nico = url.match(/h?ttps?:\/\/www.nicovideo.jp\/watch\/(.*)/i);
      if (!nico) continue
      $(ele).after('<br><iframe referrerpolicy="no-referrer" width="312" height="176" rel="nofollow external noopener noreferrer" src="https://ext.nicovideo.jp/thumb/' + nico[1] + '" scrolling="no" style="border:solid 1px #ccc;" frameborder="0"><a referrerpolicy="no-referrer" rel="nofollow external noopener noreferrer" href="' + url + '">ニコニコ動画</a></iframe>');
      break; // 一度に1つずつしかやらない
    }
    se("umedouga")
    sw("umeYT")

    // youtube埋め込み
    for (let ele of elegeta('//a[contains(@href,"youtube.com")][not(@yte)]|//a[contains(@href,"youtu.be")][not(@yte)]')) {
      if ((!isinscreen(ele))) continue; // 画面内に無い
      let url = ele.innerText;
      ele.setAttribute("yte", "yte");
      var sm = (url.match(/h?ttps?:\/\/youtu\.be\/([^&?]+.*)\?t=(\d*).*$/i) || url.match(/h?ttps?:\/\/w?w?w?m?\.youtube\.com\/watch\?v=([^&]+.*)&t=(\d*).*$/i)) || url.match(/h?ttps?:\/\/youtu\.be\/([^&?]+)/i) || url.match(/h?ttps?:\/\/w?w?w?m?\.youtube\.com\/watch\?v=([^&]+)/i);
      if (!sm) continue
      //      $(ele).after('<p><iframe referrerpolicy="no-referrer" src="https://www.youtube.com/embed/' + sm[1] + (sm[2] ? "?start=" + sm[2] : "") + '" id="ytplayer" type="text/html" width=320 height=180 frameborder=0></p>');
      $(ele).after('<p style="margin:0 0 0px;"><iframe referrerpolicy="no-referrer" src="https://www.youtube.com/embed/' + sm[1] + (sm[2] ? "?start=" + sm[2] : "") + '" id="ytplayer" type="text/html" width=320 height=180 frameborder=0></p>');
      break; // 一度に1つずつしかやらない
    };
    se("umeYT")
  }

  // eleはスクロール画面内に入ってる?
  function isinscreen(ele) {
    if (!ele) return;
    var eler = ele.getBoundingClientRect();
    return (eler.top > 0 - inlineImageThumbnailPreloadRadius && eler.left > 0 && eler.left < document.documentElement.clientWidth && eler.top < Math.min(window.innerHeight, document.documentElement.clientHeight) + inlineImageThumbnailPreloadRadius);
  }

  function popupEle(ele, eleoya, href = "") {
    var opa = 1; //ele ? 0.9 : 0.8;
    var panel = document.createElement("span");
    panel = ele.cloneNode(true);
    if (href) {
      panel.referrerPolicy = "no-referrer";
      panel.src = href;
      //alert(panel.outerHTML)
    }
    panel.height = window.innerHeight - 40;
    panel.setAttribute("style", "all:initial;width:50%;top:0px;right:0px; z-index:2147483647; opacity:" + opa + "; text-align:left; line-height:1.1; position:fixed; font-size:15px; margin:20px;  text-decoration:none; padding:0px 0px; border-radius:3px; background-color:#000000; color:#ffffff;  box-shadow:3px 3px 8px #0008; border:2px solid #fff; ");
    document.body.appendChild(panel);
    eleoya.addEventListener("mouseout", function() { if (panel.parentElement != null) { panel.parentElement.removeChild(panel) } });
    eleoya.parentNode.addEventListener("click", function() { if (panel.parentElement != null) { panel.parentElement.removeChild(panel) } });
    return;
  }

  function elegeta(xpath, node = document) {
    if (!xpath) return [];
    try {
      var array = [];
      var ele = document.evaluate("." + xpath, node, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);
      for (var i = 0; i < ele.snapshotLength; i++) array[i] = ele.snapshotItem(i);
      return array;
    } catch (e) { return []; }
  }

  function eleget0(xpath, node = document) {
    if (!xpath) return null;
    try {
      var ele = document.evaluate(xpath, node, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);
      return ele.snapshotLength > 0 ? ele.snapshotItem(0) : "";
    } catch (e) { return null; }
  }

  function xa(xpath, node = document) {
    if (!xpath) return [];
    if (xpath.match(/^\/\//)) {
      try {
        var array = [];
        var ele = document.evaluate("." + xpath, node, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);
        for (var i = 0; i < ele.snapshotLength; i++) array[i] = ele.snapshotItem(i);
        return array;
      } catch (e) { return []; }
    } else {
      return $(xpath);
    }
  }

  function sw(s) {
    if (debugTimer) console.time(s);
  }

  function se(s) {
    if (debugTimer) console.timeEnd(s);
  }
})();