Github News Feed Filter

Add filters for Github homepage news feed items

当前为 2015-09-21 提交的版本,查看 最新版本

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