Greasy Fork 还支持 简体中文。

Github News Feed Filter

Add filters for Github homepage news feed items

目前為 2014-08-02 提交的版本,檢視 最新版本

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