Github News Feed Filter

Add filters for GitHub homepage news feed items

当前为 2024-02-01 提交的版本,查看 最新版本

  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. (function () {
  23.  
  24. var ICONS = {};
  25. ICONS['octicon-book'] = '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';
  26. ICONS['octicon-comment-discussion'] = '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';
  27. ICONS['octicon-git-branch'] = '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';
  28. ICONS['octicon-git-branch-create'] = ICONS['octicon-git-branch'];
  29. ICONS['octicon-git-branch-delete'] = ICONS['octicon-git-branch'];
  30. ICONS['octicon-git-commit'] = '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';
  31. ICONS['octicon-home'] = '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';
  32. ICONS['octicon-issue-opened'] = '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';
  33. ICONS['octicon-person'] = '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';
  34. ICONS['octicon-organization'] = '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';
  35. ICONS['octicon-plus'] = 'M12 9H7v5H5V9H0V7h5V2h2v5h5z';
  36. ICONS['octicon-radio-tower'] = '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';
  37. ICONS['octicon-repo'] = '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';
  38. ICONS['octicon-repo-clone'] = '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';
  39. ICONS['octicon-repo-create'] = ICONS['octicon-plus'];
  40. ICONS['octicon-repo-push'] = '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';
  41. ICONS['octicon-repo-forked'] = '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';
  42. ICONS['octicon-repo-delete'] = ICONS['octicon-repo'];
  43. ICONS['octicon-repo-pull'] = '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';
  44. ICONS['octicon-star'] = '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';
  45. ICONS['octicon-tag'] = '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';
  46. ICONS['octicon-tag-add'] = ICONS['octicon-tag'];
  47. ICONS['octicon-tag-remove'] = ICONS['octicon-tag'];
  48. ICONS['octicon-triangle-left'] = 'M6 2L0 8l6 6z';
  49.  
  50. var ACTIONS = [
  51. { id: '*-action', text: 'All news feed', icon: 'octicon-radio-tower', classNames: ['*-action'] },
  52. {
  53. id: 'issues', text: 'Issues', icon: 'octicon-issue-opened', classNames: ['issues_labeled'], subFilters: [
  54. { id: 'issues labeled', text: 'Labeled', icon: 'octicon-tag', classNames: ['issues_labeled'] }
  55. ]
  56. },
  57. {
  58. id: 'commits', text: 'Commits', icon: 'octicon-git-commit', classNames: ['push', 'commit_comment'], subFilters: [
  59. { id: 'commits pushed', text: 'Pushed', icon: 'octicon-git-commit', classNames: ['push'] },
  60. { id: 'commits comments', text: 'Comments', icon: 'octicon-comment-discussion', classNames: ['commit_comment'] }
  61. ]
  62. },
  63. {
  64. id: 'repo', text: 'Repo', icon: 'octicon-repo', classNames: ['repo', 'create', 'public', 'fork', 'branch_create', 'branch_delete', 'tag_add', 'tag_remove', 'release', 'delete'], subFilters: [
  65. { id: 'repo created', text: 'Created', icon: 'octicon-repo-create', classNames: ['repo', 'create'] },
  66. { id: 'repo public', text: 'Public', icon: 'octicon-repo-push', classNames: ['public'] },
  67. { id: 'repo forked', text: 'Forked', icon: 'octicon-repo-forked', classNames: ['fork'] },
  68. { id: 'repo deleted', text: 'Deleted', icon: 'octicon-repo-delete', classNames: ['delete'] },
  69. { id: 'repo released', text: 'Release', icon: 'octicon-repo-pull', classNames: ['release'] },
  70. {
  71. id: 'repo branched', text: 'Branch', icon: 'octicon-git-branch', classNames: ['branch_create', 'branch_delete'], subFilters: [
  72. { id: 'repo branch created', text: 'Created', icon: 'octicon-git-branch-create', classNames: ['branch_create'] },
  73. { id: 'repo branch deleted', text: 'Deleted', icon: 'octicon-git-branch-delete', classNames: ['branch_delete'] }
  74. ]
  75. },
  76. {
  77. id: 'repo tagged', text: 'Tag', icon: 'octicon-tag', classNames: ['tag_add', 'tag_remove'], subFilters: [
  78. { id: 'repo tag added', text: 'Added', icon: 'octicon-tag-add', classNames: ['tag_add'] },
  79. { id: 'repo tag removed', text: 'Removed', icon: 'octicon-tag-remove', classNames: ['tag_remove'] }
  80. ]
  81. }
  82. ]
  83. },
  84. { id: 'user', text: 'User', icon: 'octicon-person', classNames: ['follow'] },
  85. { id: 'starred', text: 'Starred', icon: 'octicon-star', classNames: ['watch_started'] },
  86. {
  87. id: 'wiki', text: 'Wiki', icon: 'octicon-book', classNames: ['wiki_created', 'wiki_edited'], subFilters: [
  88. { id: 'wiki created', text: 'Created', icon: 'octicon-plus', classNames: ['wiki_created'] },
  89. { id: 'wiki edited', text: 'Edited', icon: 'octicon-book', classNames: ['wiki_edited'] }
  90. ]
  91. }
  92. ];
  93.  
  94. var REPOS = [];
  95.  
  96. var USERS = [];
  97.  
  98. var datasetId = 'githubNewsFeedFilter';
  99. var datasetIdLong = 'data-github-news-feed-filter';
  100. var filterElement = 'github-news-feed-filter';
  101. var filterListElement = 'github-news-feed-filter-list';
  102.  
  103. function proxy(fn) {
  104. return function () {
  105. var that = this;
  106. return function (e) {
  107. var args = that.slice(0); // Clone.
  108. args.unshift(e); // Prepend event.
  109. fn.apply(this, args);
  110. };
  111. }.call([].slice.call(arguments, 1));
  112. }
  113.  
  114. function addStyle(css) {
  115. var node = document.createElement('style');
  116. node.type = 'text/css';
  117. node.appendChild(document.createTextNode(css));
  118. document.head.appendChild(node);
  119. }
  120.  
  121. addStyle(`
  122. github-news-feed-filter { display: block; }
  123.  
  124. github-news-feed-filter .filter-bar { padding: 0; }
  125.  
  126. github-news-feed-filter .count { margin-right: 15px; }
  127.  
  128. github-news-feed-filter .filter-list .mini-repo-list-item { padding-right: 64px; }
  129.  
  130. github-news-feed-filter .filter-list .filter-list .mini-repo-list-item { padding-left: 40px; border-top: 1px dashed #E5E5E5; }
  131. github-news-feed-filter .filter-list .filter-list .filter-list .mini-repo-list-item { padding-left: 50px; }
  132.  
  133. github-news-feed-filter .filter-list { display: none; }
  134. github-news-feed-filter .open > .filter-list { display: block; }
  135. github-news-feed-filter .filter-list.open { display: block; }
  136.  
  137. github-news-feed-filter .private { font-weight: bold; }
  138.  
  139. github-news-feed-filter .stars .octicon { position: absolute; right: -4px; }
  140. github-news-feed-filter .filter-list-item.open > a > .stars > .octicon { transform: rotate(-90deg); }
  141.  
  142. .no-alerts { font-style: italic; }
  143. `);
  144.  
  145. // Add filter menu list.
  146. function addFilterMenu(type, filters, parent, newsContainer, filterContainer, main) {
  147. var ul = document.createElement('ul');
  148. ul.classList.add('filter-list');
  149. if (main) {
  150. ul.classList.add('mini-repo-list');
  151. }
  152. parent.appendChild(ul);
  153.  
  154. filters.forEach(function (subFilter) {
  155. var li = addFilterMenuItem(type, subFilter, ul, newsContainer, filterContainer);
  156.  
  157. if (subFilter.subFilters) {
  158. addFilterMenu(type, subFilter.subFilters, li, newsContainer, filterContainer, false);
  159. }
  160. });
  161. }
  162.  
  163. // Add filter menu item.
  164. function addFilterMenuItem(type, filter, parent, newsContainer, filterContainer) {
  165. // Filter item.
  166. var li = document.createElement('li');
  167. li.classList.add('filter-list-item');
  168. li.filterClassNames = filter.classNames;
  169. parent.appendChild(li);
  170.  
  171. // Filter link.
  172. var a = document.createElement('a');
  173. a.classList.add('mini-repo-list-item', 'css-truncate');
  174. a.setAttribute('href', filter.link || '/');
  175. a.setAttribute('title', filter.classNames.join(' & '));
  176. a.dataset[datasetId] = filter.id;
  177. a.addEventListener('click', proxy(onFilterItemClick, type, newsContainer, filterContainer));
  178. li.appendChild(a);
  179.  
  180. // Filter icon.
  181. var svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
  182. svg.classList.add('repo-icon', 'octicon', filter.icon);
  183. svg.setAttribute('height', '16');
  184. svg.setAttribute('width', '16');
  185. a.appendChild(svg);
  186. var path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
  187. path.setAttribute('d', ICONS[filter.icon]);
  188. svg.appendChild(path);
  189.  
  190. // Filter text.
  191. var text = filter.text.split('/');
  192. var t = document.createElement('span');
  193. t.classList.add('repo-and-owner', 'css-truncate-target');
  194. a.appendChild(t);
  195. var to = document.createElement('span');
  196. to.classList.add('owner');
  197. to.appendChild(document.createTextNode(text[0]));
  198. t.appendChild(to);
  199. if (text.length > 1) {
  200. text.shift();
  201. t.appendChild(document.createTextNode('/'));
  202. var tr = document.createElement('span');
  203. tr.classList.add('repo');
  204. tr.appendChild(document.createTextNode(text.join('/')));
  205. t.appendChild(tr);
  206. }
  207.  
  208. // Filter count & sub list arrow.
  209. var s = document.createElement('span');
  210. s.classList.add('stars');
  211. var c = document.createElement('span');
  212. c.classList.add('count');
  213. c.appendChild(document.createTextNode('0'));
  214. s.appendChild(c);
  215. if (filter.subFilters) {
  216. s.appendChild(document.createTextNode(' '));
  217. var osvg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
  218. osvg.classList.add('octicon', 'octicon-triangle-left');
  219. osvg.setAttribute('height', '16');
  220. osvg.setAttribute('width', '6');
  221. s.appendChild(osvg);
  222. var opath = document.createElementNS('http://www.w3.org/2000/svg', 'path');
  223. opath.setAttribute('d', ICONS['octicon-triangle-left']);
  224. osvg.appendChild(opath);
  225. }
  226. a.appendChild(s);
  227.  
  228. return li;
  229. }
  230.  
  231. // Filter item click event.
  232. function onFilterItemClick(e, type, newsContainer, filterContainer) {
  233. e.preventDefault();
  234.  
  235. // Store current filter.
  236. setCurrentFilter(type, this.dataset[datasetId]);
  237.  
  238. // Open/close sub list.
  239. Array.forEach(filterContainer.querySelectorAll(':scope .open'), function (item) { item.classList.remove('open'); });
  240. showParentMenu(this);
  241. this.parentNode.classList.add('open');
  242.  
  243. // Give it a colored background.
  244. Array.forEach(filterContainer.querySelectorAll(':scope .private'), function (m) { m.classList.remove('private'); });
  245. this.parentNode.classList.add('private');
  246.  
  247. // Toggle alert visibility.
  248. toggleAlertsVisibility(newsContainer);
  249. }
  250.  
  251. // Toggle alert visibility.
  252. function toggleAlertsVisibility(newsContainer) {
  253. // Get selected filters.
  254. var anyVisibleAlert = false;
  255. var classNames = [];
  256. var selected = document.querySelectorAll(":scope " + filterElement + ' .private');
  257. if (selected.length > 0) {
  258. Array.prototype.forEach.call(selected, function (item) {
  259. classNames.push(item.filterClassNames);
  260. });
  261. }
  262.  
  263. // Show/hide alerts.
  264. if (classNames.length === 0 || classNames.every(function (cl) { return cl.every(function (c) { return ~c.indexOf('*'); }); })) {
  265. anyVisibleAlert = true;
  266. getAllAlerts(newsContainer).forEach(function (alert) {
  267. alert.style.display = 'block';
  268. });
  269. } else {
  270. getAllAlerts(newsContainer).forEach(function (alert) {
  271. var show = classNames.every(function (cl) { return cl.some(function (c) { return ~c.indexOf('*') || alert.classList.contains(c); }); });
  272. anyVisibleAlert = show || anyVisibleAlert;
  273. alert.style.display = show ? 'block' : 'none';
  274. // DEBUG: uncomment following line and comment previous line to debug all alerts.
  275. //if(show) alert.style.display = 'none';
  276. });
  277. }
  278.  
  279. // Show/hide message about no alerts.
  280. var none = newsContainer.querySelector(':scope .no-alerts');
  281. if (anyVisibleAlert && none) {
  282. none.parentNode.removeChild(none);
  283. } else if (!anyVisibleAlert && !none) {
  284. none = document.createElement('div');
  285. none.classList.add('no-alerts');
  286. none.appendChild(document.createTextNode('No feed items for this filter. Please select another filter.'));
  287. var firstAlert = getAllAlerts(newsContainer)[0];
  288. firstAlert.parentNode.insertBefore(none, firstAlert);
  289. }
  290. }
  291.  
  292. // Traverse back up the tree to open sub lists.
  293. function showParentMenu(menuItem) {
  294. var parentMenuItem = menuItem.parentNode;
  295. if (parentMenuItem.classList.contains('filter-list-item')) {
  296. parentMenuItem.classList.add('open');
  297. showParentMenu(parentMenuItem.parentNode);
  298. }
  299. }
  300.  
  301. // Fix filter action identification.
  302. function fixActionAlerts(newsContainer) {
  303. getAllAlerts(newsContainer).forEach(function (alert) {
  304. if (~alert.textContent.indexOf('created branch')) {
  305. alert.classList.remove('create');
  306. alert.classList.add('branch_create');
  307. } else if (~alert.textContent.indexOf('deleted branch')) {
  308. alert.classList.remove('delete');
  309. alert.classList.add('branch_delete');
  310. } else if (alert.getElementsByClassName('octicon-tag').length > 0 && !alert.classList.contains('release')) {
  311. alert.classList.remove('create');
  312. alert.classList.add('tag_add');
  313. } else if (alert.getElementsByClassName('octicon-tag-remove').length > 0) {
  314. alert.classList.remove('delete');
  315. alert.classList.add('tag_remove');
  316. } else if (~alert.textContent.indexOf('labeled an issue')) {
  317. alert.classList.add('issues_labeled');
  318. } else if (alert.classList.contains('gollum')) {
  319. alert.classList.remove('gollum');
  320. if (~alert.innerText.indexOf(' created a wiki page in ')) {
  321. alert.classList.add('wiki_created');
  322. } else if (~alert.innerText.indexOf(' edited a wiki page in ')) {
  323. alert.classList.add('wiki_edited');
  324. }
  325. }
  326. });
  327. }
  328. // Fix filter repo identification.
  329. function fixRepoAlerts(newsContainer) {
  330. REPOS = [{ id: '*-repo', text: 'All repositories', icon: 'octicon-repo', classNames: ['*-repo'] }];
  331.  
  332. // Get unique list of repos.
  333. var userRepos = new Set();
  334. getAllAlerts(newsContainer).forEach(function (alert) {
  335. var alertRepo = alert.querySelector(':scope [data-ga-click*="target:repo"]:not([data-ga-click*="target:repositories"])');
  336. if (alertRepo) { // Follow doesn't contain a repo link.
  337. var userRepo = alertRepo.textContent;
  338. userRepos.add(userRepo);
  339. var repo = userRepo.split('/')[1];
  340. alert.classList.add(repo, userRepo);
  341. }
  342. });
  343.  
  344. // Get list of user repos (forks) per repo names.
  345. var repos = {};
  346. userRepos.forEach(function (userRepo) {
  347. var repo = userRepo.split('/')[1];
  348. if (!repos[repo]) {
  349. repos[repo] = [];
  350. }
  351. repos[repo].push(userRepo);
  352. });
  353.  
  354. // Populate global property.
  355. Object.keys(repos).forEach(function (repo) {
  356. if (repos[repo].length === 1) {
  357. var userRepo = repos[repo][0];
  358. REPOS.push({ id: userRepo, text: userRepo, link: userRepo, icon: 'octicon-repo', classNames: [userRepo] });
  359. } else {
  360. var repoForks = { id: repo, text: repo, icon: 'octicon-repo-clone', classNames: [repo], subFilters: [] };
  361. repos[repo].forEach(function (userRepo) {
  362. repoForks.classNames.push(userRepo);
  363. repoForks.subFilters.push({ id: userRepo, text: userRepo, link: userRepo, icon: 'octicon-repo', classNames: [userRepo] });
  364. });
  365. REPOS.push(repoForks);
  366. }
  367. });
  368. }
  369. // Fix filter user identification.
  370. function fixUserAlerts(newsContainer) {
  371. USERS = [{ id: '*-user', text: 'All users', icon: 'octicon-organization', classNames: ['*-user'] }];
  372.  
  373. var users = new Set();
  374. getAllAlerts(newsContainer).forEach(function (alert) {
  375. var usernameElms = alert.querySelectorAll(':scope [data-ga-click*="target:actor"]');
  376. Array.prototype.find.call(usernameElms, function (usernameElm) {
  377. var username = usernameElm.textContent;
  378. if (username) {
  379. alert.classList.add(username);
  380. users.add(username);
  381. return true;
  382. }
  383. return false;
  384. });
  385. });
  386.  
  387. [...users].sort(function (a, b) {
  388. return a.toLowerCase().localeCompare(b.toLowerCase());
  389. }).forEach(function (username) {
  390. var user = { id: username, text: username, icon: 'octicon-person', classNames: [username] };
  391. USERS.push(user);
  392. });
  393. }
  394.  
  395. // Update filter counts.
  396. function updateFilterCounts(filterContainer, newsContainer) {
  397. Array.forEach(filterContainer.querySelectorAll(':scope li.filter-list-item'), function (li) {
  398. // Count alerts based on other filters.
  399. var countFiltered = 0;
  400. var classNames = [li.filterClassNames];
  401. var selected = document.querySelectorAll(":scope " + filterElement + ' li.filter-list-item.private');
  402. if (selected.length > 0) {
  403. Array.prototype.forEach.call(selected, function (item) {
  404. if (item.parentNode.parentNode !== filterContainer) { // Exclude list item from current filter container.
  405. classNames.push(item.filterClassNames);
  406. }
  407. });
  408. }
  409. getAllAlerts(newsContainer).forEach(function (alert) {
  410. var show = classNames.every(function (cl) { return cl.some(function (c) { return ~c.indexOf('*') || alert.classList.contains(c); }); });
  411. if (show) {
  412. countFiltered++;
  413. }
  414. });
  415.  
  416. // Count alerts based on current filter.
  417. var countAll = 0;
  418. if (~li.filterClassNames[0].indexOf('*')) {
  419. countAll = getAllAlerts(newsContainer).length;
  420. } else {
  421. getAllAlerts(newsContainer).forEach(function (alert) {
  422. if (li.filterClassNames.some(function (cl) { return alert.classList.contains(cl); })) {
  423. countAll++;
  424. }
  425. });
  426. }
  427.  
  428. li.querySelector(':scope .count').textContent = countAll + ' (' + countFiltered + ')';
  429. });
  430. }
  431.  
  432. // Get all alerts.
  433. function getAllAlerts(newsContainer) {
  434. return Array.prototype.map.call(newsContainer.querySelectorAll(':scope div[data-repository-hovercards-enabled] > div > .body'), function (alert) {
  435. return alert.parentNode;
  436. });
  437. }
  438.  
  439. var CURRENT = {};
  440.  
  441. // Set current filter.
  442. function setCurrentFilter(type, filter) {
  443. CURRENT[type] = filter;
  444. }
  445.  
  446. // Get current filter.
  447. function getCurrentFilter(type, filterContainer) {
  448. var filter = CURRENT[type] || '*-' + type;
  449. filterContainer.querySelector(':scope [' + datasetIdLong + '="' + filter + '"]').dispatchEvent(new Event('click'));
  450. }
  451.  
  452. // Add filter tab.
  453. function addFilterTab(type, text, inner, filterer, onCreate, onSelect) {
  454. var filterTabInner = document.createElement('a');
  455. filterTabInner.setAttribute('href', '#');
  456. filterTabInner.classList.add('UnderlineNav-item');
  457. filterTabInner.appendChild(document.createTextNode(text));
  458. filterer.appendChild(filterTabInner);
  459.  
  460. var filterContainer = document.createElement(filterListElement);
  461. inner.appendChild(filterContainer);
  462.  
  463. filterTabInner.addEventListener('click', proxy(filterTabInnerClick, type, inner, filterContainer, onSelect));
  464.  
  465. onCreate && onCreate(type, filterContainer);
  466. }
  467.  
  468. // Filter tab click event.
  469. function filterTabInnerClick(e, type, inner, filterContainer, onSelect) {
  470. e.preventDefault();
  471.  
  472. var selected = inner.querySelector(':scope .selected');
  473. selected && selected.classList.remove('selected');
  474. this.classList.add('selected');
  475.  
  476. Array.forEach(inner.querySelectorAll(filterListElement), function (menu) {
  477. menu && menu.classList.remove('open');
  478. });
  479. filterContainer.classList.add('open');
  480.  
  481. onSelect && onSelect(type, filterContainer);
  482. }
  483.  
  484. // Init.
  485. (function init() {
  486. var newsContainer = document.querySelector('.news');
  487. if (!newsContainer) { return; }
  488.  
  489. // GitHub homepage or profile activity tab.
  490. var sidebar = document.querySelector('.dashboard-sidebar:not(.is-placeholder)') || document.querySelector('.profilecols > .column:first-child');
  491.  
  492. var wrapper = document.createElement(filterElement);
  493. wrapper.classList.add('boxed-group', 'flush', 'user-repos');
  494. sidebar.insertBefore(wrapper, sidebar.querySelector(':scope > *:not(details)'));
  495.  
  496. var headerAction = document.createElement('div');
  497. headerAction.classList.add('boxed-group-action');
  498. wrapper.appendChild(headerAction);
  499.  
  500. var headerLink = document.createElement('a');
  501. headerLink.setAttribute('href', 'https://github.com/jerone/UserScripts');
  502. headerLink.classList.add('btn', 'btn-sm');
  503. headerAction.appendChild(headerLink);
  504.  
  505. var headerLinkSvg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
  506. headerLinkSvg.classList.add('octicon', 'octicon-home');
  507. headerLinkSvg.setAttribute('height', '16');
  508. headerLinkSvg.setAttribute('width', '16');
  509. headerLinkSvg.setAttribute('title', 'Open Github News Feed Filter homepage');
  510. headerLink.appendChild(headerLinkSvg);
  511. var headerLinkPath = document.createElementNS('http://www.w3.org/2000/svg', 'path');
  512. headerLinkPath.setAttribute('d', ICONS['octicon-home']);
  513. headerLinkSvg.appendChild(headerLinkPath);
  514.  
  515. var headerText = document.createElement('h3');
  516. headerText.appendChild(document.createTextNode('News feed filter'));
  517. wrapper.appendChild(headerText);
  518.  
  519. var inner = document.createElement('div');
  520. inner.classList.add('boxed-group-inner');
  521. wrapper.appendChild(inner);
  522.  
  523. var bar = document.createElement('div');
  524. bar.classList.add('filter-bar');
  525. inner.appendChild(bar);
  526.  
  527. var filterer = document.createElement('nav');
  528. filterer.classList.add('UnderlineNav-body');
  529. bar.appendChild(filterer);
  530.  
  531. // Create filter tabs.
  532. addFilterTab('action', 'Actions', inner, filterer, function onCreateActions(type, filterContainer) {
  533. // Create filter menu.
  534. addFilterMenu(type, ACTIONS, filterContainer, newsContainer, filterContainer, true);
  535. }, function onSelectActions(type, filterContainer) {
  536. // Fix alert identification.
  537. fixActionAlerts(newsContainer);
  538. // Update filter counts.
  539. updateFilterCounts(filterContainer, newsContainer);
  540. // Restore current filter.
  541. getCurrentFilter(type, filterContainer);
  542. });
  543. addFilterTab('repo', 'Repositories', inner, filterer, function onCreateRepos(type, filterContainer) {
  544. // Fix filter identification and create repos list.
  545. fixRepoAlerts(newsContainer);
  546. // Create filter menu.
  547. addFilterMenu(type, REPOS, filterContainer, newsContainer, filterContainer, true);
  548. }, function onSelectRepos(type, filterContainer) {
  549. // Fix alert identification and create repos list.
  550. fixRepoAlerts(newsContainer);
  551. // Empty list, so it can be filled again.
  552. while (filterContainer.hasChildNodes()) {
  553. filterContainer.removeChild(filterContainer.lastChild);
  554. }
  555. // Create filter menu.
  556. addFilterMenu(type, REPOS, filterContainer, newsContainer, filterContainer, true);
  557. // Update filter counts.
  558. updateFilterCounts(filterContainer, newsContainer);
  559. // Restore current filter.
  560. getCurrentFilter(type, filterContainer);
  561. });
  562. addFilterTab('user', 'Users', inner, filterer, function onCreateUsers(type, filterContainer) {
  563. // Fix filter identification and create users list.
  564. fixUserAlerts(newsContainer);
  565. // Create filter menu.
  566. addFilterMenu(type, USERS, filterContainer, newsContainer, filterContainer, true);
  567. }, function onSelectUsers(type, filterContainer) {
  568. // Fix filter identification and create users list.
  569. fixUserAlerts(newsContainer);
  570. // Empty list, so it can be filled again.
  571. while (filterContainer.hasChildNodes()) {
  572. filterContainer.removeChild(filterContainer.lastChild);
  573. }
  574. // Create filter menu.
  575. addFilterMenu(type, USERS, filterContainer, newsContainer, filterContainer, true);
  576. // Update filter counts.
  577. updateFilterCounts(filterContainer, newsContainer);
  578. // Restore current filter.
  579. getCurrentFilter(type, filterContainer);
  580. });
  581.  
  582. // Open first filter tab.
  583. filterer.querySelector('a').dispatchEvent(new Event('click'));
  584.  
  585. // Update on clicking "More"-button.
  586. new MutationObserver(function () {
  587. // Re-click the current selected filter on open filter tab.
  588. filterer.querySelector('a.selected').dispatchEvent(new Event('click'));
  589. }).observe(newsContainer, { childList: true, subtree: true });
  590. })();
  591.  
  592. })();

QingJ © 2025

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