Sleazy Fork is available in English.

Gelbooru Image Viewer

Adds a fullscreen image view option when you click on images

La data de 12-03-2017. Vezi ultima versiune.

// ==UserScript==
// @id             gelbooru-slide
// @name           Gelbooru Image Viewer
// @version        1.8.1
// @namespace      intermission
// @author         intermission
// @license        WTFPL; http://www.wtfpl.net/about/
// @description    Adds a fullscreen image view option when you click on images
// @include        http://gelbooru.com/index.php?*
// @include        https://gelbooru.com/index.php?*
// @run-at         document-start
// @grant          GM_registerMenuCommand
// @grant          GM_xmlhttpRequest
// ==/UserScript==

(function(d, w, stor){
  "use strict";
  var ns = "gelbooru-slide", toggle = stor[ns] == "true", notification, Pos, Menu, Btn, slideshow, $, Main, Prog;

  if (!stor[ns]) stor[ns] = "false";

  GM_registerMenuCommand("Current image mode: " + (toggle ? "Always original size" : "Sample only"), () => {
    stor[ns] = toggle ? "false" : "true";
    return location.reload();
  });

  if (stor[ns + "-firstrun"] != "1.5.3") {
    (function(l){
      var a, r = /^gelbooru-slide./;
      for (a in l)
        if (r.test(a)) l.removeItem(a);
    }(stor));
    stor[ns + "-firstrun"] = "1.5.3";
  }

  $ = function(a, b) {
    return [...(b || d).querySelectorAll(a)];
  };

  $.extend = function(obj, props) {
    var key, val;
    for (key in props) {
      if (!props.hasOwnProperty(key)) continue;
      val = props[key];
      obj[key] = val;
    }
  };

  $.extend($, {
    cache: function(a, b) {
      var id = a.match(Main.r[2])[1], val = toggle ? "original" : "sample", ret, obj, temp;
      if (!b) {
        try { ret = JSON.parse(stor[ns + id])[val]; } catch(e) {}
        ret = ret || "loading";
      } else {
        try { temp = JSON.parse(stor[ns + id]); } catch(e) {}
        obj = temp || {};
        obj[val] = b;
        stor[ns + id] = JSON.stringify(obj);
        ret = b;
      }
      return ret;
    },
    base: a => a.split("/").pop().split(".")[0].split("_").pop(),
    current: src => $("img.preview[src*='" + $.base(src || Main.el.dataset.src) + "']")[0].parentNode,
    find: function(el, method) {
      var a;
      el = el.parentNode;
      do {
        try {
          el = el[(method ? "next" : "previous") + "ElementSibling"];
          a = el.querySelector("a[data-full]");
        } catch(err) {
          return false;
        }
        if (a) break;
        a = false;
      } while(!a);
      return a;
    },
    preload: function() {
      var curr = $.current();
      Main.req($.find(curr, true));
      return Main.req($.find(curr, false));
    },
    keyDown: function(e) {
      var move;
      if (slideshow) return;
      switch(e.keyCode) {
        case 32: case 39:
          move = true;
          break;
        case 37:
          move = false;
          break;
        case 38:
          if (e.event) Menu.fn(e.event);
          else w.location = $.current().href;
          return;
        case 40:
          e.preventDefault();
          return Main.el.click();
      }
      if (typeof move != "undefined") {
        e = $.find($.current(), move);
        if (e) {
          Main.slide(e.firstElementChild.src);
          Pos.fn(move);
          $.preload();
        } else if (!notification) {
          notification = d.createElement("div");
          notification.classList.add("nomoreimages");
          notification.setAttribute("style", "pointer-events: none; background: linear-gradient(to " + (move ? "right" : "left") + ", transparent, rgba(255,0,0,.5));" + (move ? "right" : "left") + ": 0;");
          d.body.insertBefore(notification, d.body.lastElementChild);
        }
      } return;
    }
  });

  Pos = {
    fn: function(a) {
      var no, thumbs, el = Pos.el;
      if (typeof a === "boolean") {
        no = el.firstElementChild;
        no.innerHTML = Number(no.innerHTML) + (a ? 1 : -1);
        if (Menu.el)
          for (let a of $("a", Menu.el)) a.href = $.current().href;
      } else {
        if (Main.el && !el) {
          thumbs = $("span.thumb a[data-full]");
          Pos.el = el = d.createElement("div");
          el.insertAdjacentHTML("beforeend", "<span>" + (thumbs.indexOf($.current()) + 1) + "</span> / " + thumbs.length);
          el.setAttribute("style", "position: fixed; bottom: 20px; left: 0; display: block; pointer-events: none;");
          Main.el.insertAdjacentElement("afterend", el);
        } else if (el) Pos.el = el.remove();
      } return;
    }
  };

  Menu = {
    fn: function(e) {
      var _l = e.clientX + 1, _t = e.clientY + 1,
        left = (_l > w.innerWidth - 139 ? (_l - 139) : _l) + "px",
        top = (_t > w.innerHeight - 48 ? (_t - 48) : _t) + "px",
        href, el = Menu.el;
      if (el) {
        el.removeAttribute("class");
        el.style.left = left;
        el.style.top = top;
        setTimeout(() => el.classList.add("menuel"), 10);
      } else {
        href = $.current().href;
        Menu.el = el = d.createElement("div");
        el.id = "menuel";
        el.insertAdjacentHTML("beforeend", '<a href="'+href+'" style="margin-bottom: 2px">Open in This Tab</a><a href="'+href+'" target="_blank">Open in New Tab</a>');
        el.style.left = left;
        el.style.top = top;
        d.body.appendChild(el);
        el.classList.add("menuel");
      }
      return;
    }
  };

  Btn = {
    fn: function() {
      var sel, el = Btn.el;
      sel = "this.previousElementSibling.firstElementChild";
      if (el) {
        Btn.clear();
        Btn.el = Btn.el.remove();
      } else {
        Btn.el = el = d.createElement("div");
        el.setAttribute("style", 'opacity: .7;');
        el.className = "slideshow";
        el.insertAdjacentHTML('beforeend', '<span title="Slideshow">' + Btn.svg_play + "</span>" + `<div style="display: none;padding: 10px 0">Options<hr><label>Loop:&nbsp;<input type="checkbox" checked></label>&nbsp;<label onclick="${sel}.checked=true;${sel}.disabled=!${sel}.disabled">Shuffle:&nbsp;<input type="checkbox"></label><br>Interval: <input type="number" value="5" style="width: 100px"></div>`);
        Btn.svg_state = true;
        el.firstElementChild.onclick = Btn.cb;
        d.body.appendChild(el);
      }
    },
    clear: () => clearTimeout(Number(Btn.el.dataset.timer) || 0),
    cb: function() {
      var el = Btn.el, sel = "span.thumb a[data-full]",
      options = $("div input", el).map(a => 
        a.type == "number" ? (+a.value >= 1 ? +a.value : 1) * 1E3 : a.checked
      ), _fnS = () => {
        Main.el.removeEventListener("load", _fnS);
        el.dataset.timer = setTimeout(_fnT, options[2]);
      }, _fnT = () => {
        var _el;
        if (thumbs.length === 0) thumbs = $(sel);
        if (options[1]) {
          thumbs.splice(thumbs.indexOf($.current()), 1);
          _el = thumbs[Math.random() * thumbs.length >> 0];
        } else _el = $.find($.current(), true);
        if (!_el && options[0]) _el = $(sel)[0];
        if (!_el) return Btn.cb();
        Main.el.addEventListener("load", _fnS);
        Main.slide(_el.firstElementChild.src);
      }, thumbs = [];
      slideshow = !!Btn.svg_state;
      Pos.fn();
      el.firstElementChild.innerHTML = (Btn.svg_state = !Btn.svg_state) ? Btn.svg_play : Btn.svg_pause;
      if (slideshow) {
        el.dataset.timer = setTimeout(_fnT, options[2]);
        el.style.opacity = ".4";
      } else {
        Btn.clear();
        el.style.opacity = ".7";
        el.removeAttribute("data-timer");
      }
      return;
    },
    svg_play: '<svg width="50" height="50" version="1.1" xmlns="http://www.w3.org/2000/svg"><rect rx="5" height="48" width="48" y="1" x="1" fill="#fff" /><polygon fill="#000" points="16 12 16 38 36 25" /></svg>',
    svg_pause: '<span><svg width="50" height="50" xmlns="http://www.w3.org/2000/svg"><rect fill="#fff" x="1" y="1" width="48" height="48" rx="5" /><rect fill="#000" x="12" y="12" width="10" height="26" /><rect fill="#000" x="28" y="12" width="10" height="26" /></svg>'
  };

  Prog = {
    check: e => Main.el.dataset.src.indexOf($.base(e.finalUrl)) > -1,
    load: function(id, e) {
      var blob, type, el = Prog.el, img = Main.el;
      if (img && Prog.check(e) && el) {
        type = e.finalUrl.match(Main.r[3])[1].replace("jpg", "jpeg");
        blob = new Blob([e.response], {type: "image/" + type});
        img.onload = function() {
          w.URL.revokeObjectURL(img.src);
          img.onload = null;
        };
        Prog.el.classList.add("progdone");
        img.src = w.URL.createObjectURL(blob);
      }
      delete Prog.reqs[id];
    },
    progress: function(e) {
      var el = Prog.el;
      if (!el) {
        Prog.el = el = d.createElement("span");
        el.setAttribute("style", "width:0");
        el.classList.add("progress");
        d.body.appendChild(el);
      }
      if (e.lengthComputable && Main.el && Prog.check(e) && el) {
        el.classList.remove("progfail");
        el.classList.remove("progdone");
        el.style.width = e.loaded / e.total * 100 + "%";
      }
    },
    error: function(id, e) {
      var el = Prog.el;
      if (Main.el && Prog.check(e) && el) {
        el.classList.add("progfail");
        try { Prog.reqs[id].abort(); }
        catch(err) {}
        delete Prog.reqs[id];
      }
    },
    fn: function(url) {
      var id = (new Date()).getTime().toString(), details, err = Prog.error.bind(Prog, id);
      if (Prog.el) Prog.el.style.width = 0;
      details = {
        method: "GET",
        url: url,
        responseType: "arraybuffer",
        onload: Prog.load.bind(Prog, id),
        onprogress: Prog.progress,
        onerror: err,
        onabort: err,
        ontimeout: err
      };
      Prog.reqs[id] = GM_xmlhttpRequest(details);
    },
    reqs: {}
  };

  Main = {
    init: function() {
      var style = d.createElement("style"), observer;
      function process(node) {
        var a;
        try {
          if (node.matches("span.thumb[id^='s']") && (a = node.firstElementChild) && !a.dataset.full) {
            if ($("img[alt*='webm']", node)[0]) return;
            a.dataset.full = $.cache(a.href);
            a.onclick = e => e.button === 0 && (e.preventDefault(), e.stopPropagation(), Main.fn(e.target.parentNode));
          }
        } catch(e) {} return;
      }
      style.appendChild(d.createTextNode(Main.css));
      d.head.appendChild(style);
      observer = new MutationObserver(mutations => mutations.forEach(mutation => [...mutation.addedNodes].forEach(process)));
      observer.observe(d, {
        childList: true,
        subtree: true
      });
      d.addEventListener("animationend", e => {
        switch(e.animationName) {
          case "Outlined":
            e.target.classList.remove("outlined");
            break;
          case "nomoreimages":
            notification = e.target.remove();
            break;
          case "menuelement":
            Menu.el = e.target.remove();
            break;
          case "progfail": case "progdone":
            Prog.el = e.target.remove();
        }
      });
      w.addEventListener("keypress", e => {
        if (e.key === "Enter" || e.keyCode === 13) {
          if (slideshow) {
            Btn.el.firstElementChild.click();
          } else if (e.target.matches("span.thumb>a[data-full]")) {
            e.preventDefault();
            Main.fn(e.target);
          }
        }
      });
      w.addEventListener("wheel", e => Main.el && $.keyDown({keyCode: e.deltaY > 0 ? 39 : e.deltaY < 0 ? 37 : 0}));
    },
    fn: function(node) {
      d.dispatchEvent(new CustomEvent(ns, {bubbles:true}));
      return Main[!!Main.el ? "off" : "on"](node);
    },
    off: function(a) {
      var center;
      slideshow = !(a = $.current());
      Main.el = Main.el.remove();
      d.body.classList.remove("sliding");
      a.classList.add("outlined");
      d.removeEventListener("keydown", $.keyDown);
      center = a.offsetTop + a.offsetHeight / 2 - w.innerHeight / 2;
      w.scrollTo(0, center < 0 ? 0 : center);
      Pos.fn(); Btn.fn();
      Prog.el = Prog.el.remove();
    },
    on: function(a) {
      var el;
      d.body.classList.add("sliding");
      for (let a of $("span>a.outlined")) a.classList.remove("outlined");
      Main.el = el = d.createElement("img");
      el.id = "slide";
      el.alt = "Loading...";
      el.onclick = Main.fn;
      el.onmouseup = e => e.button === 1 && $.keyDown({keyCode:38, event:e});
      d.body.appendChild(el);
      Main.slide(a.firstElementChild.src);
      d.addEventListener("keydown", $.keyDown);
      Pos.fn(); Btn.fn(); $.preload();
    },
    slide: function(src) {
      var data;
      if (!slideshow) Main.el.src = src;
      Main.el.dataset.src = src
      data = $.current(src).dataset.full;
      /* dirty hack ahead because GIF doesn't want to play as a blob
       * and doesn't give proper progress info for 
       * GM_xmlhttpRequest for whatever reason */
      if (data == "loading") Main.req($.current(src));
      else if (data.match(Main.r[3])[1].toLowerCase() == "gif") {
        Main.el.removeAttribute("src");
        Main.el.src = data;
      }
      else Prog.fn(data);
    },
    r: [new RegExp((toggle ? "file_url" : "sample_url") + '="?([^"]+)"?', "i"), /com\/images\//i, /id=([0-9]+)/, /\.(gif|png|jpe?g)/i],
    req: function(node) {
      var process;
      if (!node || node.dataset.alreadyLoading || node.dataset.full != "loading") return;
      node.dataset.alreadyLoading = "true";
      process = function(img) {
        img = img.match(Main.r[0])[1].replace(Main.r[1], "com//images/");
        if (!img) throw Error("API error");
        node.dataset.full = $.cache(node.href, img);
        if (Main.el && Main.el.dataset.src.indexOf($.base(img)) > -1) Main.slide(img);
        return node.removeAttribute("data-already-loading");
      };
      return fetch("/index.php?page=dapi&s=post&q=index&id=" + node.id.substr(1))
        .then(x => x.text())
        .then(process)
        .catch(err => {
          console.error("Failed HTTP request\nDo you have an internet connection?\n", err);
          return node.removeAttribute("data-already-loading");
        });
    },
    css: `
@keyframes Outlined {
  0% { outline: 6px solid orange }
  60% { outline: 6px solid orange }
  100% { outline: 6px solid transparent }
}
@keyframes nomoreimages {
  0% { opacity: 0 }
  20% { opacity: 1 }
  100% { opacity: 0 }
}
@keyframes menuelement {
  0% { opacity: 1 }
  80% { opacity: 1 }
  100% { opacity: 0 }
}
@keyframes progfail {
  0% { opacity: 1 }
  60% { opacity: 1 }
  100% { opacity: 0 }
}
body.sliding > *:not(#slide) {
  display: none
}
#slide {
  width: 100vw;
  height: 100vh;
  object-fit: contain
}
.outlined {
  outline: 6px solid transparent;
  animation-duration: 4s;
  animation-name: Outlined
}
.nomoreimages {
  display: block ! important;
  width: 33vw;
  height: 100vh;
  top: 0;
  position: fixed;
  animation-duration: 1s;
  animation-name: nomoreimages
}
span.thumb {
  max-width: 180px;
  max-height: 180px;
}
#menuel {
  opacity: 1;
  position: fixed;
  display: block ! important;
  padding: 2px;
  background: black;
  width: 139px;
  height: 44px;
  animation-duration: 1s
}
.menuel {
  animation-name: menuelement
}
#menuel:hover {
  animation-name: keepalive
}
#menuel a {
  background: #fff;
  display: block
}
.slideshow {
  display: block ! important;
  position: fixed;
  bottom: 20px;
  right: 20px
}
.progress {
  display: block ! important;
  background-color: rgb(128,200,255);
  height: 1vh;
  position: absolute;
  top: 0;
  left: 0;
  box-shadow: 0 .5vh 10px rgba(0,0,0,.7), inset 0 0 .1vh black;
  transition: ease-in-out .1s width;
  min-height: 3px;
  max-width: 100vw;
  pointer-events: none
}
.progfail {
  background-color: red ! important;
  width: 100% ! important;
  animation-name: progfail;
  animation-duration: .6s
}
.progdone {
  width: 100% ! important;
  animation-name: progfail;
  animation-duration: .6s
}
.slideshow:hover:not([data-timer]) > div {
  background: white;
  color: black;
  position: fixed;
  display: block ! important;
  bottom: 70px;
  right: 20px
}`
  };

  Main.init();

}(document, window, localStorage));