您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Add filters for Github homepage news feed items
当前为
// ==UserScript== // @name Github News Feed Filter // @namespace https://github.com/jerone/UserScripts // @description Add filters for Github homepage news feed items // @author jerone // @copyright 2014+, jerone (http://jeroenvanwarmerdam.nl) // @license GNU GPLv3 // @homepage https://github.com/jerone/UserScripts/tree/master/Github_News_Feed_Filter // @homepageURL https://github.com/jerone/UserScripts/tree/master/Github_News_Feed_Filter // @include https://github.com/ // @include https://github.com/?* // @include https://github.com/orgs/*/dashboard // @include https://github.com/orgs/*/dashboard?* // @include https://github.com/*tab=activity* // @version 6.2 // @grant none // ==/UserScript== /* global Event */ (function() { var ACTIONS = [ { id: "*-action", text: "All news feed", icon: "octicon-radio-tower", classNames: ["*-action"] }, { id: "issues", text: "Issues", icon: "octicon-issue-opened", classNames: ["issues_opened", "issues_closed", "issues_reopened", "issues_comment"], subFilters: [ { id: "issues opened", text: "Opened", icon: "octicon-issue-opened", classNames: ["issues_opened"] }, { id: "issues closed", text: "Closed", icon: "octicon-issue-closed", classNames: ["issues_closed"] }, { id: "issues reopened", text: "Reopened", icon: "octicon-issue-reopened", classNames: ["issues_reopened"] }, { id: "issues comments", text: "Comments", icon: "octicon-comment-discussion", classNames: ["issues_comment"] } ] }, { id: "commits", text: "Commits", icon: "octicon-git-commit", classNames: ["push", "commit_comment"], subFilters: [ { id: "commits pushed", text: "Pushed", icon: "octicon-git-commit", classNames: ["push"] }, { id: "commits comments", text: "Comments", icon: "octicon-comment-discussion", classNames: ["commit_comment"] } ] }, { id: "pr", text: "Pull Requests", icon: "octicon-git-pull-request", classNames: ["pull_request_opened", "pull_request_closed", "pull_request_merged", "pull_request_comment"], subFilters: [ { id: "pr opened", text: "Opened", icon: "octicon-git-pull-request", classNames: ["pull_request_opened"] }, { id: "pr closed", text: "Closed", icon: "octicon-git-pull-request-abandoned", classNames: ["pull_request_closed"] }, { id: "pr merged", text: "Merged", icon: "octicon-git-merge", classNames: ["pull_request_merged"] }, { id: "pr comments", text: "Comments", icon: "octicon-comment-discussion", classNames: ["pull_request_comment"] } ] }, { id: "repo", text: "Repo", icon: "octicon-repo", classNames: ["create", "public", "fork", "branch_create", "branch_delete", "tag_add", "tag_remove", "release", "delete"], subFilters: [ { id: "repo created", text: "Created", icon: "octicon-repo-create", classNames: ["create"] }, { id: "repo public", text: "Public", icon: "octicon-repo-push", classNames: ["public"] }, { id: "repo forked", text: "Forked", icon: "octicon-repo-forked", classNames: ["fork"] }, { id: "repo deleted", text: "Deleted", icon: "octicon-repo-delete", classNames: ["delete"] }, { id: "repo released", text: "Release", icon: "octicon-repo-pull", classNames: ["release"] }, { id: "repo branched", text: "Branch", icon: "octicon-git-branch", classNames: ["branch_create", "branch_delete"], subFilters: [ { id: "repo branch created", text: "Created", icon: "octicon-git-branch-create", classNames: ["branch_create"] }, { id: "repo branch deleted", text: "Deleted", icon: "octicon-git-branch-delete", classNames: ["branch_delete"] } ] }, { id: "repo tagged", text: "Tag", icon: "octicon-tag", classNames: ["tag_add", "tag_remove"], subFilters: [ { id: "repo tag added", text: "Added", icon: "octicon-tag-add", classNames: ["tag_add"] }, { id: "repo tag removed", text: "Removed", icon: "octicon-tag-remove", classNames: ["tag_remove"] } ] } ] }, { id: "user", text: "User", icon: "octicon-person", classNames: ["watch_started", "member_add", "team_add"], subFilters: [ { id: "user starred", text: "Starred", icon: "octicon-star", classNames: ["watch_started"] }, { id: "user added", text: "Member added", icon: "octicon-person-add", classNames: ["member_add", "team_add"] } ] }, { id: "wiki", text: "Wiki", icon: "octicon-book", classNames: ["wiki_created", "wiki_edited"], subFilters: [ { id: "wiki created", text: "Created", icon: "octicon-plus", classNames: ["wiki_created"] }, { id: "wiki edited", text: "Edited", icon: "octicon-book", classNames: ["wiki_edited"] } ] }, { id: "gist", text: "Gist", icon: "octicon-gist", classNames: ["gist_created", "gist_updated"], subFilters: [ { id: "gist created", text: "Created", icon: "octicon-gist-new", classNames: ["gist_created"] }, { id: "gist updated", text: "Updated", icon: "octicon-gist", classNames: ["gist_updated"] } ] } // Possible other classes: follow ]; var REPOS = [ ]; const datasetId = "githubNewsFeedFilter"; const datasetIdLong = "data-github-news-feed-filter"; const filterElement = "github-news-feed-filter"; const filterListElement = "github-news-feed-filter-list"; function proxy(fn) { return function() { var that = this; return function(e) { var args = that.slice(0); // clone; args.unshift(e); // prepend event; fn.apply(this, args); }; }.call([].slice.call(arguments, 1)); } function addStyle(css) { var node = document.createElement("style"); node.type = "text/css"; node.appendChild(document.createTextNode(css)); document.head.appendChild(node); } addStyle("\ github-news-feed-filter { display: block; }\ github-news-feed-filter .count { margin-right: 15px; }\ \ /* Needed for user?tab=activity; */\ .profilecols github-news-feed-filter .filter-bar { padding: 10px 10px 0px 10px; }\ .profilecols github-news-feed-filter .filter-bar .repo-filterer li { float: none; }\ \ github-news-feed-filter .filter-list .mini-repo-list-item { padding-right: 64px; }\ \ github-news-feed-filter .filter-list .filter-list .mini-repo-list-item { padding-left: 40px; border-top: 1px dashed #E5E5E5; }\ github-news-feed-filter .filter-list .filter-list .filter-list .mini-repo-list-item { padding-left: 50px; }\ \ github-news-feed-filter .filter-list { display: none; }\ github-news-feed-filter .open > .filter-list { display: block; }\ github-news-feed-filter .filter-list.open { display: block; }\ \ github-news-feed-filter .private { font-weight: bold; }\ \ github-news-feed-filter .stars .octicon { position: absolute; right: -4px; }\ github-news-feed-filter .filter-list-item.open > a > .stars > .octicon:before { content: '\\f05b'; }\ \ .no-alerts { font-style: italic; }\ "); // Add filter menu list; function addFilterMenu(type, filters, parent, newsContainer, filterContainer, main) { var ul = document.createElement("ul"); ul.classList.add("filter-list"); if (main) { ul.classList.add("mini-repo-list"); } parent.appendChild(ul); filters.forEach(function(subFilter) { var li = addFilterMenuItem(type, subFilter, ul, newsContainer, filterContainer); if (subFilter.subFilters) { addFilterMenu(type, subFilter.subFilters, li, newsContainer, filterContainer, false); } }); } // Add filte menu item; function addFilterMenuItem(type, filter, parent, newsContainer, filterContainer) { // Filter item; var li = document.createElement("li"); li.classList.add("filter-list-item"); li.filterClassNames = filter.classNames; parent.appendChild(li); // Filter link; var a = document.createElement("a"); a.classList.add("mini-repo-list-item", "css-truncate"); a.setAttribute("href", filter.link || "/"); a.setAttribute("title", filter.classNames.join(" & ")); a.dataset[datasetId] = filter.id; a.addEventListener("click", proxy(onFilterItemClick, type, newsContainer, filterContainer)); li.appendChild(a); // Filter icon; var i = document.createElement("span"); i.classList.add("repo-icon", "octicon", filter.icon); a.appendChild(i); // Filter text; var text = filter.text.split("/"); var t = document.createElement("span"); t.classList.add("repo-and-owner", "css-truncate-target"); a.appendChild(t); var to = document.createElement("span"); to.classList.add("owner"); to.appendChild(document.createTextNode(text[0])); t.appendChild(to); if (text.length > 1) { text.shift(); t.appendChild(document.createTextNode("/")); var tr = document.createElement("span"); tr.classList.add("repo"); tr.appendChild(document.createTextNode(text.join("/"))); t.appendChild(tr); } // Filter count & sub list arrow; var s = document.createElement("span"); s.classList.add("stars"); var c = document.createElement("span"); c.classList.add("count"); c.appendChild(document.createTextNode("0")); s.appendChild(c); if (filter.subFilters) { s.appendChild(document.createTextNode(" ")); var o = document.createElement("span"); o.classList.add("octicon", "octicon-triangle-left"); s.appendChild(o); } a.appendChild(s); return li; } // Filter item click event; function onFilterItemClick(e, type, newsContainer, filterContainer) { e.preventDefault(); // Store current filter; setCurrentFilter(type, this.dataset[datasetId]); // Open/close sub list; Array.forEach(filterContainer.querySelectorAll(".open"), function(item) { item.classList.remove("open"); }); showParentMenu(this); this.parentNode.classList.add("open"); // Give it a colored background; Array.forEach(filterContainer.querySelectorAll(".private"), function(m) { m.classList.remove("private"); }); this.parentNode.classList.add("private"); // Toggle alert visibility; toggleAlertsVisibility(newsContainer); } // Toggle alert visibility; function toggleAlertsVisibility(newsContainer) { // Get selected filters; var anyVisibleAlert = false; var classNames = []; var selected = document.querySelectorAll(filterElement + " .private"); if (selected.length > 0) { Array.prototype.forEach.call(selected, function(item) { classNames.push(item.filterClassNames); }); } // Show/hide alerts; if (classNames.length === 0 || classNames.every(function(cl) { return cl.every(function(c) { return !!~c.indexOf("*"); }) })) { anyVisibleAlert = true; Array.forEach(newsContainer.querySelectorAll(".alert"), function(alert) { alert.style.display = "block"; }); } else { Array.forEach(newsContainer.querySelectorAll(".alert"), function(alert) { var show = classNames.every(function(cl) { return cl.some(function(c) { return !!~c.indexOf("*") || alert.classList.contains(c); }); }); anyVisibleAlert = show || anyVisibleAlert; alert.style.display = show ? "block" : "none"; }); } // Show/hide message about no alerts; var none = newsContainer.querySelector(".no-alerts"); if (anyVisibleAlert && none) { none.parentNode.removeChild(none); } else if (!anyVisibleAlert && !none) { none = document.createElement("div"); none.classList.add("no-alerts", "protip"); none.appendChild(document.createTextNode("No feed items for this filter. Please select another filter.")); newsContainer.insertBefore(none, newsContainer.firstElementChild.nextElementSibling); } } // Traverse back up the tree to open sub lists; function showParentMenu(menuItem) { var parentMenuItem = menuItem.parentNode; if (parentMenuItem.classList.contains("filter-list-item")) { parentMenuItem.classList.add("open"); showParentMenu(parentMenuItem.parentNode); } } // Fix filter action identification; function fixActionAlerts(newsContainer) { Array.forEach(newsContainer.querySelectorAll(".alert"), function(alert) { if (alert.getElementsByClassName("octicon-git-branch").length > 0 && !alert.classList.contains("fork")) { alert.classList.remove("create"); alert.classList.add("branch_create"); } else if (alert.getElementsByClassName("octicon-git-branch-delete").length > 0) { alert.classList.remove("delete"); alert.classList.add("branch_delete"); } else if (alert.getElementsByClassName("octicon-tag").length > 0 && !alert.classList.contains("release")) { alert.classList.remove("create"); alert.classList.add("tag_add"); } else if (alert.getElementsByClassName("octicon-tag-remove").length > 0) { alert.classList.remove("delete"); alert.classList.add("tag_remove"); } else if (alert.getElementsByClassName("octicon-git-pull-request").length > 0) { if (alert.classList.contains("issues_opened")) { alert.classList.remove("issues_opened"); alert.classList.add("pull_request_opened"); } else if (alert.classList.contains("issues_closed")) { alert.classList.remove("issues_closed"); if (!!~alert.querySelector('.title').textContent.indexOf('merged pull request')) { alert.classList.add("pull_request_merged"); } else { alert.classList.add("pull_request_closed"); } } } else if (alert.classList.contains("issues_comment") && alert.querySelectorAll(".title a")[1].href.split("/")[5] === "pull") { alert.classList.remove("issues_comment"); alert.classList.add("pull_request_comment"); } else if (alert.classList.contains("gollum")) { alert.classList.remove("gollum"); if (!!~alert.querySelector('.title').textContent.indexOf(" created the ")) { alert.classList.add("wiki_created"); } else if (!!~alert.querySelector('.title').textContent.indexOf(" edited the ")) { alert.classList.add("wiki_edited"); } } else if (alert.classList.contains("gist")) { alert.classList.remove("gist"); alert.classList.add("gist_" + alert.querySelector(".title span").textContent); } }); } // Fix filter repo identification; function fixRepoAlerts(newsContainer) { REPOS = [{ id: "*-repo", text: "All repositories", icon: "octicon-repo", classNames: ["*-repo"] }]; // Get unique list of repos; var userRepos = new Set(); Array.prototype.forEach.call(newsContainer.querySelectorAll(".alert"), function(alert) { var links = alert.querySelectorAll(".title a"); var userRepo = links[links.length - 1].textContent.split("#")[0]; // Remove issue number from text; userRepos.add(userRepo); var repo = userRepo.split("/")[1]; alert.classList.add(repo, userRepo); }); // Get list of user repos (forks) per repo names; var repos = {}; userRepos.forEach(function(userRepo) { var repo = userRepo.split("/")[1]; if (!repos[repo]) { repos[repo] = []; } repos[repo].push(userRepo); }); // Populate global property; Object.keys(repos).forEach(function(repo) { if (repos[repo].length === 1) { var userRepo = repos[repo][0]; REPOS.push({ id: userRepo, text: userRepo, link: userRepo, icon: "octicon-repo", classNames: [userRepo] }); } else { var repoForks = { id: repo, text: repo, icon: "octicon-repo-clone", classNames: [repo], subFilters: [] }; repos[repo].forEach(function(userRepo) { repoForks.classNames.push(userRepo); repoForks.subFilters.push({ id: userRepo, text: userRepo, link: userRepo, icon: "octicon-repo", classNames: [userRepo] }); }); REPOS.push(repoForks); } }); } // Update filter counts; function updateFilterCounts(filterContainer, newsContainer) { Array.forEach(filterContainer.querySelectorAll("li.filter-list-item"), function(li) { // Count alerts based on other filters; var countFiltered = 0; var classNames = [li.filterClassNames]; var selected = document.querySelectorAll(filterElement + " li.filter-list-item.private"); if (selected.length > 0) { Array.prototype.forEach.call(selected, function(item) { if (item.parentNode.parentNode !== filterContainer) { // exclude list item from current filter container; classNames.push(item.filterClassNames); } }); } Array.forEach(newsContainer.querySelectorAll(".alert"), function(alert) { var show = classNames.every(function(cl) { return cl.some(function(c) { return !!~c.indexOf("*") || alert.classList.contains(c); }); }); if (show) { countFiltered++ } }); // Count alerts based on current filter; var countAll = 0; if (!!~li.filterClassNames[0].indexOf("*")) { countAll = newsContainer.querySelectorAll(".alert").length; } else { Array.forEach(newsContainer.querySelectorAll(".alert"), function(alert) { if (li.filterClassNames.some(function(cl) { return alert.classList.contains(cl); })) { countAll++; } }); } li.querySelector(".count").textContent = countAll + " (" + countFiltered + ")"; }); } var CURRENT = { }; // Set current filter; function setCurrentFilter(type, filter) { CURRENT[type] = filter; } // Get current filter; function getCurrentFilter(type, filterContainer) { var filter = CURRENT[type] || "*-" + type; filterContainer.querySelector('[' + datasetIdLong + '="' + filter + '"]').dispatchEvent(new Event("click")); } function addFilterTab(type, text, inner, filterer, onCreate, onSelect) { var filterTab = document.createElement("li"); filterer.appendChild(filterTab); var filterTabInner = document.createElement("a"); filterTabInner.setAttribute("href", "#"); filterTabInner.classList.add("repo-filter", "js-repo-filter-tab"); filterTabInner.appendChild(document.createTextNode(text)); filterTab.appendChild(filterTabInner); var filterContainer = document.createElement(filterListElement); inner.appendChild(filterContainer); filterTabInner.addEventListener("click", proxy(filterTabInnerClick, type, inner, filterContainer, onSelect)); onCreate && onCreate(type, filterContainer); } // Filter tab click event; function filterTabInnerClick(e, type, inner, filterContainer, onSelect) { e.preventDefault(); var selected = inner.querySelector(".filter-selected"); selected && selected.classList.remove("filter-selected"); this.classList.add("filter-selected"); Array.forEach(inner.querySelectorAll(filterListElement), function(menu) { menu && menu.classList.remove("open"); }); filterContainer.classList.add("open"); onSelect && onSelect(type, filterContainer); } // Init; (function init() { console.log('GitHubNewsFeedFilter', 'page load'); var newsContainer = document.querySelector(".news"); if (!newsContainer) { return; } var sidebar = document.querySelector(".dashboard-sidebar") || document.querySelector(".column.one-fourth.vcard"); var wrapper = document.createElement(filterElement); wrapper.classList.add("boxed-group", "flush", "user-repos"); sidebar.insertBefore(wrapper, sidebar.firstChild); var headerAction = document.createElement("div"); headerAction.classList.add("boxed-group-action"); wrapper.appendChild(headerAction); var headerLink = document.createElement("a"); headerLink.setAttribute("href", "https://github.com/jerone/UserScripts"); headerLink.classList.add("btn", "btn-sm"); headerAction.appendChild(headerLink); var headerLinkIcon = document.createElement("span"); headerLinkIcon.classList.add("octicon", "octicon-home"); headerLinkIcon.setAttribute("title", "Open Github News Feed Filter homepage"); headerLink.appendChild(headerLinkIcon); var headerText = document.createElement("h3"); headerText.appendChild(document.createTextNode("News feed filter")); wrapper.appendChild(headerText); var inner = document.createElement("div"); inner.classList.add("boxed-group-inner"); wrapper.appendChild(inner); var bar = document.createElement("div"); bar.classList.add("filter-repos", "filter-bar"); inner.appendChild(bar); var filterer = document.createElement("ul"); filterer.classList.add("repo-filterer"); bar.appendChild(filterer); // Create filter tabs; addFilterTab("action", "Actions", inner, filterer, function onCreateActions(type, filterContainer) { // Create filter menu; addFilterMenu(type, ACTIONS, filterContainer, newsContainer, filterContainer, true); }, function onSelectActions(type, filterContainer) { // Fix alert identification; fixActionAlerts(newsContainer); // Update filter counts; updateFilterCounts(filterContainer, newsContainer); // Restore current filter; getCurrentFilter(type, filterContainer); }); addFilterTab("repo", "Repositories", inner, filterer, function onCreateRepos(type, filterContainer) { // Fix filter identification and create repos list; fixRepoAlerts(newsContainer); // Create filter menu; addFilterMenu(type, REPOS, filterContainer, newsContainer, filterContainer, true); }, function onSelectRepos(type, filterContainer) { // Fix alert identification and create repos list; fixRepoAlerts(newsContainer); // Empty list, so it can be filled again; while (filterContainer.hasChildNodes()) { filterContainer.removeChild(filterContainer.lastChild); } // Create filter menu; addFilterMenu(type, REPOS, filterContainer, newsContainer, filterContainer, true); // Update filter counts; updateFilterCounts(filterContainer, newsContainer); // Restore current filter; getCurrentFilter(type, filterContainer); }); // Open first filter tab; filterer.querySelector("a").dispatchEvent(new Event("click")); // Update on clicking "More"-button; new MutationObserver(function() { // Re-click the current selected filter on open filter tab; filterer.querySelector("a.filter-selected").dispatchEvent(new Event("click")); }).observe(newsContainer, { childList: true }); })(); })();