您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Enhances the GitHub Notifications page, making it more productive and less noisy.
- // ==UserScript==
- // @name Refined GitHub Notifications
- // @namespace https://greasyfork.org/
- // @version 0.0.1
- // @description Enhances the GitHub Notifications page, making it more productive and less noisy.
- // @author Hunter Johnston (https://github.com/huntabyte)
- // @license MIT
- // @homepageURL https://github.com/huntabyte/refined-github-notifications
- // @supportURL https://github.com/huntabyte/refined-github-notifications
- // @match https://github.com/**
- // @icon https://www.google.com/s2/favicons?sz=64&domain=github.com
- // @grant window.close
- // @grant GM_getValue
- // @grant GM_setValue
- // @grant GM_registerMenuCommand
- // @grant GM_unregisterMenuCommand
- // ==/UserScript==
- // @ts-check
- /* eslint-disable no-console */
- /**
- * @typedef {import('./index.d').NotificationItem} Item
- * @typedef {import('./index.d').Subject} Subject
- * @typedef {import('./index.d').DetailsCache} DetailsCache
- */
- (function () {
- "use strict";
- // Fix the archive link
- if (location.pathname === "/notifications/beta/archive") location.pathname = "/notifications";
- /**
- * list of functions to be cleared on page change
- * @type {(() => void)[]}
- */
- const cleanups = [];
- const NAME = "Refined GitHub Notifications";
- const STORAGE_KEY = "refined-github-notifications";
- const STORAGE_KEY_DETAILS = "refined-github-notifications:details-cache";
- const DETAILS_CACHE_TIMEOUT = 1000 * 60 * 60 * 6; // 6 hours
- const AUTO_MARK_DONE = useOption("rgn_auto_mark_done", "Auto mark done", true);
- const HIDE_CHECKBOX = useOption("rgn_hide_checkbox", "Hide checkbox", true);
- const HIDE_ISSUE_NUMBER = useOption("rgn_hide_issue_number", "Hide issue number", true);
- const HIDE_EMPTY_INBOX_IMAGE = useOption(
- "rgn_hide_empty_inbox_image",
- "Hide empty inbox image",
- true
- );
- const ENHANCE_NOTIFICATION_SHELF = useOption(
- "rgn_enhance_notification_shelf",
- "Enhance notification shelf",
- true
- );
- const SHOW_DEATAILS = useOption("rgn_show_details", "Detail Preview", false);
- const SHOW_REACTIONS = useOption("rgn_show_reactions", "Reactions Preview", false);
- const GITHUB_TOKEN = localStorage.getItem("github_token") || "";
- const config = JSON.parse(localStorage.getItem(STORAGE_KEY) || "{}");
- /**
- * @type {Record<string, DetailsCache>}
- */
- const detailsCache = JSON.parse(localStorage.getItem(STORAGE_KEY_DETAILS) || "{}");
- let bc;
- let bcInitTime = 0;
- const reactionsMap = {
- "+1": "👍",
- "-1": "👎",
- laugh: "😄",
- hooray: "🎉",
- confused: "😕",
- heart: "❤️",
- rocket: "🚀",
- eyes: "👀"
- };
- function writeConfig() {
- localStorage.setItem(STORAGE_KEY, JSON.stringify(config));
- }
- function injectStyle() {
- const style = document.createElement("style");
- style.innerHTML = [
- `
- /* Hide blue dot on notification icon */
- .mail-status.unread {
- display: none !important;
- }
- /* Hide blue dot on notification with the new navigration */
- .AppHeader .AppHeader-button.AppHeader-button--hasIndicator::before {
- display: none !important;
- }
- /* Limit notification container width on large screen for better readability */
- .notifications-v2 .js-check-all-container {
- max-width: 1000px;
- }
- /* Hide sidebar earlier, override the breakpoints */
- @media (min-width: 768px) {
- .js-notifications-container {
- flex-direction: column !important;
- }
- .js-notifications-container > .d-none.d-md-flex {
- display: none !important;
- }
- .js-notifications-container > .col-md-9 {
- width: 100% !important;
- }
- }
- @media (min-width: 1268px) {
- .js-notifications-container {
- flex-direction: row !important;
- }
- .js-notifications-container > .d-none.d-md-flex {
- display: flex !important;
- }
- }
- `,
- HIDE_CHECKBOX.value &&
- `
- /* Hide check box on notification list */
- .notifications-list-item > *:first-child label {
- opacity: 0 !important;
- width: 0 !important;
- margin-right: -10px !important;
- }`,
- ENHANCE_NOTIFICATION_SHELF.value &&
- `
- /* Hide the notification shelf and add a FAB */
- .js-notification-shelf {
- display: none !important;
- }
- .btn-hover-primary {
- transform: scale(1.2);
- transition: all .3s ease-in-out;
- }
- .btn-hover-primary:hover {
- color: var(--color-btn-primary-text);
- background-color: var(--color-btn-primary-bg);
- border-color: var(--color-btn-primary-border);
- box-shadow: var(--color-btn-primary-shadow),var(--color-btn-primary-inset-shadow);
- }`,
- HIDE_EMPTY_INBOX_IMAGE.value &&
- `/* Hide the image on zero-inbox */
- .js-notifications-blankslate picture {
- display: none !important;
- }`
- ]
- .filter(Boolean)
- .join("\n");
- document.head.appendChild(style);
- }
- /**
- * Create UI for the options
- * @template T
- * @param {string} key
- * @param {string} title
- * @param {T} defaultValue
- * @returns {{ value: T }}
- */
- function useOption(key, title, defaultValue) {
- if (typeof GM_getValue === "undefined") {
- return {
- value: defaultValue
- };
- }
- // eslint-disable-next-line no-undef
- let value = GM_getValue(key, defaultValue);
- const ref = {
- get value() {
- return value;
- },
- set value(v) {
- value = v;
- // eslint-disable-next-line no-undef
- GM_setValue(key, v);
- location.reload();
- }
- };
- // eslint-disable-next-line no-undef
- GM_registerMenuCommand(`${title}: ${value ? "✅" : "❌"}`, () => {
- ref.value = !value;
- });
- return ref;
- }
- /**
- * To have a FAB button to close current issue,
- * where you can mark done and then close the tab automatically
- */
- function enhanceNotificationShelf() {
- function inject() {
- const shelf = document.querySelector(".js-notification-shelf");
- if (!shelf) return false;
- /** @type {HTMLButtonElement} */
- const doneButton = shelf.querySelector('button[aria-label="Done"]');
- if (!doneButton) return false;
- const clickAndClose = async () => {
- doneButton.click();
- // wait for the notification shelf to be updated
- await Promise.race([
- new Promise((resolve) => {
- const ob = new MutationObserver(() => {
- resolve();
- ob.disconnect();
- });
- ob.observe(shelf, {
- childList: true,
- subtree: true,
- attributes: true
- });
- }),
- new Promise((resolve) => setTimeout(resolve, 1000))
- ]);
- // close the tab
- window.close();
- };
- /**
- * @param {KeyboardEvent} e
- */
- const keyDownHandle = (e) => {
- if (e.altKey && e.key === "x") {
- e.preventDefault();
- clickAndClose();
- }
- };
- /** @type {*} */
- const fab = doneButton.cloneNode(true);
- fab.classList.remove("btn-sm");
- fab.classList.add("btn-hover-primary");
- fab.addEventListener("click", clickAndClose);
- Object.assign(fab.style, {
- position: "fixed",
- right: "25px",
- bottom: "25px",
- zIndex: 999,
- aspectRatio: "1/1",
- borderRadius: "50%"
- });
- const commentActions = document.querySelector("#partial-new-comment-form-actions");
- if (commentActions) {
- const key = "markDoneAfterComment";
- const label = document.createElement("label");
- const input = document.createElement("input");
- label.classList.add("color-fg-muted");
- input.type = "checkbox";
- input.checked = !!config[key];
- input.addEventListener("change", (e) => {
- // @ts-expect-error cast
- config[key] = !!e.target.checked;
- writeConfig();
- });
- label.appendChild(input);
- label.appendChild(document.createTextNode(" Mark done and close after comment"));
- Object.assign(label.style, {
- display: "flex",
- alignItems: "center",
- justifyContent: "end",
- gap: "5px",
- userSelect: "none",
- fontWeight: "400"
- });
- const div = document.createElement("div");
- Object.assign(div.style, {
- paddingBottom: "5px"
- });
- div.appendChild(label);
- commentActions.parentElement.prepend(div);
- const commentButton = commentActions.querySelector('button.btn-primary[type="submit"]');
- const closeButton = commentActions.querySelector('[name="comment_and_close"]');
- const buttons = [commentButton, closeButton].filter(Boolean);
- for (const button of buttons) {
- button.addEventListener("click", async () => {
- if (config[key]) {
- await new Promise((resolve) => setTimeout(resolve, 1000));
- clickAndClose();
- }
- });
- }
- }
- const mergeMessage = document.querySelector(".merge-message");
- if (mergeMessage) {
- const key = "markDoneAfterMerge";
- const label = document.createElement("label");
- const input = document.createElement("input");
- label.classList.add("color-fg-muted");
- input.type = "checkbox";
- input.checked = !!config[key];
- input.addEventListener("change", (e) => {
- // @ts-expect-error cast
- config[key] = !!e.target.checked;
- writeConfig();
- });
- label.appendChild(input);
- label.appendChild(document.createTextNode(" Mark done and close after merge"));
- Object.assign(label.style, {
- display: "flex",
- alignItems: "center",
- justifyContent: "end",
- gap: "5px",
- userSelect: "none",
- fontWeight: "400"
- });
- mergeMessage.prepend(label);
- /** @type {HTMLButtonElement[]} */
- const buttons = Array.from(mergeMessage.querySelectorAll(".js-auto-merge-box button"));
- for (const button of buttons) {
- button.addEventListener("click", async () => {
- if (config[key]) {
- await new Promise((resolve) => setTimeout(resolve, 1000));
- clickAndClose();
- }
- });
- }
- }
- document.body.appendChild(fab);
- document.addEventListener("keydown", keyDownHandle);
- cleanups.push(() => {
- document.body.removeChild(fab);
- document.removeEventListener("keydown", keyDownHandle);
- });
- return true;
- }
- // when first into the page, the notification shelf might not be loaded, we need to wait for it to show
- if (!inject()) {
- const observer = new MutationObserver((mutationList) => {
- /** @type {HTMLElement[]} */
- const addedNodes = /** @type {*} */ (Array.from(mutationList[0].addedNodes));
- const found = mutationList.some(
- (i) =>
- i.type === "childList" &&
- addedNodes.some((el) => el.classList.contains("js-notification-shelf"))
- );
- if (found) {
- inject();
- observer.disconnect();
- }
- });
- observer.observe(document.querySelector("[data-turbo-body]"), {
- childList: true
- });
- cleanups.push(() => {
- observer.disconnect();
- });
- }
- }
- function initBroadcastChannel() {
- bcInitTime = Date.now();
- bc = new BroadcastChannel("refined-github-notifications");
- bc.onmessage = ({ data }) => {
- if (isInNotificationPage()) {
- console.log(`[${NAME}]`, "Received message", data);
- if (data.type === "check-dedupe") {
- // If the new tab is opened after the current tab, close the current tab
- if (data.time > bcInitTime) {
- window.close();
- location.href = "https://close-me.netlify.app";
- }
- }
- }
- };
- }
- function dedupeTab() {
- if (!bc) return;
- bc.postMessage({
- type: "check-dedupe",
- time: bcInitTime,
- url: location.href
- });
- }
- function externalize() {
- document.querySelectorAll("a").forEach((r) => {
- if (r.href.startsWith("https://github.com/notifications")) return;
- // try to use the same tab
- r.target = r.href.replace("https://github.com", "").replace(/[\\/?#-]/g, "_");
- });
- }
- function initIdleListener() {
- // Auto refresh page on going back to the page
- document.addEventListener("visibilitychange", () => {
- if (document.visibilityState === "visible") refresh();
- });
- }
- function getIssues() {
- /** @type {HTMLDivElement[]} */
- const items = Array.from(document.querySelectorAll(".notifications-list-item"));
- return items
- .map((el) => {
- /** @type {HTMLLinkElement} */
- const linkEl = el.querySelector("a.notification-list-item-link");
- const url = linkEl.href;
- const status = el.querySelector(".color-fg-open")
- ? "open"
- : el.querySelector(".color-fg-done")
- ? "done"
- : el.querySelector(".color-fg-closed")
- ? "closed"
- : el.querySelector(".color-fg-muted")
- ? "muted"
- : "unknown";
- /** @type {HTMLDivElement | undefined} */
- const notificationTypeEl = /** @type {*} */ (
- el.querySelector(".AvatarStack").nextElementSibling
- );
- if (!notificationTypeEl) return null;
- const notificationType = notificationTypeEl.textContent.trim();
- /** @type {Item} */
- const item = {
- title: el.querySelector(".markdown-title").textContent.trim(),
- el,
- url,
- urlBare: url.replace(/[#?].*$/, ""),
- read: el.classList.contains("notification-read"),
- starred: el.classList.contains("notification-starred"),
- type: notificationType,
- status,
- isClosed: ["closed", "done", "muted"].includes(status),
- markDone: () => {
- console.log(`[${NAME}]`, "Mark notifications done", item);
- el.querySelector(
- "button[type=submit] .octicon-check"
- ).parentElement.parentElement.click();
- }
- };
- if (!el.classList.contains("enhanced-notification")) {
- // Colorize notification type
- if (notificationType === "mention") notificationTypeEl.classList.add("color-fg-open");
- else if (notificationType === "author")
- notificationTypeEl.style.color = "var(--color-scale-green-5)";
- else if (notificationType === "ci activity")
- notificationTypeEl.classList.add("color-fg-muted");
- else if (notificationType === "commented")
- notificationTypeEl.style.color = "var(--color-scale-blue-4)";
- else if (notificationType === "subscribed") notificationTypeEl.remove();
- else if (notificationType === "state change")
- notificationTypeEl.classList.add("color-fg-muted");
- else if (notificationType === "review requested")
- notificationTypeEl.classList.add("color-fg-done");
- // Remove plus one
- const plusOneEl = Array.from(el.querySelectorAll(".d-md-flex")).find((i) =>
- i.textContent.trim().startsWith("+")
- );
- if (plusOneEl) plusOneEl.remove();
- // Remove issue number
- if (HIDE_ISSUE_NUMBER.value) {
- const issueNo = linkEl.children[1]?.children?.[0]?.querySelector(".color-fg-muted");
- if (issueNo && issueNo.textContent.trim().startsWith("#")) issueNo.remove();
- }
- if (SHOW_DEATAILS.value || SHOW_REACTIONS.value) {
- fetchDetail(item).then((r) => {
- if (r) {
- if (SHOW_REACTIONS.value) registerReactions(item, r);
- if (SHOW_DEATAILS.value) registerPopup(item, r);
- }
- });
- }
- }
- el.classList.add("enhanced-notification");
- return item;
- })
- .filter(Boolean);
- }
- function getReasonMarkedDone(item) {
- if (item.isClosed && (item.read || item.type === "subscribed")) return "Closed / merged";
- if (item.title.startsWith("chore(deps): update ") && (item.read || item.type === "subscribed"))
- return "Renovate bot";
- if (item.url.match("/pull/[0-9]+/files/")) return "New commit pushed to PR";
- if (item.type === "ci activity" && /workflow run cancell?ed/.test(item.title))
- return "GH PR Audit Action workflow run cancelled, probably due to another run taking precedence";
- }
- function isInboxView() {
- const query = new URLSearchParams(window.location.search).get("query");
- if (!query) return true;
- const conditions = query.split(" ");
- return ["is:done", "is:saved"].every((condition) => !conditions.includes(condition));
- }
- function purgeCache() {
- const now = Date.now();
- Object.entries(detailsCache).forEach(([key, value]) => {
- if (now - value.lastUpdated > DETAILS_CACHE_TIMEOUT) delete detailsCache[key];
- });
- }
- /**
- * Add reactions count when there are more than 3 reactions
- *
- * @param {Item} item
- * @param {Subject} subject
- */
- function registerReactions(item, subject) {
- if ("reactions" in subject && subject.reactions) {
- const reactions = Object.entries(subject.reactions)
- .map(([k, v]) => ({ emoji: k, count: +v }))
- .filter((i) => i.count >= 3 && i.emoji !== "total_count");
- if (reactions.length) {
- const reactionsEl = document.createElement("div");
- reactionsEl.classList.add("Label");
- reactionsEl.classList.add("color-fg-muted");
- Object.assign(reactionsEl.style, {
- display: "flex",
- gap: "0.4em",
- alignItems: "center",
- marginRight: "-1.5em"
- });
- reactionsEl.append(
- ...reactions.map((i) => {
- const el = document.createElement("span");
- el.textContent = `${reactionsMap[i.emoji]} ${i.count}`;
- return el;
- })
- );
- const avatarStack = item.el.querySelector(".AvatarStack");
- avatarStack.parentElement.insertBefore(reactionsEl, avatarStack.nextElementSibling);
- }
- }
- }
- /** @type {HTMLElement | undefined} */
- let currentPopup;
- /** @type {Item | undefined} */
- let currentItem;
- /**
- * @param {Item} item
- * @param {Subject} subject
- */
- function registerPopup(item, subject) {
- if (!subject.body) return;
- /** @type {HTMLElement | undefined} */
- let popupEl;
- /** @type {HTMLElement} */
- const titleEl = item.el.querySelector(".markdown-title");
- async function initPopup() {
- const bodyHtml = await renderBody(item, subject);
- popupEl = document.createElement("div");
- popupEl.className = "Popover js-hovercard-content position-absolute";
- const bodyBoxEl = document.createElement("div");
- bodyBoxEl.className =
- "Popover-message Popover-message--large Box color-shadow-large Popover-message--top-right";
- // @ts-expect-error assign
- bodyBoxEl.style = "overflow: auto; width: 800px; max-height: 500px;";
- const contentEl = document.createElement("div");
- contentEl.className = "comment-body markdown-body js-comment-body";
- contentEl.innerHTML = bodyHtml;
- // @ts-expect-error assign
- contentEl.style = "padding: 1rem 1rem; transform-origin: left top;";
- if (subject.user) {
- const userAvatar = document.createElement("a");
- userAvatar.className = "author text-bold Link--primary";
- userAvatar.style.display = "flex";
- userAvatar.style.alignItems = "center";
- userAvatar.style.gap = "0.4em";
- userAvatar.href = subject.user?.html_url;
- userAvatar.innerHTML = `
- <img alt="@${subject.user?.login}" class="avatar avatar-user" height="18" src="${subject.user?.avatar_url}" width="18">
- <span>${subject.user.login}</span>
- `;
- const time = document.createElement("relative-time");
- // @ts-expect-error custom element
- time.datetime = subject.created_at;
- time.className = "color-fg-muted";
- time.style.marginLeft = "0.4em";
- const p = document.createElement("p");
- p.style.display = "flex";
- p.style.alignItems = "center";
- p.style.gap = "0.25em";
- p.append(userAvatar);
- p.append(time);
- contentEl.prepend(p);
- }
- bodyBoxEl.append(contentEl);
- popupEl.append(bodyBoxEl);
- popupEl.addEventListener("mouseenter", () => {
- popupShow();
- });
- popupEl.addEventListener("mouseleave", () => {
- if (currentPopup === popupEl) removeCurrent();
- });
- return popupEl;
- }
- /** @type {Promise<HTMLElement>} */
- let _promise;
- async function popupShow() {
- currentItem = item;
- _promise = _promise || initPopup();
- await _promise;
- removeCurrent();
- const box = titleEl.getBoundingClientRect();
- // @ts-expect-error assign
- popupEl.style = `display: block; outline: none; top: ${
- box.top + box.height + window.scrollY + 5
- }px; left: ${box.left - 10}px; z-index: 100;`;
- document.body.append(popupEl);
- currentPopup = popupEl;
- }
- function removeCurrent() {
- if (currentPopup && Array.from(document.body.children).includes(currentPopup))
- document.body.removeChild(currentPopup);
- }
- titleEl.addEventListener("mouseenter", popupShow);
- titleEl.addEventListener("mouseleave", () => {
- if (currentItem === item) currentItem = undefined;
- setTimeout(() => {
- if (!currentItem) removeCurrent();
- }, 500);
- });
- }
- /**
- * @param {Item[]} items
- */
- function autoMarkDone(items) {
- console.info(`[${NAME}] ${items.length} notifications found`);
- console.table(items);
- let count = 0;
- const done = [];
- items.forEach((i) => {
- // skip bookmarked notifications
- if (i.starred) return;
- const reason = getReasonMarkedDone(i);
- if (!reason) return;
- count++;
- i.markDone();
- done.push({
- title: i.title,
- reason,
- url: i.url
- });
- });
- if (done.length) {
- console.log(`[${NAME}]`, `${count} notifications marked done`);
- console.table(done);
- }
- // Refresh page after marking done (expand the pagination)
- if (count >= 5) setTimeout(() => refresh(), 200);
- }
- function removeBotAvatars() {
- /** @type {HTMLLinkElement[]} */
- const avatars = Array.from(document.querySelectorAll(".AvatarStack-body > a"));
- avatars.forEach((r) => {
- if (r.href.startsWith("/apps/") || r.href.startsWith("https://github.com/apps/")) r.remove();
- });
- }
- /**
- * The "x new notifications" badge
- */
- function hasNewNotifications() {
- return !!document.querySelector('.js-updatable-content a[href="/notifications?query="]');
- }
- function cleanup() {
- cleanups.forEach((fn) => fn());
- cleanups.length = 0;
- }
- // Click the notification tab to do soft refresh
- function refresh() {
- if (!isInNotificationPage()) return;
- /** @type {HTMLButtonElement} */
- const button = document.querySelector('.filter-list a[href="/notifications"]');
- button.click();
- }
- function isInNotificationPage() {
- return location.href.startsWith("https://github.com/notifications");
- }
- function initNewNotificationsObserver() {
- try {
- const observer = new MutationObserver(() => {
- if (hasNewNotifications()) refresh();
- });
- observer.observe(document.querySelector(".js-check-all-container").children[0], {
- childList: true,
- subtree: true
- });
- } catch (e) {
- //
- }
- }
- /**
- * @param {Item} item
- */
- async function fetchDetail(item) {
- if (detailsCache[item.urlBare]?.subject) return detailsCache[item.urlBare].subject;
- console.log(`[${NAME}]`, "Fetching issue details", item);
- const apiUrl = item.urlBare
- .replace("https://github.com", "https://api.github.com/repos")
- .replace("/pull/", "/pulls/");
- if (!apiUrl.includes("/issues/") && !apiUrl.includes("/pulls/")) return;
- try {
- /** @type {Subject} */
- const data = await fetch(apiUrl, {
- headers: {
- "Content-Type": "application/vnd.github+json",
- Authorization: GITHUB_TOKEN ? `Bearer ${GITHUB_TOKEN}` : undefined
- }
- }).then((r) => r.json());
- detailsCache[item.urlBare] = {
- url: item.urlBare,
- lastUpdated: Date.now(),
- subject: data
- };
- localStorage.setItem(STORAGE_KEY_DETAILS, JSON.stringify(detailsCache));
- return data;
- } catch (e) {
- console.error(`[${NAME}]`, `Failed to fetch issue details of ${item.urlBare}`, e);
- }
- }
- /**
- * @param {Item} item
- * @param {Subject} subject
- */
- async function renderBody(item, subject) {
- if (!subject.body) return;
- if (detailsCache[item.urlBare]?.bodyHtml) return detailsCache[item.urlBare].bodyHtml;
- const repoName = subject.repository?.full_name || item.urlBare.split("/").slice(3, 5).join("/");
- const bodyHtml = await fetch("https://api.github.com/markdown", {
- method: "POST",
- body: JSON.stringify({
- text: subject.body,
- mode: "gfm",
- context: repoName
- }),
- headers: {
- "Content-Type": "application/vnd.github+json",
- Authorization: GITHUB_TOKEN ? `Bearer ${GITHUB_TOKEN}` : undefined
- }
- }).then((r) => r.text());
- if (detailsCache[item.urlBare]) {
- detailsCache[item.urlBare].bodyHtml = bodyHtml;
- localStorage.setItem(STORAGE_KEY_DETAILS, JSON.stringify(detailsCache));
- }
- return bodyHtml;
- }
- ////////////////////////////////////////
- let initialized = false;
- function run() {
- cleanup();
- if (isInNotificationPage()) {
- // Run only once
- if (!initialized) {
- initIdleListener();
- initBroadcastChannel();
- initNewNotificationsObserver();
- initialized = true;
- }
- const items = getIssues();
- // Run every render
- dedupeTab();
- externalize();
- removeBotAvatars();
- // Only mark on "Inbox" view
- if (isInboxView() && AUTO_MARK_DONE.value) autoMarkDone(items);
- } else {
- if (ENHANCE_NOTIFICATION_SHELF.value) enhanceNotificationShelf();
- }
- }
- injectStyle();
- purgeCache();
- run();
- // listen to github page loaded event
- document.addEventListener("pjax:end", () => run());
- document.addEventListener("turbo:render", () => run());
- })();