Greasy Fork 增强

增进 Greasyfork 浏览体验。

当前为 2023-07-07 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Greasy Fork Enhance
  3. // @name:zh-CN Greasy Fork 增强
  4. // @namespace http://tampermonkey.net/
  5. // @version 0.4.1
  6. // @description Enhance your experience at Greasyfork.
  7. // @description:zh-CN 增进 Greasyfork 浏览体验。
  8. // @author PRO
  9. // @grant GM_setValue
  10. // @grant GM_getValue
  11. // @grant GM_registerMenuCommand
  12. // @grant GM_unregisterMenuCommand
  13. // @match https://greasyfork.org/*
  14. // @require https://greasyfork.org/scripts/470224-tampermonkey-config/code/Tampermonkey%20Config.js?version=1216049
  15. // @icon https://greasyfork.org/vite/assets/blacklogo16-bc64b9f7.png
  16. // @license gpl-3.0
  17. // ==/UserScript==
  18.  
  19. (function() {
  20. 'use strict';
  21. // Judge if the script should run
  22. let no_run = [".json", ".user.js"];
  23. let is_run = true;
  24. no_run.forEach((suffix) => {
  25. if (window.location.href.endsWith(suffix)) {
  26. is_run = false;
  27. }
  28. });
  29. if (!is_run) return;
  30. // Config
  31. let config_desc = {
  32. "opacity/default": {
  33. name: "Default opacity",
  34. value: 0.2,
  35. processor: parseFloat
  36. },
  37. "opacity/hover": {
  38. name: "Hover opacity",
  39. value: 0.8,
  40. processor: parseFloat
  41. },
  42. "opacity/none": {
  43. name: "None-hover opacity",
  44. value: 0.8,
  45. processor: parseFloat
  46. }
  47. };
  48. let config = GM_config(config_desc);
  49. // Functions
  50. let body = document.querySelector("body");
  51. function sanitify(s) {
  52. // Remove emojis (such a headache)
  53. s = s.replaceAll(/([\uE000-\uF8FF]|\uD83C[\uDC00-\uDFFF]|\uD83D[\uDC00-\uDFFF]|[\u2580-\u27BF]|\uD83E[\uDD10-\uDEFF]|\uFE0F)/g, "");
  54. // Trim spaces and newlines
  55. s = s.trim();
  56. // Replace spaces
  57. s = s.replaceAll(" ", "-");
  58. s = s.replaceAll("%20", "-");
  59. // No more multiple "-"
  60. s = s.replaceAll(/-+/g, "-");
  61. return s;
  62. }
  63. function process(node) { // Add anchor and assign id to given node; Add to outline. Return true if node is actually processed.
  64. if (node.childElementCount > 1 || node.classList.length > 0) return false; // Ignore complex nodes
  65. let text = node.textContent;
  66. node.id = sanitify(text); // Assign id
  67. // Add anchors
  68. let node_ = document.createElement('a');
  69. node_.className = 'anchor';
  70. node_.href = '#' + node.id;
  71. node.appendChild(node_);
  72. let list_item = document.createElement("li");
  73. outline.appendChild(list_item);
  74. let link = document.createElement("a");
  75. link.href = "#" + node.id;
  76. link.text = text;
  77. list_item.appendChild(link);
  78. return true;
  79. }
  80. function copyCode() {
  81. let code = this.parentNode.nextElementSibling;
  82. let text = code.textContent;
  83. navigator.clipboard.writeText(text).then(() => {
  84. this.textContent = "Copied!";
  85. setTimeout(() => {
  86. this.textContent = "Copy code";
  87. }, 1000);
  88. });
  89. }
  90. function toggleCode() {
  91. let code = this.parentNode.nextElementSibling;
  92. if (code.style.display == "none") {
  93. code.style.display = "";
  94. this.textContent = "Hide code";
  95. } else {
  96. code.style.display = "none";
  97. this.textContent = "Show code";
  98. }
  99. }
  100. function create_toolbar() {
  101. let toolbar = document.createElement("div");
  102. let copy = document.createElement("a");
  103. let toggle = document.createElement("a");
  104. toolbar.appendChild(copy);
  105. toolbar.appendChild(toggle);
  106. copy.textContent = "Copy code";
  107. copy.addEventListener("click", copyCode);
  108. toggle.textContent = "Hide code";
  109. toggle.addEventListener("click", toggleCode);
  110. // Css
  111. toolbar.style.display = "flex";
  112. toolbar.style.gap = "1em";
  113. toolbar.style.fontStyle = "italic";
  114. return toolbar;
  115. }
  116. // Css
  117. let css = document.createElement("style");
  118. css.textContent = `
  119. html {
  120. scroll-behavior: smooth;
  121. }
  122. a.anchor::before {
  123. content: "#";
  124. }
  125. a.anchor {
  126. opacity: 0;
  127. text-decoration: none;
  128. padding: 0px 0.5em;
  129. -moz-transition: all 0.25s ease-in-out;
  130. -o-transition: all 0.25s ease-in-out;
  131. -webkit-transition: all 0.25s ease-in-out;
  132. transition: all 0.25s ease-in-out;
  133. }
  134. h1:hover>a.anchor,
  135. h2:hover>a.anchor,
  136. h3:hover>a.anchor,
  137. h4:hover>a.anchor,
  138. h5:hover>a.anchor,
  139. h6:hover>a.anchor {
  140. opacity: 1;
  141. -moz-transition: all 0.25s ease-in-out;
  142. -o-transition: all 0.25s ease-in-out;
  143. -webkit-transition: all 0.25s ease-in-out;
  144. transition: all 0.25s ease-in-out;
  145. }
  146. a.button {
  147. margin: 0.5em 0 0 0;
  148. display: flex;
  149. align-items: center;
  150. justify-content: center;
  151. text-decoration: none;
  152. color: black;
  153. background-color: #a42121ab;
  154. border-radius: 50%;
  155. width: 2em;
  156. height: 2em;
  157. font-size: 1.8em;
  158. font-weight: bold;
  159. }
  160. div.lum-lightbox {
  161. z-index: 2;
  162. }
  163. div#float-buttons {
  164. position: fixed;
  165. bottom: 1em;
  166. right: 1em;
  167. display: flex;
  168. flex-direction: column;
  169. user-select: none;
  170. z-index: 1;
  171. }
  172. aside.panel {
  173. display: none;
  174. }
  175. .dynamic-opacity {
  176. transition: opacity 0.2s ease-in-out;
  177. opacity: ${config["opacity/default"]};
  178. }
  179. .dynamic-opacity:hover {
  180. opacity: ${config["opacity/hover"]};
  181. }
  182. input[type=file] {
  183. border-style: dashed;
  184. border-radius: 0.5em;
  185. padding: 0.5em;
  186. background: rgba(169, 169, 169, 0.4);
  187. }
  188. @media (any-hover: none) {
  189. .dynamic-opacity {
  190. opacity: ${config["opacity/none"]};
  191. }
  192. .dynamic-opacity:hover {
  193. opacity: ${config["opacity/none"]};
  194. }
  195. }
  196. @media screen and (min-width: 767px) {
  197. aside.panel {
  198. display: contents;
  199. line-height: 1.5;
  200. }
  201. ul.outline {
  202. position: sticky;
  203. float: right;
  204. padding: 0 0 0 0.5em;
  205. margin: 0 0.5em;
  206. max-height: 80vh;
  207. border: 1px solid #BBBBBB;
  208. border-left: 2px solid #F2E5E5;
  209. box-shadow: 0 0 5px #ddd;
  210. background: linear-gradient(to right, #fcf1f1, #FFF 1em);
  211. list-style: none;
  212. width: 10.5%;
  213. color: gray;
  214. border-radius: 5px;
  215. overflow-y: scroll;
  216. z-index: 1;
  217. }
  218. ul.outline > li {
  219. overflow: hidden;
  220. text-overflow: ellipsis;
  221. }
  222. ul.outline > li > a {
  223. color: gray;
  224. white-space: nowrap;
  225. text-decoration: none;
  226. }
  227. }`;
  228. document.querySelector("head").appendChild(css); // Inject css
  229. // Aside panel & Anchors
  230. let outline;
  231. let is_script = /^\/[^\/]+\/scripts/;
  232. let is_specific_script = /^\/[^\/]+\/scripts\/\d+/;
  233. let is_disccussion = /^\/[^\/]+\/discussions/;
  234. let path = window.location.pathname;
  235. if ((!is_script.test(path) && !is_disccussion.test(path)) || is_specific_script.test(path)) {
  236. let panel = document.createElement("aside");
  237. panel.className = "panel";
  238. body.insertBefore(panel, document.querySelector("body > div.width-constraint"));
  239. let reference_node = document.querySelector("body > div.width-constraint > section");
  240. outline = document.createElement("ul");
  241. outline.classList.add("outline");
  242. outline.classList.add("dynamic-opacity");
  243. outline.style.top = reference_node ? getComputedStyle(reference_node).marginTop : "1em";
  244. outline.style.marginTop = outline.style.top;
  245. panel.appendChild(outline);
  246. let flag = false;
  247. document.querySelectorAll("body > div.width-constraint h1, h2, h3, h4, h5, h6").forEach((node) => {
  248. flag = process(node) || flag; // Not `flag || process(node)`!
  249. });
  250. if (!flag) {
  251. panel.remove();
  252. }
  253. }
  254. // Navigate to hash
  255. let hash = window.location.hash.slice(1);
  256. if (hash) {
  257. let ele = document.getElementById(decodeURIComponent(hash));
  258. if (ele) {
  259. ele.scrollIntoView();
  260. }
  261. }
  262. // Buttons
  263. let buttons = document.createElement("div");
  264. buttons.id = "float-buttons";
  265. let to_top = document.createElement("a");
  266. to_top.classList.add("button");
  267. to_top.classList.add("dynamic-opacity");
  268. to_top.href = "#top";
  269. to_top.text = "↑";
  270. buttons.appendChild(to_top);
  271. body.appendChild(buttons);
  272. // Double click to get to top
  273. body.addEventListener("dblclick", (e) => {
  274. if (e.target == body) {
  275. to_top.click();
  276. }
  277. });
  278. // Fix current tab link
  279. let tab = document.querySelector("ul#script-links > li.current");
  280. if (tab) {
  281. let link = document.createElement("a");
  282. link.href = window.location.pathname;
  283. let orig_child = tab.firstChild;
  284. link.appendChild(orig_child);
  285. tab.appendChild(link);
  286. }
  287. let parts = window.location.pathname.split("/");
  288. if (parts.length <= 2 || (parts.length == 3 && parts[2] === '')) {
  289. let banner = document.querySelector("header#main-header div#site-name");
  290. let img = banner.querySelector("img");
  291. let text = banner.querySelector("#site-name-text > h1");
  292. let link1 = document.createElement("a");
  293. link1.href = window.location.pathname;
  294. img.parentNode.replaceChild(link1, img);
  295. link1.appendChild(img);
  296. let link2 = document.createElement("a");
  297. link2.href = window.location.pathname;
  298. link2.textContent = text.textContent;
  299. text.textContent = "";
  300. text.appendChild(link2);
  301. }
  302. // Toolbar for code blocks
  303. let code_blocks = document.getElementsByTagName("pre");
  304. for (let code_block of code_blocks) {
  305. code_block.insertAdjacentElement("afterbegin", create_toolbar(code_block));
  306. }
  307. })();