Skip to content

Instantly share code, notes, and snippets.

@shtrom
Last active May 22, 2025 06:54
Show Gist options
  • Save shtrom/e9c25e103aaf2f8ede6f740787b50e33 to your computer and use it in GitHub Desktop.
Save shtrom/e9c25e103aaf2f8ede6f740787b50e33 to your computer and use it in GitHub Desktop.
Github BMO Linker GreaseMonkey script
// ==UserScript==
// @name GitHub BMO Linker
// @version 3
// @author Olivier Mehani <[email protected]>
// @downloadURL https://gist.github.com/shtrom/e9c25e103aaf2f8ede6f740787b50e33
// @grant none
// @match https://github.com/*
// @run-at document-idle
// ==/UserScript==
const baseUrl = "https://bugzilla.mozilla.org/show_bug.cgi?id=";
const bugRE = /bug ([0-9]+)/i;
const logName = "GreaseMonkey GBL";
let debugLog = function() {} ;
//debugLog = console.debug;
let count = 0;
const skippableTags = [
//"A",
"IMG",
"INPUT",
"OBJECT",
"PRE"
];
function linkifyBugs(s) {
return s.replace(bugRE, '<a href="' + baseUrl + '$1">$&</a>');
}
function bugUrl(s) {
return s.replace(bugRE, baseUrl + '$1');
}
function recursiveLinkifyBugs(el) {
if (el.childNodes === undefined) {
//debugLog(logName + ": no child nodes in", el);
return;
}
let i = el.childNodes.length;
while (n = el.childNodes[--i]) {
//debugLog(logName + ": processing node", i, n, "from", el);
if (n.nodeType == n.TEXT_NODE) {
// https://stackoverflow.com/a/12212700
while (r = bugRE.exec(n.textContent)) {
debugLog(logName + ": found bug string in", n, "from", el);
n = n.splitText(r.index);
n = n.splitText(r[0].length);
linkTextNode = n.previousSibling;
linkNode = document.createElement('a');
linkNode.href = bugUrl(linkTextNode.textContent);
linkNode.appendChild(linkTextNode);
debugLog(logName + ": replacement", linkNode);
el.insertBefore(linkNode, n);
}
} else if (n.nodeType == n.ELEMENT_NODE
&& !skippableTags.includes(n.tagName)) {
//debugLog(logName + ": going deeper into", n);
recursiveLinkifyBugs(n);
}
}
//debugLog(logName + ": finished for", el);
}
function callback (mutations) {
me = logName + "[" + (++count) + "]";
//debugLog(me + ": mutations detected", mutations);
let addedNodes = [];
for (let m of mutations) {
//debugLog(me + ": mutation", m);
for (let n of m.addedNodes) {
if (n.innerHTML?.length && !skippableTags.includes(n.tagName)) {
//debugLog(me + ": added node", n);
addedNodes.push(n);
}
}
}
if (addedNodes.length > 0) {
//debugLog(me + ": linkifying new content from", addedNodes);
recursiveLinkifyBugs(addedNodes);
}
}
let observer = new MutationObserver(callback);
let observerOptions = {
childList: true,
subtree: true
};
recursiveLinkifyBugs(document.body);
observer.observe(document.body, observerOptions);
console.info(logName + ": ready");
return s.replace(bugRE, '<a href="' + baseUrl + '$1">$&</a>');
}
function bugUrl(s) {
return s.replace(bugRE, baseUrl + '$1');
}
function recursiveLinkifyBugs(el) {
if (el.childNodes === undefined) {
//debugLog(logName + ": no child nodes in", el);
return;
}
let i = el.childNodes.length;
while (n = el.childNodes[--i]) {
//debugLog(logName + ": processing node", i, n, "from", el);
if (n.nodeType == n.TEXT_NODE) {
// https://stackoverflow.com/a/12212700
while (r = bugRE.exec(n.textContent)) {
debugLog(logName + ": found bug string in", n, "from", el);
n = n.splitText(r.index);
n = n.splitText(r[0].length);
linkTextNode = n.previousSibling;
linkNode = document.createElement('a');
linkNode.href = bugUrl(linkTextNode.textContent);
linkNode.appendChild(linkTextNode);
debugLog(logName + ": replacement", linkNode);
el.insertBefore(linkNode, n);
}
} else if (n.nodeType == n.ELEMENT_NODE
&& !skippableTags.includes(n.tagName)) {
//debugLog(logName + ": going deeper into", n);
recursiveLinkifyBugs(n);
}
}
//debugLog(logName + ": finished for", el);
}
function callback (mutations) {
me = logName + "[" + (++count) + "]";
//debugLog(me + ": mutations detected", mutations);
let addedNodes = [];
for (let m of mutations) {
//debugLog(me + ": mutation", m);
for (let n of m.addedNodes) {
if (n.innerHTML?.length && !skippableTags.includes(n.tagName)) {
//debugLog(me + ": added node", n);
addedNodes.push(n);
}
}
}
if (addedNodes.length > 0) {
//debugLog(me + ": linkifying new content from", addedNodes);
recursiveLinkifyBugs(addedNodes);
}
}
let observer = new MutationObserver(callback);
let observerOptions = {
childList: true,
subtree: true
};
recursiveLinkifyBugs(document.body);
observer.observe(document.body, observerOptions);
console.info(logName + ": ready");
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment