Github News Feed Filter

Add filters for GitHub homepage news feed items

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey, Greasemonkey of Violentmonkey.

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey of Violentmonkey.

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey of Violentmonkey.

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey of Userscripts.

Voor het installeren van scripts heb je een extensie nodig, zoals {tampermonkey_link:Tampermonkey}.

Voor het installeren van scripts heb je een gebruikersscriptbeheerder nodig.

(Ik heb al een user script manager, laat me het downloaden!)

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

(Ik heb al een beheerder - laat me doorgaan met de installatie!)

// ==UserScript==
// @name             Github News Feed Filter
// @namespace        https://github.com/jerone/UserScripts
// @description      Add filters for GitHub homepage news feed items
// @author           jerone
// @contributor      darkred
// @copyright        2014+, jerone (https://github.com/jerone)
// @license          CC-BY-NC-SA-4.0; https://creativecommons.org/licenses/by-nc-sa/4.0/legalcode
// @license          GPL-3.0-or-later; http://www.gnu.org/licenses/gpl-3.0.txt
// @homepage         https://github.com/jerone/UserScripts/tree/master/Github_News_Feed_Filter
// @homepageURL      https://github.com/jerone/UserScripts/tree/master/Github_News_Feed_Filter
// @contributionURL  https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=VCYMHWQ7ZMBKW
// @icon             https://github.githubassets.com/pinned-octocat.svg
// @include          https://github.com/
// @include          https://github.com/?*
// @include          https://github.com/orgs/*/dashboard
// @include          https://github.com/orgs/*/dashboard?*
// @version          8.2.8
// @grant            none
// ==/UserScript==

// cSpell:ignore transform, osvg, opath, gollum, hovercards, profilecols
/* eslint security/detect-object-injection: "off" */

