FA Embedded Image Viewer

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

目前为 2024-01-27 提交的版本。查看 最新版本

  1. // ==UserScript==
  2. // @name FA Embedded Image Viewer
  3. // @namespace Violentmonkey Scripts
  4. // @match *://*.furaffinity.net/*
  5. // @require https://update.greasyfork.org/scripts/475041/1267274/Furaffinity-Custom-Settings.js
  6. // @require https://update.greasyfork.org/scripts/483952/1306729/Furaffinity-Request-Helper.js
  7. // @require https://update.greasyfork.org/scripts/485153/1316289/Furaffinity-Loading-Animations.js
  8. // @require https://update.greasyfork.org/scripts/476762/1318215/Furaffinity-Custom-Pages.js
  9. // @grant none
  10. // @version 2.0.7
  11. // @author Midori Dragon
  12. // @description Embedds the clicked Image on the Current Site, so you can view it without loading the submission Page
  13. // @icon https://www.furaffinity.net/themes/beta/img/banners/fa_logo.png?v2
  14. // @homepageURL https://greasyfork.org/de/scripts/458971-embedded-image-viewer
  15. // @supportURL https://greasyfork.org/de/scripts/458971-embedded-image-viewer/feedback
  16. // @license MIT
  17. // ==/UserScript==
  18.  
  19. // jshint esversion: 8
  20.  
  21. const matchList = ['net/browse', 'net/gallery', 'net/search', 'net/favorites', 'net/scraps', 'net/controls/favorites', 'net/controls/submissions', 'net/msg/submissions', 'd.furaffinity.net'];
  22.  
  23. const page = new CustomPage("d.furaffinity.net", "eidownload");
  24. page.onopen = (data) => {
  25. downloadImage();
  26. return;
  27. };
  28.  
  29. CustomSettings.name = "Extension Settings";
  30. CustomSettings.provider = "Midori's Script Settings";
  31. CustomSettings.headerName = `${GM_info.script.name} Settings`;
  32. const preventInstantDownloadSetting = CustomSettings.newSetting("Prevent Instant Download", "Sets wether to instantly download the Image when the download Button is pressed.", SettingTypes.Boolean, "Prevent Instant Download", false);
  33. 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);
  34. 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);
  35. CustomSettings.loadSettings();
  36.  
  37. let color = "color: blue";
  38. if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches)
  39. color = "color: aqua";
  40. if (window.location.toString().includes("?extension")) {
  41. console.info(`%cSettings: ${GM_info.script.name} v${GM_info.script.version}`, color);
  42. return;
  43. }
  44.  
  45. if (!matchList.some(x => window.location.toString().includes(x)))
  46. return;
  47.  
  48. console.info(`%cRunning: ${GM_info.script.name} v${GM_info.script.version} ${CustomSettings.toString()}`, color);
  49.  
  50. const requestHelper = new FARequestHelper(2);
  51.  
  52. class EmbeddedImage {
  53. constructor(figure) {
  54. this.embeddedElem;
  55. this.backgroundElem;
  56. this.submissionContainer;
  57. this.submissionImg;
  58. this.buttonsContainer;
  59. this.favButton;
  60. this.downloadButton;
  61. this.closeButton;
  62.  
  63. this._onRemoveAction;
  64.  
  65. this.createStyle();
  66. this.createElements(figure);
  67.  
  68. this.loadingSpinner = new LoadingSpinner(this.submissionContainer);
  69. this.loadingSpinner.delay = loadingSpinSpeedSetting.value;
  70. this.loadingSpinner.spinnerThickness = 6;
  71. this.loadingSpinner.visible = true;
  72. this.fillSubDocInfos(figure);
  73. }
  74.  
  75. createStyle() {
  76. if (document.getElementById("embeddedStyle")) return;
  77. const style = document.createElement("style");
  78. style.id = "embeddedStyle";
  79. style.type = "text/css";
  80. style.innerHTML = `
  81. #embeddedElem {
  82. position: fixed;
  83. width: 100vw;
  84. height: 100vh;
  85. max-width: 1850px;
  86. z-index: 999999;
  87. background: rgba(30,33,38,.65);
  88. }
  89. #embeddedBackgroundElem {
  90. position: fixed;
  91. display: flex;
  92. flex-direction: column;
  93. left: 50%;
  94. transform: translate(-50%, 0%);
  95. margin-top: 20px;
  96. padding: 20px;
  97. background: rgba(30,33,38,.90);
  98. border-radius: 10px;
  99. }
  100. #embeddedSubmissionImg {
  101. max-width: inherit;
  102. max-height: inherit;
  103. border-radius: 10px;
  104. }
  105. #embeddedButtonsContainer {
  106. margin-top: 20px;
  107. margin-bottom: 20px;
  108. margin-left: 20px;
  109. }
  110. .embeddedButton {
  111. margin-left: 4px;
  112. margin-right: 4px;
  113. }
  114. `;
  115. document.head.appendChild(style);
  116. }
  117.  
  118. onRemove(action) {
  119. this._onRemoveAction = action;
  120. }
  121.  
  122. remove() {
  123. this.embeddedElem.parentNode.removeChild(this.embeddedElem);
  124. if (this._onRemoveAction)
  125. this._onRemoveAction();
  126. }
  127.  
  128. createElements(figure) {
  129. this.embeddedElem = document.createElement("div");
  130. this.embeddedElem.id = "embeddedElem";
  131. this.embeddedElem.onclick = (event) => {
  132. if (event.target == this.embeddedElem)
  133. this.remove();
  134. };
  135.  
  136. this.backgroundElem = document.createElement("div");
  137. this.backgroundElem.id = "embeddedBackgroundElem";
  138. notClosingElemsArr.push(this.backgroundElem.id);
  139.  
  140. this.submissionContainer = document.createElement("a");
  141. this.submissionContainer.id = "embeddedSubmissionContainer";
  142. notClosingElemsArr.push(this.submissionContainer.id);
  143.  
  144. this.backgroundElem.appendChild(this.submissionContainer);
  145.  
  146. this.buttonsContainer = document.createElement("div");
  147. this.buttonsContainer.id = "embeddedButtonsContainer";
  148. notClosingElemsArr.push(this.buttonsContainer.id);
  149.  
  150. this.favButton = document.createElement("a");
  151. this.favButton.id = "embeddedFavButton";
  152. notClosingElemsArr.push(this.favButton.id);
  153. this.favButton.type = "button";
  154. this.favButton.className = "embeddedButton button standard mobile-fix";
  155. this.favButton.textContent = "⠀⠀";
  156. this.buttonsContainer.appendChild(this.favButton);
  157.  
  158. this.downloadButton = document.createElement("a");
  159. this.downloadButton.id = "embeddedDownloadButton";
  160. notClosingElemsArr.push(this.downloadButton.id);
  161. this.downloadButton.type = "button";
  162. this.downloadButton.className = "embeddedButton button standard mobile-fix";
  163. this.downloadButton.textContent = "Download";
  164. this.downloadButton.target = "_blank";
  165. this.buttonsContainer.appendChild(this.downloadButton);
  166.  
  167. this.openGalleryButton = document.createElement("a");
  168. this.openGalleryButton.id = "embeddedOpenGalleryButton";
  169. notClosingElemsArr.push(this.openGalleryButton.id);
  170. this.openGalleryButton.type = "button";
  171. this.openGalleryButton.className = "embeddedButton button standard mobile-fix";
  172. this.openGalleryButton.textContent = "Open Gallery";
  173. const links = figure.querySelector("figcaption").querySelectorAll("a[href][title]");
  174. const galleryLink = links[links.length - 1].toString().replace("user", "gallery");
  175. this.openGalleryButton.href = galleryLink;
  176. this.openGalleryButton.target = "_blank";
  177. this.buttonsContainer.appendChild(this.openGalleryButton);
  178.  
  179. this.closeButton = document.createElement("a");
  180. this.closeButton.id = "embeddedCloseButton";
  181. notClosingElemsArr.push(this.closeButton.id);
  182. this.closeButton.type = "button";
  183. this.closeButton.className = "embeddedButton button standard mobile-fix";
  184. this.closeButton.textContent = "Close";
  185. this.closeButton.onclick = () => this.remove();
  186. this.buttonsContainer.appendChild(this.closeButton);
  187.  
  188. this.backgroundElem.appendChild(this.buttonsContainer);
  189.  
  190. this.embeddedElem.appendChild(this.backgroundElem);
  191.  
  192. const ddmenu = document.getElementById("ddmenu");
  193. ddmenu.appendChild(this.embeddedElem);
  194. }
  195.  
  196. async fillSubDocInfos(figure) {
  197. const sid = figure.id.split("-")[1];
  198. const ddmenu = document.getElementById("ddmenu");
  199. const doc = await requestHelper.SubmissionRequests.getSubmissionPage(sid);
  200. if (this.loadingSpinner)
  201. this.loadingSpinner.visible = false;
  202. if (doc) {
  203. this.submissionImg = doc.getElementById("submissionImg");
  204. this.submissionImg.style.maxWidth = window.innerWidth - 20 * 2 + "px";
  205. this.submissionImg.style.maxHeight = window.innerHeight - ddmenu.clientHeight - 38 * 2 - 20 * 2 - 100 + "px";
  206. this.submissionContainer.appendChild(this.submissionImg);
  207. this.submissionContainer.href = doc.querySelector('meta[property="og:url"]').content;
  208.  
  209. const result = getFavKey(doc);
  210. this.favButton.textContent = result.isFav ? "+Fav" : "-Fav";
  211. this.favButton.setAttribute("isFav", result.isFav);
  212. this.favButton.setAttribute("key", result.favKey);
  213. this.favButton.onclick = () => this.doFavRequest(sid);
  214.  
  215. this.downloadButton.href = this.submissionImg.src + "?eidownload";
  216. }
  217. }
  218.  
  219. async doFavRequest(sid) {
  220. const loadingTextSpinner = new LoadingTextSpinner(this.favButton);
  221. loadingTextSpinner.delay = loadingSpinSpeedFavSetting.value;
  222. loadingTextSpinner.visible = true;
  223. let favKey = this.favButton.getAttribute("key");
  224. let isFav = this.favButton.getAttribute("isFav");
  225. if (isFav == "true") {
  226. favKey = await requestHelper.SubmissionRequests.favSubmission(sid, favKey);
  227. loadingTextSpinner.visible = false;
  228. if (favKey) {
  229. this.favButton.setAttribute("key", favKey);
  230. isFav = false;
  231. this.favButton.setAttribute("isFav", isFav);
  232. this.favButton.textContent = "-Fav";
  233. } else {
  234. this.favButton.textContent = "x";
  235. setTimeout(() => this.favButton.textContent = "+Fav", 1000);
  236. }
  237. } else {
  238. favKey = await requestHelper.SubmissionRequests.unfavSubmission(sid, favKey);
  239. loadingTextSpinner.visible = false;
  240. if (favKey) {
  241. this.favButton.setAttribute("key", favKey);
  242. isFav = true;
  243. this.favButton.setAttribute("isFav", isFav);
  244. this.favButton.textContent = "+Fav";
  245. } else {
  246. this.favButton.textContent = "x";
  247. setTimeout(() => this.favButton.textContent = "-Fav", 1000);
  248. }
  249. }
  250. }
  251. }
  252.  
  253. function getFavKey(doc) {
  254. const columnPage = doc.getElementById("columnpage");
  255. const navbar = columnPage.querySelector('div[class*="favorite-nav"');
  256. const buttons = navbar.querySelectorAll('a[class*="button"][href]');
  257. let favButton;
  258. for (const button of buttons) {
  259. if (button.textContent.toLowerCase().includes("fav"))
  260. favButton = button;
  261. }
  262.  
  263. if (favButton) {
  264. const favKey = favButton.href.split("?key=")[1];
  265. const isFav = !favButton.href.toLowerCase().includes("unfav");
  266. return { favKey, isFav };
  267. }
  268. }
  269.  
  270. let isShowing = false;
  271. let notClosingElemsArr = [];
  272. let embeddedImage;
  273.  
  274. addEmbedded();
  275. window.updateEmbedded = addEmbedded;
  276.  
  277. document.addEventListener("click", (event) => {
  278. if (event.target.parentNode instanceof HTMLDocument && embeddedImage)
  279. embeddedImage.remove();
  280. })
  281.  
  282. async function addEmbedded() {
  283. for (const figure of document.querySelectorAll('figure:not([embedded])')) {
  284. figure.setAttribute('embedded', true);
  285. figure.addEventListener("click", function (event) {
  286. if (!event.ctrlKey && !event.target.id.includes("favbutton") && event.target.type != "checkbox") {
  287. if (event.target.href)
  288. return;
  289. else
  290. event.preventDefault();
  291. if (!isShowing)
  292. showImage(figure);
  293. }
  294. });
  295. }
  296. }
  297.  
  298. async function showImage(figure) {
  299. isShowing = true;
  300. embeddedImage = new EmbeddedImage(figure);
  301. embeddedImage.onRemove(() => {
  302. embeddedImage = null;
  303. isShowing = false;
  304. });
  305. }
  306.  
  307. function downloadImage() {
  308. console.log("Embedded Image Viewer downloading Image...");
  309. let url = window.location.toString();
  310. if (url.includes("?")) {
  311. const parts = url.split('?');
  312. url = parts[0];
  313. }
  314. const download = document.createElement('a');
  315. download.href = url;
  316. download.download = url.substring(url.lastIndexOf("/") + 1);
  317. download.style.display = 'none';
  318. document.body.appendChild(download);
  319. download.click();
  320. document.body.removeChild(download);
  321.  
  322. window.close();
  323. }