GitHub Sort Content

A userscript that makes some lists & markdown tables sortable

ของเมื่อวันที่ 12-09-2016 ดู เวอร์ชันล่าสุด

คุณจะต้องติดตั้งส่วนขยาย เช่น Tampermonkey, Greasemonkey หรือ Violentmonkey เพื่อติดตั้งสคริปต์นี้

คุณจะต้องติดตั้งส่วนขยาย เช่น Tampermonkey หรือ Violentmonkey เพื่อติดตั้งสคริปต์นี้

คุณจะต้องติดตั้งส่วนขยาย เช่น Tampermonkey หรือ Violentmonkey เพื่อติดตั้งสคริปต์นี้

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

  1. // ==UserScript==
  2. // @name GitHub Sort Content
  3. // @version 1.0.8
  4. // @description A userscript that makes some lists & markdown tables sortable
  5. // @license https://creativecommons.org/licenses/by-sa/4.0/
  6. // @namespace http://github.com/Mottie
  7. // @include https://github.com/*
  8. // @grant GM_addStyle
  9. // @require https://cdnjs.cloudflare.com/ajax/libs/tinysort/2.3.6/tinysort.min.js
  10. // @run-at document-idle
  11. // @author Rob Garrison
  12. // ==/UserScript==
  13. /* global GM_addStyle, tinysort */
  14. /* jshint esnext:true, unused:true */
  15. (() => {
  16. "use strict";
  17. /* example pages:
  18. tables - https://github.com/Mottie/GitHub-userscripts
  19. Contribute repos & Your Repos - https://github.com/
  20. organization repos - https://github.com/jquery
  21. organization members - https://github.com/orgs/jquery/people
  22. pinned & no pinned repos - https://github.com/addyosmani
  23. repos - https://github.com/addyosmani?tab=repositories
  24. stars - https://github.com/stars
  25. watching - https://github.com/watching
  26. */
  27. const sorts = ["asc", "desc"],
  28. icons = {
  29. white: {
  30. unsorted: "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiI+PHBhdGggZD0iTTE1IDggMSA4IDggMHpNMTUgOSAxIDkgOCAxNnoiIHN0eWxlPSJmaWxsOiNkZGQ7b3BhY2l0eTowLjIiLz48L3N2Zz4=",
  31. asc: "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiI+PHBhdGggZD0iTTE1IDggMSA4IDggMHoiIHN0eWxlPSJmaWxsOiNkZGQiLz48cGF0aCBkPSJNMTUgOSAxIDkgOCAxNnoiIHN0eWxlPSJmaWxsOiNkZGQ7b3BhY2l0eTowLjIiLz48L3N2Zz4=",
  32. desc: "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiI+PHBhdGggZD0iTTE1IDggMSA4IDggMHoiIHN0eWxlPSJmaWxsOiNkZGQ7b3BhY2l0eTowLjIiLz48cGF0aCBkPSJNMTUgOSAxIDkgOCAxNnoiIHN0eWxlPSJmaWxsOiNkZGQiLz48L3N2Zz4="
  33. },
  34. black: {
  35. unsorted: "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiI+PHBhdGggZD0iTTE1IDggMSA4IDggMHpNMTUgOSAxIDkgOCAxNnoiIHN0eWxlPSJmaWxsOiMyMjI7b3BhY2l0eTowLjIiLz48L3N2Zz4=",
  36. asc: "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiI+PHBhdGggZD0iTTE1IDggMSA4IDggMHoiIHN0eWxlPSJmaWxsOiMyMjIiLz48cGF0aCBkPSJNMTUgOSAxIDkgOCAxNnoiIHN0eWxlPSJmaWxsOiMyMjI7b3BhY2l0eTowLjIiLz48L3N2Zz4=",
  37. desc: "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiI+PHBhdGggZD0iTTE1IDggMSA4IDggMHoiIHN0eWxlPSJmaWxsOiMyMjI7b3BhY2l0eTowLjIiLz48cGF0aCBkPSJNMTUgOSAxIDkgOCAxNnoiIHN0eWxlPSJmaWxsOiMyMjIiLz48L3N2Zz4="
  38. }
  39. },
  40. // toolbars - target for sort arrows
  41. regexBars = /\b(filter-bar|org-toolbar|sort-bar|tabnav-tabs)\b/;
  42.  
  43. function initSortTable(el) {
  44. removeSelection();
  45. const dir = el.classList.contains(sorts[0]) ? sorts[1] : sorts[0],
  46. table = closest(el, "table");
  47. tinysort($$("tbody tr", table), {
  48. order: dir,
  49. natural: true,
  50. selector: `td:nth-child(${el.cellIndex + 1})`
  51. });
  52. $$("th", table).forEach(elm => {
  53. elm.classList.remove(...sorts);
  54. });
  55. el.classList.add(dir);
  56. }
  57.  
  58. function initSortUl(arrows, list, selector) {
  59. if (list && list.children) {
  60. removeSelection();
  61. const dir = arrows.classList.contains(sorts[0]) ? sorts[1] : sorts[0],
  62. options = {
  63. order: dir,
  64. natural: true
  65. };
  66. if (selector) {
  67. options.selector = selector;
  68. }
  69. // using children because the big repo contains UL > DIV
  70. tinysort(list.children, options);
  71. arrows.classList.remove(...sorts);
  72. arrows.classList.add(dir);
  73. }
  74. }
  75.  
  76. function needDarkTheme() {
  77. let brightest = 0,
  78. // color will be "rgb(#, #, #)" or "rgba(#, #, #, #)"
  79. color = window.getComputedStyle(document.body).backgroundColor;
  80. const rgb = (color || "").replace(/\s/g, "").match(/^rgba?\((\d+),(\d+),(\d+)/i);
  81. if (rgb) {
  82. color = rgb.slice(1); // remove "rgb.." part from match
  83. color.forEach(c => {
  84. // http://stackoverflow.com/a/15794784/145346
  85. brightest = Math.max(brightest, parseInt(c, 10));
  86. });
  87. // return true if we have a dark background
  88. return brightest < 128;
  89. }
  90. // fallback to bright background
  91. return false;
  92. }
  93.  
  94. function $(str, el) {
  95. return (el || document).querySelector(str);
  96. }
  97.  
  98. function $$(str, el) {
  99. return Array.from((el || document).querySelectorAll(str));
  100. }
  101.  
  102. function closest(el, selector) {
  103. while (el && el.nodeName !== "BODY" && !el.matches(selector)) {
  104. el = el.parentNode;
  105. }
  106. return el && el.matches(selector) ? el : null;
  107. }
  108.  
  109. function removeSelection() {
  110. // remove text selection - http://stackoverflow.com/a/3171348/145346
  111. const sel = window.getSelection ? window.getSelection() : document.selection;
  112. if (sel) {
  113. if (sel.removeAllRanges) {
  114. sel.removeAllRanges();
  115. } else if (sel.empty) {
  116. sel.empty();
  117. }
  118. }
  119. }
  120.  
  121. function init() {
  122. const styles = needDarkTheme() ? icons.white : icons.black;
  123.  
  124. GM_addStyle(`
  125. /* unsorted icon */
  126. .markdown-body table thead th {
  127. cursor:pointer;
  128. padding-right:22px !important;
  129. background:url(${styles.unsorted}) no-repeat calc(100% - 5px) center !important;
  130. }
  131. div.js-pinned-repos-reorder-container > h3, .dashboard-sidebar .boxed-group > h3,
  132. div.filter-repos, div.js-repo-filter .filter-bar, .org-toolbar, .sort-bar,
  133. h2 + .tabnav > .tabnav-tabs, .subscriptions-content .boxed-group > h3 {
  134. cursor:pointer;
  135. padding-right:10px;
  136. background-image:url(${styles.unsorted}) !important;
  137. background-repeat:no-repeat !important;
  138. background-position:calc(100% - 5px) center !important;
  139. }
  140. /* https://github.com/ -> your repositories */
  141. .dashboard-sidebar .user-repos h3 { background-position: 175px 10px !important; }
  142. /* https://github.com/:user?tab=repositories */
  143. div.js-repo-filter .filter-bar { background-position:338px 10px !important; }
  144. /* https://github.com/:organization */
  145. .org-toolbar { background-position:calc(100% - 5px) 10px !important; }
  146. /* https://github.com/stars */
  147. .sort-bar { background-position:525px 10px !important; }
  148. /* https://github.com/watching */
  149. .subscriptions-content .boxed-group > h3 {
  150. background-position:150px 10px !important;
  151. }
  152. /* asc/dec icons */
  153. table thead th.asc, div.boxed-group h3.asc,
  154. div.js-repo-filter.asc, div.filter-bar.asc,
  155. .org-toolbar.asc, .sort-bar.asc, h2 + .tabnav > .tabnav-tabs.asc,
  156. .subscriptions-content .boxed-group > h3.asc {
  157. background-image:url(${styles.asc}) !important;
  158. background-repeat:no-repeat !important;
  159. }
  160. table thead th.desc, div.boxed-group h3.desc,
  161. div.js-repo-filter.desc, div.filter-bar.desc,
  162. .org-toolbar.desc, .sort-bar.desc, h2 + .tabnav > .tabnav-tabs.desc,
  163. .subscriptions-content .boxed-group > h3.desc {
  164. background-image:url(${styles.desc}) !important;
  165. background-repeat:no-repeat !important;
  166. }
  167. /* remove sort arrows */
  168. .popular-repos + div.boxed-group h3 {
  169. background-image:none !important;
  170. cursor:default;
  171. }
  172. /* Remove margin that overlaps sort arrow - https://github.com/:user?tab=repositories */
  173. .filter-bar li:last-child { margin-left: 0 !important; }
  174. /* move "Customize your pinned..." - https://github.com/:self */
  175. .pinned-repos-setting-link { margin-right:14px; }
  176. `);
  177.  
  178. document.body.addEventListener("click", event => {
  179. let el;
  180. const target = event.target,
  181. name = target.nodeName;
  182. if (target && target.nodeType === 1 && (
  183. // nodes th|h3 - form for stars page
  184. name === "H3" || name === "TH" || name === "FORM" ||
  185. // mini-repo & https://github.com/:user?tab=repositories (filter-bar)
  186. // https://github.com/:organization filter bar (org-toolbar)
  187. // https://github.com/stars (sort-bar)
  188. regexBars.test(target.className)
  189. )) {
  190. // don't sort tables not inside of markdown
  191. if (name === "TH" && closest(target, ".markdown-body")) {
  192. return initSortTable(target);
  193. }
  194.  
  195. // following
  196. el = $("ol.follow-list", closest(target, ".container"));
  197. if (el) {
  198. return initSortUl(target, el, ".follow-list-name a");
  199. }
  200.  
  201. // organization people - https://github.com/orgs/:organization/people
  202. el = $("ul.member-listing", target.parentNode);
  203. if (el) {
  204. return initSortUl(target, el, ".member-link");
  205. }
  206.  
  207. // big repo list - https://github.com/:user?tab=repositories
  208. // stars - https://github.com/stars
  209. el = closest(target, ".sort-bar, .filter-bar, .org-toolbar");
  210. if (el && $(".repo-list", el.parentNode)) {
  211. return initSortUl(el, $(".repo-list", el.parentNode), ".repo-list-name a");
  212. }
  213.  
  214. // https://github.com/watching
  215. el = closest(target, ".subscriptions-content");
  216. if (el && $(".repo-list", el)) {
  217. return initSortUl(target, $(".repo-list", el), "li a");
  218. }
  219.  
  220. // mini-repo listings with & without filter - https://github.com/
  221. // and pinned repo lists
  222. el = closest(target, ".boxed-group");
  223. // prevent clicking on the H3 header of filtered repos
  224. if (el && name === "H3" && (
  225. el.classList.contains("js-repo-filter") ||
  226. el.classList.contains("js-pinned-repos-reorder-container")
  227. )) {
  228. return initSortUl(target, $(".mini-repo-list", el));
  229. }
  230. }
  231. });
  232. }
  233.  
  234. init();
  235. })();