(function () {
	var ICONS = {};
	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";
	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";
	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";
	ICONS["octicon-git-branch-create"] = ICONS["octicon-git-branch"];
	ICONS["octicon-git-branch-delete"] = ICONS["octicon-git-branch"];
	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";
	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";
	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";
	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";
	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";
	ICONS["octicon-plus"] = "M12 9H7v5H5V9H0V7h5V2h2v5h5z";
	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";
	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";
	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";
	ICONS["octicon-repo-create"] = ICONS["octicon-plus"];
	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";
	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";
	ICONS["octicon-repo-delete"] = ICONS["octicon-repo"];
	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";
	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";
	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";
	ICONS["octicon-tag-add"] = ICONS["octicon-tag"];
	ICONS["octicon-tag-remove"] = ICONS["octicon-tag"];
	ICONS["octicon-triangle-left"] = "M6 2L0 8l6 6z";

	var ACTIONS = [
		{
			id: "*-action",
			text: "All news feed",
			icon: "octicon-radio-tower",
			classNames: ["*-action"],
		},
		{
			id: "issues",
			text: "Issues",
			icon: "octicon-issue-opened",
			classNames: ["issues_labeled"],
			subFilters: [
				{
					id: "issues labeled",
					text: "Labeled",
					icon: "octicon-tag",
					classNames: ["issues_labeled"],
				},
			],
		},
		{
			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: "repo",
			text: "Repo",
			icon: "octicon-repo",
			classNames: [
				"repo",
				"create",
				"public",
				"fork",
				"branch_create",
				"branch_delete",
				"tag_add",
				"tag_remove",
				"release",
				"delete",
			],
			subFilters: [
				{
					id: "repo created",
					text: "Created",
					icon: "octicon-repo-create",
					classNames: ["repo", "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: ["follow"],
		},
		{
			id: "starred",
			text: "Starred",
			icon: "octicon-star",
			classNames: ["watch_started"],
		},
		{
			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"],
				},
			],
		},
	];

	var REPOS = [];

	var USERS = [];

	var datasetId = "githubNewsFeedFilter";
	var datasetIdLong = "data-github-news-feed-filter";
	var filterElement = "github-news-feed-filter";
	var 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 .filter-bar { padding: 0; }

		github-news-feed-filter .count { margin-right: 15px; }

		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 { transform: rotate(-90deg); }

		.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 filter 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 svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
		svg.classList.add("repo-icon", "octicon", filter.icon);
		svg.setAttribute("height", "16");
		svg.setAttribute("width", "16");
		a.appendChild(svg);
		var path = document.createElementNS(
			"http://www.w3.org/2000/svg",
			"path",
		);
		path.setAttribute("d", ICONS[filter.icon]);
		svg.appendChild(path);

		// 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 osvg = document.createElementNS(
				"http://www.w3.org/2000/svg",
				"svg",
			);
			osvg.classList.add("octicon", "octicon-triangle-left");
			osvg.setAttribute("height", "16");
			osvg.setAttribute("width", "6");
			s.appendChild(osvg);
			var opath = document.createElementNS(
				"http://www.w3.org/2000/svg",
				"path",
			);
			opath.setAttribute("d", ICONS["octicon-triangle-left"]);
			osvg.appendChild(opath);
		}
		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(":scope .open"),
			function (item) {
				item.classList.remove("open");
			},
		);
		showParentMenu(this);
		this.parentNode.classList.add("open");

		// Give it a colored background.
		Array.forEach(
			filterContainer.querySelectorAll(":scope .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(
			":scope " + 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;
			getAllAlerts(newsContainer).forEach(function (alert) {
				alert.style.display = "block";
			});
		} else {
			getAllAlerts(newsContainer).forEach(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";
				// DEBUG: uncomment following line and comment previous line to debug all alerts.
				//if(show) alert.style.display = 'none';
			});
		}

		// Show/hide message about no alerts.
		var none = newsContainer.querySelector(":scope .no-alerts");
		if (anyVisibleAlert && none) {
			none.parentNode.removeChild(none);
		} else if (!anyVisibleAlert && !none) {
			none = document.createElement("div");
			none.classList.add("no-alerts");
			none.appendChild(
				document.createTextNode(
					"No feed items for this filter. Please select another filter.",
				),
			);
			var firstAlert = getAllAlerts(newsContainer)[0];
			firstAlert.parentNode.insertBefore(none, firstAlert);
		}
	}

	// 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) {
		getAllAlerts(newsContainer).forEach(function (alert) {
			if (~alert.textContent.indexOf("created branch")) {
				alert.classList.remove("create");
				alert.classList.add("branch_create");
			} else if (~alert.textContent.indexOf("deleted branch")) {
				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.textContent.indexOf("labeled an issue")) {
				alert.classList.add("issues_labeled");
			} else if (alert.classList.contains("gollum")) {
				alert.classList.remove("gollum");
				if (~alert.innerText.indexOf(" created a wiki page in ")) {
					alert.classList.add("wiki_created");
				} else if (
					~alert.innerText.indexOf(" edited a wiki page in ")
				) {
					alert.classList.add("wiki_edited");
				}
			}
		});
	}
	// 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();
		getAllAlerts(newsContainer).forEach(function (alert) {
			var alertRepo = alert.querySelector(
				':scope [data-ga-click*="target:repo"]:not([data-ga-click*="target:repositories"])',
			);
			if (alertRepo) {
				// Follow doesn't contain a repo link.
				var userRepo = alertRepo.textContent;
				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);
			}
		});
	}
	// Fix filter user identification.
	function fixUserAlerts(newsContainer) {
		USERS = [
			{
				id: "*-user",
				text: "All users",
				icon: "octicon-organization",
				classNames: ["*-user"],
			},
		];

		var users = new Set();
		getAllAlerts(newsContainer).forEach(function (alert) {
			var usernameElms = alert.querySelectorAll(
				':scope [data-ga-click*="target:actor"]',
			);
			Array.prototype.find.call(usernameElms, function (usernameElm) {
				var username = usernameElm.textContent;
				if (username) {
					alert.classList.add(username);
					users.add(username);
					return true;
				}
				return false;
			});
		});

		[...users]
			.sort(function (a, b) {
				return a.toLowerCase().localeCompare(b.toLowerCase());
			})
			.forEach(function (username) {
				var user = {
					id: username,
					text: username,
					icon: "octicon-person",
					classNames: [username],
				};
				USERS.push(user);
			});
	}

	// Update filter counts.
	function updateFilterCounts(filterContainer, newsContainer) {
		Array.forEach(
			filterContainer.querySelectorAll(":scope li.filter-list-item"),
			function (li) {
				// Count alerts based on other filters.
				var countFiltered = 0;
				var classNames = [li.filterClassNames];
				var selected = document.querySelectorAll(
					":scope " + 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);
						}
					});
				}
				getAllAlerts(newsContainer).forEach(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 = getAllAlerts(newsContainer).length;
				} else {
					getAllAlerts(newsContainer).forEach(function (alert) {
						if (
							li.filterClassNames.some(function (cl) {
								return alert.classList.contains(cl);
							})
						) {
							countAll++;
						}
					});
				}

				li.querySelector(":scope .count").textContent =
					countAll + " (" + countFiltered + ")";
			},
		);
	}

	// Get all alerts.
	function getAllAlerts(newsContainer) {
		return Array.prototype.map.call(
			newsContainer.querySelectorAll(
				":scope div[data-repository-hovercards-enabled] > div > .body",
			),
			function (alert) {
				return alert.parentNode;
			},
		);
	}

	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(":scope [" + datasetIdLong + '="' + filter + '"]')
			.dispatchEvent(new Event("click"));
	}

	// Add filter tab.
	function addFilterTab(type, text, inner, filterer, onCreate, onSelect) {
		var filterTabInner = document.createElement("a");
		filterTabInner.setAttribute("href", "#");
		filterTabInner.classList.add("UnderlineNav-item");
		filterTabInner.appendChild(document.createTextNode(text));
		filterer.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(":scope .selected");
		selected && selected.classList.remove("selected");
		this.classList.add("selected");

		Array.forEach(
			inner.querySelectorAll(filterListElement),
			function (menu) {
				menu && menu.classList.remove("open");
			},
		);
		filterContainer.classList.add("open");

		onSelect && onSelect(type, filterContainer);
	}

	// Init.
	(function init() {
		var newsContainer = document.querySelector(".news");
		if (!newsContainer) {
			return;
		}

		// GitHub homepage or profile activity tab.
		var sidebar =
			document.querySelector(".dashboard-sidebar:not(.is-placeholder)") ||
			document.querySelector(".profilecols > .column:first-child");

		var wrapper = document.createElement(filterElement);
		wrapper.classList.add("boxed-group", "flush", "user-repos");
		sidebar.insertBefore(
			wrapper,
			sidebar.querySelector(":scope > *:not(details)"),
		);

		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 headerLinkSvg = document.createElementNS(
			"http://www.w3.org/2000/svg",
			"svg",
		);
		headerLinkSvg.classList.add("octicon", "octicon-home");
		headerLinkSvg.setAttribute("height", "16");
		headerLinkSvg.setAttribute("width", "16");
		headerLinkSvg.setAttribute(
			"title",
			"Open Github News Feed Filter homepage",
		);
		headerLink.appendChild(headerLinkSvg);
		var headerLinkPath = document.createElementNS(
			"http://www.w3.org/2000/svg",
			"path",
		);
		headerLinkPath.setAttribute("d", ICONS["octicon-home"]);
		headerLinkSvg.appendChild(headerLinkPath);

		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-bar");
		inner.appendChild(bar);

		var filterer = document.createElement("nav");
		filterer.classList.add("UnderlineNav-body");
		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);
			},
		);
		addFilterTab(
			"user",
			"Users",
			inner,
			filterer,
			function onCreateUsers(type, filterContainer) {
				// Fix filter identification and create users list.
				fixUserAlerts(newsContainer);
				// Create filter menu.
				addFilterMenu(
					type,
					USERS,
					filterContainer,
					newsContainer,
					filterContainer,
					true,
				);
			},
			function onSelectUsers(type, filterContainer) {
				// Fix filter identification and create users list.
				fixUserAlerts(newsContainer);
				// Empty list, so it can be filled again.
				while (filterContainer.hasChildNodes()) {
					filterContainer.removeChild(filterContainer.lastChild);
				}
				// Create filter menu.
				addFilterMenu(
					type,
					USERS,
					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.selected")
				.dispatchEvent(new Event("click"));
		}).observe(newsContainer, { childList: true, subtree: true });
	})();
})();