Adds a button to open verification bugs in new windows on Launchpad bug pages, excluding the current bug ID
// ==UserScript==
// @name Open Launchpad Kernel SRU Verification Bugs
// @namespace http://anthonywong.net/
// @version 1.2
// @description Adds a button to open verification bugs in new windows on Launchpad bug pages, excluding the current bug ID
// @author Grok
// @match https://bugs.launchpad.net/kernel-sru-workflow/+bug/*
// @grant GM_openInTab
// @license GPLv2
// ==/UserScript==
(function() {
'use strict';
console.log('Tampermonkey script started');
// Extract current bug ID from URL
const url = window.location.href;
const urlRegex = /\+bug\/(\d+)/;
const urlMatch = url.match(urlRegex);
const currentBugId = urlMatch ? urlMatch[1] : null;
console.log('Current bug ID from URL:', currentBugId);
// Find the div with class yui3-editable_text-text
const div = document.querySelector('div.yui3-editable_text-text');
if (!div) {
console.log('No div with class yui3-editable_text-text found');
return;
}
console.log('Found div with class yui3-editable_text-text');
// Find the p element inside the div
const pElements = div.getElementsByTagName('p');
console.log(`Found ${pElements.length} p elements in div`);
let targetP;
let bugNumbers = [];
// Iterate through p elements to find the one with verification-bugs
for (let p of pElements) {
if (p.textContent.includes('verification-bugs')) {
console.log('Found verification-bugs in p element');
console.log('P element content (first 200 chars):', p.textContent.substring(0, 200));
targetP = p;
// Extract bug numbers using regex
const bugsRegex = /verification-bugs:\s*\[([\d,\s]*)\]/;
const match = p.textContent.match(bugsRegex);
if (match && match[1]) {
bugNumbers = match[1].split(',').map(num => num.trim()).filter(num => num);
console.log('Extracted bug numbers:', bugNumbers);
} else {
console.log('No bug numbers matched in regex');
}
break;
}
}
if (!targetP) {
console.log('No p element with verification-bugs found in div');
return;
}
if (bugNumbers.length === 0) {
console.log('No bug numbers extracted');
return;
}
// Check if button already exists
const existingButtons = div.querySelectorAll('button');
for (let btn of existingButtons) {
if (btn.textContent === 'Open bugs in new window') {
console.log('Button already exists, skipping');
return;
}
}
// Create button
console.log('Creating button');
const button = document.createElement('button');
button.textContent = 'Open bugs in new window';
button.style.marginLeft = '10px';
button.style.cursor = 'pointer';
button.style.padding = '5px 10px';
button.style.backgroundColor = '#007bff';
button.style.color = 'white';
button.style.border = 'none';
button.style.borderRadius = '4px';
button.style.display = 'inline-block';
button.style.verticalAlign = 'middle';
// Add click event listener, excluding current bug ID
button.addEventListener('click', () => {
console.log('Button clicked, processing bug numbers:', bugNumbers);
const filteredBugNumbers = bugNumbers.filter(num => num !== currentBugId);
console.log('Filtered bug numbers (excluding current bug ID):', filteredBugNumbers);
filteredBugNumbers.forEach(bugNumber => {
const url = `http://launchpad.net/bugs/${bugNumber}`;
console.log(`Opening tab for bug ${bugNumber}: ${url}`);
GM_openInTab(url, { active: false });
});
});
// Insert button inline after verification-bugs line
console.log('Modifying p element to insert button');
const innerHTML = targetP.innerHTML;
// Log a snippet of innerHTML around verification-bugs for debugging
const verificationIndex = innerHTML.toLowerCase().indexOf('verification-<wbr>bugs');
if (verificationIndex !== -1) {
const snippetStart = Math.max(0, verificationIndex - 100);
const snippetEnd = verificationIndex + 200;
console.log('innerHTML snippet around verification-bugs:', innerHTML.substring(snippetStart, snippetEnd));
} else {
console.log('verification-bugs not found in innerHTML');
}
// Find the verification-bugs text node (search for 'verification-' due to <wbr>)
const textNodes = document.evaluate(
".//text()[contains(., 'verification-')]",
targetP,
null,
XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,
null
);
if (textNodes.snapshotLength > 0) {
console.log(`Found ${textNodes.snapshotLength} text nodes containing 'verification-'`);
const verificationNode = textNodes.snapshotItem(0);
let currentNode = verificationNode;
let foundBracket = false;
// Traverse until we find the closing ]
while (currentNode && !foundBracket) {
if (currentNode.nodeType === Node.TEXT_NODE && currentNode.textContent.includes(']')) {
foundBracket = true;
// Split the text node at ]
const textContent = currentNode.textContent;
const bracketIndex = textContent.indexOf(']');
if (bracketIndex !== -1) {
const beforeBracket = textContent.substring(0, bracketIndex + 1);
const afterBracket = textContent.substring(bracketIndex + 1);
currentNode.textContent = beforeBracket;
if (afterBracket) {
const newTextNode = document.createTextNode(afterBracket);
currentNode.parentNode.insertBefore(newTextNode, currentNode.nextSibling);
}
// Insert button after the ]
console.log('Inserting button after closing ] of bug list');
currentNode.parentNode.insertBefore(button, currentNode.nextSibling);
}
}
currentNode = currentNode.nextSibling;
}
if (!foundBracket) {
console.log('No closing ] found, appending to p');
targetP.appendChild(button);
}
} else {
console.log('No verification- text node found, appending button after p');
targetP.insertAdjacentElement('afterend', button);
}
console.log('Button insertion complete');
})();