Github News Feed Filter

Add filters for GitHub homepage news feed items

目前為 2017-10-13 提交的版本,檢視 最新版本

  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. // @contributionURL https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=VCYMHWQ7ZMBKW
  11. // @icon https://github.com/fluidicon.png
  12. // @include https://github.com/
  13. // @include https://github.com/?*
  14. // @include https://github.com/orgs/*/dashboard
  15. // @include https://github.com/orgs/*/dashboard?*
  16. // @version 7.2.0
  17. // @grant none
  18. // ==/UserScript==
  19.  
  20. (function() {
  21.  
  22. var ICONS = {};
  23. ICONS['octicon-book'] = 'M2 5h4v1H2v-1z m0 3h4v-1H2v1z m0 2h4v-1H2v1z m11-5H9v1h4v-1z m0 2H9v1h4v-1z m0 2H9v1h4v-1z m2-6v9c0 0.55-0.45 1-1 1H8.5l-1 1-1-1H1c-0.55 0-1-0.45-1-1V3c0-0.55 0.45-1 1-1h5.5l1 1 1-1h5.5c0.55 0 1 0.45 1 1z m-8 0.5l-0.5-0.5H1v9h6V3.5z m7-0.5H8.5l-0.5 0.5v8.5h6V3z';
  24. ICONS['octicon-comment-discussion'] = 'M15 2H6c-0.55 0-1 0.45-1 1v2H1c-0.55 0-1 0.45-1 1v6c0 0.55 0.45 1 1 1h1v3l3-3h4c0.55 0 1-0.45 1-1V10h1l3 3V10h1c0.55 0 1-0.45 1-1V3c0-0.55-0.45-1-1-1zM9 12H4.5l-1.5 1.5v-1.5H1V6h4v3c0 0.55 0.45 1 1 1h3v2z m6-3H13v1.5l-1.5-1.5H6V3h9v6z';
  25. ICONS['octicon-gist'] = 'M7.5 5l2.5 2.5-2.5 2.5-0.75-0.75 1.75-1.75-1.75-1.75 0.75-0.75z m-3 0L2 7.5l2.5 2.5 0.75-0.75-1.75-1.75 1.75-1.75-0.75-0.75zM0 13V2c0-0.55 0.45-1 1-1h10c0.55 0 1 0.45 1 1v11c0 0.55-0.45 1-1 1H1c-0.55 0-1-0.45-1-1z m1 0h10V2H1v11z';
  26. ICONS['octicon-gist-new'] = ICONS['octicon-plus'] = 'M12 9H7v5H5V9H0V7h5V2h2v5h5v2z';
  27. ICONS['octicon-git-branch'] = 'M10 5c0-1.11-0.89-2-2-2s-2 0.89-2 2c0 0.73 0.41 1.38 1 1.72v0.3c-0.02 0.52-0.23 0.98-0.63 1.38s-0.86 0.61-1.38 0.63c-0.83 0.02-1.48 0.16-2 0.45V4.72c0.59-0.34 1-0.98 1-1.72 0-1.11-0.89-2-2-2S0 1.89 0 3c0 0.73 0.41 1.38 1 1.72v6.56C0.41 11.63 0 12.27 0 13c0 1.11 0.89 2 2 2s2-0.89 2-2c0-0.53-0.2-1-0.53-1.36 0.09-0.06 0.48-0.41 0.59-0.47 0.25-0.11 0.56-0.17 0.94-0.17 1.05-0.05 1.95-0.45 2.75-1.25s1.2-1.98 1.25-3.02h-0.02c0.61-0.36 1.02-1 1.02-1.73zM2 1.8c0.66 0 1.2 0.55 1.2 1.2s-0.55 1.2-1.2 1.2-1.2-0.55-1.2-1.2 0.55-1.2 1.2-1.2z m0 12.41c-0.66 0-1.2-0.55-1.2-1.2s0.55-1.2 1.2-1.2 1.2 0.55 1.2 1.2-0.55 1.2-1.2 1.2z m6-8c-0.66 0-1.2-0.55-1.2-1.2s0.55-1.2 1.2-1.2 1.2 0.55 1.2 1.2-0.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-0.45-1.72-2-3-3.86-3s-3.41 1.28-3.86 3H0v2h3.14c0.45 1.72 2 3 3.86 3s3.41-1.28 3.86-3h3.14V7H10.86zM7 10.2c-1.22 0-2.2-0.98-2.2-2.2s0.98-2.2 2.2-2.2 2.2 0.98 2.2 2.2-0.98 2.2-2.2 2.2z';
  31. ICONS['octicon-git-merge'] = 'M10 7c-0.73 0-1.38 0.41-1.73 1.02v-0.02c-1.05-0.02-2.27-0.36-3.13-1.02-0.75-0.58-1.5-1.61-1.89-2.44 0.45-0.36 0.75-0.92 0.75-1.55 0-1.11-0.89-2-2-2S0 1.89 0 3c0 0.73 0.41 1.38 1 1.72v6.56C0.41 11.63 0 12.27 0 13c0 1.11 0.89 2 2 2s2-0.89 2-2c0-0.73-0.41-1.38-1-1.72V7.67c0.67 0.7 1.44 1.27 2.3 1.69s2.03 0.63 2.97 0.64v-0.02c0.36 0.61 1 1.02 1.73 1.02 1.11 0 2-0.89 2-2s-0.89-2-2-2zM3.2 13c0 0.66-0.55 1.2-1.2 1.2s-1.2-0.55-1.2-1.2 0.55-1.2 1.2-1.2 1.2 0.55 1.2 1.2z m-1.2-8.8c-0.66 0-1.2-0.55-1.2-1.2s0.55-1.2 1.2-1.2 1.2 0.55 1.2 1.2-0.55 1.2-1.2 1.2z m8 6c-0.66 0-1.2-0.55-1.2-1.2s0.55-1.2 1.2-1.2 1.2 0.55 1.2 1.2-0.55 1.2-1.2 1.2z';
  32. ICONS['octicon-git-pull-request'] = 'M11 11.28c0-1.73 0-6.28 0-6.28-0.03-0.78-0.34-1.47-0.94-2.06s-1.28-0.91-2.06-0.94c0 0-1.02 0-1 0V0L4 3l3 3V4h1c0.27 0.02 0.48 0.11 0.69 0.31s0.3 0.42 0.31 0.69v6.28c-0.59 0.34-1 0.98-1 1.72 0 1.11 0.89 2 2 2s2-0.89 2-2c0-0.73-0.41-1.38-1-1.72z m-1 2.92c-0.66 0-1.2-0.55-1.2-1.2s0.55-1.2 1.2-1.2 1.2 0.55 1.2 1.2-0.55 1.2-1.2 1.2zM4 3c0-1.11-0.89-2-2-2S0 1.89 0 3c0 0.73 0.41 1.38 1 1.72 0 1.55 0 5.56 0 6.56-0.59 0.34-1 0.98-1 1.72 0 1.11 0.89 2 2 2s2-0.89 2-2c0-0.73-0.41-1.38-1-1.72V4.72c0.59-0.34 1-0.98 1-1.72z m-0.8 10c0 0.66-0.55 1.2-1.2 1.2s-1.2-0.55-1.2-1.2 0.55-1.2 1.2-1.2 1.2 0.55 1.2 1.2z m-1.2-8.8c-0.66 0-1.2-0.55-1.2-1.2s0.55-1.2 1.2-1.2 1.2 0.55 1.2 1.2-0.55 1.2-1.2 1.2z';
  33. ICONS['octicon-git-pull-request-abandoned'] = ICONS['octicon-git-pull-request'];
  34. ICONS['octicon-home'] = 'M16 9L13 6V2H11v2L8 1 0 9h2l1 5c0 0.55 0.45 1 1 1h8c0.55 0 1-0.45 1-1l1-5h2zM12 14H9V10H7v4H4l-1.19-6.31 5.19-5.19 5.19 5.19-1.19 6.31z';
  35. ICONS['octicon-issue-closed'] = 'M7 10h2v2H7V10z m2-6H7v5h2V4z m1.5 1.5l-1 1 2.5 2.5 4-4.5-1-1-3 3.5-1.5-1.5zM8 13.7c-3.14 0-5.7-2.56-5.7-5.7s2.56-5.7 5.7-5.7c1.83 0 3.45 0.88 4.5 2.2l0.92-0.92C12.14 2 10.19 1 8 1 4.14 1 1 4.14 1 8s3.14 7 7 7 7-3.14 7-7l-1.52 1.52c-0.66 2.41-2.86 4.19-5.48 4.19z';
  36. 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';
  37. ICONS['octicon-issue-reopened'] = 'M8 9H6V4h2v5zM6 12h2V10H6v2z m6.33-2H10l1.5 1.5c-1.05 1.33-2.67 2.2-4.5 2.2-3.14 0-5.7-2.56-5.7-5.7 0-0.34 0.03-0.67 0.09-1H0.08c-0.05 0.33-0.08 0.66-0.08 1 0 3.86 3.14 7 7 7 2.19 0 4.13-1.02 5.41-2.59l1.59 1.59V10H12.33zM1.67 6h2.33l-1.5-1.5c1.05-1.33 2.67-2.2 4.5-2.2 3.14 0 5.7 2.56 5.7 5.7 0 0.34-0.03 0.67-0.09 1h1.31c0.05-0.33 0.08-0.66 0.08-1 0-3.86-3.14-7-7-7-2.19 0-4.13 1.02-5.41 2.59L0 2v4h1.67z';
  38. ICONS['octicon-person'] = 'M7 6H1c-0.55 0-1 0.45-1 1v5h2v3c0 0.55 0.45 1 1 1h2c0.55 0 1-0.45 1-1V12h2V7c0-0.55-0.45-1-1-1z m0 5h-1V9h-1v6H3V9h-1v2H1V7h6v4z m0-8C7 1.34 5.66 0 4 0S1 1.34 1 3s1.34 3 3 3 3-1.34 3-3zM4 5c-1.11 0-2-0.89-2-2S2.89 1 4 1s2 0.89 2 2-0.89 2-2 2z';
  39. ICONS['octicon-person-add'] = ICONS['octicon-person'];
  40. ICONS['octicon-plus'] = 'M12 9H7v5H5V9H0V7h5V2h2v5h5v2z';
  41. ICONS['octicon-radio-tower'] = 'M4.79 6.11c0.25-0.25 0.25-0.67 0-0.92-0.32-0.33-0.48-0.76-0.48-1.19 0-0.43 0.16-0.86 0.48-1.19 0.25-0.26 0.25-0.67 0-0.92-0.12-0.13-0.29-0.19-0.45-0.19-0.16 0-0.33 0.06-0.45 0.19-0.57 0.58-0.85 1.35-0.85 2.11 0 0.76 0.29 1.53 0.85 2.11C4.14 6.36 4.55 6.36 4.79 6.11zM2.33 0.52c-0.13-0.13-0.29-0.19-0.46-0.19-0.16 0-0.33 0.06-0.46 0.19C0.48 1.48 0.01 2.74 0.01 3.99 0.01 5.25 0.48 6.51 1.41 7.47c0.25 0.26 0.66 0.26 0.91 0 0.25-0.26 0.25-0.68 0-0.94-0.68-0.7-1.02-1.62-1.02-2.54s0.34-1.84 1.02-2.54C2.58 1.2 2.58 0.78 2.33 0.52zM8.02 5.62c0.9 0 1.62-0.73 1.62-1.62 0-0.9-0.73-1.62-1.62-1.62-0.9 0-1.62 0.73-1.62 1.62C6.39 4.89 7.12 5.62 8.02 5.62zM14.59 0.53c-0.25-0.26-0.66-0.26-0.91 0-0.25 0.26-0.25 0.68 0 0.94 0.68 0.7 1.02 1.62 1.02 2.54 0 0.92-0.34 1.83-1.02 2.54-0.25 0.26-0.25 0.68 0 0.94 0.13 0.13 0.29 0.19 0.46 0.19 0.16 0 0.33-0.06 0.46-0.19 0.93-0.96 1.4-2.22 1.4-3.48C15.99 2.75 15.52 1.49 14.59 0.53zM8.02 6.92L8.02 6.92c-0.41 0-0.83-0.1-1.2-0.3L3.67 14.99h1.49l0.86-1h4l0.84 1h1.49L9.21 6.62C8.83 6.82 8.43 6.92 8.02 6.92zM8.01 7.4L9.02 11H7.02L8.01 7.4zM6.02 12.99l1-1h2l1 1H6.02zM11.21 1.89c-0.25 0.25-0.25 0.67 0 0.92 0.32 0.33 0.48 0.76 0.48 1.19 0 0.43-0.16 0.86-0.48 1.19-0.25 0.26-0.25 0.67 0 0.92 0.12 0.13 0.29 0.19 0.45 0.19 0.16 0 0.32-0.06 0.45-0.19 0.57-0.58 0.85-1.35 0.85-2.11 0-0.76-0.28-1.53-0.85-2.11C11.86 1.64 11.45 1.64 11.21 1.89z';
  42. ICONS['octicon-repo'] = 'M4 9h-1v-1h1v1z m0-3h-1v1h1v-1z m0-2h-1v1h1v-1z m0-2h-1v1h1v-1z m8-1v12c0 0.55-0.45 1-1 1H6v2l-1.5-1.5-1.5 1.5V14H1c-0.55 0-1-0.45-1-1V1C0 0.45 0.45 0 1 0h10c0.55 0 1 0.45 1 1z m-1 10H1v2h2v-1h3v1h5V11z m0-10H2v9h9V1z';
  43. ICONS['octicon-repo-clone'] = 'M15 0H9v7c0 0.55 0.45 1 1 1h1v1h1v-1h3c0.55 0 1-0.45 1-1V1c0-0.55-0.45-1-1-1zM11 7h-1v-1h1v1z m4 0H12v-1h3v1z m0-2H11V1h4v4z m-11 0h-1v-1h1v1z m0-2h-1v-1h1v1zM2 1h6V0H1C0.45 0 0 0.45 0 1v12c0 0.55 0.45 1 1 1h2v2l1.5-1.5 1.5 1.5V14h5c0.55 0 1-0.45 1-1V10H2V1z m9 10v2H6v-1H3v1H1V11h10zM3 8h1v1h-1v-1z m1-1h-1v-1h1v1z';
  44. ICONS['octicon-repo-create'] = ICONS['octicon-plus'];
  45. ICONS['octicon-repo-push'] = 'M4 3h-1v-1h1v1z m-1 2h1v-1h-1v1z m4 0L4 9h2v7h2V9h2L7 5zM11 0H1C0.45 0 0 0.45 0 1v12c0 0.55 0.45 1 1 1h4v-1H1V11h4v-1H2V1h9.02l-0.02 9H9v1h2v2H9v1h2c0.55 0 1-0.45 1-1V1c0-0.55-0.45-1-1-1z';
  46. ICONS['octicon-repo-forked'] = 'M8 1c-1.11 0-2 0.89-2 2 0 0.73 0.41 1.38 1 1.72v1.28L5 8 3 6v-1.28c0.59-0.34 1-0.98 1-1.72 0-1.11-0.89-2-2-2S0 1.89 0 3c0 0.73 0.41 1.38 1 1.72v1.78l3 3v1.78c-0.59 0.34-1 0.98-1 1.72 0 1.11 0.89 2 2 2s2-0.89 2-2c0-0.73-0.41-1.38-1-1.72V9.5l3-3V4.72c0.59-0.34 1-0.98 1-1.72 0-1.11-0.89-2-2-2zM2 4.2c-0.66 0-1.2-0.55-1.2-1.2s0.55-1.2 1.2-1.2 1.2 0.55 1.2 1.2-0.55 1.2-1.2 1.2z m3 10c-0.66 0-1.2-0.55-1.2-1.2s0.55-1.2 1.2-1.2 1.2 0.55 1.2 1.2-0.55 1.2-1.2 1.2z m3-10c-0.66 0-1.2-0.55-1.2-1.2s0.55-1.2 1.2-1.2 1.2 0.55 1.2 1.2-0.55 1.2-1.2 1.2z';
  47. ICONS['octicon-repo-delete'] = ICONS['octicon-repo'];
  48. ICONS['octicon-repo-pull'] = 'M13 8V6H7V4h6V2l3 3-3 3zM4 2h-1v1h1v-1z m7 5h1v6c0 0.55-0.45 1-1 1H6v2l-1.5-1.5-1.5 1.5V14H1c-0.55 0-1-0.45-1-1V1C0 0.45 0.45 0 1 0h10c0.55 0 1 0.45 1 1v2h-1V1H2v9h9V7z m0 4H1v2h2v-1h3v1h5V11zM4 6h-1v1h1v-1z m0-2h-1v1h1v-1z m-1 5h1v-1h-1v1z';
  49. ICONS['octicon-star'] = 'M14 6l-4.9-0.64L7 1 4.9 5.36 0 6l3.6 3.26L2.67 14l4.33-2.33 4.33 2.33L10.4 9.26 14 6z';
  50. ICONS['octicon-tag'] = 'M6.73 2.73c-0.47-0.47-1.11-0.73-1.77-0.73H2.5C1.13 2 0 3.13 0 4.5v2.47c0 0.66 0.27 1.3 0.73 1.77l6.06 6.06c0.39 0.39 1.02 0.39 1.41 0l4.59-4.59c0.39-0.39 0.39-1.02 0-1.41L6.73 2.73zM1.38 8.09c-0.31-0.3-0.47-0.7-0.47-1.13V4.5c0-0.88 0.72-1.59 1.59-1.59h2.47c0.42 0 0.83 0.16 1.13 0.47l6.14 6.13-4.73 4.73L1.38 8.09z m0.63-4.09h2v2H2V4z';
  51. ICONS['octicon-tag-add'] = ICONS['octicon-tag'];
  52. ICONS['octicon-tag-remove'] = ICONS['octicon-tag'];
  53. ICONS['octicon-triangle-left'] = 'M6 2L0 8l6 6V2z';
  54.  
  55. var ACTIONS = [
  56. { id: '*-action', text: 'All news feed', icon: 'octicon-radio-tower', classNames: ['*-action'] },
  57. {
  58. id: 'issues', text: 'Issues', icon: 'octicon-issue-opened', classNames: ['issues_opened', 'issues_closed', 'issues_reopened', 'issues_comment'], subFilters: [
  59. { id: 'issues opened', text: 'Opened', icon: 'octicon-issue-opened', classNames: ['issues_opened'] },
  60. { id: 'issues closed', text: 'Closed', icon: 'octicon-issue-closed', classNames: ['issues_closed'] },
  61. { id: 'issues reopened', text: 'Reopened', icon: 'octicon-issue-reopened', classNames: ['issues_reopened'] },
  62. { id: 'issues comments', text: 'Comments', icon: 'octicon-comment-discussion', classNames: ['issues_comment'] }
  63. ]
  64. },
  65. {
  66. id: 'commits', text: 'Commits', icon: 'octicon-git-commit', classNames: ['push', 'commit_comment'], subFilters: [
  67. { id: 'commits pushed', text: 'Pushed', icon: 'octicon-git-commit', classNames: ['push'] },
  68. { id: 'commits comments', text: 'Comments', icon: 'octicon-comment-discussion', classNames: ['commit_comment'] }
  69. ]
  70. },
  71. {
  72. id: 'pr', text: 'Pull Requests', icon: 'octicon-git-pull-request', classNames: ['pull_request_opened', 'pull_request_closed', 'pull_request_merged', 'pull_request_comment'], subFilters: [
  73. { id: 'pr opened', text: 'Opened', icon: 'octicon-git-pull-request', classNames: ['pull_request_opened'] },
  74. { id: 'pr closed', text: 'Closed', icon: 'octicon-git-pull-request-abandoned', classNames: ['pull_request_closed'] },
  75. { id: 'pr merged', text: 'Merged', icon: 'octicon-git-merge', classNames: ['pull_request_merged'] },
  76. { id: 'pr comments', text: 'Comments', icon: 'octicon-comment-discussion', classNames: ['pull_request_comment'] }
  77. ]
  78. },
  79. {
  80. id: 'repo', text: 'Repo', icon: 'octicon-repo', classNames: ['create', 'public', 'fork', 'branch_create', 'branch_delete', 'tag_add', 'tag_remove', 'release', 'delete'], subFilters: [
  81. { id: 'repo created', text: 'Created', icon: 'octicon-repo-create', classNames: ['create'] },
  82. { id: 'repo public', text: 'Public', icon: 'octicon-repo-push', classNames: ['public'] },
  83. { id: 'repo forked', text: 'Forked', icon: 'octicon-repo-forked', classNames: ['fork'] },
  84. { id: 'repo deleted', text: 'Deleted', icon: 'octicon-repo-delete', classNames: ['delete'] },
  85. { id: 'repo released', text: 'Release', icon: 'octicon-repo-pull', classNames: ['release'] },
  86. {
  87. id: 'repo branched', text: 'Branch', icon: 'octicon-git-branch', classNames: ['branch_create', 'branch_delete'], subFilters: [
  88. { id: 'repo branch created', text: 'Created', icon: 'octicon-git-branch-create', classNames: ['branch_create'] },
  89. { id: 'repo branch deleted', text: 'Deleted', icon: 'octicon-git-branch-delete', classNames: ['branch_delete'] }
  90. ]
  91. },
  92. {
  93. id: 'repo tagged', text: 'Tag', icon: 'octicon-tag', classNames: ['tag_add', 'tag_remove'], subFilters: [
  94. { id: 'repo tag added', text: 'Added', icon: 'octicon-tag-add', classNames: ['tag_add'] },
  95. { id: 'repo tag removed', text: 'Removed', icon: 'octicon-tag-remove', classNames: ['tag_remove'] }
  96. ]
  97. }
  98. ]
  99. },
  100. {
  101. id: 'user', text: 'User', icon: 'octicon-person', classNames: ['watch_started', 'member_add', 'team_add'], subFilters: [
  102. { id: 'user starred', text: 'Starred', icon: 'octicon-star', classNames: ['watch_started'] },
  103. { id: 'user added', text: 'Member added', icon: 'octicon-person-add', classNames: ['member_add', 'team_add'] }
  104. ]
  105. },
  106. {
  107. id: 'wiki', text: 'Wiki', icon: 'octicon-book', classNames: ['wiki_created', 'wiki_edited'], subFilters: [
  108. { id: 'wiki created', text: 'Created', icon: 'octicon-plus', classNames: ['wiki_created'] },
  109. { id: 'wiki edited', text: 'Edited', icon: 'octicon-book', classNames: ['wiki_edited'] }
  110. ]
  111. },
  112. {
  113. id: 'gist', text: 'Gist', icon: 'octicon-gist', classNames: ['gist_created', 'gist_updated'], subFilters: [
  114. { id: 'gist created', text: 'Created', icon: 'octicon-gist-new', classNames: ['gist_created'] },
  115. { id: 'gist updated', text: 'Updated', icon: 'octicon-gist', classNames: ['gist_updated'] }
  116. ]
  117. }
  118. ];
  119.  
  120. var REPOS = [];
  121.  
  122. var USERS = [];
  123.  
  124. var datasetId = 'githubNewsFeedFilter';
  125. var datasetIdLong = 'data-github-news-feed-filter';
  126. var filterElement = 'github-news-feed-filter';
  127. var filterListElement = 'github-news-feed-filter-list';
  128.  
  129. function proxy(fn) {
  130. return function() {
  131. var that = this;
  132. return function(e) {
  133. var args = that.slice(0); // Clone.
  134. args.unshift(e); // Prepend event.
  135. fn.apply(this, args);
  136. };
  137. }.call([].slice.call(arguments, 1));
  138. }
  139.  
  140. function addStyle(css) {
  141. var node = document.createElement('style');
  142. node.type = 'text/css';
  143. node.appendChild(document.createTextNode(css));
  144. document.head.appendChild(node);
  145. }
  146.  
  147. addStyle(`
  148. github-news-feed-filter { display: block; }
  149. github-news-feed-filter .count { margin-right: 15px; }
  150.  
  151. github-news-feed-filter .filter-list .mini-repo-list-item { padding-right: 64px; }
  152.  
  153. github-news-feed-filter .filter-list .filter-list .mini-repo-list-item { padding-left: 40px; border-top: 1px dashed #E5E5E5; }
  154. github-news-feed-filter .filter-list .filter-list .filter-list .mini-repo-list-item { padding-left: 50px; }
  155.  
  156. github-news-feed-filter .filter-list { display: none; }
  157. github-news-feed-filter .open > .filter-list { display: block; }
  158. github-news-feed-filter .filter-list.open { display: block; }
  159.  
  160. github-news-feed-filter .private { font-weight: bold; }
  161.  
  162. github-news-feed-filter .stars .octicon { position: absolute; right: -4px; }
  163. github-news-feed-filter .filter-list-item.open > a > .stars > .octicon { transform: rotate(-90deg); }
  164.  
  165. .no-alerts { font-style: italic; }
  166. `);
  167.  
  168. // Add filter menu list.
  169. function addFilterMenu(type, filters, parent, newsContainer, filterContainer, main) {
  170. var ul = document.createElement('ul');
  171. ul.classList.add('filter-list');
  172. if (main) {
  173. ul.classList.add('mini-repo-list');
  174. }
  175. parent.appendChild(ul);
  176.  
  177. filters.forEach(function(subFilter) {
  178. var li = addFilterMenuItem(type, subFilter, ul, newsContainer, filterContainer);
  179.  
  180. if (subFilter.subFilters) {
  181. addFilterMenu(type, subFilter.subFilters, li, newsContainer, filterContainer, false);
  182. }
  183. });
  184. }
  185.  
  186. // Add filter menu item.
  187. function addFilterMenuItem(type, filter, parent, newsContainer, filterContainer) {
  188. // Filter item.
  189. var li = document.createElement('li');
  190. li.classList.add('filter-list-item');
  191. li.filterClassNames = filter.classNames;
  192. parent.appendChild(li);
  193.  
  194. // Filter link.
  195. var a = document.createElement('a');
  196. a.classList.add('mini-repo-list-item', 'css-truncate');
  197. a.setAttribute('href', filter.link || '/');
  198. a.setAttribute('title', filter.classNames.join(' & '));
  199. a.dataset[datasetId] = filter.id;
  200. a.addEventListener('click', proxy(onFilterItemClick, type, newsContainer, filterContainer));
  201. li.appendChild(a);
  202.  
  203. // Filter icon.
  204. var svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
  205. svg.classList.add('repo-icon', 'octicon', filter.icon);
  206. svg.setAttribute('height', '16');
  207. svg.setAttribute('width', '16');
  208. a.appendChild(svg);
  209. var path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
  210. path.setAttribute('d', ICONS[filter.icon]);
  211. svg.appendChild(path);
  212.  
  213. // Filter text.
  214. var text = filter.text.split('/');
  215. var t = document.createElement('span');
  216. t.classList.add('repo-and-owner', 'css-truncate-target');
  217. a.appendChild(t);
  218. var to = document.createElement('span');
  219. to.classList.add('owner');
  220. to.appendChild(document.createTextNode(text[0]));
  221. t.appendChild(to);
  222. if (text.length > 1) {
  223. text.shift();
  224. t.appendChild(document.createTextNode('/'));
  225. var tr = document.createElement('span');
  226. tr.classList.add('repo');
  227. tr.appendChild(document.createTextNode(text.join('/')));
  228. t.appendChild(tr);
  229. }
  230.  
  231. // Filter count & sub list arrow.
  232. var s = document.createElement('span');
  233. s.classList.add('stars');
  234. var c = document.createElement('span');
  235. c.classList.add('count');
  236. c.appendChild(document.createTextNode('0'));
  237. s.appendChild(c);
  238. if (filter.subFilters) {
  239. s.appendChild(document.createTextNode(' '));
  240. var osvg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
  241. osvg.classList.add('octicon', 'octicon-triangle-left');
  242. osvg.setAttribute('height', '16');
  243. osvg.setAttribute('width', '6');
  244. s.appendChild(osvg);
  245. var opath = document.createElementNS('http://www.w3.org/2000/svg', 'path');
  246. opath.setAttribute('d', ICONS['octicon-triangle-left']);
  247. osvg.appendChild(opath);
  248. }
  249. a.appendChild(s);
  250.  
  251. return li;
  252. }
  253.  
  254. // Filter item click event.
  255. function onFilterItemClick(e, type, newsContainer, filterContainer) {
  256. e.preventDefault();
  257.  
  258. // Store current filter.
  259. setCurrentFilter(type, this.dataset[datasetId]);
  260.  
  261. // Open/close sub list.
  262. Array.forEach(filterContainer.querySelectorAll('.open'), function(item) { item.classList.remove('open'); });
  263. showParentMenu(this);
  264. this.parentNode.classList.add('open');
  265.  
  266. // Give it a colored background.
  267. Array.forEach(filterContainer.querySelectorAll('.private'), function(m) { m.classList.remove('private'); });
  268. this.parentNode.classList.add('private');
  269.  
  270. // Toggle alert visibility.
  271. toggleAlertsVisibility(newsContainer);
  272. }
  273.  
  274. // Toggle alert visibility.
  275. function toggleAlertsVisibility(newsContainer) {
  276. // Get selected filters.
  277. var anyVisibleAlert = false;
  278. var classNames = [];
  279. var selected = document.querySelectorAll(filterElement + ' .private');
  280. if (selected.length > 0) {
  281. Array.prototype.forEach.call(selected, function(item) {
  282. classNames.push(item.filterClassNames);
  283. });
  284. }
  285.  
  286. // Show/hide alerts.
  287. if (classNames.length === 0 || classNames.every(function(cl) { return cl.every(function(c) { return !!~c.indexOf('*'); }); })) {
  288. anyVisibleAlert = true;
  289. Array.prototype.forEach.call(newsContainer.querySelectorAll('.body'), function(alert) {
  290. alert.parentNode.style.display = 'block';
  291. });
  292. } else {
  293. Array.prototype.map.call(newsContainer.querySelectorAll('.body'), function(alert) {
  294. return alert.parentNode;
  295. }).forEach(function(alert) {
  296. var show = classNames.every(function(cl) { return cl.some(function(c) { return !!~c.indexOf('*') || alert.classList.contains(c); }); });
  297. anyVisibleAlert = show || anyVisibleAlert;
  298. alert.style.display = show ? 'block' : 'none';
  299. });
  300. }
  301.  
  302. // Show/hide message about no alerts.
  303. var none = newsContainer.querySelector('.no-alerts');
  304. if (anyVisibleAlert && none) {
  305. none.parentNode.removeChild(none);
  306. } else if (!anyVisibleAlert && !none) {
  307. none = document.createElement('div');
  308. none.classList.add('no-alerts', 'protip');
  309. none.appendChild(document.createTextNode('No feed items for this filter. Please select another filter.'));
  310. newsContainer.insertBefore(none, newsContainer.firstElementChild.nextElementSibling);
  311. }
  312. }
  313.  
  314. // Traverse back up the tree to open sub lists.
  315. function showParentMenu(menuItem) {
  316. var parentMenuItem = menuItem.parentNode;
  317. if (parentMenuItem.classList.contains('filter-list-item')) {
  318. parentMenuItem.classList.add('open');
  319. showParentMenu(parentMenuItem.parentNode);
  320. }
  321. }
  322.  
  323. // Fix filter action identification.
  324. function fixActionAlerts(newsContainer) {
  325. Array.prototype.map.call(newsContainer.querySelectorAll('.body'), function(alert) {
  326. return alert.parentNode;
  327. }).forEach(function(alert) {
  328. if (!!~alert.textContent.indexOf('created branch')) {
  329. alert.classList.remove('create');
  330. alert.classList.add('branch_create');
  331. } else if (!!~alert.textContent.indexOf('deleted branch')) {
  332. alert.classList.remove('delete');
  333. alert.classList.add('branch_delete');
  334. } else if (alert.getElementsByClassName('octicon-tag').length > 0 && !alert.classList.contains('release')) {
  335. alert.classList.remove('create');
  336. alert.classList.add('tag_add');
  337. } else if (alert.getElementsByClassName('octicon-tag-remove').length > 0) {
  338. alert.classList.remove('delete');
  339. alert.classList.add('tag_remove');
  340. } else if (alert.getElementsByClassName('octicon-git-pull-request').length > 0) {
  341. if (alert.classList.contains('issues_opened')) {
  342. alert.classList.remove('issues_opened');
  343. alert.classList.add('pull_request_opened');
  344. } else if (alert.classList.contains('issues_closed')) {
  345. alert.classList.remove('issues_closed');
  346. if (!!~alert.textContent.indexOf('merged pull request')) {
  347. alert.classList.add('pull_request_merged');
  348. } else {
  349. alert.classList.add('pull_request_closed');
  350. }
  351. }
  352. } else if (alert.classList.contains('issues_comment') && alert.querySelectorAll('[data-ga-click*="target"]')[1].href.split('/')[5] === 'pull') {
  353. alert.classList.remove('issues_comment');
  354. alert.classList.add('pull_request_comment');
  355. } else if (alert.classList.contains('gollum')) {
  356. alert.classList.remove('gollum');
  357. if (!!~alert.textContent.indexOf(' created the ')) {
  358. alert.classList.add('wiki_created');
  359. } else if (!!~alert.textContent.indexOf(' edited a wiki page in ')) {
  360. alert.classList.add('wiki_edited');
  361. }
  362. } else if (alert.classList.contains('gist')) {
  363. alert.classList.remove('gist');
  364. alert.classList.add('gist_' + alert.querySelector('.title span').textContent);
  365. }
  366. });
  367. }
  368. // Fix filter repo identification.
  369. function fixRepoAlerts(newsContainer) {
  370. REPOS = [{ id: '*-repo', text: 'All repositories', icon: 'octicon-repo', classNames: ['*-repo'] }];
  371.  
  372. // Get unique list of repos.
  373. var userRepos = new Set();
  374. Array.prototype.map.call(newsContainer.querySelectorAll('.body'), function(alert) {
  375. return alert.parentNode;
  376. }).forEach(function(alert) {
  377. var userRepo = alert.querySelector('[data-ga-click*="target:repo"]').textContent;
  378. userRepos.add(userRepo);
  379. var repo = userRepo.split('/')[1];
  380. alert.classList.add(repo, userRepo);
  381. });
  382.  
  383. // Get list of user repos (forks) per repo names.
  384. var repos = {};
  385. userRepos.forEach(function(userRepo) {
  386. var repo = userRepo.split('/')[1];
  387. if (!repos[repo]) {
  388. repos[repo] = [];
  389. }
  390. repos[repo].push(userRepo);
  391. });
  392.  
  393. // Populate global property.
  394. Object.keys(repos).forEach(function(repo) {
  395. if (repos[repo].length === 1) {
  396. var userRepo = repos[repo][0];
  397. REPOS.push({ id: userRepo, text: userRepo, link: userRepo, icon: 'octicon-repo', classNames: [userRepo] });
  398. } else {
  399. var repoForks = { id: repo, text: repo, icon: 'octicon-repo-clone', classNames: [repo], subFilters: [] };
  400. repos[repo].forEach(function(userRepo) {
  401. repoForks.classNames.push(userRepo);
  402. repoForks.subFilters.push({ id: userRepo, text: userRepo, link: userRepo, icon: 'octicon-repo', classNames: [userRepo] });
  403. });
  404. REPOS.push(repoForks);
  405. }
  406. });
  407. }
  408. // Fix filter user identification.
  409. function fixUserAlerts(newsContainer) {
  410. USERS = [{ id: '*-user', text: 'All users', icon: 'octicon-person', classNames: ['*-user'] }];
  411.  
  412. var users = new Set();
  413. Array.prototype.map.call(newsContainer.querySelectorAll('.body'), function(alert) {
  414. return alert.parentNode;
  415. }).forEach(function(alert) {
  416. var username = alert.querySelector('[data-ga-click*="target:actor"]').textContent;
  417. alert.classList.add(username);
  418. users.add(username);
  419.  
  420. //// Add member too.
  421. //if (alert.classList.contains('member_add')) {
  422. // var member = links[1].textContent;
  423. // alert.classList.add(member);
  424. // users.add(member);
  425. //}
  426. });
  427.  
  428. [...users].sort(function(a, b) {
  429. return a.toLowerCase().localeCompare(b.toLowerCase());
  430. }).forEach(function(username) {
  431. var user = { id: username, text: username, icon: 'octicon-person', classNames: [username] };
  432. USERS.push(user);
  433. });
  434. }
  435.  
  436. // Update filter counts.
  437. function updateFilterCounts(filterContainer, newsContainer) {
  438. Array.forEach(filterContainer.querySelectorAll('li.filter-list-item'), function(li) {
  439. // Count alerts based on other filters.
  440. var countFiltered = 0;
  441. var classNames = [li.filterClassNames];
  442. var selected = document.querySelectorAll(filterElement + ' li.filter-list-item.private');
  443. if (selected.length > 0) {
  444. Array.prototype.forEach.call(selected, function(item) {
  445. if (item.parentNode.parentNode !== filterContainer) { // Exclude list item from current filter container.
  446. classNames.push(item.filterClassNames);
  447. }
  448. });
  449. }
  450. Array.prototype.map.call(newsContainer.querySelectorAll('.body'), function(alert) {
  451. return alert.parentNode;
  452. }).forEach(function(alert) {
  453. var show = classNames.every(function(cl) { return cl.some(function(c) { return !!~c.indexOf('*') || alert.classList.contains(c); }); });
  454. if (show) {
  455. countFiltered++;
  456. }
  457. });
  458.  
  459. // Count alerts based on current filter.
  460. var countAll = 0;
  461. if (!!~li.filterClassNames[0].indexOf('*')) {
  462. countAll = newsContainer.querySelectorAll('.body').length;
  463. } else {
  464. Array.prototype.map.call(newsContainer.querySelectorAll('.body'), function(alert) {
  465. return alert.parentNode;
  466. }).forEach(function(alert) {
  467. if (li.filterClassNames.some(function(cl) { return alert.classList.contains(cl); })) {
  468. countAll++;
  469. }
  470. });
  471. }
  472.  
  473. li.querySelector('.count').textContent = countAll + ' (' + countFiltered + ')';
  474. });
  475. }
  476.  
  477. var CURRENT = {};
  478.  
  479. // Set current filter.
  480. function setCurrentFilter(type, filter) {
  481. CURRENT[type] = filter;
  482. }
  483.  
  484. // Get current filter.
  485. function getCurrentFilter(type, filterContainer) {
  486. var filter = CURRENT[type] || '*-' + type;
  487. filterContainer.querySelector('[' + datasetIdLong + '="' + filter + '"]').dispatchEvent(new Event('click'));
  488. }
  489.  
  490. // Add filter tab.
  491. function addFilterTab(type, text, inner, filterer, onCreate, onSelect) {
  492. var filterTab = document.createElement('li');
  493. filterer.appendChild(filterTab);
  494. var filterTabInner = document.createElement('a');
  495. filterTabInner.setAttribute('href', '#');
  496. filterTabInner.classList.add('repo-filter', 'js-repo-filter-tab');
  497. filterTabInner.appendChild(document.createTextNode(text));
  498. filterTab.appendChild(filterTabInner);
  499.  
  500. var filterContainer = document.createElement(filterListElement);
  501. inner.appendChild(filterContainer);
  502.  
  503. filterTabInner.addEventListener('click', proxy(filterTabInnerClick, type, inner, filterContainer, onSelect));
  504.  
  505. onCreate && onCreate(type, filterContainer);
  506. }
  507.  
  508. // Filter tab click event.
  509. function filterTabInnerClick(e, type, inner, filterContainer, onSelect) {
  510. e.preventDefault();
  511.  
  512. var selected = inner.querySelector('.filter-selected');
  513. selected && selected.classList.remove('filter-selected');
  514. this.classList.add('filter-selected');
  515.  
  516. Array.forEach(inner.querySelectorAll(filterListElement), function(menu) {
  517. menu && menu.classList.remove('open');
  518. });
  519. filterContainer.classList.add('open');
  520.  
  521. onSelect && onSelect(type, filterContainer);
  522. }
  523.  
  524. // Init.
  525. (function init() {
  526. var newsContainer = document.querySelector('.news');
  527. if (!newsContainer) { return; }
  528.  
  529. // GitHub homepage or profile activity tab.
  530. var sidebar = document.querySelector('.dashboard-sidebar') || document.querySelector('.profilecols > .column:first-child');
  531.  
  532. var wrapper = document.createElement(filterElement);
  533. wrapper.classList.add('boxed-group', 'flush', 'user-repos');
  534. sidebar.insertBefore(wrapper, sidebar.firstChild);
  535.  
  536. var headerAction = document.createElement('div');
  537. headerAction.classList.add('boxed-group-action');
  538. wrapper.appendChild(headerAction);
  539.  
  540. var headerLink = document.createElement('a');
  541. headerLink.setAttribute('href', 'https://github.com/jerone/UserScripts');
  542. headerLink.classList.add('btn', 'btn-sm');
  543. headerAction.appendChild(headerLink);
  544.  
  545. var headerLinkSvg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
  546. headerLinkSvg.classList.add('octicon', 'octicon-home');
  547. headerLinkSvg.setAttribute('height', '16');
  548. headerLinkSvg.setAttribute('width', '16');
  549. headerLinkSvg.setAttribute('title', 'Open Github News Feed Filter homepage');
  550. headerLink.appendChild(headerLinkSvg);
  551. var headerLinkPath = document.createElementNS('http://www.w3.org/2000/svg', 'path');
  552. headerLinkPath.setAttribute('d', ICONS['octicon-home']);
  553. headerLinkSvg.appendChild(headerLinkPath);
  554.  
  555. var headerText = document.createElement('h3');
  556. headerText.appendChild(document.createTextNode('News feed filter'));
  557. wrapper.appendChild(headerText);
  558.  
  559. var inner = document.createElement('div');
  560. inner.classList.add('boxed-group-inner');
  561. wrapper.appendChild(inner);
  562.  
  563. var bar = document.createElement('div');
  564. bar.classList.add('filter-repos', 'filter-bar');
  565. inner.appendChild(bar);
  566.  
  567. var filterer = document.createElement('ul');
  568. filterer.classList.add('repo-filterer');
  569. bar.appendChild(filterer);
  570.  
  571. // Create filter tabs.
  572. addFilterTab('action', 'Actions', inner, filterer, function onCreateActions(type, filterContainer) {
  573. // Create filter menu.
  574. addFilterMenu(type, ACTIONS, filterContainer, newsContainer, filterContainer, true);
  575. }, function onSelectActions(type, filterContainer) {
  576. // Fix alert identification.
  577. fixActionAlerts(newsContainer);
  578. // Update filter counts.
  579. updateFilterCounts(filterContainer, newsContainer);
  580. // Restore current filter.
  581. getCurrentFilter(type, filterContainer);
  582. });
  583. addFilterTab('repo', 'Repositories', inner, filterer, function onCreateRepos(type, filterContainer) {
  584. // Fix filter identification and create repos list.
  585. fixRepoAlerts(newsContainer);
  586. // Create filter menu.
  587. addFilterMenu(type, REPOS, filterContainer, newsContainer, filterContainer, true);
  588. }, function onSelectRepos(type, filterContainer) {
  589. // Fix alert identification and create repos list.
  590. fixRepoAlerts(newsContainer);
  591. // Empty list, so it can be filled again.
  592. while (filterContainer.hasChildNodes()) {
  593. filterContainer.removeChild(filterContainer.lastChild);
  594. }
  595. // Create filter menu.
  596. addFilterMenu(type, REPOS, filterContainer, newsContainer, filterContainer, true);
  597. // Update filter counts.
  598. updateFilterCounts(filterContainer, newsContainer);
  599. // Restore current filter.
  600. getCurrentFilter(type, filterContainer);
  601. });
  602. addFilterTab('user', 'Users', inner, filterer, function onCreateUsers(type, filterContainer) {
  603. // Fix filter identification and create users list.
  604. fixUserAlerts(newsContainer);
  605. // Create filter menu.
  606. addFilterMenu(type, USERS, filterContainer, newsContainer, filterContainer, true);
  607. }, function onSelectUsers(type, filterContainer) {
  608. // Fix filter identification and create users list.
  609. fixUserAlerts(newsContainer);
  610. // Empty list, so it can be filled again.
  611. while (filterContainer.hasChildNodes()) {
  612. filterContainer.removeChild(filterContainer.lastChild);
  613. }
  614. // Create filter menu.
  615. addFilterMenu(type, USERS, filterContainer, newsContainer, filterContainer, true);
  616. // Update filter counts.
  617. updateFilterCounts(filterContainer, newsContainer);
  618. // Restore current filter.
  619. getCurrentFilter(type, filterContainer);
  620. });
  621.  
  622. // Open first filter tab.
  623. filterer.querySelector('a').dispatchEvent(new Event('click'));
  624.  
  625. // Update on clicking "More"-button.
  626. new MutationObserver(function() {
  627. // Re-click the current selected filter on open filter tab.
  628. filterer.querySelector('a.filter-selected').dispatchEvent(new Event('click'));
  629. }).observe(newsContainer, { childList: true });
  630. })();
  631.  
  632. })();