Greasy Fork

Greasy Fork is available in English.

5chサムネイル表示他

r:ホバー中のレスへのアンカーを記入(R:追記) c(文字列非選択時):ホバー中のレスを引用 c(文字列選択時):選択文字列を引用 .:ホバー中のレス以降を表示 ,:ホバー中のレスの10個前からを表示 Esc:書き込み欄にスクロール F:re.Find2chで検索

目前为 2021-02-21 提交的版本,查看 最新版本

// ==UserScript==
// @name         5chサムネイル表示他
// @description  r:ホバー中のレスへのアンカーを記入(R:追記) c(文字列非選択時):ホバー中のレスを引用 c(文字列選択時):選択文字列を引用 .:ホバー中のレス以降を表示 ,:ホバー中のレスの10個前からを表示 Esc:書き込み欄にスクロール F:re.Find2chで検索
// @version      0.1.4
// @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; // 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" || e.key == "R") { // 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", e.key == "R");
          $('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 === "." || e.key === ",") { // .::そのレス以降を表示 ,::そのレスの10個前以降を表示
      let num = getNearest('//span[@class="number"]');
      if (num) {
        let num2 = (num.innerText) - (e.key === "," ? 10 : 0);
        $(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/") + Math.max(1, num2) + "-";
      }
      return false;
    }
    if (e.key === "F") { // F::re.Find2chで検索
      let query = prompt("re.Find2chでキーワード検索します\n\nhttps://refind2ch.org/search?q=${キーワード}&sort=rate\n\n");
      if (query) window.open(`https://refind2ch.org/search?q=${query}&sort=rate`);
      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);
  setTimeout(scrollKakikomi, WAIT);

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

    //$(eleget0('//p/input[@name="mail"]')).css("ime-mode", "inactive");

    let ma = $(xa('//input[@placeholder="メールアドレス(省略可)"]'));
    ma.dblclick(() => {
      ma.val(ma.val() == "sage" ? "" : "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 = "", addMode = 0) {
    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((addMode ? $("form>p>textarea").val() + "\r\n" : "") + 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) { for (let a of elegeta('//a[not(contains(@href,"twimg"))]/div[@div="thumb5ch"]')) a.remove(); }
    if (ALTERNATIVE_THUMBNAIL) { for (let a of elegeta('//a/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) => {
    for (let ele of elegeta(ALTERNATIVE_THUMBNAIL ? '//a[not(@imge)]' : '//a[not(@imge) and not(contains(text(),"http"))]').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>'));
        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 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);
  }
})();