GitLab Sort Content

A userscript that makes some lists & markdown tables sortable

As of 2018-04-09. See the latest version.

  1. // ==UserScript==
  2. // @name GitLab Sort Content
  3. // @version 0.1.1-beta
  4. // @description A userscript that makes some lists & markdown tables sortable
  5. // @license MIT
  6. // @author Rob Garrison
  7. // @namespace https://gitlab.com/Mottie
  8. // @include https://gitlab.com/*
  9. // @run-at document-idle
  10. // @grant GM.addStyle
  11. // @require https://cdnjs.cloudflare.com/ajax/libs/tinysort/2.3.6/tinysort.min.js
  12. // @icon https://gitlab.com/assets/gitlab_logo-7ae504fe4f68fdebb3c2034e36621930cd36ea87924c11ff65dbcb8ed50dca58.png
  13. // ==/UserScript==
  14. (() => {
  15. "use strict";
  16. /* example pages:
  17. tables/repo files - https://github.com/Mottie/GitLab-userscripts
  18. */
  19. const sorts = ["asc", "desc"],
  20. icons = {
  21. white: {
  22. unsorted: "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiI+PHBhdGggZD0iTTE1IDhIMWw3LTh6bTAgMUgxbDcgN3oiIGZpbGw9IiNkZGQiIG9wYWNpdHk9Ii4yIi8+PC9zdmc+",
  23. asc: "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiI+PHBhdGggZD0iTTE1IDhIMWw3LTh6IiBmaWxsPSIjZGRkIi8+PHBhdGggZD0iTTE1IDlIMWw3IDd6IiBmaWxsPSIjZGRkIiBvcGFjaXR5PSIuMiIvPjwvc3ZnPg==",
  24. desc: "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiI+PHBhdGggZD0iTTE1IDhIMWw3LTh6IiBmaWxsPSIjZGRkIiBvcGFjaXR5PSIuMiIvPjxwYXRoIGQ9Ik0xNSA5SDFsNyA3eiIgZmlsbD0iI2RkZCIvPjwvc3ZnPg=="
  25. },
  26. black: {
  27. unsorted: "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiI+PHBhdGggZD0iTTE1IDhIMWw3LTh6bTAgMUgxbDcgN3oiIGZpbGw9IiMyMjIiIG9wYWNpdHk9Ii4yIi8+PC9zdmc+",
  28. asc: "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiI+PHBhdGggZD0iTTE1IDhIMWw3LTh6IiBmaWxsPSIjMjIyIi8+PHBhdGggZD0iTTE1IDlIMWw3IDd6IiBmaWxsPSIjMjIyIiBvcGFjaXR5PSIuMiIvPjwvc3ZnPg==",
  29. desc: "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiI+PHBhdGggZD0iTTE1IDhIMWw3LTh6IiBmaWxsPSIjMjIyIiBvcGFjaXR5PSIuMiIvPjxwYXRoIGQ9Ik0xNSA5SDFsNyA3eiIgZmlsbD0iIzIyMiIvPjwvc3ZnPg=="
  30. }
  31. };
  32.  
  33. function initSortTable(el) {
  34. removeSelection();
  35. const dir = el.classList.contains(sorts[0]) ? sorts[1] : sorts[0],
  36. table = el.closest("table"),
  37. firstRow = $("tbody tr:first-child", table),
  38. link = $("a", firstRow),
  39. options = {
  40. order: dir,
  41. natural: true,
  42. selector: `td:nth-child(${el.cellIndex + 1})`
  43. };
  44. if (el.textContent.trim() === "Last update") {
  45. // sort repo age column using ISO 8601 datetime format
  46. options.selector += " time";
  47. options.attr = "datetime";
  48. }
  49. // Don't sort directory up row
  50. if (link && link.textContent === "..") {
  51. firstRow.classList.add("no-sort");
  52. }
  53. tinysort($$("tbody tr:not(.no-sort)", table), options);
  54. $$("th", table).forEach(elm => {
  55. elm.classList.remove(...sorts);
  56. });
  57. el.classList.add(dir);
  58. }
  59.  
  60. function needDarkTheme() {
  61. let brightest = 0,
  62. // color will be "rgb(#, #, #)" or "rgba(#, #, #, #)"
  63. color = window.getComputedStyle(document.body).backgroundColor;
  64. const rgb = (color || "")
  65. .replace(/\s/g, "")
  66. .match(/^rgba?\((\d+),(\d+),(\d+)/i);
  67. if (rgb) {
  68. color = rgb.slice(1); // remove "rgb.." part from match
  69. color.forEach(c => {
  70. // http://stackoverflow.com/a/15794784/145346
  71. brightest = Math.max(brightest, parseInt(c, 10));
  72. });
  73. // return true if we have a dark background
  74. return brightest < 128;
  75. }
  76. // fallback to bright background
  77. return false;
  78. }
  79.  
  80. function $(str, el) {
  81. return (el || document).querySelector(str);
  82. }
  83.  
  84. function $$(str, el) {
  85. return Array.from((el || document).querySelectorAll(str));
  86. }
  87.  
  88. function removeSelection() {
  89. // remove text selection - http://stackoverflow.com/a/3171348/145346
  90. const sel = window.getSelection ?
  91. window.getSelection() :
  92. document.selection;
  93. if (sel) {
  94. if (sel.removeAllRanges) {
  95. sel.removeAllRanges();
  96. } else if (sel.empty) {
  97. sel.empty();
  98. }
  99. }
  100. }
  101.  
  102. function init() {
  103. const styles = needDarkTheme() ? icons.white : icons.black;
  104.  
  105. GM.addStyle(`
  106. /* unsorted icon */
  107. [data-rich-type="markup"] thead th, .tree-table th, .wiki th {
  108. cursor:pointer;
  109. padding-right:22px !important;
  110. background-image:url(${styles.unsorted}) !important;
  111. background-repeat:no-repeat !important;
  112. background-position:calc(100% - 5px) center !important;
  113. text-align:left;
  114. }
  115. /* asc/dec icons */
  116. table thead th.asc {
  117. background-image:url(${styles.asc}) !important;
  118. background-repeat:no-repeat !important;
  119. }
  120. table thead th.desc {
  121. background-image:url(${styles.desc}) !important;
  122. background-repeat:no-repeat !important;
  123. }
  124. `);
  125.  
  126. document.body.addEventListener("click", event => {
  127. const target = event.target;
  128. if (target && target.nodeType === 1 && target.nodeName === "TH") {
  129. // don't sort tables not inside of markdown,
  130. // except for the repo "code" tab file list
  131. if (target.closest(".blob-viewer, .tree-table, .wiki")) {
  132. return initSortTable(target);
  133. }
  134. }
  135. });
  136. }
  137. init();
  138. })();