Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ jobs:

- run: npm run format:check

- run: npm test
- run: npm run typecheck

- run: npm run coverage

- run: npm run build
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ Thumbs.db
*.zip
*.xpi
node_modules/
coverage/
6 changes: 6 additions & 0 deletions background.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// @ts-check
// GitHub PR Reverse Comments — background service worker
//
// Single job: keep the toolbar icon in sync with whether the current tab
Expand All @@ -19,10 +20,15 @@ const DISABLED_PATH = { 16: "icon-16-disabled.png", 48: "icon-48-disabled.png" }
const ACTIVE_URL_RE =
/^https:\/\/github\.com\/[^/]+\/[^/]+\/pull\/\d+(?:\/commits)?\/?(?:[?#].*)?$/;

/** @param {unknown} url */
function isActiveUrl(url) {
return typeof url === "string" && ACTIVE_URL_RE.test(url);
}

/**
* @param {number | undefined} tabId
* @param {string | undefined} url
*/
function setIconForTab(tabId, url) {
const path = isActiveUrl(url) ? ACTIVE_PATH : DISABLED_PATH;
chrome.action.setIcon({ tabId, path }).catch(() => {
Expand Down
5 changes: 5 additions & 0 deletions checks.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// @ts-check
// PR status-checks detection for the Conversation page (issue #1).
//
// GitHub renders the PR's status checks in the merge box near the BOTTOM
Expand All @@ -23,6 +24,7 @@
// names are CSS-module-hashed (`MergeBox-module__mergePartialContainer__x`)
// so we match on the stable human-readable prefix and fall back through
// a couple of related containers.
/** @param {ParentNode} [root] */
function findChecksBox(root) {
const scope = root || document;
return (
Expand All @@ -34,6 +36,7 @@
}

// The accessible labels of the individual check rows within the box.
/** @param {ParentNode} [root] */
function getCheckLabels(root) {
const scope = root || document;
const box = findChecksBox(scope) || scope;
Expand All @@ -46,8 +49,10 @@
// Precedence: any failure -> failing; else any in-flight -> running;
// else any success -> passing; else unknown. Checked in that order so a
// single red check dominates the summary, matching GitHub's own rollup.
/** @param {string[] | string} labels */
function deriveChecksState(labels) {
const list = Array.isArray(labels) ? labels : [labels || ""];
/** @param {RegExp} re */
const any = (re) => list.some((l) => re.test(l));

if (any(/(fail|error|timed out|cancel|denied|action required)/i)) {
Expand Down
1 change: 1 addition & 0 deletions constants.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// @ts-check
// Shared constants for the extension.
//
// Loaded as a plain (non-module) script before content.js and popup.js.
Expand Down
16 changes: 12 additions & 4 deletions content.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// @ts-check
// GitHub PR Reverse Comments — content script
//
// Reverses chronological lists on GitHub PR pages so the newest entry
Expand Down Expand Up @@ -136,7 +137,9 @@

let currentOrder = ORDER.NEWEST;
let isSorting = false;
/** @type {MutationObserver[]} */
let observers = [];
/** @type {PrrcTarget[]} */
let activeTargets = []; // [{ el, item, ... }]

function getCurrentPageConfig() {
Expand All @@ -148,6 +151,7 @@
return getCurrentPageConfig() !== null;
}

/** @param {string} order */
function applyOrder(order) {
const cfg = getCurrentPageConfig();
if (!cfg) return;
Expand Down Expand Up @@ -246,7 +250,7 @@
}

function scrollToChecksBox() {
const box = findChecksBox();
const box = /** @type {HTMLElement | null} */ (findChecksBox());
if (!box) return;
box.scrollIntoView({ behavior: "smooth", block: "center" });
box.style.outline = "2px solid #1f6feb";
Expand All @@ -271,7 +275,9 @@
}

const state = deriveChecksState(getCheckLabels());
const indicator = existing || document.createElement("button");
const indicator = /** @type {HTMLButtonElement} */ (
existing || document.createElement("button")
);
if (!existing) {
indicator.id = CHECKS_STATUS_ID;
indicator.type = "button";
Expand Down Expand Up @@ -303,14 +309,15 @@
}
}

/** @param {HTMLElement} btn */
function updateButtonLabel(btn) {
btn.textContent = currentOrder === ORDER.NEWEST ? "↓ Newest first" : "↑ Oldest first";
btn.title = `Click to switch to ${currentOrder === ORDER.NEWEST ? ORDER.OLDEST : ORDER.NEWEST} first`;
}

chrome.storage.onChanged.addListener((changes, area) => {
if (area !== "local" || !changes[STORAGE_KEY]) return;
currentOrder = changes[STORAGE_KEY].newValue || ORDER.NEWEST;
currentOrder = /** @type {string} */ (changes[STORAGE_KEY].newValue || ORDER.NEWEST);
const btn = document.getElementById(BUTTON_ID);
if (btn) updateButtonLabel(btn);
applyOrder(currentOrder);
Expand Down Expand Up @@ -343,6 +350,7 @@
injectOrUpdateChecksIndicator();

const cfg = getCurrentPageConfig();
if (!cfg) return;
const freshTargets = cfg.getTargets();
if (!freshTargets.length) return;

Expand Down Expand Up @@ -373,7 +381,7 @@
await chrome.storage.local.set({ [RESET_VERSION_KEY]: CURRENT_RESET_VERSION });
currentOrder = ORDER.NEWEST;
} else {
currentOrder = stored[STORAGE_KEY] || ORDER.NEWEST;
currentOrder = /** @type {string} */ (stored[STORAGE_KEY] || ORDER.NEWEST);
}

startBodyWatcher();
Expand Down
Loading