Greasy Fork 增强

增进 Greasyfork 浏览体验。

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

  1. // ==UserScript==
  2. // @name Greasy Fork Enhance
  3. // @name:zh-CN Greasy Fork 增强
  4. // @namespace http://tampermonkey.net/
  5. // @version 0.3.1
  6. // @description Enhance your experience at Greasyfork.
  7. // @description:zh-CN 增进 Greasyfork 浏览体验。
  8. // @author PRO
  9. // @match https://greasyfork.org/*
  10. // @icon https://greasyfork.org/vite/assets/blacklogo16-bc64b9f7.png
  11. // @license gpl-3.0
  12. // @grant none
  13. // ==/UserScript==
  14.  
  15. (function() {
  16. 'use strict';
  17. let body = document.querySelector("body");
  18. let config = {
  19. opacity: {
  20. "default": 0.2,
  21. "hover": 0.8,
  22. "none": 0.8,
  23. "transition": "opacity 0.2s ease-in-out"
  24. }
  25. };
  26. function sanitify(s) {
  27. // Remove emojis (such a headache)
  28. s = s.replaceAll(/([\uE000-\uF8FF]|\uD83C[\uDC00-\uDFFF]|\uD83D[\uDC00-\uDFFF]|[\u2580-\u27BF]|\uD83E[\uDD10-\uDEFF]|\uFE0F)/g, "");
  29. // Trim spaces and newlines
  30. s = s.trim();
  31. // Replace spaces
  32. s = s.replaceAll(" ", "-");
  33. s = s.replaceAll("%20", "-");
  34. // No more multiple "-"
  35. s = s.replaceAll(/-+/g, "-");
  36. return s;
  37. }
  38. function process(node) { // Add anchor and assign id to given node; Add to outline. Return true if node is actually processed.
  39. if (node.childElementCount > 1 || node.classList.length > 0) return false; // Ignore complex nodes
  40. let text = node.textContent;
  41. node.id = sanitify(text); // Assign id
  42. // Add anchors
  43. let node_ = document.createElement('a');
  44. node_.className = 'anchor';
  45. node_.href = '#' + node.id;
  46. node.appendChild(node_);
  47. let list_item = document.createElement("li");
  48. outline.appendChild(list_item);
  49. let link = document.createElement("a");
  50. link.href = "#" + node.id;
  51. link.text = text;
  52. list_item.appendChild(link);
  53. return true;
  54. }
  55. // Css
  56. let css = document.createElement("style");
  57. css.textContent = `
  58. html {
  59. scroll-behavior: smooth;
  60. }
  61. a.anchor::before {
  62. content: "#";
  63. }
  64. a.anchor {
  65. opacity: 0;
  66. text-decoration: none;
  67. padding: 0px 0.5em;
  68. -moz-transition: all 0.25s ease-in-out;
  69. -o-transition: all 0.25s ease-in-out;
  70. -webkit-transition: all 0.25s ease-in-out;
  71. transition: all 0.25s ease-in-out;
  72. }
  73. h1:hover>a.anchor,
  74. h2:hover>a.anchor,
  75. h3:hover>a.anchor,
  76. h4:hover>a.anchor,
  77. h5:hover>a.anchor,
  78. h6:hover>a.anchor {
  79. opacity: 1;
  80. -moz-transition: all 0.25s ease-in-out;
  81. -o-transition: all 0.25s ease-in-out;
  82. -webkit-transition: all 0.25s ease-in-out;
  83. transition: all 0.25s ease-in-out;
  84. }
  85. a.button {
  86. margin: 0.5em 0 0 0;
  87. display: flex;
  88. align-items: center;
  89. justify-content: center;
  90. text-decoration: none;
  91. color: black;
  92. background-color: #a42121ab;
  93. border-radius: 50%;
  94. width: 2em;
  95. height: 2em;
  96. font-size: 1.8em;
  97. font-weight: bold;
  98. }
  99. div.lum-lightbox {
  100. z-index: 2;
  101. }
  102. div#float-buttons {
  103. position: fixed;
  104. bottom: 1em;
  105. right: 1em;
  106. display: flex;
  107. flex-direction: column;
  108. user-select: none;
  109. z-index: 1;
  110. }
  111. aside.panel {
  112. display: none;
  113. }
  114. .dynamic-opacity {
  115. transition: ${config.opacity.transition};
  116. opacity: ${config.opacity.default};
  117. }
  118. .dynamic-opacity:hover {
  119. opacity: ${config.opacity.hover};
  120. }
  121. @media (any-hover: none) {
  122. .dynamic-opacity {
  123. opacity: ${config.opacity.none};
  124. }
  125. .dynamic-opacity:hover {
  126. opacity: ${config.opacity.none};
  127. }
  128. }
  129. @media screen and (min-width: 767px) {
  130. aside.panel {
  131. display: block;
  132. line-height: 1.5;
  133. padding: 0;
  134. position: sticky;
  135. top: 0;
  136. height: 0;
  137. z-index: 1;
  138. }
  139. ul.outline {
  140. float: right;
  141. padding: 0 0 0 0.5em;
  142. margin: 0 0.5em;
  143. border: 1px solid #BBBBBB;
  144. border-left: 2px solid #F2E5E5;
  145. box-shadow: 0 0 5px #ddd;
  146. background: linear-gradient(to right, #fcf1f1, #FFF 1em);
  147. list-style: none;
  148. width: 10.5%;
  149. color: gray;
  150. border-radius: 5px;
  151. }
  152. ul.outline > li {
  153. overflow: hidden;
  154. text-overflow: ellipsis;
  155. }
  156. ul.outline > li > a {
  157. color: gray;
  158. white-space: nowrap;
  159. text-decoration: none;
  160. }
  161. }`;
  162. document.querySelector("head").appendChild(css); // Inject css
  163. // Aside panel & Anchors
  164. let outline;
  165. let no_display = ["scripts", "discussions", "code", "versions", "versions/new"];
  166. let is_display = true;
  167. no_display.forEach((p) => {
  168. is_display = is_display && !window.location.pathname.endsWith(p) && !window.location.pathname.endsWith(p + "/");
  169. });
  170. if (is_display) {
  171. let panel = document.createElement("aside");
  172. panel.className = "panel";
  173. body.insertBefore(panel, document.querySelector("body > div.width-constraint"));
  174. let dummy = document.createElement("div");
  175. let reference_node = document.querySelector("body > div.width-constraint > section");
  176. dummy.style.display = "none";
  177. panel.appendChild(dummy);
  178. outline = document.createElement("ul");
  179. outline.classList.add("outline");
  180. outline.classList.add("dynamic-opacity");
  181. outline.style.marginTop = reference_node ? getComputedStyle(reference_node).marginTop : "1em";
  182. panel.appendChild(outline);
  183. let flag = false;
  184. document.querySelectorAll("body > div.width-constraint h1, h2, h3, h4, h5, h6").forEach((node) => {
  185. flag = process(node) || flag; // Not `flag || process(node)`!
  186. });
  187. if (!flag) {
  188. let placeholder = document.createElement("li");
  189. placeholder.textContent = "Nothing to show.";
  190. outline.appendChild(placeholder);
  191. }
  192. }
  193. // Navigate to hash
  194. let hash = window.location.hash.slice(1);
  195. if (hash) {
  196. let ele = document.getElementById(decodeURIComponent(hash));
  197. if (ele) {
  198. ele.scrollIntoView();
  199. }
  200. }
  201. // Buttons
  202. let buttons = document.createElement("div");
  203. buttons.id = "float-buttons";
  204. let to_top = document.createElement("a");
  205. to_top.classList.add("button");
  206. to_top.classList.add("dynamic-opacity");
  207. to_top.href = "#top";
  208. to_top.text = "↑";
  209. buttons.appendChild(to_top);
  210. body.appendChild(buttons);
  211. // Double click to get to top
  212. body.addEventListener("dblclick", (e) => {
  213. if (e.target == body) {
  214. to_top.click();
  215. }
  216. });
  217. // Fix current tab link
  218. let tab = document.querySelector("ul#script-links > li.current");
  219. if (tab) {
  220. let link = document.createElement("a");
  221. link.href = window.location.pathname;
  222. let orig_child = tab.firstChild;
  223. link.appendChild(orig_child);
  224. tab.appendChild(link);
  225. }
  226. let parts = window.location.pathname.split("/");
  227. if (parts.length <= 2 || (parts.length == 3 && parts[2] === '')) {
  228. let banner = document.querySelector("header#main-header div#site-name");
  229. let img = banner.querySelector("img");
  230. let text = banner.querySelector("#site-name-text > h1");
  231. let link1 = document.createElement("a");
  232. link1.href = window.location.pathname;
  233. img.parentNode.replaceChild(link1, img);
  234. link1.appendChild(img);
  235. let link2 = document.createElement("a");
  236. link2.href = window.location.pathname;
  237. link2.textContent = text.textContent;
  238. text.textContent = "";
  239. text.appendChild(link2);
  240. }
  241. })();