- // ==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/1329447/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 none
- // @version 2.2.0
- // @author Midori Dragon
- // @description Embedds 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
- // @homepageURL https://greatest.deepsurf.us/de/scripts/458971-embedded-image-viewer
- // @supportURL https://greatest.deepsurf.us/de/scripts/458971-embedded-image-viewer/feedback
- // @license MIT
- // ==/UserScript==
-
- // jshint esversion: 8
-
- 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", "Sets wether to open links in a new Tab or the current one.", SettingTypes.Boolean, "Open in new Tab", true);
- const loadingSpinSpeedFavSetting = CustomSettings.newSetting("Fav Loading Animation", "Sets the duration that the loading animation, for faving a submission, takes for a full rotation in milliseconds.", SettingTypes.Number, "", 600);
- const loadingSpinSpeedSetting = CustomSettings.newSetting("Embedded Loading Animation", "Sets the duration that the loading animation of the Embedded element to load takes for a full rotation in milliseconds.", SettingTypes.Number, "", 1000);
- const closeEmbedAfterOpenSetting = CustomSettings.newSetting("Close Embed after open", "Closes the current embedded Submission after it is opened in a new Tab (also for open Gallery)", SettingTypes.Boolean, "Close Embed after open", true);
- const useCtrlForZoomSetting = CustomSettings.newSetting("Use Ctrl for Zoom", "Whether the Ctrl-Key needs to be pressed while scrolling to zoom the Embedded Image", SettingTypes.Boolean, "Use Ctrl for Zoom", false);
- CustomSettings.loadSettings();
-
- const matchList = new MatchList(CustomSettings);
- 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 = true;
- if (!matchList.hasMatch())
- return;
-
- const page = new CustomPage("d.furaffinity.net", "eidownload");
- page.onopen = (data) => {
- downloadImage();
- return;
- };
-
- if (matchList.isWindowIFrame() == true)
- return;
-
- const requestHelper = new FARequestHelper(2);
-
- class EmbeddedImage {
- constructor(figure) {
- this._previewLoaded;
- this._imageLoaded;
-
- this.embeddedElem;
- this.backgroundElem;
- this.submissionContainer;
- this.submissionImg;
- this.buttonsContainer;
- this.previewLoadingSpinnerContainer;
- this.favButton;
- this.downloadButton;
- this.closeButton;
-
- this.favRequestRunning = false;
- this.downloadRequestRunning = false;
-
- this._onRemoveAction;
-
- this.createStyle();
- this.createElements(figure);
-
- this.loadingSpinner = new LoadingSpinner(this.submissionContainer);
- this.loadingSpinner.delay = loadingSpinSpeedSetting.value;
- this.loadingSpinner.spinnerThickness = 6;
- this.loadingSpinner.visible = true;
-
- this.previewLoadingSpinner = new LoadingSpinner(this.previewLoadingSpinnerContainer);
- this.previewLoadingSpinner.delay = loadingSpinSpeedSetting.value;
- this.previewLoadingSpinner.spinnerThickness = 4;
- this.previewLoadingSpinner.size = 40;
-
- this.fillSubDocInfos(figure);
- }
-
- createStyle() {
- if (document.getElementById("embeddedStyle")) return;
- const style = document.createElement("style");
- style.id = "embeddedStyle";
- style.type = "text/css";
- style.innerHTML = `
- #embeddedElem {
- position: fixed;
- width: 100vw;
- height: 100vh;
- max-width: 1850px;
- z-index: 999999;
- background: rgba(30,33,38,.65);
- }
- #embeddedBackgroundElem {
- position: fixed;
- display: flex;
- flex-direction: column;
- left: 50%;
- transform: translate(-50%, 0%);
- margin-top: 20px;
- padding: 20px;
- background: rgba(30,33,38,.90);
- border-radius: 10px;
- }
- .embeddedSubmissionImg {
- max-width: inherit;
- max-height: inherit;
- border-radius: 10px;
- user-select: none;
- }
- #embeddedButtonsContainer {
- position: relative;
- margin-top: 20px;
- margin-bottom: 20px;
- margin-left: 20px;
- }
- #embeddedButtonsWrapper {
- display: flex;
- justify-content: center;
- align-items: center;
- }
- #previewLoadingSpinnerContainer {
- position: absolute;
- top: 50%;
- right: 0;
- transform: translateY(-50%);
- }
- .embeddedButton {
- margin-left: 4px;
- margin-right: 4px;
- user-select: none;
- }
- `;
- document.head.appendChild(style);
- }
-
- onRemove(action) {
- this._onRemoveAction = action;
- }
-
- remove() {
- this.embeddedElem.parentNode.removeChild(this.embeddedElem);
- if (this._onRemoveAction)
- this._onRemoveAction();
- }
-
- createElements(figure) {
- this.embeddedElem = document.createElement("div");
- this.embeddedElem.id = "embeddedElem";
- this.embeddedElem.onclick = (event) => {
- if (event.target == this.embeddedElem)
- this.remove();
- };
-
- const zoomLevels = new WeakMap();
- this.backgroundElem = document.createElement("div");
- this.backgroundElem.id = "embeddedBackgroundElem";
- this.backgroundElem.addEventListener('wheel', (event) => {
- if (useCtrlForZoomSetting.value === true && !event.ctrlKey) {
- return;
- }
- event.preventDefault(); // Prevent page scroll
-
- // Initialize zoom level for this image if not already set
- if (!zoomLevels.has(this.backgroundElem)) {
- zoomLevels.set(this.backgroundElem, 1);
- }
-
- // Get the current zoom level
- let zoomLevel = zoomLevels.get(this.backgroundElem);
-
- // Adjust zoom level based on scroll direction
- if (event.deltaY < 0) {
- zoomLevel += 0.1; // Zoom in
- } else {
- zoomLevel = Math.max(0.1, zoomLevel - 0.1); // Zoom out, with a minimum limit
- }
-
- // Save the updated zoom level
- zoomLevels.set(this.backgroundElem, zoomLevel);
-
- // Calculate mouse position relative to the image
- const rect = this.backgroundElem.getBoundingClientRect();
- const mouseX = ((event.clientX - rect.left) / rect.width) * 100;
- const mouseY = ((event.clientY - rect.top) / rect.height) * 100;
-
- // Get the current transform value
- const existingTransform = this.backgroundElem.style.transform || '';
-
- // Extract any existing translate transform
- const translateMatch = existingTransform.match(/translate\([^)]+\)/);
- const translateValue = translateMatch ? translateMatch[0] : 'translate(-50%, 0%)';
-
- // Apply the combined transform with scale
- this.backgroundElem.style.transform = `${translateValue} scale(${zoomLevel})`;
- this.backgroundElem.style.transformOrigin = `${mouseX}% ${mouseY}%`;
- });
- notClosingElemsArr.push(this.backgroundElem.id);
-
- this.submissionContainer = document.createElement("a");
- this.submissionContainer.id = "embeddedSubmissionContainer";
- if (openInNewTabSetting.value == true)
- this.submissionContainer.target = "_blank";
- this.submissionContainer.onclick = () => {
- if (closeEmbedAfterOpenSetting.value == true)
- this.remove();
- };
- notClosingElemsArr.push(this.submissionContainer.id);
-
- this.backgroundElem.appendChild(this.submissionContainer);
-
- this.buttonsContainer = document.createElement("div");
- this.buttonsContainer.id = "embeddedButtonsContainer";
- notClosingElemsArr.push(this.buttonsContainer.id);
-
- this.buttonsWrapper = document.createElement("div");
- this.buttonsWrapper.id = "embeddedButtonsWrapper";
- notClosingElemsArr.push(this.buttonsWrapper.id);
- this.buttonsContainer.appendChild(this.buttonsWrapper);
-
- this.favButton = document.createElement("a");
- this.favButton.id = "embeddedFavButton";
- notClosingElemsArr.push(this.favButton.id);
- this.favButton.type = "button";
- this.favButton.className = "embeddedButton button standard mobile-fix";
- this.favButton.textContent = "⠀⠀";
- this.buttonsWrapper.appendChild(this.favButton);
-
- this.downloadButton = document.createElement("a");
- this.downloadButton.id = "embeddedDownloadButton";
- notClosingElemsArr.push(this.downloadButton.id);
- this.downloadButton.type = "button";
- this.downloadButton.className = "embeddedButton button standard mobile-fix";
- this.downloadButton.textContent = "Download";
- this.buttonsWrapper.appendChild(this.downloadButton);
-
- const userLink = getByLinkFromFigcaption(figure.querySelector("figcaption"));
- if (userLink) {
- const galleryLink = trimEnd(userLink, "/").replace("user", "gallery");
- const scrapsLink = trimEnd(userLink, "/").replace("user", "scraps");
- if (!window.location.toString().includes(userLink) && !window.location.toString().includes(galleryLink) && !window.location.toString().includes(scrapsLink)) {
- this.openGalleryButton = document.createElement("a");
- this.openGalleryButton.id = "embeddedOpenGalleryButton";
- notClosingElemsArr.push(this.openGalleryButton.id);
- this.openGalleryButton.type = "button";
- this.openGalleryButton.className = "embeddedButton button standard mobile-fix";
- this.openGalleryButton.textContent = "Open Gallery";
- this.openGalleryButton.href = galleryLink;
- if (openInNewTabSetting.value == true)
- this.openGalleryButton.target = "_blank";
- this.openGalleryButton.onclick = () => {
- if (closeEmbedAfterOpenSetting.value == true)
- this.remove();
- };
- this.buttonsWrapper.appendChild(this.openGalleryButton);
- }
- }
-
- this.openButton = document.createElement("a");
- this.openButton.id = "embeddedOpenButton";
- notClosingElemsArr.push(this.openButton.id);
- this.openButton.type = "button";
- this.openButton.className = "embeddedButton button standard mobile-fix";
- this.openButton.textContent = "Open";
- const link = figure.querySelector("a[href]");
- this.openButton.href = link;
- if (openInNewTabSetting.value == true)
- this.openButton.target = "_blank";
- this.openButton.onclick = () => {
- if (closeEmbedAfterOpenSetting.value == true)
- this.remove();
- };
- this.buttonsWrapper.appendChild(this.openButton);
-
- this.closeButton = document.createElement("a");
- this.closeButton.id = "embeddedCloseButton";
- notClosingElemsArr.push(this.closeButton.id);
- this.closeButton.type = "button";
- this.closeButton.className = "embeddedButton button standard mobile-fix";
- this.closeButton.textContent = "Close";
- this.closeButton.onclick = () => this.remove();
- this.buttonsWrapper.appendChild(this.closeButton);
-
- this.previewLoadingSpinnerContainer = document.createElement("div");
- this.previewLoadingSpinnerContainer.id = "previewLoadingSpinnerContainer";
- notClosingElemsArr.push(this.previewLoadingSpinnerContainer.id);
- this.previewLoadingSpinnerContainer.onclick = () => {
- this.previewLoadingSpinner.visible = false;
- };
- this.buttonsContainer.appendChild(this.previewLoadingSpinnerContainer);
-
- this.backgroundElem.appendChild(this.buttonsContainer);
-
- this.embeddedElem.appendChild(this.backgroundElem);
-
- const ddmenu = document.getElementById("ddmenu");
- ddmenu.appendChild(this.embeddedElem);
- }
-
- async fillSubDocInfos(figure) {
- const sid = figure.id.split("-")[1];
- const ddmenu = document.getElementById("ddmenu");
- const doc = await requestHelper.SubmissionRequests.getSubmissionPage(sid);
- if (doc) {
- this.submissionImg = doc.getElementById("submissionImg");
- const imgSrc = this.submissionImg.src;
- const prevSrc = this.submissionImg.getAttribute("data-preview-src");
- const prevPrevSrc = prevSrc.replace("@600", "@300");
-
- 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 - 20 * 2 + "px";
- faImageViewer.faImage.style.maxHeight = faImageViewer.faImagePreview.style.maxHeight = window.innerHeight - ddmenu.clientHeight - 38 * 2 - 20 * 2 - 100 + "px";
- faImageViewer.onImageLoadStart = () => {
- this._previewLoaded = false;
- this._imageLoaded = false;
- if (this.loadingSpinner)
- this.loadingSpinner.visible = false;
- };
- faImageViewer.onImageLoad = () => {
- this._imageLoaded = true;
- if (this.loadingSpinner && this.loadingSpinner.visible === true)
- this.loadingSpinner.visible = false;
- if (this.previewLoadingSpinner && this.previewLoadingSpinner.visible === true)
- this.previewLoadingSpinner.visible = false;
- };
- faImageViewer.onPreviewImageLoad = () => {
- this._previewLoaded = true;
- if (this._imageLoaded === false)
- this.previewLoadingSpinner.visible = true;
- };
- faImageViewer.load(this.submissionContainer);
-
- this.submissionContainer.href = doc.querySelector('meta[property="og:url"]').content;
-
- const result = getFavKey(doc);
- this.favButton.textContent = result.isFav ? "+Fav" : "-Fav";
- this.favButton.setAttribute("isFav", result.isFav);
- this.favButton.setAttribute("key", result.favKey);
- this.favButton.onclick = () => {
- if (this.favRequestRunning == false)
- this.doFavRequest(sid);
- };
-
- this.downloadButton.onclick = () => {
- if (this.downloadRequestRunning == true)
- return;
- this.downloadRequestRunning = true;
- const loadingTextSpinner = new LoadingTextSpinner(this.downloadButton);
- loadingTextSpinner.delay = loadingSpinSpeedFavSetting.value;
- loadingTextSpinner.visible = true;
- const iframe = document.createElement("iframe");
- iframe.style.display = "none";
- iframe.src = this.submissionImg.src + "?eidownload";
- iframe.onload = () => {
- this.downloadRequestRunning = false;
- loadingTextSpinner.visible = false;
- setTimeout(() => iframe.parentNode.removeChild(iframe), 100);
- };
- document.body.appendChild(iframe);
- };
- }
- }
-
- async doFavRequest(sid) {
- this.favRequestRunning = true;
- const loadingTextSpinner = new LoadingTextSpinner(this.favButton);
- loadingTextSpinner.delay = loadingSpinSpeedFavSetting.value;
- loadingTextSpinner.visible = true;
- let favKey = this.favButton.getAttribute("key");
- let isFav = this.favButton.getAttribute("isFav");
- if (isFav == "true") {
- favKey = await requestHelper.SubmissionRequests.favSubmission(sid, favKey);
- loadingTextSpinner.visible = false;
- if (favKey) {
- this.favButton.setAttribute("key", favKey);
- isFav = false;
- this.favButton.setAttribute("isFav", isFav);
- this.favButton.textContent = "-Fav";
- } else {
- this.favButton.textContent = "x";
- setTimeout(() => this.favButton.textContent = "+Fav", 1000);
- }
- } else {
- favKey = await requestHelper.SubmissionRequests.unfavSubmission(sid, favKey);
- loadingTextSpinner.visible = false;
- if (favKey) {
- this.favButton.setAttribute("key", favKey);
- isFav = true;
- this.favButton.setAttribute("isFav", isFav);
- this.favButton.textContent = "+Fav";
- } else {
- this.favButton.textContent = "x";
- setTimeout(() => this.favButton.textContent = "-Fav", 1000);
- }
- }
- this.favRequestRunning = false;
- }
- }
-
- function getByLinkFromFigcaption(figcaption) {
- if (figcaption) {
- const infos = figcaption.querySelectorAll("i");
- let userLink;
- for (const info of infos) {
- if (info.textContent.toLowerCase().includes("by")) {
- const linkElem = info.parentNode.querySelector("a[href][title]");
- if (linkElem)
- userLink = linkElem.href;
- }
- }
- return userLink;
- }
- }
-
- function getFavKey(doc) {
- const columnPage = doc.getElementById("columnpage");
- const navbar = columnPage.querySelector('div[class*="favorite-nav"');
- const buttons = navbar.querySelectorAll('a[class*="button"][href]');
- let favButton;
- for (const button of buttons) {
- if (button.textContent.toLowerCase().includes("fav"))
- favButton = button;
- }
-
- if (favButton) {
- const favKey = favButton.href.split("?key=")[1];
- const isFav = !favButton.href.toLowerCase().includes("unfav");
- return { favKey, isFav };
- }
- }
-
- let isShowing = false;
- let notClosingElemsArr = [];
- let embeddedImage;
-
- addEmbedded();
- window.updateEmbedded = addEmbedded;
-
- document.addEventListener("click", (event) => {
- if (event.target.parentNode instanceof HTMLDocument && embeddedImage)
- embeddedImage.remove();
- });
-
- async function addEmbedded() {
- for (const figure of document.querySelectorAll('figure:not([embedded])')) {
- figure.setAttribute('embedded', true);
- figure.addEventListener("click", function (event) {
- if (!event.ctrlKey && !event.target.id.includes("favbutton") && event.target.type != "checkbox") {
- if (event.target.href)
- return;
- else
- event.preventDefault();
- if (!isShowing)
- showImage(figure);
- }
- });
- }
- }
-
- async function showImage(figure) {
- isShowing = true;
- embeddedImage = new EmbeddedImage(figure);
- embeddedImage.onRemove(() => {
- embeddedImage = null;
- isShowing = false;
- });
- }
-
- function downloadImage() {
- console.log("Embedded Image Viewer downloading Image...");
- 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();
- }
-
- function trimEnd(string, toRemove) {
- if (string.endsWith(toRemove))
- string = string.slice(0, -1);
- return string;
- }