Github News Feed Filter

Add filters for GitHub homepage news feed items

  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. // @contributor darkred
  7. // @copyright 2014+, jerone (https://github.com/jerone)
  8. // @license CC-BY-NC-SA-4.0; https://creativecommons.org/licenses/by-nc-sa/4.0/legalcode
  9. // @license GPL-3.0-or-later; http://www.gnu.org/licenses/gpl-3.0.txt
  10. // @homepage https://github.com/jerone/UserScripts/tree/master/Github_News_Feed_Filter
  11. // @homepageURL https://github.com/jerone/UserScripts/tree/master/Github_News_Feed_Filter
  12. // @contributionURL https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=VCYMHWQ7ZMBKW
  13. // @icon https://github.githubassets.com/pinned-octocat.svg
  14. // @include https://github.com/
  15. // @include https://github.com/?*
  16. // @include https://github.com/orgs/*/dashboard
  17. // @include https://github.com/orgs/*/dashboard?*
  18. // @version 8.2.8
  19. // @grant none
  20. // ==/UserScript==
  21.  
  22. // cSpell:ignore transform, osvg, opath, gollum, hovercards, profilecols
  23. /* eslint security/detect-object-injection: "off" */
  24.  
  25. (function () {
  26. var ICONS = {};
  27. ICONS["octicon-book"] =
  28. "M3 5h4v1H3V5zm0 3h4V7H3v1zm0 2h4V9H3v1zm11-5h-4v1h4V5zm0 2h-4v1h4V7zm0 2h-4v1h4V9zm2-6v9c0 .55-.45 1-1 1H9.5l-1 1-1-1H2c-.55 0-1-.45-1-1V3c0-.55.45-1 1-1h5.5l1 1 1-1H15c.55 0 1 .45 1 1zm-8 .5L7.5 3H2v9h6V3.5zm7-.5H9.5l-.5.5V12h6V3z";
  29. ICONS["octicon-comment-discussion"] =
  30. "M15 1H6c-.55 0-1 .45-1 1v2H1c-.55 0-1 .45-1 1v6c0 .55.45 1 1 1h1v3l3-3h4c.55 0 1-.45 1-1V9h1l3 3V9h1c.55 0 1-.45 1-1V2c0-.55-.45-1-1-1zM9 11H4.5L3 12.5V11H1V5h4v3c0 .55.45 1 1 1h3v2zm6-3h-2v1.5L11.5 8H6V2h9v6z";
  31. ICONS["octicon-git-branch"] =
  32. "M10 5c0-1.11-.89-2-2-2a1.993 1.993 0 0 0-1 3.72v.3c-.02.52-.23.98-.63 1.38-.4.4-.86.61-1.38.63-.83.02-1.48.16-2 .45V4.72a1.993 1.993 0 0 0-1-3.72C.88 1 0 1.89 0 3a2 2 0 0 0 1 1.72v6.56c-.59.35-1 .99-1 1.72 0 1.11.89 2 2 2 1.11 0 2-.89 2-2 0-.53-.2-1-.53-1.36.09-.06.48-.41.59-.47.25-.11.56-.17.94-.17 1.05-.05 1.95-.45 2.75-1.25S8.95 7.77 9 6.73h-.02C9.59 6.37 10 5.73 10 5zM2 1.8c.66 0 1.2.55 1.2 1.2 0 .65-.55 1.2-1.2 1.2C1.35 4.2.8 3.65.8 3c0-.65.55-1.2 1.2-1.2zm0 12.41c-.66 0-1.2-.55-1.2-1.2 0-.65.55-1.2 1.2-1.2.65 0 1.2.55 1.2 1.2 0 .65-.55 1.2-1.2 1.2zm6-8c-.66 0-1.2-.55-1.2-1.2 0-.65.55-1.2 1.2-1.2.65 0 1.2.55 1.2 1.2 0 .65-.55 1.2-1.2 1.2z";
  33. ICONS["octicon-git-branch-create"] = ICONS["octicon-git-branch"];
  34. ICONS["octicon-git-branch-delete"] = ICONS["octicon-git-branch"];
  35. ICONS["octicon-git-commit"] =
  36. "M10.86 7c-.45-1.72-2-3-3.86-3-1.86 0-3.41 1.28-3.86 3H0v2h3.14c.45 1.72 2 3 3.86 3 1.86 0 3.41-1.28 3.86-3H14V7h-3.14zM7 10.2c-1.22 0-2.2-.98-2.2-2.2 0-1.22.98-2.2 2.2-2.2 1.22 0 2.2.98 2.2 2.2 0 1.22-.98 2.2-2.2 2.2z";
  37. ICONS["octicon-home"] =
  38. "M16 9l-3-3V2h-2v2L8 1 0 9h2l1 5c0 .55.45 1 1 1h8c.55 0 1-.45 1-1l1-5h2zm-4 5H9v-4H7v4H4L2.81 7.69 8 2.5l5.19 5.19L12 14z";
  39. ICONS["octicon-issue-opened"] =
  40. "M7 2.3c3.14 0 5.7 2.56 5.7 5.7S10.14 13.7 7 13.7 1.3 11.14 1.3 8s2.56-5.7 5.7-5.7m0-1.3C3.14 1 0 4.14 0 8s3.14 7 7 7 7-3.14 7-7S10.86 1 7 1z m1 3H6v5h2V4z m0 6H6v2h2V10z";
  41. ICONS["octicon-person"] =
  42. "M12 14.002a.998.998 0 0 1-.998.998H1.001A1 1 0 0 1 0 13.999V13c0-2.633 4-4 4-4s.229-.409 0-1c-.841-.62-.944-1.59-1-4 .173-2.413 1.867-3 3-3s2.827.586 3 3c-.056 2.41-.159 3.38-1 4-.229.59 0 1 0 1s4 1.367 4 4v1.002z";
  43. ICONS["octicon-organization"] =
  44. "M16 12.999c0 .439-.45 1-1 1H7.995c-.539 0-.994-.447-.995-.999H1c-.54 0-1-.561-1-1 0-2.634 3-4 3-4s.229-.409 0-1c-.841-.621-1.058-.59-1-3 .058-2.419 1.367-3 2.5-3s2.442.58 2.5 3c.058 2.41-.159 2.379-1 3-.229.59 0 1 0 1s1.549.711 2.42 2.088C9.196 9.369 10 8.999 10 8.999s.229-.409 0-1c-.841-.62-1.058-.59-1-3 .058-2.419 1.367-3 2.5-3s2.437.581 2.495 3c.059 2.41-.158 2.38-1 3-.229.59 0 1 0 1s3.005 1.366 3.005 4";
  45. ICONS["octicon-plus"] = "M12 9H7v5H5V9H0V7h5V2h2v5h5z";
  46. ICONS["octicon-radio-tower"] =
  47. "M4.79 6.11c.25-.25.25-.67 0-.92-.32-.33-.48-.76-.48-1.19 0-.43.16-.86.48-1.19.25-.26.25-.67 0-.92a.613.613 0 0 0-.45-.19c-.16 0-.33.06-.45.19-.57.58-.85 1.35-.85 2.11 0 .76.29 1.53.85 2.11.25.25.66.25.9 0zM2.33.52a.651.651 0 0 0-.92 0C.48 1.48.01 2.74.01 3.99c0 1.26.47 2.52 1.4 3.48.25.26.66.26.91 0s.25-.68 0-.94c-.68-.7-1.02-1.62-1.02-2.54 0-.92.34-1.84 1.02-2.54a.66.66 0 0 0 .01-.93zm5.69 5.1A1.62 1.62 0 1 0 6.4 4c-.01.89.72 1.62 1.62 1.62zM14.59.53a.628.628 0 0 0-.91 0c-.25.26-.25.68 0 .94.68.7 1.02 1.62 1.02 2.54 0 .92-.34 1.83-1.02 2.54-.25.26-.25.68 0 .94a.651.651 0 0 0 .92 0c.93-.96 1.4-2.22 1.4-3.48A5.048 5.048 0 0 0 14.59.53zM8.02 6.92c-.41 0-.83-.1-1.2-.3l-3.15 8.37h1.49l.86-1h4l.84 1h1.49L9.21 6.62c-.38.2-.78.3-1.19.3zm-.01.48L9.02 11h-2l.99-3.6zm-1.99 5.59l1-1h2l1 1h-4zm5.19-11.1c-.25.25-.25.67 0 .92.32.33.48.76.48 1.19 0 .43-.16.86-.48 1.19-.25.26-.25.67 0 .92a.63.63 0 0 0 .9 0c.57-.58.85-1.35.85-2.11 0-.76-.28-1.53-.85-2.11a.634.634 0 0 0-.9 0z";
  48. ICONS["octicon-repo"] =
  49. "M4 9H3V8h1v1zm0-3H3v1h1V6zm0-2H3v1h1V4zm0-2H3v1h1V2zm8-1v12c0 .55-.45 1-1 1H6v2l-1.5-1.5L3 16v-2H1c-.55 0-1-.45-1-1V1c0-.55.45-1 1-1h10c.55 0 1 .45 1 1zm-1 10H1v2h2v-1h3v1h5v-2zm0-10H2v9h9V1z";
  50. ICONS["octicon-repo-clone"] =
  51. "M15 0H9v7c0 .55.45 1 1 1h1v1h1V8h3c.55 0 1-.45 1-1V1c0-.55-.45-1-1-1zm-4 7h-1V6h1v1zm4 0h-3V6h3v1zm0-2h-4V1h4v4zM4 5H3V4h1v1zm0-2H3V2h1v1zM2 1h6V0H1C.45 0 0 .45 0 1v12c0 .55.45 1 1 1h2v2l1.5-1.5L6 16v-2h5c.55 0 1-.45 1-1v-3H2V1zm9 10v2H6v-1H3v1H1v-2h10zM3 8h1v1H3V8zm1-1H3V6h1v1z";
  52. ICONS["octicon-repo-create"] = ICONS["octicon-plus"];
  53. ICONS["octicon-repo-push"] =
  54. "M4 3H3V2h1v1zM3 5h1V4H3v1zm4 0L4 9h2v7h2V9h2L7 5zm4-5H1C.45 0 0 .45 0 1v12c0 .55.45 1 1 1h4v-1H1v-2h4v-1H2V1h9.02L11 10H9v1h2v2H9v1h2c.55 0 1-.45 1-1V1c0-.55-.45-1-1-1z";
  55. ICONS["octicon-repo-forked"] =
  56. "M8 1a1.993 1.993 0 0 0-1 3.72V6L5 8 3 6V4.72A1.993 1.993 0 0 0 2 1a1.993 1.993 0 0 0-1 3.72V6.5l3 3v1.78A1.993 1.993 0 0 0 5 15a1.993 1.993 0 0 0 1-3.72V9.5l3-3V4.72A1.993 1.993 0 0 0 8 1zM2 4.2C1.34 4.2.8 3.65.8 3c0-.65.55-1.2 1.2-1.2.65 0 1.2.55 1.2 1.2 0 .65-.55 1.2-1.2 1.2zm3 10c-.66 0-1.2-.55-1.2-1.2 0-.65.55-1.2 1.2-1.2.65 0 1.2.55 1.2 1.2 0 .65-.55 1.2-1.2 1.2zm3-10c-.66 0-1.2-.55-1.2-1.2 0-.65.55-1.2 1.2-1.2.65 0 1.2.55 1.2 1.2 0 .65-.55 1.2-1.2 1.2z";
  57. ICONS["octicon-repo-delete"] = ICONS["octicon-repo"];
  58. ICONS["octicon-repo-pull"] =
  59. "M13 8V6H7V4h6V2l3 3-3 3zM4 2H3v1h1V2zm7 5h1v6c0 .55-.45 1-1 1H6v2l-1.5-1.5L3 16v-2H1c-.55 0-1-.45-1-1V1c0-.55.45-1 1-1h10c.55 0 1 .45 1 1v2h-1V1H2v9h9V7zm0 4H1v2h2v-1h3v1h5v-2zM4 6H3v1h1V6zm0-2H3v1h1V4zM3 9h1V8H3v1z";
  60. ICONS["octicon-star"] =
  61. "M14 6l-4.9-.64L7 1 4.9 5.36 0 6l3.6 3.26L2.67 14 7 11.67 11.33 14l-.93-4.74z";
  62. ICONS["octicon-tag"] =
  63. "M7.73 1.73C7.26 1.26 6.62 1 5.96 1H3.5C2.13 1 1 2.13 1 3.5v2.47c0 .66.27 1.3.73 1.77l6.06 6.06c.39.39 1.02.39 1.41 0l4.59-4.59a.996.996 0 0 0 0-1.41L7.73 1.73zM2.38 7.09c-.31-.3-.47-.7-.47-1.13V3.5c0-.88.72-1.59 1.59-1.59h2.47c.42 0 .83.16 1.13.47l6.14 6.13-4.73 4.73-6.13-6.15zM3.01 3h2v2H3V3h.01z";
  64. ICONS["octicon-tag-add"] = ICONS["octicon-tag"];
  65. ICONS["octicon-tag-remove"] = ICONS["octicon-tag"];
  66. ICONS["octicon-triangle-left"] = "M6 2L0 8l6 6z";
  67.  
  68. var ACTIONS = [
  69. {
  70. id: "*-action",
  71. text: "All news feed",
  72. icon: "octicon-radio-tower",
  73. classNames: ["*-action"],
  74. },
  75. {
  76. id: "issues",
  77. text: "Issues",
  78. icon: "octicon-issue-opened",
  79. classNames: ["issues_labeled"],
  80. subFilters: [
  81. {
  82. id: "issues labeled",
  83. text: "Labeled",
  84. icon: "octicon-tag",
  85. classNames: ["issues_labeled"],
  86. },
  87. ],
  88. },
  89. {
  90. id: "commits",
  91. text: "Commits",
  92. icon: "octicon-git-commit",
  93. classNames: ["push", "commit_comment"],
  94. subFilters: [
  95. {
  96. id: "commits pushed",
  97. text: "Pushed",
  98. icon: "octicon-git-commit",
  99. classNames: ["push"],
  100. },
  101. {
  102. id: "commits comments",
  103. text: "Comments",
  104. icon: "octicon-comment-discussion",
  105. classNames: ["commit_comment"],
  106. },
  107. ],
  108. },
  109. {
  110. id: "repo",
  111. text: "Repo",
  112. icon: "octicon-repo",
  113. classNames: [
  114. "repo",
  115. "create",
  116. "public",
  117. "fork",
  118. "branch_create",
  119. "branch_delete",
  120. "tag_add",
  121. "tag_remove",
  122. "release",
  123. "delete",
  124. ],
  125. subFilters: [
  126. {
  127. id: "repo created",
  128. text: "Created",
  129. icon: "octicon-repo-create",
  130. classNames: ["repo", "create"],
  131. },
  132. {
  133. id: "repo public",
  134. text: "Public",
  135. icon: "octicon-repo-push",
  136. classNames: ["public"],
  137. },
  138. {
  139. id: "repo forked",
  140. text: "Forked",
  141. icon: "octicon-repo-forked",
  142. classNames: ["fork"],
  143. },
  144. {
  145. id: "repo deleted",
  146. text: "Deleted",
  147. icon: "octicon-repo-delete",
  148. classNames: ["delete"],
  149. },
  150. {
  151. id: "repo released",
  152. text: "Release",
  153. icon: "octicon-repo-pull",
  154. classNames: ["release"],
  155. },
  156. {
  157. id: "repo branched",
  158. text: "Branch",
  159. icon: "octicon-git-branch",
  160. classNames: ["branch_create", "branch_delete"],
  161. subFilters: [
  162. {
  163. id: "repo branch created",
  164. text: "Created",
  165. icon: "octicon-git-branch-create",
  166. classNames: ["branch_create"],
  167. },
  168. {
  169. id: "repo branch deleted",
  170. text: "Deleted",
  171. icon: "octicon-git-branch-delete",
  172. classNames: ["branch_delete"],
  173. },
  174. ],
  175. },
  176. {
  177. id: "repo tagged",
  178. text: "Tag",
  179. icon: "octicon-tag",
  180. classNames: ["tag_add", "tag_remove"],
  181. subFilters: [
  182. {
  183. id: "repo tag added",
  184. text: "Added",
  185. icon: "octicon-tag-add",
  186. classNames: ["tag_add"],
  187. },
  188. {
  189. id: "repo tag removed",
  190. text: "Removed",
  191. icon: "octicon-tag-remove",
  192. classNames: ["tag_remove"],
  193. },
  194. ],
  195. },
  196. ],
  197. },
  198. {
  199. id: "user",
  200. text: "User",
  201. icon: "octicon-person",
  202. classNames: ["follow"],
  203. },
  204. {
  205. id: "starred",
  206. text: "Starred",
  207. icon: "octicon-star",
  208. classNames: ["watch_started"],
  209. },
  210. {
  211. id: "wiki",
  212. text: "Wiki",
  213. icon: "octicon-book",
  214. classNames: ["wiki_created", "wiki_edited"],
  215. subFilters: [
  216. {
  217. id: "wiki created",
  218. text: "Created",
  219. icon: "octicon-plus",
  220. classNames: ["wiki_created"],
  221. },
  222. {
  223. id: "wiki edited",
  224. text: "Edited",
  225. icon: "octicon-book",
  226. classNames: ["wiki_edited"],
  227. },
  228. ],
  229. },
  230. ];
  231.  
  232. var REPOS = [];
  233.  
  234. var USERS = [];
  235.  
  236. var datasetId = "githubNewsFeedFilter";
  237. var datasetIdLong = "data-github-news-feed-filter";
  238. var filterElement = "github-news-feed-filter";
  239. var filterListElement = "github-news-feed-filter-list";
  240.  
  241. function proxy(fn) {
  242. return function () {
  243. var that = this;
  244. return function (e) {
  245. var args = that.slice(0); // Clone.
  246. args.unshift(e); // Prepend event.
  247. fn.apply(this, args);
  248. };
  249. }.call([].slice.call(arguments, 1));
  250. }
  251.  
  252. function addStyle(css) {
  253. var node = document.createElement("style");
  254. node.type = "text/css";
  255. node.appendChild(document.createTextNode(css));
  256. document.head.appendChild(node);
  257. }
  258.  
  259. addStyle(`
  260. github-news-feed-filter { display: block; }
  261.  
  262. github-news-feed-filter .filter-bar { padding: 0; }
  263.  
  264. github-news-feed-filter .count { margin-right: 15px; }
  265.  
  266. github-news-feed-filter .filter-list .mini-repo-list-item { padding-right: 64px; }
  267.  
  268. github-news-feed-filter .filter-list .filter-list .mini-repo-list-item { padding-left: 40px; border-top: 1px dashed #E5E5E5; }
  269. github-news-feed-filter .filter-list .filter-list .filter-list .mini-repo-list-item { padding-left: 50px; }
  270.  
  271. github-news-feed-filter .filter-list { display: none; }
  272. github-news-feed-filter .open > .filter-list { display: block; }
  273. github-news-feed-filter .filter-list.open { display: block; }
  274.  
  275. github-news-feed-filter .private { font-weight: bold; }
  276.  
  277. github-news-feed-filter .stars .octicon { position: absolute; right: -4px; }
  278. github-news-feed-filter .filter-list-item.open > a > .stars > .octicon { transform: rotate(-90deg); }
  279.  
  280. .no-alerts { font-style: italic; }
  281. `);
  282.  
  283. // Add filter menu list.
  284. function addFilterMenu(
  285. type,
  286. filters,
  287. parent,
  288. newsContainer,
  289. filterContainer,
  290. main,
  291. ) {
  292. var ul = document.createElement("ul");
  293. ul.classList.add("filter-list");
  294. if (main) {
  295. ul.classList.add("mini-repo-list");
  296. }
  297. parent.appendChild(ul);
  298.  
  299. filters.forEach(function (subFilter) {
  300. var li = addFilterMenuItem(
  301. type,
  302. subFilter,
  303. ul,
  304. newsContainer,
  305. filterContainer,
  306. );
  307.  
  308. if (subFilter.subFilters) {
  309. addFilterMenu(
  310. type,
  311. subFilter.subFilters,
  312. li,
  313. newsContainer,
  314. filterContainer,
  315. false,
  316. );
  317. }
  318. });
  319. }
  320.  
  321. // Add filter menu item.
  322. function addFilterMenuItem(
  323. type,
  324. filter,
  325. parent,
  326. newsContainer,
  327. filterContainer,
  328. ) {
  329. // Filter item.
  330. var li = document.createElement("li");
  331. li.classList.add("filter-list-item");
  332. li.filterClassNames = filter.classNames;
  333. parent.appendChild(li);
  334.  
  335. // Filter link.
  336. var a = document.createElement("a");
  337. a.classList.add("mini-repo-list-item", "css-truncate");
  338. a.setAttribute("href", filter.link || "/");
  339. a.setAttribute("title", filter.classNames.join(" & "));
  340. a.dataset[datasetId] = filter.id;
  341. a.addEventListener(
  342. "click",
  343. proxy(onFilterItemClick, type, newsContainer, filterContainer),
  344. );
  345. li.appendChild(a);
  346.  
  347. // Filter icon.
  348. var svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
  349. svg.classList.add("repo-icon", "octicon", filter.icon);
  350. svg.setAttribute("height", "16");
  351. svg.setAttribute("width", "16");
  352. a.appendChild(svg);
  353. var path = document.createElementNS(
  354. "http://www.w3.org/2000/svg",
  355. "path",
  356. );
  357. path.setAttribute("d", ICONS[filter.icon]);
  358. svg.appendChild(path);
  359.  
  360. // Filter text.
  361. var text = filter.text.split("/");
  362. var t = document.createElement("span");
  363. t.classList.add("repo-and-owner", "css-truncate-target");
  364. a.appendChild(t);
  365. var to = document.createElement("span");
  366. to.classList.add("owner");
  367. to.appendChild(document.createTextNode(text[0]));
  368. t.appendChild(to);
  369. if (text.length > 1) {
  370. text.shift();
  371. t.appendChild(document.createTextNode("/"));
  372. var tr = document.createElement("span");
  373. tr.classList.add("repo");
  374. tr.appendChild(document.createTextNode(text.join("/")));
  375. t.appendChild(tr);
  376. }
  377.  
  378. // Filter count & sub list arrow.
  379. var s = document.createElement("span");
  380. s.classList.add("stars");
  381. var c = document.createElement("span");
  382. c.classList.add("count");
  383. c.appendChild(document.createTextNode("0"));
  384. s.appendChild(c);
  385. if (filter.subFilters) {
  386. s.appendChild(document.createTextNode(" "));
  387. var osvg = document.createElementNS(
  388. "http://www.w3.org/2000/svg",
  389. "svg",
  390. );
  391. osvg.classList.add("octicon", "octicon-triangle-left");
  392. osvg.setAttribute("height", "16");
  393. osvg.setAttribute("width", "6");
  394. s.appendChild(osvg);
  395. var opath = document.createElementNS(
  396. "http://www.w3.org/2000/svg",
  397. "path",
  398. );
  399. opath.setAttribute("d", ICONS["octicon-triangle-left"]);
  400. osvg.appendChild(opath);
  401. }
  402. a.appendChild(s);
  403.  
  404. return li;
  405. }
  406.  
  407. // Filter item click event.
  408. function onFilterItemClick(e, type, newsContainer, filterContainer) {
  409. e.preventDefault();
  410.  
  411. // Store current filter.
  412. setCurrentFilter(type, this.dataset[datasetId]);
  413.  
  414. // Open/close sub list.
  415. Array.forEach(
  416. filterContainer.querySelectorAll(":scope .open"),
  417. function (item) {
  418. item.classList.remove("open");
  419. },
  420. );
  421. showParentMenu(this);
  422. this.parentNode.classList.add("open");
  423.  
  424. // Give it a colored background.
  425. Array.forEach(
  426. filterContainer.querySelectorAll(":scope .private"),
  427. function (m) {
  428. m.classList.remove("private");
  429. },
  430. );
  431. this.parentNode.classList.add("private");
  432.  
  433. // Toggle alert visibility.
  434. toggleAlertsVisibility(newsContainer);
  435. }
  436.  
  437. // Toggle alert visibility.
  438. function toggleAlertsVisibility(newsContainer) {
  439. // Get selected filters.
  440. var anyVisibleAlert = false;
  441. var classNames = [];
  442. var selected = document.querySelectorAll(
  443. ":scope " + filterElement + " .private",
  444. );
  445. if (selected.length > 0) {
  446. Array.prototype.forEach.call(selected, function (item) {
  447. classNames.push(item.filterClassNames);
  448. });
  449. }
  450.  
  451. // Show/hide alerts.
  452. if (
  453. classNames.length === 0 ||
  454. classNames.every(function (cl) {
  455. return cl.every(function (c) {
  456. return ~c.indexOf("*");
  457. });
  458. })
  459. ) {
  460. anyVisibleAlert = true;
  461. getAllAlerts(newsContainer).forEach(function (alert) {
  462. alert.style.display = "block";
  463. });
  464. } else {
  465. getAllAlerts(newsContainer).forEach(function (alert) {
  466. var show = classNames.every(function (cl) {
  467. return cl.some(function (c) {
  468. return ~c.indexOf("*") || alert.classList.contains(c);
  469. });
  470. });
  471. anyVisibleAlert = show || anyVisibleAlert;
  472. alert.style.display = show ? "block" : "none";
  473. // DEBUG: uncomment following line and comment previous line to debug all alerts.
  474. //if(show) alert.style.display = 'none';
  475. });
  476. }
  477.  
  478. // Show/hide message about no alerts.
  479. var none = newsContainer.querySelector(":scope .no-alerts");
  480. if (anyVisibleAlert && none) {
  481. none.parentNode.removeChild(none);
  482. } else if (!anyVisibleAlert && !none) {
  483. none = document.createElement("div");
  484. none.classList.add("no-alerts");
  485. none.appendChild(
  486. document.createTextNode(
  487. "No feed items for this filter. Please select another filter.",
  488. ),
  489. );
  490. var firstAlert = getAllAlerts(newsContainer)[0];
  491. firstAlert.parentNode.insertBefore(none, firstAlert);
  492. }
  493. }
  494.  
  495. // Traverse back up the tree to open sub lists.
  496. function showParentMenu(menuItem) {
  497. var parentMenuItem = menuItem.parentNode;
  498. if (parentMenuItem.classList.contains("filter-list-item")) {
  499. parentMenuItem.classList.add("open");
  500. showParentMenu(parentMenuItem.parentNode);
  501. }
  502. }
  503.  
  504. // Fix filter action identification.
  505. function fixActionAlerts(newsContainer) {
  506. getAllAlerts(newsContainer).forEach(function (alert) {
  507. if (~alert.textContent.indexOf("created branch")) {
  508. alert.classList.remove("create");
  509. alert.classList.add("branch_create");
  510. } else if (~alert.textContent.indexOf("deleted branch")) {
  511. alert.classList.remove("delete");
  512. alert.classList.add("branch_delete");
  513. } else if (
  514. alert.getElementsByClassName("octicon-tag").length > 0 &&
  515. !alert.classList.contains("release")
  516. ) {
  517. alert.classList.remove("create");
  518. alert.classList.add("tag_add");
  519. } else if (
  520. alert.getElementsByClassName("octicon-tag-remove").length > 0
  521. ) {
  522. alert.classList.remove("delete");
  523. alert.classList.add("tag_remove");
  524. } else if (~alert.textContent.indexOf("labeled an issue")) {
  525. alert.classList.add("issues_labeled");
  526. } else if (alert.classList.contains("gollum")) {
  527. alert.classList.remove("gollum");
  528. if (~alert.innerText.indexOf(" created a wiki page in ")) {
  529. alert.classList.add("wiki_created");
  530. } else if (
  531. ~alert.innerText.indexOf(" edited a wiki page in ")
  532. ) {
  533. alert.classList.add("wiki_edited");
  534. }
  535. }
  536. });
  537. }
  538. // Fix filter repo identification.
  539. function fixRepoAlerts(newsContainer) {
  540. REPOS = [
  541. {
  542. id: "*-repo",
  543. text: "All repositories",
  544. icon: "octicon-repo",
  545. classNames: ["*-repo"],
  546. },
  547. ];
  548.  
  549. // Get unique list of repos.
  550. var userRepos = new Set();
  551. getAllAlerts(newsContainer).forEach(function (alert) {
  552. var alertRepo = alert.querySelector(
  553. ':scope [data-ga-click*="target:repo"]:not([data-ga-click*="target:repositories"])',
  554. );
  555. if (alertRepo) {
  556. // Follow doesn't contain a repo link.
  557. var userRepo = alertRepo.textContent;
  558. userRepos.add(userRepo);
  559. var repo = userRepo.split("/")[1];
  560. alert.classList.add(repo, userRepo);
  561. }
  562. });
  563.  
  564. // Get list of user repos (forks) per repo names.
  565. var repos = {};
  566. userRepos.forEach(function (userRepo) {
  567. var repo = userRepo.split("/")[1];
  568. if (!repos[repo]) {
  569. repos[repo] = [];
  570. }
  571. repos[repo].push(userRepo);
  572. });
  573.  
  574. // Populate global property.
  575. Object.keys(repos).forEach(function (repo) {
  576. if (repos[repo].length === 1) {
  577. var userRepo = repos[repo][0];
  578. REPOS.push({
  579. id: userRepo,
  580. text: userRepo,
  581. link: userRepo,
  582. icon: "octicon-repo",
  583. classNames: [userRepo],
  584. });
  585. } else {
  586. var repoForks = {
  587. id: repo,
  588. text: repo,
  589. icon: "octicon-repo-clone",
  590. classNames: [repo],
  591. subFilters: [],
  592. };
  593. repos[repo].forEach(function (userRepo) {
  594. repoForks.classNames.push(userRepo);
  595. repoForks.subFilters.push({
  596. id: userRepo,
  597. text: userRepo,
  598. link: userRepo,
  599. icon: "octicon-repo",
  600. classNames: [userRepo],
  601. });
  602. });
  603. REPOS.push(repoForks);
  604. }
  605. });
  606. }
  607. // Fix filter user identification.
  608. function fixUserAlerts(newsContainer) {
  609. USERS = [
  610. {
  611. id: "*-user",
  612. text: "All users",
  613. icon: "octicon-organization",
  614. classNames: ["*-user"],
  615. },
  616. ];
  617.  
  618. var users = new Set();
  619. getAllAlerts(newsContainer).forEach(function (alert) {
  620. var usernameElms = alert.querySelectorAll(
  621. ':scope [data-ga-click*="target:actor"]',
  622. );
  623. Array.prototype.find.call(usernameElms, function (usernameElm) {
  624. var username = usernameElm.textContent;
  625. if (username) {
  626. alert.classList.add(username);
  627. users.add(username);
  628. return true;
  629. }
  630. return false;
  631. });
  632. });
  633.  
  634. [...users]
  635. .sort(function (a, b) {
  636. return a.toLowerCase().localeCompare(b.toLowerCase());
  637. })
  638. .forEach(function (username) {
  639. var user = {
  640. id: username,
  641. text: username,
  642. icon: "octicon-person",
  643. classNames: [username],
  644. };
  645. USERS.push(user);
  646. });
  647. }
  648.  
  649. // Update filter counts.
  650. function updateFilterCounts(filterContainer, newsContainer) {
  651. Array.forEach(
  652. filterContainer.querySelectorAll(":scope li.filter-list-item"),
  653. function (li) {
  654. // Count alerts based on other filters.
  655. var countFiltered = 0;
  656. var classNames = [li.filterClassNames];
  657. var selected = document.querySelectorAll(
  658. ":scope " + filterElement + " li.filter-list-item.private",
  659. );
  660. if (selected.length > 0) {
  661. Array.prototype.forEach.call(selected, function (item) {
  662. if (item.parentNode.parentNode !== filterContainer) {
  663. // Exclude list item from current filter container.
  664. classNames.push(item.filterClassNames);
  665. }
  666. });
  667. }
  668. getAllAlerts(newsContainer).forEach(function (alert) {
  669. var show = classNames.every(function (cl) {
  670. return cl.some(function (c) {
  671. return (
  672. ~c.indexOf("*") || alert.classList.contains(c)
  673. );
  674. });
  675. });
  676. if (show) {
  677. countFiltered++;
  678. }
  679. });
  680.  
  681. // Count alerts based on current filter.
  682. var countAll = 0;
  683. if (~li.filterClassNames[0].indexOf("*")) {
  684. countAll = getAllAlerts(newsContainer).length;
  685. } else {
  686. getAllAlerts(newsContainer).forEach(function (alert) {
  687. if (
  688. li.filterClassNames.some(function (cl) {
  689. return alert.classList.contains(cl);
  690. })
  691. ) {
  692. countAll++;
  693. }
  694. });
  695. }
  696.  
  697. li.querySelector(":scope .count").textContent =
  698. countAll + " (" + countFiltered + ")";
  699. },
  700. );
  701. }
  702.  
  703. // Get all alerts.
  704. function getAllAlerts(newsContainer) {
  705. return Array.prototype.map.call(
  706. newsContainer.querySelectorAll(
  707. ":scope div[data-repository-hovercards-enabled] > div > .body",
  708. ),
  709. function (alert) {
  710. return alert.parentNode;
  711. },
  712. );
  713. }
  714.  
  715. var CURRENT = {};
  716.  
  717. // Set current filter.
  718. function setCurrentFilter(type, filter) {
  719. CURRENT[type] = filter;
  720. }
  721.  
  722. // Get current filter.
  723. function getCurrentFilter(type, filterContainer) {
  724. var filter = CURRENT[type] || "*-" + type;
  725. filterContainer
  726. .querySelector(":scope [" + datasetIdLong + '="' + filter + '"]')
  727. .dispatchEvent(new Event("click"));
  728. }
  729.  
  730. // Add filter tab.
  731. function addFilterTab(type, text, inner, filterer, onCreate, onSelect) {
  732. var filterTabInner = document.createElement("a");
  733. filterTabInner.setAttribute("href", "#");
  734. filterTabInner.classList.add("UnderlineNav-item");
  735. filterTabInner.appendChild(document.createTextNode(text));
  736. filterer.appendChild(filterTabInner);
  737.  
  738. var filterContainer = document.createElement(filterListElement);
  739. inner.appendChild(filterContainer);
  740.  
  741. filterTabInner.addEventListener(
  742. "click",
  743. proxy(filterTabInnerClick, type, inner, filterContainer, onSelect),
  744. );
  745.  
  746. onCreate && onCreate(type, filterContainer);
  747. }
  748.  
  749. // Filter tab click event.
  750. function filterTabInnerClick(e, type, inner, filterContainer, onSelect) {
  751. e.preventDefault();
  752.  
  753. var selected = inner.querySelector(":scope .selected");
  754. selected && selected.classList.remove("selected");
  755. this.classList.add("selected");
  756.  
  757. Array.forEach(
  758. inner.querySelectorAll(filterListElement),
  759. function (menu) {
  760. menu && menu.classList.remove("open");
  761. },
  762. );
  763. filterContainer.classList.add("open");
  764.  
  765. onSelect && onSelect(type, filterContainer);
  766. }
  767.  
  768. // Init.
  769. (function init() {
  770. var newsContainer = document.querySelector(".news");
  771. if (!newsContainer) {
  772. return;
  773. }
  774.  
  775. // GitHub homepage or profile activity tab.
  776. var sidebar =
  777. document.querySelector(".dashboard-sidebar:not(.is-placeholder)") ||
  778. document.querySelector(".profilecols > .column:first-child");
  779.  
  780. var wrapper = document.createElement(filterElement);
  781. wrapper.classList.add("boxed-group", "flush", "user-repos");
  782. sidebar.insertBefore(
  783. wrapper,
  784. sidebar.querySelector(":scope > *:not(details)"),
  785. );
  786.  
  787. var headerAction = document.createElement("div");
  788. headerAction.classList.add("boxed-group-action");
  789. wrapper.appendChild(headerAction);
  790.  
  791. var headerLink = document.createElement("a");
  792. headerLink.setAttribute(
  793. "href",
  794. "https://github.com/jerone/UserScripts",
  795. );
  796. headerLink.classList.add("btn", "btn-sm");
  797. headerAction.appendChild(headerLink);
  798.  
  799. var headerLinkSvg = document.createElementNS(
  800. "http://www.w3.org/2000/svg",
  801. "svg",
  802. );
  803. headerLinkSvg.classList.add("octicon", "octicon-home");
  804. headerLinkSvg.setAttribute("height", "16");
  805. headerLinkSvg.setAttribute("width", "16");
  806. headerLinkSvg.setAttribute(
  807. "title",
  808. "Open Github News Feed Filter homepage",
  809. );
  810. headerLink.appendChild(headerLinkSvg);
  811. var headerLinkPath = document.createElementNS(
  812. "http://www.w3.org/2000/svg",
  813. "path",
  814. );
  815. headerLinkPath.setAttribute("d", ICONS["octicon-home"]);
  816. headerLinkSvg.appendChild(headerLinkPath);
  817.  
  818. var headerText = document.createElement("h3");
  819. headerText.appendChild(document.createTextNode("News feed filter"));
  820. wrapper.appendChild(headerText);
  821.  
  822. var inner = document.createElement("div");
  823. inner.classList.add("boxed-group-inner");
  824. wrapper.appendChild(inner);
  825.  
  826. var bar = document.createElement("div");
  827. bar.classList.add("filter-bar");
  828. inner.appendChild(bar);
  829.  
  830. var filterer = document.createElement("nav");
  831. filterer.classList.add("UnderlineNav-body");
  832. bar.appendChild(filterer);
  833.  
  834. // Create filter tabs.
  835. addFilterTab(
  836. "action",
  837. "Actions",
  838. inner,
  839. filterer,
  840. function onCreateActions(type, filterContainer) {
  841. // Create filter menu.
  842. addFilterMenu(
  843. type,
  844. ACTIONS,
  845. filterContainer,
  846. newsContainer,
  847. filterContainer,
  848. true,
  849. );
  850. },
  851. function onSelectActions(type, filterContainer) {
  852. // Fix alert identification.
  853. fixActionAlerts(newsContainer);
  854. // Update filter counts.
  855. updateFilterCounts(filterContainer, newsContainer);
  856. // Restore current filter.
  857. getCurrentFilter(type, filterContainer);
  858. },
  859. );
  860. addFilterTab(
  861. "repo",
  862. "Repositories",
  863. inner,
  864. filterer,
  865. function onCreateRepos(type, filterContainer) {
  866. // Fix filter identification and create repos list.
  867. fixRepoAlerts(newsContainer);
  868. // Create filter menu.
  869. addFilterMenu(
  870. type,
  871. REPOS,
  872. filterContainer,
  873. newsContainer,
  874. filterContainer,
  875. true,
  876. );
  877. },
  878. function onSelectRepos(type, filterContainer) {
  879. // Fix alert identification and create repos list.
  880. fixRepoAlerts(newsContainer);
  881. // Empty list, so it can be filled again.
  882. while (filterContainer.hasChildNodes()) {
  883. filterContainer.removeChild(filterContainer.lastChild);
  884. }
  885. // Create filter menu.
  886. addFilterMenu(
  887. type,
  888. REPOS,
  889. filterContainer,
  890. newsContainer,
  891. filterContainer,
  892. true,
  893. );
  894. // Update filter counts.
  895. updateFilterCounts(filterContainer, newsContainer);
  896. // Restore current filter.
  897. getCurrentFilter(type, filterContainer);
  898. },
  899. );
  900. addFilterTab(
  901. "user",
  902. "Users",
  903. inner,
  904. filterer,
  905. function onCreateUsers(type, filterContainer) {
  906. // Fix filter identification and create users list.
  907. fixUserAlerts(newsContainer);
  908. // Create filter menu.
  909. addFilterMenu(
  910. type,
  911. USERS,
  912. filterContainer,
  913. newsContainer,
  914. filterContainer,
  915. true,
  916. );
  917. },
  918. function onSelectUsers(type, filterContainer) {
  919. // Fix filter identification and create users list.
  920. fixUserAlerts(newsContainer);
  921. // Empty list, so it can be filled again.
  922. while (filterContainer.hasChildNodes()) {
  923. filterContainer.removeChild(filterContainer.lastChild);
  924. }
  925. // Create filter menu.
  926. addFilterMenu(
  927. type,
  928. USERS,
  929. filterContainer,
  930. newsContainer,
  931. filterContainer,
  932. true,
  933. );
  934. // Update filter counts.
  935. updateFilterCounts(filterContainer, newsContainer);
  936. // Restore current filter.
  937. getCurrentFilter(type, filterContainer);
  938. },
  939. );
  940.  
  941. // Open first filter tab.
  942. filterer.querySelector("a").dispatchEvent(new Event("click"));
  943.  
  944. // Update on clicking "More"-button.
  945. new MutationObserver(function () {
  946. // Re-click the current selected filter on open filter tab.
  947. filterer
  948. .querySelector("a.selected")
  949. .dispatchEvent(new Event("click"));
  950. }).observe(newsContainer, { childList: true, subtree: true });
  951. })();
  952. })();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址