Greasy Fork is available in English.

FA Embedded Image Viewer

Embeds the clicked Image on the Current Site, so you can view it without loading the submission Page

Mint 2024.11.18.. Lásd a legutóbbi verzió

// ==UserScript==
// @name        FA Embedded Image Viewer
// @namespace   Violentmonkey Scripts
// @match       *://*.furaffinity.net/*
// @require     https://updategreasyfork.deno.dev/scripts/475041/1267274/Furaffinity-Custom-Settings.js
// @require     https://updategreasyfork.deno.dev/scripts/483952/1486330/Furaffinity-Request-Helper.js
// @require     https://updategreasyfork.deno.dev/scripts/485153/1316289/Furaffinity-Loading-Animations.js
// @require     https://updategreasyfork.deno.dev/scripts/476762/1318215/Furaffinity-Custom-Pages.js
// @require     https://updategreasyfork.deno.dev/scripts/485827/1326313/Furaffinity-Match-List.js
// @require     https://updategreasyfork.deno.dev/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();
        })));
    }
})();