From 2f217dfe37a0708a663c224241a60e85c06b9fe8 Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Tue, 16 Feb 2016 02:34:52 +0700 Subject: [PATCH] linkify branch references in pull requests - fixes #1 --- extension/content.css | 6 +++ extension/content.js | 72 ++++++++++++++++++--------- extension/manifest.json | 2 + extension/vendor/gh-injection.js | 84 ++++++++++++++++++++++++++++++++ extension/vendor/sprint.min.js | 35 +++++++++++++ readme.md | 1 + 6 files changed, 176 insertions(+), 24 deletions(-) create mode 100644 extension/vendor/gh-injection.js create mode 100644 extension/vendor/sprint.min.js diff --git a/extension/content.css b/extension/content.css index 70ac173..c22297b 100755 --- a/extension/content.css +++ b/extension/content.css @@ -105,3 +105,9 @@ .file-navigation a[href$="/pull/new/master"] { display: none !important; } + +/* add hover underline for `content.js` linkified branch refs in pull requests */ +.commit-ref:hover, +.commit-ref:hover span { + text-decoration: underline !important; +} diff --git a/extension/content.js b/extension/content.js index 21be25a..7fe471f 100755 --- a/extension/content.js +++ b/extension/content.js @@ -1,35 +1,59 @@ -document.addEventListener('DOMContentLoaded', () => { - 'use strict'; +'use strict'; +const path = location.pathname; +const isDashboard = path === '/'; +const isRepo = /^\/.*\/.*\//.test(location.pathname) +const isPR = () => /^\/.*\/.*\/pull\/\d+$/.test(location.pathname); +const repoName = path.split('/')[2]; + +function linkifyBranchRefs() { + $('.commit-ref').each((i, el) => { + const parts = $(el).find('.css-truncate-target'); + const username = parts.eq(0).text(); + const branch = parts.eq(1).text(); + $(el).wrap(``); + }); +} +document.addEventListener('DOMContentLoaded', () => { const username = document.querySelector('meta[name="user-login"]').getAttribute('content'); - // hide other users starring/forking your repos - { - const hideStarsOwnRepos = () => { - const items = Array.from(document.querySelectorAll('#dashboard .news .watch_started, #dashboard .news .fork')); + if (isDashboard) { + // hide other users starring/forking your repos + { + const hideStarsOwnRepos = () => { + const items = Array.from(document.querySelectorAll('#dashboard .news .watch_started, #dashboard .news .fork')); - for (const item of items) { - if (item.querySelector('.title a[href^="/' + username + '"')) { - item.style.display = 'none'; + for (const item of items) { + if (item.querySelector('.title a[href^="/' + username + '"')) { + item.style.display = 'none'; + } } - } - }; + }; - hideStarsOwnRepos(); + hideStarsOwnRepos(); - new MutationObserver(() => hideStarsOwnRepos()) - .observe(document.querySelector('#dashboard .news'), {childList: true}); - } + new MutationObserver(() => hideStarsOwnRepos()) + .observe(document.querySelector('#dashboard .news'), {childList: true}); + } - // expand all the news feed pages - (function more() { - const btn = document.querySelector('.ajax-pagination-btn'); + // expand all the news feed pages + (function more() { + const btn = document.querySelector('.ajax-pagination-btn'); - if (!btn) { - return; - } + if (!btn) { + return; + } + + btn.click(); + setTimeout(more, 200); + })(); + } - btn.click(); - setTimeout(more, 200); - })(); + if (isRepo) { + gitHubInjection(window, () => { + if (isPR()) { + linkifyBranchRefs(); + } + }); + } }); diff --git a/extension/manifest.json b/extension/manifest.json index 4efd3b3..a1a49a3 100755 --- a/extension/manifest.json +++ b/extension/manifest.json @@ -22,6 +22,8 @@ "custom.css" ], "js": [ + "vendor/sprint.min.js", + "vendor/gh-injection.js", "content.js" ] } diff --git a/extension/vendor/gh-injection.js b/extension/vendor/gh-injection.js new file mode 100644 index 0000000..41151d8 --- /dev/null +++ b/extension/vendor/gh-injection.js @@ -0,0 +1,84 @@ +'use strict'; +// https://github.com/octo-linker/injection + +// Grabbed from underscore.js +// http://underscorejs.org/#debounce +function debounce(func, wait, immediate) { + var timeout; + return function() { + var context = this, args = arguments; + var later = function() { + timeout = null; + if (!immediate) { + func.apply(context, args); + } + }; + var callNow = immediate && !timeout; + clearTimeout(timeout); + timeout = setTimeout(later, wait); + if (callNow) { + func.apply(context, args); + } + }; +} + +var gitHubInjection = function (global, options, cb) { + if (!global) { + throw new Error('Missing argument global'); + } + if (!global.document || !global.document.getElementById) { + throw new Error('The given argument global is not a valid window object'); + } + + if (!cb) { + cb = options; + options = {}; + } else if (typeof cb !== 'function') { + throw new Error('Callback is not a function'); + } + + if (!cb) { + throw new Error('Missing argument callback'); + } + + options = options || {}; + options.context = options.context || null; + options.wait = options.wait || 250; + + cb = debounce(cb, options.wait).bind(options.context); + + var domElement = global.document.getElementById('js-repo-pjax-container'); + if (!domElement || !global.MutationObserver) { + return cb(null); + } + + var viewSpy = new global.MutationObserver(function (mutations) { + mutations.forEach(function (mutation) { + if (mutation.type === 'childList' && mutation.addedNodes.length) { + cb(null); + } + }); + }); + + viewSpy.observe(domElement, { + attributes: true, + childList: true, + characterData: true + }); + + cb(null); +}; + +// Export the gitHubInjection function for **Node.js**, with +// backwards-compatibility for the old `require()` API. If we're in +// the browser, add `gitHubInjection` as a global object. +if (typeof exports !== 'undefined') { + if (typeof module !== 'undefined' && module.exports) { + exports = module.exports = gitHubInjection; + } + exports.gitHubInjection = gitHubInjection; +} else { + /*jshint -W040 */ + this.gitHubInjection = gitHubInjection; + /*jshint +W040 */ +} diff --git a/extension/vendor/sprint.min.js b/extension/vendor/sprint.min.js new file mode 100644 index 0000000..d591e56 --- /dev/null +++ b/extension/vendor/sprint.min.js @@ -0,0 +1,35 @@ +// Sprint v0.9.2 - sprintjs.com/license +var Sprint; +(function(){var D=function(a,b){for(var c=Sprint(b),d=Object.keys(a),e=d.length,f=0;fa?d:a):d==window?window["inner"+a]:d.getBoundingClientRect()[b]}var e="function"==typeof c,f=e?"":w(b,c);return a.each(function(a){this==document||this== +window||1 +Object.keys(a.sprintEventListeners).filter(function(a){return q(b)[0]===q(a)[0]}).map(function(b){return a.sprintEventListeners[b]}).reduce(function(a,b){return a.concat(b)}).filter(function(a){return a===c}).length?!1:!0},b=function(b,c,f){return function(g){f&&f!==g||(b.removeEventListener(c,g),/\./.test(c)&&!a(b,c,g)&&b.removeEventListener(q(c)[0],g))}},c=function(a,b){return a.filter(function(a){return b&&b!==a})};return function(a,e){return function(f){a.sprintEventListeners[f].forEach(b(a,f, +e));a.sprintEventListeners[f]=c(a.sprintEventListeners[f],e)}}}(),M=function(a,b){return function(c){F(a,c).forEach(H(a,b))}},m=document.documentElement,A=function(a,b,c){for(var d=a.length,e=d;e--;)if(!a[e]&&0!==a[e]||b&&a[e]instanceof n||c&&("string"==typeof a[e]||"number"==typeof a[e])){for(var e=[],f=0;fe?m:document.body}if(null==d){b=b.get(0);if(!b)return;if(b==window||b==document)b=a;return b[c]}return b.each(function(){var b=this;if(b==window||b==document)b=a;b[c]=d})}}(),y=function(a,b,c,d){var e=[],f=b+"ElementSibling";a.each(function(){for(var b=this;(b=b[f])&&(!d||!a.is(d,b));)c&&!a.is(c,b)||e.push(b)});return Sprint(x(e))},J=function(a,b,c){var d=b+"ElementSibling";return a.map(function(){var b= +this[d];if(b&&(!c||a.is(c,b)))return b},!1)},r=function(a,b){b=b||document;if(/^[\#.]?[\w-]+$/.test(a)){var c=a[0];return"."==c?t(b.getElementsByClassName(a.slice(1))):"#"==c?(c=b.getElementById(a.slice(1)))?[c]:[]:"body"==a?[document.body]:t(b.getElementsByTagName(a))}return t(b.querySelectorAll(a))},q=function(a){return A(a.split("."))},t=function(a){for(var b=[],c=a.length;c--;)b[c]=a[c];return b},C=function(){var a=function(a,c){var d=Sprint(a).clone(!0).get(0),e=d;if(d&&!(1",outro:""}, +area:{intro:"",outro:""},param:{intro:"",outro:""},thead:{intro:"",outro:"
"},tr:{intro:"",outro:"
"},col:{intro:"",outro:"
"},td:{intro:"",outro:"
"}};["tbody","tfoot","colgroup","caption"].forEach(function(a){u[a]=u.thead});u.th=u.td;var n=function(a,b){if("string"==typeof a)if("<"==a[0]){var c=document.createElement("div"),d=/[\w:-]+/.exec(a)[0], +d=u[d],e=a.trim();d&&(e=d.intro+e+d.outro);c.insertAdjacentHTML("afterbegin",e);e=c.lastChild;if(d)for(d=d.outro.match(/a&&(a+=this.length);return this.dom[a]}, +has:function(a){if("string"==typeof a)return this.map(function(){if(!(1b?f+=b:0<=b&&(f=b>this.length?this.length:b);e