您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Adds a fullscreen image view option when you click on images
当前为
// ==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: <input type="checkbox" checked></label> <label onclick="${sel}.checked=true;${sel}.disabled=!${sel}.disabled">Shuffle: <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));