Skip to content

Commit

Permalink
bump v1.2.0
Browse files Browse the repository at this point in the history
  • Loading branch information
giacomoferretti committed Aug 1, 2022
1 parent 77fd6ce commit 106d8db
Show file tree
Hide file tree
Showing 6 changed files with 202 additions and 195 deletions.
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@
same "printed page" as the copyright notice for easier
identification within third-party archives.

Copyright 2020-2021 Giacomo Ferretti
Copyright 2020-2022 Giacomo Ferretti

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
Expand Down
24 changes: 21 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,27 @@
# Forks Diff Counter for GitHub

## Download
## Installation

Get the extension on the [chrome web store](https://chrome.google.com/webstore/detail/eencojgimolmahmdfpnfbcldppmlokfg).
### Chrome Web Store

Get the extension on the [Chrome Web Store](https://chrome.google.com/webstore/detail/eencojgimolmahmdfpnfbcldppmlokfg).

### Firefox Browser Add-ons

Get the extension on the [Firefox Browser Add-ons](https://addons.mozilla.org/en-US/firefox/addon/forks-diff-counter-for-github/).

### Unpacked

#### Google Chrome

1. Download the repo by using `git clone https://github.com/giacomoferretti/forks-diff-chrome`
2. Visit `chrome://extensions/` in Google Chrome
3. Enable "Developer mode"
4. Click on "Load unpacked"
5. Select the cloned folder

When updating the plugin, remember to reload it.

## How to pack

Use the `pack.sh` script to generate the zip file.
Use the `pack.sh` script to generate the zip file.
19 changes: 12 additions & 7 deletions js/inject.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
/**
* Because we are running in an "isolated world" and need to hook 'replaceState',
* Because we are running in an "isolated world" and need to hook 'replaceState',
* we need to inject our script into the page.
*
* https://developer.chrome.com/docs/extensions/mv3/content_scripts/#isolated_world
*/
(function () {
var s = document.createElement('script');
s.src = chrome.runtime.getURL('js/main.js');
s.onload = function() {
this.remove();
};
(document.head || document.documentElement).appendChild(s);
// ...chrome pls...
if (typeof browser === "undefined") {
var browser = chrome;
}

const s = document.createElement("script");
s.src = browser.runtime.getURL("js/main.js");
s.onload = function () {
this.remove();
};
(document.head || document.documentElement).appendChild(s);
})();
299 changes: 142 additions & 157 deletions js/main.js
Original file line number Diff line number Diff line change
@@ -1,167 +1,152 @@
'use strict';

const forksDiff = (function() {
// Icons
const starIcon = '<svg style="" aria-hidden="true" viewBox="0 0 16 16" version="1.1" height="16" width="16" class="octicon octicon-star"><path fill-rule="evenodd" d="M8 .25a.75.75 0 01.673.418l1.882 3.815 4.21.612a.75.75 0 01.416 1.279l-3.046 2.97.719 4.192a.75.75 0 01-1.088.791L8 12.347l-3.766 1.98a.75.75 0 01-1.088-.79l.72-4.194L.818 6.374a.75.75 0 01.416-1.28l4.21-.611L7.327.668A.75.75 0 018 .25zm0 2.445L6.615 5.5a.75.75 0 01-.564.41l-3.097.45 2.24 2.184a.75.75 0 01.216.664l-.528 3.084 2.769-1.456a.75.75 0 01.698 0l2.77 1.456-.53-3.084a.75.75 0 01.216-.664l2.24-2.183-3.096-.45a.75.75 0 01-.564-.41L8 2.694v.001z"></path></svg>';
const forkIcon = '<svg style="margin-left: 0;" aria-hidden="true" viewBox="0 0 16 16" version="1.1" height="16" width="16" class="octicon octicon-repo-forked"><path fill-rule="evenodd" d="M5 3.25a.75.75 0 11-1.5 0 .75.75 0 011.5 0zm0 2.122a2.25 2.25 0 10-1.5 0v.878A2.25 2.25 0 005.75 8.5h1.5v2.128a2.251 2.251 0 101.5 0V8.5h1.5a2.25 2.25 0 002.25-2.25v-.878a2.25 2.25 0 10-1.5 0v.878a.75.75 0 01-.75.75h-4.5A.75.75 0 015 6.25v-.878zm3.75 7.378a.75.75 0 11-1.5 0 .75.75 0 011.5 0zm3-8.75a.75.75 0 100-1.5.75.75 0 000 1.5z"></path></svg>';
const loadingIcon = '<svg style="box-sizing: content-box; color: var(--color-icon-primary); margin-bottom: 0 !important; vertical-align: middle;" viewBox="0 0 16 16" fill="none" data-view-component="true" width="16" height="16" class="graph-loading dots mb-2 anim-rotate"><circle cx="8" cy="8" r="7" stroke="currentColor" stroke-opacity="0.25" stroke-width="2" vector-effect="non-scaling-stroke"></circle><path d="M15 8a7.002 7.002 0 00-7-7" stroke="currentColor" stroke-width="2" stroke-linecap="round" vector-effect="non-scaling-stroke"></path></svg>';

// Regex
const isEvenRegex = /<div class="d-flex flex-auto">[\s]+This branch is even/;
const commitsAheadRegex = /<div class="d-flex flex-auto">[\s]+This branch is ([0-9]*) commits? ahead/;
const commitsBehindRegex = /<div class="d-flex flex-auto">[\s]+This branch is ([0-9]*) commits? behind/;
const commitsFullRegex = /<div class="d-flex flex-auto">[\s]+This branch is ([0-9]*) commits? ahead, ([0-9]*) commit/;
const starsRegex = /([0-9]+) users? starred this repository/;
const githubUrlRegex = /https?:\/\/github\.com\/.*\/network\/members/;

function appendSpace(e) {
e.appendChild(document.createTextNode(' '));
}

function processRepo(repoElement) {
// Add spinner
const spinner = document.createElement('span');
spinner.innerHTML = loadingIcon;
repoElement.appendChild(spinner);

// Extract repo URL
const repoUrl = repoElement.getElementsByTagName('a')[2].href;

// Prepare request
const request = new XMLHttpRequest();
request.addEventListener('load', function() {
repoElement.removeChild(spinner);

const body = this.responseText;

let commitsAhead = 0;
let commitsBehind = 0;

// Check if repo is even
if (isEvenRegex.test(body)) {
const evenSpan = document.createElement('span');
evenSpan.className = 'text-gray';
evenSpan.appendChild(document.createTextNode('is even'));
repoElement.appendChild(evenSpan);
} else {
// Extract ahead and behind
let commitHistoryData = body.match(commitsFullRegex);
if (commitHistoryData != null) {
commitsAhead = parseInt(commitHistoryData[1]);
commitsBehind = parseInt(commitHistoryData[2]);
} else {
// Extract ahead
commitHistoryData = body.match(commitsAheadRegex);
if (commitHistoryData != null) {
commitsAhead = parseInt(commitHistoryData[1]);
}

// Extract behind
commitHistoryData = body.match(commitsBehindRegex);
if (commitHistoryData != null) {
commitsBehind = parseInt(commitHistoryData[1]);
}
}

// Add ahead commits
if (commitsAhead != 0) {
appendSpace(repoElement);
const commitsAheadText = document.createElement('span');
commitsAheadText.className = 'cadd';
commitsAheadText.appendChild(document.createTextNode('+' + commitsAhead));
repoElement.appendChild(commitsAheadText);
}

// Add behind commits
if (commitsBehind != 0) {
appendSpace(repoElement);
const commitsBehindCounter = document.createElement('span');
commitsBehindCounter.className = 'cdel';
commitsBehindCounter.appendChild(document.createTextNode('-' + commitsBehind));
repoElement.appendChild(commitsBehindCounter);
}
}

// Read stars
appendSpace(repoElement);
const stars = body.match(starsRegex);
const starIndicator = document.createElement('span');
starIndicator.innerHTML = starIcon + ' ' + stars[1];
repoElement.appendChild(starIndicator);
});

// Send request
request.open('GET', repoUrl);
request.send();
}

function mainButtonAction(e) {
// Disable button
e.target.setAttribute('class', 'btn btn-sm disabled');
e.target.removeEventListener('click', mainButtonAction);

// Iterate through repos
const repos = network.children;
for (let i = 0; i < repos.length; i++) {
if (repos[i].getElementsByClassName('network-tree').length === 0) continue; // Skip original

processRepo(repos[i]);
"use strict";

const forksDiff = (function () {
// Icons
const starIcon =
'<svg aria-hidden="true" viewBox="0 0 16 16" version="1.1" height="16" width="16" class="octicon octicon-star"><path fill-rule="evenodd" d="M8 .25a.75.75 0 01.673.418l1.882 3.815 4.21.612a.75.75 0 01.416 1.279l-3.046 2.97.719 4.192a.75.75 0 01-1.088.791L8 12.347l-3.766 1.98a.75.75 0 01-1.088-.79l.72-4.194L.818 6.374a.75.75 0 01.416-1.28l4.21-.611L7.327.668A.75.75 0 018 .25zm0 2.445L6.615 5.5a.75.75 0 01-.564.41l-3.097.45 2.24 2.184a.75.75 0 01.216.664l-.528 3.084 2.769-1.456a.75.75 0 01.698 0l2.77 1.456-.53-3.084a.75.75 0 01.216-.664l2.24-2.183-3.096-.45a.75.75 0 01-.564-.41L8 2.694v.001z"></path></svg>';
const forkIcon =
'<svg aria-hidden="true" style="margin-left: 0;" viewBox="0 0 16 16" version="1.1" height="16" width="16" class="octicon octicon-repo-forked"><path fill-rule="evenodd" d="M5 3.25a.75.75 0 11-1.5 0 .75.75 0 011.5 0zm0 2.122a2.25 2.25 0 10-1.5 0v.878A2.25 2.25 0 005.75 8.5h1.5v2.128a2.251 2.251 0 101.5 0V8.5h1.5a2.25 2.25 0 002.25-2.25v-.878a2.25 2.25 0 10-1.5 0v.878a.75.75 0 01-.75.75h-4.5A.75.75 0 015 6.25v-.878zm3.75 7.378a.75.75 0 11-1.5 0 .75.75 0 011.5 0zm3-8.75a.75.75 0 100-1.5.75.75 0 000 1.5z"></path></svg>';
const loadingIcon =
'<svg aria-hidden="true" style="box-sizing: content-box; color: var(--color-icon-primary); vertical-align: middle;" width="16" height="16" viewBox="0 0 16 16" fill="none" class="status-indicator-spinner anim-rotate"><circle cx="8" cy="8" r="7" stroke="currentColor" stroke-opacity="0.25" stroke-width="2" vector-effect="non-scaling-stroke"></circle><path d="M15 8a7.002 7.002 0 00-7-7" stroke="currentColor" stroke-width="2" stroke-linecap="round" vector-effect="non-scaling-stroke"></path></svg>';

// Regex
const diffRegex =
/<div class="d-flex flex-auto">[\S\s]*?This branch is[\S\s]*?((?<a1>[0-9]*) commits? ahead[\S\s]*?(?<a2>[0-9]*) commits? behind|(?<b>[0-9]*) commits? behind|(?<c>[0-9]*) commits? ahead)/;
const starsRegex = /([0-9]+) users? starred this repository/;
const githubUrlRegex = /https?:\/\/github\.com\/.*\/network\/members/;

const queue = [];
const parallelNum = 3;

const addSpan = (parent, text, className) => {
const span = document.createElement("span");
span.className = className;
span.appendChild(document.createTextNode(text));
parent.appendChild(span);
};

const processRepo = () => {
const currentRepo = queue.shift();

// Add spinner
const spinner = document.createElement("span");
spinner.innerHTML = loadingIcon;
currentRepo.appendChild(spinner);

const request = new XMLHttpRequest();
request.addEventListener("load", function () {
if (this.status == 429) {
// Rate limited :(
console.log(this.getResponseHeader("Retry-After"));
return;
}

const body = this.responseText;
currentRepo.removeChild(spinner);

// Read diff
const diffRegexResult = diffRegex.exec(body);
if (!diffRegexResult) {
addSpan(currentRepo, "is even", "text-gray");
} else {
const {
groups: { a1, a2, b, c },
} = diffRegexResult;

if (a1 && a2) {
addSpan(currentRepo, "+" + a1, "cadd");
addSpan(currentRepo, " ");
addSpan(currentRepo, "-" + a2, "cdel");
} else if (b) {
addSpan(currentRepo, "-" + b, "cdel");
} else if (c) {
addSpan(currentRepo, "+" + c, "cadd");
}
}

// Read stars
addSpan(currentRepo, " ");
const stars = body.match(starsRegex);
const starIndicator = document.createElement("span");
starIndicator.innerHTML = starIcon + " " + stars[1];
currentRepo.appendChild(starIndicator);

// Process next repo
if (queue.length > 0) {
processRepo();
}
});

// Send request
request.open("GET", currentRepo.getElementsByTagName("a")[2].href);
request.send();
};

const mainButtonAction = (e) => {
// Disable button
e.target.className = "btn ml-2 float-right disabled";
e.target.removeEventListener("click", mainButtonAction);

// Iterate through repos
const repos = network.children;
for (let i = 0; i < repos.length; i++) {
if (repos[i].getElementsByClassName("network-tree").length === 0)
continue; // Skip original

queue.push(repos[i]);
}

function addButton() {
const network = document.getElementById('network');

// Check if we have at least one div.repo, if not we are on Network page and not Forks page
if (network === null) return;
if (network.querySelector('div.repo') === null) return;

const mainButton = document.createElement('button');
mainButton.className = 'btn btn-sm';
mainButton.style.float = 'right';
mainButton.innerHTML = forkIcon + ' Load diff';
mainButton.addEventListener('click', mainButtonAction);
network.insertBefore(mainButton, network.childNodes[0]);
}

function addReplaceStateEventListener() {
// https://gist.github.com/rudiedirkx/fd568b08d7bffd6bd372
const _wr = function(type) {
const orig = history[type];
return function() {
const rv = orig.apply(this, arguments);
const e = new Event(type);
e.arguments = arguments;
window.dispatchEvent(e);
return rv;
};
};
history.pushState = _wr('pushState');
history.replaceState = _wr('replaceState');
// Start
for (let i = parallelNum - 1; i >= 0; i--) {
processRepo();
}
};

const addButton = () => {
const network = document.getElementById("network");

// Check if we have at least one div.repo, if not we are on Network page and not Forks page
if (network === null) return;
if (network.querySelector("div.repo") === null) return;

const mainButton = document.createElement("button");
mainButton.className = "btn ml-2 float-right";
mainButton.innerHTML = forkIcon + " Load diff";
mainButton.addEventListener("click", mainButtonAction);
network.insertBefore(mainButton, network.childNodes[0]);
};

const addReplaceStateEventListener = () => {
// https://gist.github.com/rudiedirkx/fd568b08d7bffd6bd372
const _wr = (type) => {
const orig = history[type];
return () => {
const rv = orig.apply(this, arguments);
const e = new Event(type);
e.arguments = arguments;
window.dispatchEvent(e);
return rv;
};
};
history.pushState = _wr("pushState");
history.replaceState = _wr("replaceState");
};

function replaceStateListener() {
function action() {
addButton();
document.removeEventListener('pjax:end', action);
}

if (githubUrlRegex.test(location.href)) {
document.addEventListener('pjax:end', action);
}
const replaceStateListener = () => {
if (githubUrlRegex.test(location.href)) {
addButton();
}
};

return {
addButton: addButton,
addReplaceStateEventListener: addReplaceStateEventListener,
replaceStateListener: replaceStateListener,
};
return {
addButton: addButton,
addReplaceStateEventListener: addReplaceStateEventListener,
replaceStateListener: replaceStateListener,
};
})();

(function() {
// Add 'replaceState' listener
forksDiff.addReplaceStateEventListener();
window.addEventListener('replaceState', forksDiff.replaceStateListener);

forksDiff.addButton();
(function () {
// Add 'replaceState' listener
forksDiff.addReplaceStateEventListener();
window.addEventListener("replaceState", forksDiff.replaceStateListener);
})();
Loading

0 comments on commit 106d8db

Please sign in to comment.