GVP-Select-Box

GVP - Gitee最有价值开源项目 的项目列表按照 star、fork 和编程语言进行筛选排序,增强使用体验

  1. // ==UserScript==
  2. // @name GVP-Select-Box
  3. // @namespace http://tampermonkey.net/
  4. // @version 1.0.2
  5. // @description GVP - Gitee最有价值开源项目 的项目列表按照 star、fork 和编程语言进行筛选排序,增强使用体验
  6. // @author qdz
  7. // @match https://gitee.com/gvp/all
  8. // @icon https://gitee.com/favicon.ico
  9. // @run-at document-start
  10. // @grant none
  11. // @license MIT
  12. // ==/UserScript==
  13.  
  14. (function () {
  15. "use strict"
  16.  
  17. // DOM树构建完成之后触发
  18. document.addEventListener('DOMContentLoaded', () => {
  19. main()
  20. })
  21.  
  22. // 排序类型 star | fork
  23. let sortType = "star"
  24.  
  25. // card所包含的编程语言
  26. let languages = []
  27.  
  28. // 选中的编程语言
  29. let checkedLanguages = []
  30.  
  31. // 给没有编程语言标签的项目取一个标签名
  32. let undefinedLanguage = '无标签'
  33.  
  34. // 定位元素
  35. let positionElement = null
  36.  
  37. // card父容器元素
  38. let cardContainerElement = null
  39.  
  40. // card数量元素
  41. let cardNumberElement = null
  42.  
  43. // card原始数据
  44. let originalCardArray = null
  45.  
  46. // 入口函数
  47. function main() {
  48. let result = initPageElement()
  49. if (result) {
  50. initLanguage()
  51. renderBox()
  52. addSelectListener()
  53. }
  54. }
  55.  
  56. function initPageElement() {
  57. positionElement = document.querySelector("#open-source-vip-page .gvp-category-container")
  58. cardContainerElement = document.querySelector("#open-source-vip-page .categorical-project-cards")
  59. cardNumberElement = document.querySelector('#open-source-vip-page .breadcrumb .text-muted')
  60. originalCardArray = Array.from(document.querySelectorAll("#open-source-vip-page .categorical-project-cards > div"))
  61.  
  62. return positionElement && cardContainerElement && cardNumberElement && originalCardArray.length > 0
  63. }
  64.  
  65. function initLanguage() {
  66. const languageMap = new Map()
  67.  
  68. originalCardArray.forEach((item) => {
  69. let language = item.querySelector('.project-labels > div')?.innerText
  70. // 没有编程语言标签的项目给一个默认值
  71. let key = language === undefined ? undefinedLanguage : language
  72. // 不存在则设置值为1,存在则+1
  73. increment(languageMap, key)
  74. })
  75.  
  76. // 将Map按照值的大小进行降序排序后,再获取排序后的键名数组
  77. languages = Array.from(languageMap).sort((a, b) => b[1] - a[1]).map(item => item[0])
  78. checkedLanguages = [...languages]
  79. }
  80.  
  81. function renderBox() {
  82. // 定位元素设置相对定位,方便子元素进行绝对定位
  83. positionElement.style.position = "relative"
  84.  
  85. // 将selectBox追加到定位元素的最后一个子元素之后
  86. positionElement.insertAdjacentHTML("beforeend", htmlCode)
  87.  
  88. // 渲染编程语言checkbox
  89. let languageElements = ""
  90. languages.forEach((item) => {
  91. languageElements += `<label><input type="checkbox" value="${item}" checked /><span>${item}</span></label>`
  92. })
  93. document.getElementById("_languageBox").insertAdjacentHTML("beforeend", languageElements)
  94.  
  95. // 往head元素追加css
  96. const styleElement = document.createElement("style")
  97. styleElement.appendChild(document.createTextNode(cssCode))
  98. document.head.appendChild(styleElement)
  99. }
  100.  
  101. function renderCard() {
  102. // 清空card容器中的数据
  103. cardContainerElement.innerHTML = ""
  104.  
  105. // 根据选中的编程语言进行过滤
  106. const filteredCards = originalCardArray.filter((item) => {
  107. const language = item.querySelector(".label")?.innerText
  108. // 没有编程语言标签的项目给一个默认值
  109. return checkedLanguages.includes(language === undefined ? undefinedLanguage : language)
  110. })
  111.  
  112. // 根据排序类型,降序排序
  113. filteredCards.sort((a, b) => {
  114. const countA = convertToNumber(a.querySelector(`.icon-${sortType} + span`)?.innerText)
  115. const countB = convertToNumber(b.querySelector(`.icon-${sortType} + span`)?.innerText)
  116. return countB - countA
  117. })
  118.  
  119. // 重新往card容器中添加数据
  120. filteredCards.forEach((item) => {
  121. cardContainerElement.appendChild(item)
  122. })
  123.  
  124. // 重新渲染数量
  125. cardNumberElement.innerText = '(' + cardContainerElement.children.length + ')'
  126. }
  127.  
  128. function addSelectListener() {
  129. // 排序类型添加监听事件
  130. const sortTypeElement = document.getElementById("_sortTypeBox")
  131. sortTypeElement.addEventListener("change", (event) => {
  132. if (event.target.type === "radio" && event.target.name === "sortType") {
  133. sortType = event.target.value
  134.  
  135. // 重新渲染card
  136. renderCard()
  137. }
  138. })
  139.  
  140. // 编程语言添加监听事件
  141. const languageElement = document.getElementById("_languageBox")
  142. languageElement.addEventListener("change", (event) => {
  143. if (event.target.type === "checkbox") {
  144. // 清空编程语言列表
  145. checkedLanguages = []
  146.  
  147. // 获取所有选中的编程语言元素
  148. const checkedElements = languageElement.querySelectorAll('input[type="checkbox"]:checked')
  149. checkedElements.forEach((item) => {
  150. checkedLanguages.push(item.value)
  151. })
  152.  
  153. // 重新渲染cards
  154. renderCard()
  155. }
  156. })
  157.  
  158. // 编程语言全选按钮添加监听事件
  159. const checkAllLanguageElement = document.getElementById("_checkAll")
  160. checkAllLanguageElement.addEventListener("change", (event) => {
  161. if (event.target.type === "checkbox" && languages.length > 0) {
  162. // 全选按钮的选中状态
  163. let state = event.target.checked
  164.  
  165. // 全选则赋值所有编程语言,取消全选则清空
  166. checkedLanguages = state ? languages : []
  167.  
  168. // 遍历所有编程语言元素,更改选中状态
  169. const languageLabelElements = languageElement.querySelectorAll("#_languageBox > label")
  170. languageLabelElements.forEach((item) => {
  171. item.querySelector("input[type='checkbox']").checked = state
  172. })
  173. }
  174.  
  175. // 重新渲染cards
  176. renderCard()
  177. })
  178. }
  179.  
  180. // Map键名不存在则值赋值为1,存在则值自增1
  181. function increment(map, key) {
  182. if (map.has(key)) {
  183. map.set(key, map.get(key) + 1)
  184. } else {
  185. map.set(key, 1)
  186. }
  187. }
  188.  
  189. // 转换具体的数量,1k = 1000,未包含数量的,默认为0
  190. function convertToNumber(str = '0') {
  191. return str.includes('K') ? parseFloat(str.replace('K', '')) * 1000 : parseInt(str)
  192. }
  193.  
  194. const htmlCode = `
  195. <div id="_selectBox">
  196. <p>排序类型</p>
  197. <div id="_sortTypeBox">
  198. <label>
  199. <input type="radio" name="sortType" value="star" />
  200. <span>Star</span>
  201. </label>
  202. <label>
  203. <input type="radio" name="sortType" value="fork" />
  204. <span>Fork</span>
  205. </label>
  206. </div>
  207. <p>编程语言</p>
  208. <label>
  209. <input id="_checkAll" type="checkbox" value="checkAll" checked />
  210. <span>全选</span>
  211. </label>
  212. <div id="_languageBox"></div>
  213. </div>
  214. `
  215.  
  216. const cssCode = `
  217. #_selectBox {
  218. position: absolute;
  219. top: 0px;
  220. right: -205px;
  221. width: 205px;
  222. background-color: #fff;
  223. padding: 0px 2px 2px 10px;
  224. border: 1px solid #e3e9ed;
  225. border-radius: 10px;
  226. box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
  227. }
  228.  
  229. #_selectBox p {
  230. display: inline-block;
  231. margin: 12px 0;
  232. font-size: 14px;
  233. font-weight: bold;
  234. }
  235.  
  236. #_languageBox {
  237. display: flex;
  238. flex-wrap: wrap;
  239. }
  240.  
  241. #_languageBox label {
  242. margin-bottom: 8px;
  243. margin-right: 8px;
  244. }
  245.  
  246. #_sortTypeBox input[type="radio"],
  247. #_languageBox input[type="checkbox"],
  248. #_checkAll[type="checkbox"] {
  249. display: none;
  250. }
  251.  
  252. #_sortTypeBox input[type="radio"]+span,
  253. #_languageBox input[type="checkbox"]+span,
  254. #_checkAll+span {
  255. padding: 3px 6px;
  256. background-color: #ddd;
  257. color: #666;
  258. border-radius: 10px;
  259. cursor: pointer;
  260. font-size: 12px;
  261. }
  262.  
  263. #_sortTypeBox input[type="radio"]:checked+span {
  264. background-color: #4caf50;
  265. color: #fff;
  266. }
  267.  
  268. #_languageBox input[type="checkbox"]:checked+span {
  269. background-color: #4caf50;
  270. color: #fff;
  271. }
  272.  
  273. #_checkAll:checked+span {
  274. background-color: #4caf50;
  275. color: #fff;
  276. }
  277. `
  278.  
  279. })()

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址