Github News Feed Filter

Add filters for Github homepage news feed items

当前为 2016-02-03 提交的版本,查看 最新版本

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