您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Embeds the clicked Image on the Current Site, so you can view it without loading the submission Page
当前为
// ==UserScript== // @name FA Embedded Image Viewer // @namespace Violentmonkey Scripts // @match *://*.furaffinity.net/* // @require https://update.greasyfork.org/scripts/475041/1267274/Furaffinity-Custom-Settings.js // @require https://update.greasyfork.org/scripts/483952/1486330/Furaffinity-Request-Helper.js // @require https://update.greasyfork.org/scripts/485153/1316289/Furaffinity-Loading-Animations.js // @require https://update.greasyfork.org/scripts/476762/1318215/Furaffinity-Custom-Pages.js // @require https://update.greasyfork.org/scripts/485827/1326313/Furaffinity-Match-List.js // @require https://update.greasyfork.org/scripts/492931/1363921/Furaffinity-Submission-Image-Viewer.js // @grant GM_info // @version 2.3.0 // @author Midori Dragon // @description Embeds the clicked Image on the Current Site, so you can view it without loading the submission Page // @icon https://www.furaffinity.net/themes/beta/img/banners/fa_logo.png?v2 // @license MIT // ==/UserScript== // jshint esversion: 8 (() => { "use strict"; var __webpack_require__ = { d: (exports, definition) => { for (var key in definition) __webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key) && Object.defineProperty(exports, key, { enumerable: !0, get: definition[key] }); }, o: (obj, prop) => Object.prototype.hasOwnProperty.call(obj, prop) }; __webpack_require__.d({}, { qJ: () => alwaysZoomCenterSetting, yr: () => closeEmbedAfterOpenSetting, xe: () => loadingSpinSpeedFavSetting, _d: () => loadingSpinSpeedSetting, h_: () => openInNewTabSetting, e2: () => previewQualitySetting, uL: () => requestHelper, t0: () => useCtrlForZoomSetting }); class EmbeddedCSS { constructor() { this.createStyle(); } createStyle() { if (document.getElementById("embeddedStyle")) return; const style = document.createElement("style"); style.id = "embeddedStyle", style.type = "text/css", style.innerHTML = EmbeddedCSS.css, document.head.appendChild(style); } static get css() { return "\n#embeddedElem {\n position: fixed;\n width: 100vw;\n height: 100vh;\n max-width: 1850px;\n z-index: 999999;\n background: rgba(30,33,38,.65);\n}\n#embeddedBackgroundElem {\n position: fixed;\n display: flex;\n flex-direction: column;\n left: 50%;\n transform: translate(-50%, 0%);\n margin-top: 20px;\n padding: 20px;\n background: rgba(30,33,38,.90);\n border-radius: 10px;\n}\n.embeddedSubmissionImg {\n max-width: inherit;\n max-height: inherit;\n border-radius: 10px;\n user-select: none;\n}\n#embeddedButtonsContainer {\n position: relative;\n margin-top: 20px;\n margin-bottom: 20px;\n margin-left: 20px;\n}\n#embeddedButtonsWrapper {\n display: flex;\n justify-content: center;\n align-items: center;\n}\n#previewLoadingSpinnerContainer {\n position: absolute;\n top: 50%;\n right: 0;\n transform: translateY(-50%);\n}\n.embeddedButton {\n margin-left: 4px;\n margin-right: 4px;\n user-select: none;\n}"; } } class EmbeddedHTML { generateHtmlString() { return EmbeddedHTML.generateHtmlString(); } static generateHtmlString() { return '\n<div id="embeddedBackgroundElem">\n <a id="embeddedSubmissionContainer"></a>\n <div id="embeddedButtonsContainer">\n <div id="embeddedButtonsWrapper">\n <a id="embeddedFavButton" type="button" class="embeddedButton button standard mobile-fix">⠀⠀</a>\n <a id="embeddedDownloadButton" type="button" class="embeddedButton button standard mobile-fix">Download</a>\n <a id="embeddedOpenButton" type="button" class="embeddedButton button standard mobile-fix">Open</a>\n <a id="embeddedOpenGalleryButton" type="button" class="embeddedButton button standard mobile-fix" style="display: none;">Open Gallery</a>\n <a id="embeddedCloseButton" type="button" class="embeddedButton button standard mobile-fix">Close</a>\n </div>\n <div id="previewLoadingSpinnerContainer"></div>\n </div>\n</div>'; } } class EmbeddedImage { constructor(figure) { this._imageLoaded = !1, this.embeddedElem, this.submissionImg, this.favRequestRunning = !1, this.downloadRequestRunning = !1, this._onRemoveAction, this.createElements(figure); const submissionContainer = document.getElementById("embeddedSubmissionContainer"), previewLoadingSpinnerContainer = document.getElementById("previewLoadingSpinnerContainer"); this.loadingSpinner = new LoadingSpinner(submissionContainer), this.loadingSpinner.delay = loadingSpinSpeedSetting.value, this.loadingSpinner.spinnerThickness = 6, this.loadingSpinner.visible = !0, this.previewLoadingSpinner = new LoadingSpinner(previewLoadingSpinnerContainer), this.previewLoadingSpinner.delay = loadingSpinSpeedSetting.value, this.previewLoadingSpinner.spinnerThickness = 4, this.previewLoadingSpinner.size = 40, this._onDocumentClick = this._onDocumentClick.bind(this), document.addEventListener("click", this._onDocumentClick), this.fillSubDocInfos(figure); } static get embeddedExists() { return !!document.getElementById("embeddedElem"); } onRemove(action) { this._onRemoveAction = action; } remove() { this.embeddedElem.parentNode.removeChild(this.embeddedElem), document.removeEventListener("click", this._onDocumentClick), this._onRemoveAction && this._onRemoveAction(); } _onDocumentClick(event) { event.target === document.documentElement && this.remove(); } createElements(figure) { this.embeddedElem = document.createElement("div"), this.embeddedElem.id = "embeddedElem", this.embeddedElem.innerHTML = EmbeddedHTML.generateHtmlString(); document.getElementById("ddmenu").appendChild(this.embeddedElem), this.embeddedElem.addEventListener("click", (event => { event.target == this.embeddedElem && this.remove(); })); const zoomLevels = new WeakMap, backgroundElem = document.getElementById("embeddedBackgroundElem"); backgroundElem.addEventListener("wheel", (event => { if (!0 === useCtrlForZoomSetting.value && !event.ctrlKey) return; event.preventDefault(), zoomLevels.has(backgroundElem) || zoomLevels.set(backgroundElem, 1); let zoomLevel = zoomLevels.get(backgroundElem); if (zoomLevel = event.deltaY < 0 ? zoomLevel + .1 : Math.max(.1, zoomLevel - .1), zoomLevels.set(backgroundElem, zoomLevel), !0 === alwaysZoomCenterSetting.value) { const rect = backgroundElem.getBoundingClientRect(), mouseX = (event.clientX - rect.left) / rect.width * 100, mouseY = (event.clientY - rect.top) / rect.height * 100; backgroundElem.style.transformOrigin = `${mouseX}% ${mouseY}%`; } else backgroundElem.style.transformOrigin = "center"; const translateMatch = (backgroundElem.style.transform || "").match(/translate\([^)]+\)/), translateValue = translateMatch ? translateMatch[0] : "translate(-50%, 0%)"; backgroundElem.style.transform = `${translateValue} scale(${zoomLevel})`; })); const submissionContainer = document.getElementById("embeddedSubmissionContainer"); !0 === openInNewTabSetting.value && submissionContainer.setAttribute("target", "_blank"), submissionContainer.addEventListener("click", (() => { !0 === closeEmbedAfterOpenSetting.value && this.remove(); })); const userLink = function(figcaption) { if (figcaption) { const infos = figcaption.querySelectorAll("i"); let userLink; for (const info of Array.from(infos)) if (info.textContent.toLowerCase().includes("by")) { const linkElem = info.parentNode.querySelector("a[href][title]"); linkElem && (userLink = linkElem.getAttribute("href")); } return userLink; } }(figure.querySelector("figcaption")); if (userLink) { const galleryLink = trimEnd(userLink, "/").replace("user", "gallery"), scrapsLink = trimEnd(userLink, "/").replace("user", "scraps"); if (!window.location.toString().includes(userLink) && !window.location.toString().includes(galleryLink) && !window.location.toString().includes(scrapsLink)) { const openGalleryButton = document.getElementById("embeddedOpenGalleryButton"); openGalleryButton.style.display = "block", openGalleryButton.setAttribute("href", galleryLink), !0 === openInNewTabSetting.value && openGalleryButton.setAttribute("target", "_blank"), openGalleryButton.onclick = () => { !0 === closeEmbedAfterOpenSetting.value && this.remove(); }; } } const link = figure.querySelector("a[href]").getAttribute("href"), openButton = document.getElementById("embeddedOpenButton"); openButton.setAttribute("href", link), !0 === openInNewTabSetting.value && openButton.setAttribute("target", "_blank"), openButton.onclick = () => { !0 === closeEmbedAfterOpenSetting.value && this.remove(); }; document.getElementById("embeddedCloseButton").onclick = () => this.remove(); document.getElementById("previewLoadingSpinnerContainer").onclick = () => { this.previewLoadingSpinner.visible = !1; }; } async fillSubDocInfos(figure) { const sid = figure.id.split("-")[1], ddmenu = document.getElementById("ddmenu"), doc = await requestHelper.SubmissionRequests.getSubmissionPage(sid); if (doc) { this.submissionImg = doc.getElementById("submissionImg"); const imgSrc = this.submissionImg.src; let prevSrc = this.submissionImg.getAttribute("data-preview-src"); previewQualitySetting.value <= 2 ? prevSrc = prevSrc.replace("@600", "@200") : 3 === previewQualitySetting.value ? prevSrc = prevSrc.replace("@600", "@300") : 4 === previewQualitySetting.value && (prevSrc = prevSrc.replace("@600", "@400")); const faImageViewer = new CustomImageViewer(imgSrc, prevSrc); faImageViewer.faImage.id = "embeddedSubmissionImg", faImageViewer.faImagePreview.id = "previewSubmissionImg", faImageViewer.faImage.className = faImageViewer.faImagePreview.className = "embeddedSubmissionImg", faImageViewer.faImage.style.maxWidth = faImageViewer.faImagePreview.style.maxWidth = window.innerWidth - 40 + "px", faImageViewer.faImage.style.maxHeight = faImageViewer.faImagePreview.style.maxHeight = window.innerHeight - ddmenu.clientHeight - 76 - 40 - 100 + "px", faImageViewer.onImageLoadStart = () => { this._imageLoaded = !1, this.loadingSpinner && (this.loadingSpinner.visible = !1); }, faImageViewer.onImageLoad = () => { this._imageLoaded = !0, this.loadingSpinner && !0 === this.loadingSpinner.visible && (this.loadingSpinner.visible = !1), this.previewLoadingSpinner && !0 === this.previewLoadingSpinner.visible && (this.previewLoadingSpinner.visible = !1); }, faImageViewer.onPreviewImageLoad = () => { !1 === this._imageLoaded && (this.previewLoadingSpinner.visible = !0); }; const submissionContainer = document.getElementById("embeddedSubmissionContainer"); faImageViewer.load(submissionContainer); const url = doc.querySelector('meta[property="og:url"]').content; submissionContainer.setAttribute("href", url); const result = function(doc) { const columnPage = doc.getElementById("columnpage"), buttons = columnPage.querySelector('div[class*="favorite-nav"').querySelectorAll('a[class*="button"][href]'); let favButton; for (const button of Array.from(buttons)) button.textContent.toLowerCase().includes("fav") && (favButton = button); if (favButton) { return { favKey: favButton.getAttribute("href").split("?key=")[1], isFav: !favButton.getAttribute("href").toLowerCase().includes("unfav") }; } return null; }(doc), favButton = document.getElementById("embeddedFavButton"); favButton.textContent = result.isFav ? "+Fav" : "-Fav", favButton.setAttribute("isFav", result.isFav), favButton.setAttribute("key", result.favKey), favButton.addEventListener("click", (() => { !1 === this.favRequestRunning && this.doFavRequest(sid); })); const downloadButton = document.getElementById("embeddedDownloadButton"); downloadButton.addEventListener("click", (() => { if (!0 === this.downloadRequestRunning) return; this.downloadRequestRunning = !0; const loadingTextSpinner = new LoadingTextSpinner(downloadButton); loadingTextSpinner.delay = loadingSpinSpeedFavSetting.value, loadingTextSpinner.visible = !0; const iframe = document.createElement("iframe"); iframe.style.display = "none", iframe.src = this.submissionImg.src + "?eidownload", iframe.addEventListener("load", (() => { this.downloadRequestRunning = !1, loadingTextSpinner.visible = !1, setTimeout((() => iframe.parentNode.removeChild(iframe)), 100); })), document.body.appendChild(iframe); })); } } async doFavRequest(sid) { const favButton = document.getElementById("embeddedFavButton"); this.favRequestRunning = !0; const loadingTextSpinner = new LoadingTextSpinner(favButton); loadingTextSpinner.delay = loadingSpinSpeedFavSetting.value, loadingTextSpinner.visible = !0; let favKey = favButton.getAttribute("key"), isFav = "true" == favButton.getAttribute("isFav"); !0 === isFav ? (favKey = await requestHelper.SubmissionRequests.favSubmission(sid, favKey), loadingTextSpinner.visible = !1, favKey ? (favButton.setAttribute("key", favKey), isFav = !1, favButton.setAttribute("isFav", isFav.toString()), favButton.textContent = "-Fav") : (favButton.textContent = "x", setTimeout((() => favButton.textContent = "+Fav"), 1e3))) : (favKey = await requestHelper.SubmissionRequests.unfavSubmission(sid, favKey), loadingTextSpinner.visible = !1, favKey ? (favButton.setAttribute("key", favKey), isFav = !0, favButton.setAttribute("isFav", isFav.toString()), favButton.textContent = "+Fav") : (favButton.textContent = "x", setTimeout((() => favButton.textContent = "-Fav"), 1e3))), this.favRequestRunning = !1; } } async function addEmbedded() { const nonEmbeddedFigures = document.querySelectorAll("figure:not([embedded])"); for (const figure of Array.from(nonEmbeddedFigures)) figure.setAttribute("embedded", "true"), figure.addEventListener("click", (event => { if (event instanceof MouseEvent && event.target instanceof HTMLElement && !event.ctrlKey && !event.target.id.includes("favbutton") && "checkbox" !== event.target.getAttribute("type")) { if (event.target.getAttribute("href")) return; event.preventDefault(), !EmbeddedImage.embeddedExists && figure instanceof HTMLElement && new EmbeddedImage(figure); } })); } function trimEnd(string, toRemove) { return string.endsWith(toRemove) && (string = string.slice(0, -1)), string; } CustomSettings.name = "Extension Settings", CustomSettings.provider = "Midori's Script Settings", CustomSettings.headerName = `${GM_info.script.name} Settings`; const openInNewTabSetting = CustomSettings.newSetting("Open in new Tab", "Wether to open links in a new Tab or the current one.", SettingTypes.Boolean, "Open in new Tab", !0), loadingSpinSpeedFavSetting = CustomSettings.newSetting("Fav Loading Animation", "The duration that the loading animation, for faving a submission, takes for a full rotation in milliseconds.", SettingTypes.Number, "", 600), loadingSpinSpeedSetting = CustomSettings.newSetting("Embedded Loading Animation", "The duration that the loading animation of the Embedded element to load takes for a full rotation in milliseconds.", SettingTypes.Number, "", 1e3), closeEmbedAfterOpenSetting = CustomSettings.newSetting("Close Embed after open", "Wether to clos the current embedded Submission after it is opened in a new Tab (also for open Gallery).", SettingTypes.Boolean, "Close Embed after open", !0), useCtrlForZoomSetting = CustomSettings.newSetting("Use Ctrl for Zoom", "Wether the Ctrl-Key needs to be pressed while scrolling to zoom the Embedded Image.", SettingTypes.Boolean, "Use Ctrl for Zoom", !1), alwaysZoomCenterSetting = CustomSettings.newSetting("Zoom from Mouse Location", "Wether the Embedded Image should be zoomed from the Mouse Location. (Otherwise from Center)", SettingTypes.Boolean, "Zoom from Mouse Location", !0), previewQualitySetting = CustomSettings.newSetting("Preview Quality", "The quality of the preview image. Value range is 2-6. (Higher values can be slower)", SettingTypes.Number, "", 3); CustomSettings.loadSettings(); const requestHelper = new FARequestHelper(2), matchList = new MatchList(CustomSettings); if (matchList.matches = [ "net/browse", "net/user", "net/gallery", "net/search", "net/favorites", "net/scraps", "net/controls/favorites", "net/controls/submissions", "net/msg/submissions", "d.furaffinity.net" ], matchList.runInIFrame = !0, matchList.hasMatch()) { const page = new CustomPage("d.furaffinity.net", "eidownload"); let pageDownload = !1; page.onopen = () => { !function() { let url = window.location.toString(); if (url.includes("?")) { const parts = url.split("?"); url = parts[0]; } const download = document.createElement("a"); download.href = url, download.download = url.substring(url.lastIndexOf("/") + 1), download.style.display = "none", document.body.appendChild(download), download.click(), document.body.removeChild(download), window.close(); }(), pageDownload = !0; }, !1 === pageDownload && !1 === matchList.isWindowIFrame() && (new EmbeddedCSS, addEmbedded(), window.addEventListener("updateEmbeddedEvent", (async () => { await addEmbedded(); }))); } })();