GitHub Sort Content

A userscript that makes some lists & markdown tables sortable

目前為 2016-07-17 提交的版本,檢視 最新版本

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