Github News Feed Filter

Add filters for Github homepage news feed items

当前为 2014-06-30 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Github News Feed Filter
  3. // @namespace https://github.com/jerone/UserScripts
  4. // @description Add filters for Github homepage news feed items
  5. // @author jerone
  6. // @copyright 2014+, jerone (http://jeroenvanwarmerdam.nl)
  7. // @license GNU GPLv3
  8. // @homepage https://github.com/jerone/UserScripts/tree/master/Github_News_Feed_Filter
  9. // @homepageURL https://github.com/jerone/UserScripts/tree/master/Github_News_Feed_Filter
  10. // @include https://github.com/
  11. // @include https://github.com/orgs/*/dashboard
  12. // @include https://github.com/*?tab=activity
  13. // @version 5.2
  14. // @grant none
  15. // ==/UserScript==
  16. /* global Event */
  17.  
  18. (function() {
  19.  
  20. var FILTERS = [
  21. { text: "All News Feed", icon: "octicon-radio-tower", classNames: ["*"] },
  22. {
  23. text: "Issues", icon: "octicon-issue-opened", classNames: ["issues_opened", "issues_closed", "issues_reopened", "issues_comment"], subFilters: [
  24. { text: "Opened", icon: "octicon-issue-opened", classNames: ["issues_opened"] },
  25. { text: "Closed", icon: "octicon-issue-closed", classNames: ["issues_closed"] },
  26. { text: "Reopened", icon: "octicon-issue-reopened", classNames: ["issues_reopened"] },
  27. { text: "Comments", icon: "octicon-comment-discussion", classNames: ["issues_comment"] }
  28. ]
  29. },
  30. {
  31. text: "Commits", icon: "octicon-git-commit", classNames: ["push", "commit_comment"], subFilters: [
  32. { text: "Pushed", icon: "octicon-git-commit", classNames: ["push"] },
  33. { text: "Comments", icon: "octicon-comment-discussion", classNames: ["commit_comment"] }
  34. ]
  35. },
  36. {
  37. text: "Pull Requests", icon: "octicon-git-pull-request", classNames: ["pull_request_opened", "pull_request_closed", "pull_request_merged", "pull_request_comment"], subFilters: [
  38. { text: "Opened", icon: "octicon-git-pull-request", classNames: ["pull_request_opened"] },
  39. { text: "Closed", icon: "octicon-git-pull-request-abandoned", classNames: ["pull_request_closed"] },
  40. { text: "Merged", icon: "octicon-git-merge", classNames: ["pull_request_merged"] },
  41. { text: "Comments", icon: "octicon-comment-discussion", classNames: ["pull_request_comment"] }
  42. ]
  43. },
  44. {
  45. text: "Repo", icon: "octicon-repo", classNames: ["create", "public", "fork", "branch_create", "branch_delete", "tag_add", "tag_remove", "release", "delete"], subFilters: [
  46. { text: "Created", icon: "octicon-repo-create", classNames: ["create"] },
  47. { text: "Public", icon: "octicon-repo-push", classNames: ["public"] },
  48. { text: "Forked", icon: "octicon-repo-forked", classNames: ["fork"] },
  49. {
  50. text: "Branched", icon: "octicon-git-branch", classNames: ["branch_create", "branch_delete"], subFilters: [
  51. { text: "Created", icon: "octicon-git-branch-create", classNames: ["branch_create"] },
  52. { text: "Deleted", icon: "octicon-git-branch-delete", classNames: ["branch_delete"] }
  53. ]
  54. },
  55. {
  56. text: "Tagged", icon: "octicon-tag", classNames: ["tag_add", "tag_remove"], subFilters: [
  57. { text: "Added", icon: "octicon-tag-add", classNames: ["tag_add"] },
  58. { text: "Removed", icon: "octicon-tag-remove", classNames: ["tag_remove"] }
  59. ]
  60. },
  61. { text: "Released", icon: "octicon-repo-pull", classNames: ["release"] },
  62. { text: "Deleted", icon: "octicon-repo-delete", classNames: ["delete"] }
  63. ]
  64. },
  65. {
  66. text: "User", icon: "octicon-person", classNames: ["watch_started", "member_add", "team_add"], subFilters: [
  67. { text: "Starred", icon: "octicon-star", classNames: ["watch_started"] },
  68. { text: "Member added", icon: "octicon-person-add", classNames: ["member_add", "team_add"] }
  69. ]
  70. },
  71. { text: "Wiki", icon: "octicon-book", classNames: ["gollum"] },
  72. {
  73. text: "Gist", icon: "octicon-gist", classNames: ["gist_created", "gist_updated"], subFilters: [
  74. { text: "Created", icon: "octicon-gist-new", classNames: ["gist_created"] },
  75. { text: "Updated", icon: "octicon-gist", classNames: ["gist_updated"] }
  76. ]
  77. }
  78. // Possible other classes: follow
  79. ];
  80.  
  81. function proxy(fn) {
  82. return function() {
  83. var that = this;
  84. return function(e) {
  85. var args = that.slice(0); // clone;
  86. args.unshift(e); // prepend event;
  87. fn.apply(this, args);
  88. };
  89. }.call([].slice.call(arguments, 1));
  90. }
  91.  
  92. function addFilterMenu(filters, parent, container, sidebar, main) {
  93. var ul = document.createElement("ul");
  94. ul.classList.add("filter-list");
  95. if (!main) {
  96. ul.classList.add("small");
  97. ul.style.marginLeft = "10px";
  98. ul.style.display = "none";
  99. }
  100. parent.appendChild(ul);
  101.  
  102. filters.forEach(function(subFilter) {
  103. var li = addFilterMenuItem(subFilter, ul, container, sidebar);
  104.  
  105. if (subFilter.subFilters) {
  106. addFilterMenu(subFilter.subFilters, li, container, sidebar, false);
  107. }
  108. });
  109. }
  110.  
  111. function addFilterMenuItem(filter, parent, container, sidebar) {
  112. var a = document.createElement("a");
  113. a.classList.add("filter-item");
  114. a.setAttribute("href", "/");
  115. a.setAttribute("title", filter.classNames.join(" & "));
  116. if (filter.classNames[0] === "*") {
  117. a.classList.add("selected");
  118. a.style.fontWeight = "bold";
  119. }
  120.  
  121. var s = document.createElement("span");
  122. s.classList.add("octicon", filter.icon);
  123. s.style.marginRight = "10px";
  124. s.style.cssFloat = "left";
  125. s.style.minWidth = "16px";
  126. a.appendChild(s);
  127.  
  128. var c = document.createElement("span");
  129. c.classList.add("count");
  130. c.appendChild(document.createTextNode("0"));
  131. a.appendChild(c);
  132.  
  133. a.appendChild(document.createTextNode(filter.text));
  134.  
  135. a.addEventListener("click", proxy(function(e, classNames) {
  136. e.preventDefault();
  137.  
  138. var any = false,
  139. all = classNames[0] === "*",
  140. some = function(alert) { return classNames.some(function(cl) { return alert.classList.contains(cl); }); };
  141. Array.forEach(container.querySelectorAll(".alert"), function(alert) {
  142. alert.style.display = (all || some(alert)) && (any = true) ? "block" : "none";
  143. });
  144. var none = container.querySelector(".no-alerts");
  145. if (any && none) {
  146. none.parentNode.removeChild(none);
  147. } else if (!any && !none) {
  148. none = document.createElement("div");
  149. none.classList.add("no-alerts");
  150. none.style.padding = "0 0 1em 45px";
  151. none.style.fontStyle = "italic";
  152. none.appendChild(document.createTextNode("No feed items for this filter. Press the button below to load more items..."));
  153. container.insertBefore(none, container.firstChild);
  154. }
  155.  
  156. Array.forEach(sidebar.querySelectorAll(".filter-list.small"), function(ul) { ul.style.display = "none"; });
  157. showParentMenu(a.parentNode);
  158. var subMenu = a.parentNode.querySelector("ul");
  159. if (subMenu) { subMenu.style.display = "block"; }
  160.  
  161. Array.forEach(sidebar.querySelectorAll(".selected"), function(m) { m.classList.remove("selected"); });
  162. this.classList.add("selected");
  163. }, filter.classNames));
  164.  
  165. var li = document.createElement("li");
  166. li.appendChild(a);
  167. li.filterClassNames = filter.classNames;
  168.  
  169. parent.appendChild(li);
  170.  
  171. return li;
  172. }
  173.  
  174. function showParentMenu(menuItem) {
  175. var parentMenuItem = menuItem.parentNode;
  176. if (parentMenuItem.classList.contains("filter-list")) {
  177. parentMenuItem.style.display = "block";
  178. showParentMenu(parentMenuItem.parentNode);
  179. }
  180. }
  181.  
  182. function pageUpdate(container, sidebar, wrapper) {
  183. Array.forEach(container.querySelectorAll(".alert"), function(alert) {
  184. if (alert.getElementsByClassName("octicon-git-branch-create").length > 0) {
  185. alert.classList.remove("create");
  186. alert.classList.add("branch_create");
  187. } else if (alert.getElementsByClassName("octicon-git-branch-delete").length > 0) {
  188. alert.classList.remove("delete");
  189. alert.classList.add("branch_delete");
  190. } else if (alert.getElementsByClassName("octicon-tag-add").length > 0) {
  191. alert.classList.remove("create");
  192. alert.classList.add("tag_add");
  193. } else if (alert.getElementsByClassName("octicon-tag-remove").length > 0) {
  194. alert.classList.remove("delete");
  195. alert.classList.add("tag_remove");
  196. } else if (alert.getElementsByClassName("octicon-git-pull-request").length > 0) {
  197. alert.classList.remove("issues_opened", "issues_closed");
  198. if (alert.querySelector(".title span").textContent.toUpperCase() === "OPENED") { // English localisation;
  199. alert.classList.add("pull_request_opened");
  200. } else if (alert.querySelector(".title span").textContent.toUpperCase() === "MERGED") { // English localisation;
  201. alert.classList.add("pull_request_merged");
  202. } else if (alert.querySelector(".title span").textContent.toUpperCase() === "CLOSED") { // English localisation;
  203. alert.classList.add("pull_request_closed");
  204. }
  205. } else if (alert.classList.contains("issues_comment") && alert.querySelectorAll(".title a")[1].getAttribute("href").split("/")[5] === "pull") {
  206. alert.classList.remove("issues_comment");
  207. alert.classList.add("pull_request_comment");
  208. } else if (alert.classList.contains("gist")) {
  209. alert.classList.remove("gist");
  210. alert.classList.add("gist_" + alert.querySelector(".title span").textContent);
  211. }
  212. });
  213.  
  214. Array.forEach(wrapper.querySelectorAll("li"), function(li) {
  215. var c = li.querySelector(".count");
  216. if (li.filterClassNames[0] === "*") {
  217. c.textContent = container.querySelectorAll(".alert").length;
  218. } else {
  219. c.textContent = "0";
  220. Array.forEach(container.querySelectorAll(".alert"), function(alert) {
  221. if (li.filterClassNames.some(function(cl) { return alert.classList.contains(cl); })) {
  222. c.textContent = parseInt(c.textContent, 10) + 1;
  223. }
  224. });
  225. }
  226. });
  227.  
  228. sidebar.querySelector(".selected").dispatchEvent(new Event("click"));
  229. }
  230.  
  231. function addFilters() {
  232. var container = document.querySelector(".news");
  233. if (!container) { return; }
  234.  
  235. var sidebar = document.querySelector(".dashboard-sidebar") || document.querySelector(".column.one-fourth.vcard");
  236.  
  237. var rule = document.createElement("div");
  238. rule.classList.add("rule");
  239. sidebar.insertBefore(rule, sidebar.firstChild);
  240.  
  241. var wrapper = document.createElement("div");
  242. sidebar.insertBefore(wrapper, sidebar.firstChild);
  243.  
  244. addFilterMenu(FILTERS, wrapper, container, sidebar, true);
  245.  
  246. pageUpdate(container, sidebar, wrapper);
  247.  
  248. // update on clicking "More"-button;
  249. new MutationObserver(function() {
  250. pageUpdate(container, sidebar, wrapper);
  251. }).observe(container, { childList: true });
  252. }
  253.  
  254. // init;
  255. addFilters();
  256.  
  257. })();