FA Embedded Image Viewer

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

Version vom 18.11.2024. Aktuellste Version

Du musst eine Erweiterung wie Tampermonkey, Greasemonkey oder Violentmonkey installieren, um dieses Skript zu installieren.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

Sie müssten eine Skript Manager Erweiterung installieren damit sie dieses Skript installieren können

(Ich habe schon ein Skript Manager, Lass mich es installieren!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

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