FA Embedded Image Viewer

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

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

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