GitLab MR Viewed File Counter

Displays viewed/total file count on GitLab merge request pages

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         GitLab MR Viewed File Counter
// @namespace    http://tampermonkey.net/
// @version      1.0.1
// @description  Displays viewed/total file count on GitLab merge request pages
// @match        https://gitlab.com/*/-/merge_requests/*
// @grant        none
// @run-at       document-idle
// @author       bzly
// @license      GPL-3.0-or-later
// ==/UserScript==

(function () {
  'use strict';

  const badge = document.querySelector('.js-changes-tab-count');
  let total = badge.textContent.trim();
  const mrPath = window.location.pathname.match(/.*\/merge_requests\/\d+/)?.[0];
  const mrKey = `code-review-${mrPath}`;
  const pill = document.querySelector('.diffs-tab .gl-badge');

  function getViewedCount() {
    try {
      const viewed = JSON.parse(localStorage.getItem(mrKey));
      return Array.isArray(viewed) ? viewed.length : 0;
    } catch {
      return 0;
    }
  }

  function updateBadge() {
    const viewed = getViewedCount();
    const pct = total > 0 ? Math.round((viewed / total) * 100) : 0;
    badge.textContent = `${viewed}/${total}`;
    if (viewed > 0) {
        // when any files are marked as viewed, gradually turn from the GitLab 'closed' colour to the 'merged' colour.
        pill.style.backgroundColor = `color-mix(in srgb, var(--gl-badge-success-background-color-default) ${pct}%, var(--gl-badge-danger-background-color-default))`;
    } else {
       pill.style.backgroundColor = 'var(--gl-badge-neutral-background-color-default)';
    }
  }

  // update count when GitLab changes the element back at random times
  new MutationObserver(() => {
    if (!badge.textContent.includes('/')) {
      total = badge.textContent.trim();
      updateBadge();
    }
  }).observe(badge, { childList: true, characterData: true, subtree: true });

  // update count on same-tab changes
  const _setItem = localStorage.setItem.bind(localStorage);
  localStorage.setItem = function (key, value) {
    _setItem(key, value);
    if (key === mrKey) updateBadge();
  };

  // update count when going back to 0 viewed
  const _removeItem = localStorage.removeItem.bind(localStorage);
  localStorage.removeItem = function (key) {
    _removeItem(key);
    if (key === mrKey) updateBadge();
  };

  // update count on different-tab change (i.e. you are working across multiple tabs)
  window.addEventListener('storage', (e) => {
    if (e.key === mrKey) updateBadge();
  });
  
  updateBadge();
})();