diff --git a/manifest.template.json b/manifest.template.json
index a521e84..2db449f 100644
--- a/manifest.template.json
+++ b/manifest.template.json
@@ -7,6 +7,10 @@
"icons": {
"128": "webext/icons/Heart_and_fork_inside_128.png"
},
+ "web_accessible_resources": [
+ "webext/icons/star.svg",
+ "webext/icons/flame.svg"
+ ],
"permissions": [
"*://github.com/*",
"*://api.github.com/*"
diff --git a/webext/data/contentscript.js b/webext/data/contentscript.js
index dbb1d07..8191deb 100644
--- a/webext/data/contentscript.js
+++ b/webext/data/contentscript.js
@@ -1,41 +1,20 @@
-'use strict';
/*jshint browser: true, es5: true, sub:true */
-var _logName = 'lovely-forks:';
-var DEBUG = false;
-var text;
-
-var svgNS = 'http://www.w3.org/2000/svg';
+const _logName = 'lovely-forks:';
+const DEBUG = false;
+let text;
function createIconSVG(type) {
- var svg = document.createElementNS(svgNS, 'svg');
- svg.setAttributeNS(null, 'height', 12);
- svg.setAttributeNS(null, 'width', 10.5);
- svg.setAttributeNS(null, 'viewBox', '0 0 14 16');
- svg.style['vertical-align'] = 'bottom';
- svg.style['fill'] = 'currentColor';
-
- svg.classList.add('opticon', 'opticon-' + type);
-
- var title = document.createElementNS(svgNS, 'title');
-
- var iconPath = document.createElementNS(svgNS, 'path');
- switch(type) {
- case 'star':
- title.appendChild(document.createTextNode('Number of stars'));
- iconPath.setAttributeNS(null, 'd', 'M14 6l-4.9-0.64L7 1 4.9 5.36 0 6l3.6 3.26L2.67 14l4.33-2.33 4.33 2.33L10.4 9.26 14 6z');
- break;
- case 'flame':
- title.appendChild(document.createTextNode('Fork may be more recent than upstream.'));
- iconPath.setAttributeNS(null, 'd', 'M5.05 0.31c0.81 2.17 0.41 3.38-0.52 4.31-0.98 1.05-2.55 1.83-3.63 3.36-1.45 2.05-1.7 6.53 3.53 7.7-2.2-1.16-2.67-4.52-0.3-6.61-0.61 2.03 0.53 3.33 1.94 2.86 1.39-0.47 2.3 0.53 2.27 1.67-0.02 0.78-0.31 1.44-1.13 1.81 3.42-0.59 4.78-3.42 4.78-5.56 0-2.84-2.53-3.22-1.25-5.61-1.52 0.13-2.03 1.13-1.89 2.75 0.09 1.08-1.02 1.8-1.86 1.33-0.67-0.41-0.66-1.19-0.06-1.78 1.25-1.23 1.75-4.09-1.88-6.22l-0.02-0.02z');
- iconPath.setAttributeNS(null, 'fill', '#d26911');
- break;
+ const icon = document.createElement('img');
+ if (type === 'star') {
+ icon.title = 'Number of stars';
+ } else if (type === 'flame') {
+ icon.title = 'Fork may be more recent than upstream.';
+ } else {
+ return icon;
}
-
- iconPath.appendChild(title);
- svg.appendChild(iconPath);
-
- return svg;
+ icon.src = chrome.extension.getURL(`webext/icons/${type}.svg`);
+ return icon;
}
function emptyElem(elem) {
@@ -47,34 +26,30 @@ function mbStrToMs(dateStr) {
}
function isExpired(timeMs) {
- var currentTime = new Date();
+ const currentTime = new Date();
// The time of expiry of data is set to be an hour ago
- var expiryTimeMs = currentTime.valueOf() - 1000 * 60 * 60;
+ const expiryTimeMs = currentTime.valueOf() - 1000 * 60 * 60;
return timeMs < expiryTimeMs;
}
function makeSelfDataKey(user, repo) {
- return 'lovely-forks@self:' + user + '/' + repo;
+ return `lovely-forks@self:${user}/${repo}`;
}
function makeRemoteDataKey(user, repo) {
- return 'lovely-forks@remote:' + user + '/' + repo;
+ return `lovely-forks@remote:${user}/${repo}`;
}
-var reDateKey = new RegExp('^lovely-forks@date:(.*)/(.*)$');
+const reDateKey = /^lovely-forks@date:(.*)[/](.*)$/;
function makeTimeKey(user, repo) {
- return 'lovely-forks@date:' + user + '/' + repo;
+ return `lovely-forks@date:${user}/${repo}`;
}
function parseTimeKey(key) {
- var match = reDateKey.exec(key);
- if (match !== null) {
- return [match[1], match[2]];
- } else {
- return null;
- }
+ const [, user, repo] = reDateKey.exec(key) || [];
+ return {user, repo};
}
function getForksElement() {
@@ -86,7 +61,7 @@ function getForksElement() {
// If the layout of the page changes, we'll have to change this location.
// We should make sure that we do not accidentally cause errors here.
- var repoName = document.querySelector('.repohead-details-container .public');
+ const repoName = document.querySelector('.repohead-details-container .public');
if (repoName) {
try {
text = document.createElement('span');
@@ -94,7 +69,7 @@ function getForksElement() {
// Stealing the styling from GitHub fork-info
text.classList.add('fork-flag', 'lovely-forks-addon');
- repoName.appendChild(text);
+ repoName.append(text);
return text;
} catch (e) {
@@ -109,23 +84,18 @@ function getForksElement() {
}
function clearLocalStorage() {
- var keysToUnset = [];
-
/* Remove all items which have expired. */
- for(var ii = 0; ii < localStorage.length; ii++) {
- var key = localStorage.key(ii);
- var mbUserRepo = parseTimeKey(key);
- if (mbUserRepo !== null) {
-
- var timeMs = mbStrToMs(localStorage.getItem(key));
+ for(let ii = 0; ii < localStorage.length; ii++) {
+ const key = localStorage.key(ii);
+ const {user, repo} = parseTimeKey(key);
+ if (user && repo) {
+ const timeMs = mbStrToMs(localStorage.getItem(key));
if (timeMs) {
if (isExpired(timeMs)) {
- var user = mbUserRepo[0],
- repo = mbUserRepo[1];
- keysToUnset.push(makeRemoteDataKey(user, repo));
- keysToUnset.push(makeSelfDataKey(user, repo));
- keysToUnset.push(makeTimeKey(user, repo));
+ removeFromLocalStorage(makeRemoteDataKey(user, repo));
+ removeFromLocalStorage(makeSelfDataKey(user, repo));
+ removeFromLocalStorage(makeTimeKey(user, repo));
}
} else {
console.warn(_logName,
@@ -134,19 +104,19 @@ function clearLocalStorage() {
}
}
}
+}
- keysToUnset.forEach(function (key) {
- if (DEBUG) {
- console.log(_logName,
- 'Removing key: ', key);
- }
- localStorage.removeItem(key);
- });
+function removeFromLocalStorage(key) {
+ if (DEBUG) {
+ console.log(_logName,
+ 'Removing key: ', key);
+ }
+ localStorage.removeItem(key);
}
function safeUpdateDOM(action, actionName) {
// Get the stored version or create it if it doesn't exist
- var text = getForksElement();
+ const text = getForksElement();
// We should make sure that we do not accidentally cause errors here.
if (text) {
@@ -166,41 +136,31 @@ function safeUpdateDOM(action, actionName) {
}
function showDetails(fullName, url, numStars, remoteIsNewer) {
- return function (text) {
- var forkA = document.createElement('a');
- forkA.href = url;
- forkA.appendChild(document.createTextNode(fullName));
-
- text.appendChild(document.createTextNode('also forked to '));
- text.appendChild(forkA);
- text.appendChild(document.createTextNode(' '));
- text.appendChild(createIconSVG('star'));
- text.appendChild(document.createTextNode('' + numStars + ' '));
-
- if (remoteIsNewer) {
- text.appendChild(createIconSVG('flame'));
- }
+ const forkA = document.createElement('a');
+ forkA.href = url;
+ forkA.append(fullName);
- text.parentNode.classList.add('has-lovely-forks');
- };
+ text.append('also forked to ', forkA, ' ', createIconSVG('star'), `${numStars} `);
+
+ if (remoteIsNewer) {
+ text.append(createIconSVG('flame'));
+ }
+
+ text.parentNode.classList.add('has-lovely-forks');
}
function makeRemoteDataURL(user, repo) {
- return 'https://api.github.com/repos/' +
- user + '/' + repo + '/forks?sort=stargazers';
+ return `https://api.github.com/repos/${user}/${repo}/forks?sort=stargazers`;
}
function makeCommitDiffURL(user, repo, remoteUser, default_branch) {
- return 'https://api.github.com/repos/' +
- user + '/' + repo + '/compare/' +
- user + ':' + default_branch + '...' +
- remoteUser + ':' + default_branch;
+ return `https://api.github.com/repos/${user}/${repo}/compare/${user}:${default_branch}...${remoteUser}:${default_branch}`;
}
// From: http://crocodillon.com/blog/always-catch-localstorage-security-and-quota-exceeded-errors
function isQuotaExceeded(e) {
- var quotaExceeded = false;
+ let quotaExceeded = false;
if (e) {
if (e.code) {
switch (e.code) {
@@ -224,30 +184,31 @@ function processWithData(user, repo, remoteDataStr, selfDataStr, isFreshData) {
/* Parse fork data */
/* Can either be just one data element,
* or could be the list of all forks. */
- var allForksData = JSON.parse(remoteDataStr);
- var mostStarredFork = allForksData[0];
+ const allForksData = JSON.parse(remoteDataStr);
+ const mostStarredFork = allForksData[0];
- var forkUrl = mostStarredFork['html_url'],
- fullName = mostStarredFork['full_name'];
+ const forkUrl = mostStarredFork['html_url'];
+ const fullName = mostStarredFork['full_name'];
/* Parse self data */
/* This could either be the commit-diff data (v2)
* or `all_commits` data (v1). */
/* selfData can also be null, if the commit difference API resulted in
* an error. */
- var selfData = JSON.parse(selfDataStr),
- selfDataToSave = selfData,
- remoteIsNewer = false;
+ const selfData = JSON.parse(selfDataStr);
+
+ let selfDataToSave = selfData;
+ let remoteIsNewer = false;
if (selfData !== null) {
if (selfData.hasOwnProperty('ahead_by')) {
// New version
- var diffData = selfData;
+ const diffData = selfData;
remoteIsNewer = (diffData['ahead_by'] - diffData['behind_by']) > 0;
} else {
// Old version
- var allCommits = selfData;
- var remoteUpdateTimeMs = mbStrToMs(mostStarredFork['pushed_at']);
+ const allCommits = selfData;
+ const remoteUpdateTimeMs = mbStrToMs(mostStarredFork['pushed_at']);
if (!allCommits || allCommits.length < 1) {
if (DEBUG) {
@@ -257,8 +218,8 @@ function processWithData(user, repo, remoteDataStr, selfDataStr, isFreshData) {
return;
}
- var latestCommit = allCommits[0]['commit'];
- var committer = latestCommit['committer'];
+ const latestCommit = allCommits[0]['commit'];
+ const committer = latestCommit['committer'];
if (!committer) {
if (DEBUG) {
@@ -268,7 +229,7 @@ function processWithData(user, repo, remoteDataStr, selfDataStr, isFreshData) {
return;
}
- var selfUpdateTimeMs = mbStrToMs(committer['date']);
+ const selfUpdateTimeMs = mbStrToMs(committer['date']);
remoteIsNewer = remoteUpdateTimeMs > selfUpdateTimeMs;
selfDataToSave = [allCommits[0]];
@@ -279,7 +240,7 @@ function processWithData(user, repo, remoteDataStr, selfDataStr, isFreshData) {
/* Cache data, if necessary */
if (isFreshData) {
- var currentTimeMs = (new Date()).toString();
+ const currentTimeMs = (new Date()).toString();
if (DEBUG) {
console.log(_logName, 'Saving data');
@@ -290,12 +251,12 @@ function processWithData(user, repo, remoteDataStr, selfDataStr, isFreshData) {
localStorage.setItem(makeTimeKey(user, repo), currentTimeMs);
// Only the most starred fork is relevant
- var relevantRemoteDataStr = JSON.stringify([mostStarredFork]);
+ const relevantRemoteDataStr = JSON.stringify([mostStarredFork]);
localStorage.setItem(makeRemoteDataKey(user, repo),
relevantRemoteDataStr);
// Only the latest commit is relevant
- var relevantSelfDataStr = JSON.stringify(selfDataToSave);
+ const relevantSelfDataStr = JSON.stringify(selfDataToSave);
localStorage.setItem(makeSelfDataKey(user, repo),
relevantSelfDataStr);
} catch(e) {
@@ -309,7 +270,7 @@ function processWithData(user, repo, remoteDataStr, selfDataStr, isFreshData) {
// Now if the repository doesn't have any notable forks, so not
// touch the DOM.
- var starGazers = mostStarredFork['stargazers_count'];
+ const starGazers = mostStarredFork['stargazers_count'];
if (!starGazers) {
if (DEBUG) {
@@ -319,7 +280,7 @@ function processWithData(user, repo, remoteDataStr, selfDataStr, isFreshData) {
return;
}
- safeUpdateDOM(showDetails(fullName, forkUrl, starGazers,
+ safeUpdateDOM(() => showDetails(fullName, forkUrl, starGazers,
remoteIsNewer),
'showing details');
} catch (e) {
@@ -329,82 +290,56 @@ function processWithData(user, repo, remoteDataStr, selfDataStr, isFreshData) {
}
}
-function onreadystatechangeFactory(xhr, successFn) {
- return function () {
- if (xhr.readyState === 4) {
- if (xhr.status === 200) {
- successFn();
- } else if (xhr.status === 403) {
- console.warn(_logName,
- 'Looks like the rate-limit was exceeded.');
- } else {
- console.warn(_logName,
- 'GitHub API returned status:', xhr.status);
- }
- } else {
- // Request is still in progress
- // Do nothing.
- }
- };
-}
+async function makeFreshRequest(user, repo) {
+ const response = await fetch(makeRemoteDataURL(user, repo));
-function makeFreshRequest(user, repo) {
- var xhrFork = new XMLHttpRequest();
-
- xhrFork.onreadystatechange = onreadystatechangeFactory(
- xhrFork,
- function () {
- var forksDataJson = JSON.parse(xhrFork.responseText);
- if (!forksDataJson || forksDataJson.length === 0) {
- if (DEBUG) {
- console.log(_logName,
- 'Repository does not have any forks.');
- }
- return;
- }
+ if (response.status === 403) {
+ return console.warn(_logName,
+ 'Looks like the rate-limit was exceeded.');
+ }
- var mostStarredFork = forksDataJson[0],
- forksDataStr = JSON.stringify([mostStarredFork]);
-
- var defaultBranch = mostStarredFork['default_branch'],
- remoteUser = mostStarredFork['owner']['login'];
-
- var xhrDiff = new XMLHttpRequest();
-
- xhrDiff.onreadystatechange = function () {
- if (xhrDiff.readyState === 4) {
- if (xhrDiff.status === 200) {
- var commitDiffJson = JSON.parse(xhrDiff.responseText);
- // Dropping the list of commits to conserve space.
- commitDiffJson['commits'] = [];
- var commitDiffStr = JSON.stringify(commitDiffJson);
- processWithData(user, repo, forksDataStr, commitDiffStr, true);
- } else {
- // In case of any error, ignore recency data.
- processWithData(user, repo, forksDataStr, null, true);
- }
- }
- };
+ if (!response.ok) {
+ return console.warn(_logName,
+ 'GitHub API returned status:', response.status);
+ }
+
+ const [mostStarredFork] = await response.json();
- xhrDiff.open('GET', makeCommitDiffURL(user, repo, remoteUser, defaultBranch));
- xhrDiff.send();
+ if (!mostStarredFork) {
+ if (DEBUG) {
+ console.log(_logName,
+ 'Repository does not have any forks.');
}
- );
+ return;
+ }
+
+ const forksDataStr = JSON.stringify([mostStarredFork]);
+ const defaultBranch = mostStarredFork['default_branch'];
+ const remoteUser = mostStarredFork['owner']['login'];
+
+ const response2 = await fetch(makeCommitDiffURL(user, repo, remoteUser, defaultBranch));
+
+ let commitDiffStr = null;
+ if (response2.ok) {
+ const commitDiffJson = await response2.json();
+ // Dropping the list of commits to conserve space.
+ delete commitDiffJson.commits;
+ commitDiffStr = JSON.stringify(commitDiffJson);
+ }
- xhrFork.open('GET', makeRemoteDataURL(user, repo));
- xhrFork.send();
+ processWithData(user, repo, forksDataStr, commitDiffStr, true);
}
function getDataFor(user, repo) {
- var lfTimeKey = makeTimeKey(user, repo),
- lfRemoteDataKey = makeRemoteDataKey(user, repo),
- lfSelfDataKey = makeSelfDataKey(user, repo);
+ const lfTimeKey = makeTimeKey(user, repo);
+ const lfRemoteDataKey = makeRemoteDataKey(user, repo);
+ const lfSelfDataKey = makeSelfDataKey(user, repo);
- var ret = { hasData: false };
+ const ret = { hasData: false };
- var savedRemoteDataStr = localStorage.getItem(lfRemoteDataKey);
- var savedSelfDataStr = localStorage.getItem(lfSelfDataKey);
- var saveTimeMs = mbStrToMs(localStorage.getItem(lfTimeKey));
+ const savedRemoteDataStr = localStorage.getItem(lfRemoteDataKey);
+ const savedSelfDataStr = localStorage.getItem(lfSelfDataKey);
+ const saveTimeMs = mbStrToMs(localStorage.getItem(lfTimeKey));
if (saveTimeMs === null ||
savedRemoteDataStr === null ||
@@ -422,7 +357,7 @@ function getDataFor(user, repo) {
function runFor(user, repo) {
try {
- var cache = getDataFor(user, repo);
+ const cache = getDataFor(user, repo);
if (cache.hasData && !isExpired(cache.saveTimeMs)) {
if (DEBUG) {
console.log(_logName,
@@ -439,16 +374,15 @@ function runFor(user, repo) {
makeFreshRequest(user, repo);
}
} catch (e) {
- console.error(_logName, 'Could not run for ', user + '/' + repo,
+ console.error(_logName, 'Could not run for ', `${user}/${repo}`,
'Exception: ', e);
}
}
/* Script execution */
-var pathComponents = window.location.pathname.split('/');
-if (pathComponents.length >= 3) {
- var user = pathComponents[1], repo = pathComponents[2];
+const [, user, repo] = window.location.pathname.split('/');
+if (user && repo) {
runFor(user, repo);
} else {
if (DEBUG) {
diff --git a/webext/data/style.css b/webext/data/style.css
index b5fea99..e9955d1 100644
--- a/webext/data/style.css
+++ b/webext/data/style.css
@@ -24,3 +24,8 @@
opacity: 0;
}
}
+
+/* icons */
+.lovely-forks-addon img {
+ vertical-align: bottom;
+}
diff --git a/webext/icons/flame.svg b/webext/icons/flame.svg
new file mode 100644
index 0000000..075b68e
--- /dev/null
+++ b/webext/icons/flame.svg
@@ -0,0 +1,3 @@
+
diff --git a/webext/icons/star.svg b/webext/icons/star.svg
new file mode 100644
index 0000000..1d5f9dc
--- /dev/null
+++ b/webext/icons/star.svg
@@ -0,0 +1,3 @@
+