Greasyfork Beautify

Greasyfork网站美化。

目前为 2022-06-24 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Greasyfork Beautify
  3. // @namespace https://github.com/kiccer
  4. // @version 0.9.alpha
  5. // @description Greasyfork网站美化。
  6. // @author kiccer<1072907338@qq.com>
  7. // @supportURL https://github.com/kiccer/TampermonkeyScripts/issues
  8. // @license MIT
  9. // @match https://greasyfork.org/*
  10. // @icon https://greasyfork.org/packs/media/images/blacklogo96-b2384000fca45aa17e45eb417cbcbb59.png
  11. // @require https://cdn.bootcdn.net/ajax/libs/vue/2.6.12/vue.min.js
  12. // @require https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.min.js
  13. // @resource less https://cdn.bootcdn.net/ajax/libs/less.js/4.1.3/less.min.js
  14. // @resource normalize.css https://cdn.bootcdn.net/ajax/libs/normalize/8.0.1/normalize.min.css
  15. // @run-at document-start
  16. // @grant GM_info
  17. // @grant GM_addStyle
  18. // @grant GM_getResourceText
  19. // ==/UserScript==
  20.  
  21. /* globals GM_info GM_addStyle GM_getResourceText $ less Vue */
  22.  
  23. const VERSION = GM_info.script.version
  24.  
  25. // 自动根据浏览器语言设置当前语言
  26. if (!new RegExp(`/${navigator.language}/?`).test(location.href)) {
  27. location.href = location.href.replace(/^(https:\/\/greasyfork\.org\/)[a-zA-Z-]+(\/?.*)/, `$1${navigator.language}$2`)
  28. }
  29.  
  30. window.addEventListener('load', () => {
  31. // 样式初始化
  32. GM_addStyle(GM_getResourceText('normalize.css'))
  33. // eslint-disable-next-line no-eval
  34. eval(GM_getResourceText('less'))
  35.  
  36. const lessOptions = {}
  37.  
  38. const lessInput = `
  39. // --------------------------------------------- 变量
  40.  
  41. @nav_height: 60px;
  42. @user_container_height: 24px;
  43.  
  44. // --------------------------------------------- 混合宏
  45.  
  46. .ellipsis (@lines) {
  47. display: -webkit-box;
  48. -webkit-box-orient: vertical;
  49. overflow: hidden;
  50. line-height: 1.5;
  51. -webkit-line-clamp: @lines;
  52. }
  53.  
  54. // --------------------------------------------- 通用样式
  55.  
  56. * {
  57. box-sizing: border-box;
  58. outline: none;
  59. }
  60.  
  61. body {
  62. line-height: 1.5;
  63. min-height: 100vh;
  64. background-color: #f7f7f7;
  65.  
  66. > .width-constraint {
  67. min-height: 100vh;
  68. background-color: #fff;
  69. padding: 20px;
  70. padding-top: calc(@nav_height + @user_container_height + 20px);
  71. .text-content {
  72. border: 0;
  73. box-shadow: none;
  74. padding: 0;
  75. }
  76. }
  77. }
  78.  
  79. a {
  80. color: rgb(38, 38, 38);
  81. text-decoration: none;
  82.  
  83. &:hover {
  84. text-decoration: underline;
  85. }
  86.  
  87. &:visited {
  88. color: rgb(38, 38, 38);
  89. }
  90. }
  91.  
  92. pre.lang-js {
  93. font-family: Consolas;
  94.  
  95. ol.linenums {
  96. li.L1, li.L3, li.L5, li.L7, li.L9 {
  97. background-color: rgba(0, 0, 0, .02);
  98. }
  99. }
  100. }
  101.  
  102. // --------------------------------------------- 输入框
  103.  
  104. input[type=search] {
  105. padding: 3px 6px;
  106. padding-right: 2.4em !important;
  107. border: 1px solid #bfbfbf;
  108. border-radius: 4px;
  109. }
  110.  
  111. form {
  112. input.search-submit {
  113. top: 50% !important;
  114. transform: translateY(-50%);
  115. cursor: pointer;
  116. }
  117. }
  118.  
  119. .home-search {
  120. margin-bottom: 20px;
  121. }
  122.  
  123. .sidebar-search {
  124. margin-bottom: 20px;
  125.  
  126. input[type="search"] {
  127. margin: 0;
  128. }
  129. }
  130.  
  131. // --------------------------------------------- header
  132.  
  133. #main-header {
  134. background-color: #000;
  135. background-image: none;
  136. width: 100%;
  137. padding: 0;
  138. position: fixed;
  139. top: 0;
  140. z-index: 1;
  141. user-select: none;
  142. box-shadow: 0 0 5px 2px rgb(0 0 0 / 50%);
  143.  
  144. .width-constraint {
  145. display: flex;
  146. justify-content: space-between;
  147. height: 100%;
  148. padding: 0;
  149.  
  150. #site-name {
  151. display: flex;
  152. align-items: center;
  153. a {
  154. display: block;
  155. }
  156.  
  157. img {
  158. width: auto;
  159. height: 50px;
  160. }
  161.  
  162. #site-name-text {
  163. margin-left: 10px;
  164.  
  165. h1 {
  166. font-size: 36px;
  167. display: flex;
  168. display: flex;
  169. align-items: baseline;
  170.  
  171. &::after {
  172. content: "Greasyfork Beautify V${VERSION}";
  173. font-size: 12px;
  174. letter-spacing: 1px;
  175. font-family: "微软雅黑";
  176. font-weight: 200;
  177. color: rgba(255, 255, 255, .5);
  178. line-height: 1;
  179. margin-left: 10px;
  180. }
  181. }
  182. }
  183. }
  184. }
  185.  
  186. #user-container {
  187. width: 100%;
  188. height: @user_container_height;
  189. background-color: #343434;
  190.  
  191. .user-main {
  192. margin: auto;
  193. max-width: 1200px;
  194. height: @user_container_height;
  195. line-height: @user_container_height;
  196. text-align: right;
  197. font-size: 14px;
  198. padding-right: 10px;
  199.  
  200. @media screen and (max-width: 1228px) {
  201. margin: auto 1.2vw;
  202. }
  203. }
  204. }
  205. }
  206.  
  207. #site-nav {
  208. width: 0;
  209. height: 0;
  210. border: 0;
  211. padding: 0;
  212. overflow: hidden;
  213. position: relative;
  214. }
  215.  
  216. #site-nav-vue {
  217. display: flex;
  218. .nav-item {
  219. line-height: @nav_height;
  220. padding: 0 10px;
  221. transition: all .2s ease;
  222. text-decoration: none;
  223. position: relative;
  224. white-space: nowrap;
  225.  
  226. &:hover {
  227. background-color: rgba(255, 255, 255, .2);
  228.  
  229. .sub-nav {
  230. display: flex;
  231. }
  232. }
  233.  
  234. .sub-nav {
  235. display: none;
  236. flex-direction: column;
  237. position: absolute;
  238. top: 100%;
  239. right: 0;
  240. background-color: rgba(0, 0, 0, .8);
  241.  
  242. .nav-item {
  243. line-height: 40px;
  244. }
  245. }
  246. }
  247. }
  248.  
  249. // --------------------------------------------- 脚本列表
  250.  
  251. #browse-script-list {
  252. display: grid;
  253. grid-template-columns: repeat(2, 1fr);
  254. grid-gap: 20px;
  255. border: 0;
  256. box-shadow: none;
  257.  
  258. li {
  259. border: 1px solid #bbb;
  260. box-shadow: 0 0 5px #ddd;
  261. border-radius: 5px;
  262. padding: 10px;
  263. position: relative;
  264.  
  265. a.script-link {
  266. .ellipsis(2);
  267. height: calc(3em + 8px);
  268. font-size: 16px;
  269. margin: 4px -10px 4px -14px;
  270. padding: 4px 10px;
  271. background: linear-gradient(#fff, #eee);
  272. border-left: 7px solid #800;
  273. box-shadow: inset 0 1px rgb(0 0 0 / 10%), inset 0 -1px rgb(0 0 0 / 10%);
  274. }
  275.  
  276. .script-description {
  277. .ellipsis(3);
  278. text-indent: 2em;
  279. margin: 10px 0 10px;
  280. height: 4.5em;
  281. font-size: 14px;
  282. }
  283.  
  284. .inline-script-stats {
  285. padding-top: 10px;
  286. border-top: 1px solid #ebebeb;
  287.  
  288. dt {
  289. width: 40%;
  290. }
  291.  
  292. dd {
  293. width: 60%;
  294. }
  295. }
  296. }
  297. }
  298. `
  299.  
  300. less.render(lessInput, lessOptions).then(output => {
  301. // output.css = string of css
  302. // output.map = string of sourcemap
  303. // output.imports = array of string filenames of the imports referenced
  304.  
  305. GM_addStyle(output.css)
  306. }, err => {
  307. console.error(err)
  308. })
  309.  
  310. // 导航
  311. const navContainer = document.createElement('div')
  312. navContainer.id = 'site-nav-vue'
  313. document.querySelector('.width-constraint').appendChild(navContainer)
  314.  
  315. // eslint-disable-next-line no-unused-vars
  316. const nav = new Vue({
  317. el: '#site-nav-vue',
  318.  
  319. template: `
  320. <div id="site-nav-vue">
  321. <a
  322. class="nav-item"
  323. v-for="(nav, nav_i) in navList"
  324. :key="nav_i"
  325. :href="nav.url"
  326. >
  327. <span>{{ nav.label }}</span>
  328.  
  329. <div class="sub-nav" v-if="nav.list?.length">
  330. <a
  331. class="nav-item"
  332. v-for="(sub, sub_i) in nav.list"
  333. :key="sub_i"
  334. :href="sub.url"
  335. >
  336. <span>{{ sub.label }}</span>
  337. </a>
  338. </div>
  339. </a>
  340. </div>
  341. `,
  342.  
  343. data () {
  344. return {
  345. navList: [...$('#site-nav > nav > li')].map(n => {
  346. const a = $(n).find('> a')
  347. const subNav = [...$(n).find('> nav > li')]
  348.  
  349. return {
  350. label: a.text(),
  351. url: a.attr('href'),
  352. list: subNav.map(m => {
  353. const subA = $(m).find('> a')
  354.  
  355. return {
  356. label: subA.text(),
  357. url: subA.attr('href')
  358. }
  359. })
  360. }
  361. })
  362. }
  363. }
  364. })
  365.  
  366. // 用户
  367. const userContainer = document.createElement('div')
  368. userContainer.id = 'user-container'
  369. document.querySelector('#main-header').appendChild(userContainer)
  370.  
  371. // eslint-disable-next-line no-unused-vars
  372. const user = new Vue({
  373. el: '#user-container',
  374. template: `
  375. <div id="user-container">
  376. <div class="user-main">
  377. <a
  378. :href="dom.attr('href')"
  379. >{{ dom.text() }}</a>
  380.  
  381. <template v-if="isLogin">
  382. [<a :href="logoutDom.attr('href')">{{ logoutDom.text() }}</a>]
  383. </template>
  384. </div>
  385. </div>
  386. `,
  387.  
  388. data () {
  389. return {
  390. dom: $('#nav-user-info .user-profile-link a, #nav-user-info .sign-in-link a'),
  391. logoutDom: $('.sign-out-link a'),
  392. isLogin: $('.sign-out-link').length > 0 // 存在登出按钮则表示已登录
  393. }
  394. }
  395. })
  396.  
  397. // 脚本列表页面
  398. // if (/^https:\/\/greasyfork\.org\/[a-zA-Z-]+\/scripts$/.test(location.href)) {
  399. // $('#browse-script-list li[data-script-id]').each((i, dom) => {
  400. // const li = $(dom)
  401. // const cardHeader = $('<div class="card-header">')
  402. // const score = li.find('dd.script-list-ratings').data('rating-score')
  403. // const scoreWrap = $('<span class="score">')
  404. // const install = $('<a>')
  405. // li.find('> article').before(cardHeader)
  406. // cardHeader.append(scoreWrap)
  407. // cardHeader.append(install)
  408. // scoreWrap.html(score)
  409. // install.html('INSTALL')
  410. // })
  411. // }
  412. })