This Greasemonkey script adds links to tags on StackExchange sites which allow for activating, deactivating and ignoring favorite and related tags by using UI components.
Since JavaScript is not my favourite language I'd be glad to hear what can be done more elegant, more beautiful, more performant, etc. (also by using jQuery, for instance).
// ==UserScript==
// @name SEQTAIL - StackExchange Questions' TAgs Inline Links
// @author Gerold 'Geri' Broser
// @license GNU GPLv3 <http://www.gnu.org/licenses/gpl-3.0.html>
// @namespace igb
// @description Adds links to all tags that allow for activating, deactivating and ignoring favorite and related tags by UI components rather than by editing the search field.
// @description:de Fügt zu allen Tags Links hinzu, die es erlauben Favorite und Related Tags über UI-Konponenten zu aktivieren oder zu deaktivieren, anstelle das Suchfeld zu bearbeiten.
// @include http://stackoverflow.com/questions*
// @include http://stackoverflow.com/unanswered*
// @include http://codereview.stackexchange.com/questions*
// @version 16.4.7
// @icon http://cdn.sstatic.net/Sites/stackoverflow/img/favicon.ico
// @run-at document-idle
// @tested-with Firefox 45.0.1, Greasemonkey 3.7; Chrome 49.0.2623.110, TamperMonkey 4.0.10
// ==/UserScript==
(function() { 'use strict';
console.debug("BEGIN SEQTAIL...");
function qouteRegexSpecialCharsIn(string) {
return string.replace(/([\[\]\(\)\{\}\|\?\+\-\*\^\$\\\.\!\=])/g, "\\$1");
}
// Adds links to all tags that allow to active, deactivate and ignore favorite and related tags
// by selecting rather than by editing the search field.
function addLinksTo(tags) {
for (var n = 0; n < tags.snapshotLength; n++) {
var tag = tags.snapshotItem(n);
var tagName = tag.innerHTML.replace(/<img.+>/g, "");
// 'ignore' tag
if (search !== "" // if search field is empty (an ignored tag cannot exist on its own in search field)...
&& search != "[" + tagName + "]" // ...and tag not the only active (see above)...
&& search.indexOf("-[" + tagName + "]") < 0) { // ...and not already ignored
var i = document.createElement('a');
var rgx = new RegExp("\\[" + qouteRegexSpecialCharsIn(tagName) + "\\]\\+*", "g"); // to remove tag from link below if it is active
i.href = "/questions/tagged/" +
search.replace(rgx, "").trim().replace(/ +/g, "+") + (search == "" ? "" : "+") + "-[" + tagName + "]";
i.title = "ignore questions tagged '" + tagName + "'";
i.style = "background-color: #e6e6e6; padding: 0 0.4em;";
i.innerHTML = '!';
tag.parentNode.insertBefore(i, tag.nextSibling);
}
// 'activate' and 'deactivate' tag
var ad = document.createElement('a');
if (search.indexOf("[" + tagName + "]") < 0 // if tag not active...
|| search.indexOf("-[" + tagName + "]") >= 0) { // ...or ignored
rgx = new RegExp("-\\[" + qouteRegexSpecialCharsIn(tagName) + "\\]", "g"); // to remove tag from link below if it is ignored
ad.href = "/questions/tagged/" +
search.replace(rgx, "").trim().replace(/ +/g, "+") + (search == "" ? "" : "+") + "[" + tagName + "]";
ad.title = "add '" + tagName + "' to active tags";
ad.style = "background-color: #e6ffe6; padding: 0 0.3em;";
//ad.innerHTML = '+'; // if using the regular '+' sign it is added to the tag name by SO after editing Favorite Tags,
// since '+' is a valid character in tag names
//const AND = "ࢳ" // ∧ ... logical AND is not displayed properly in FF
ad.innerHTML = "↑";
}
else {
rgx = new RegExp("\\[" + qouteRegexSpecialCharsIn(tagName) + "\\]", "g"); // to remove tag from link below if it is active
ad.href = "/questions/tagged/" +
search.trim().replace(rgx, "").trim().replace(/ +/g, "+");
ad.title = "remove '" + tagName + "' from active tags";
ad.style = "background-color: #ffe6e6; padding: 0 0.3em;";
//ad.innerHTML = '−';
ad.innerHTML = "↓"; // since up arrow is used at 'add' above, use down arrow here for the sake of consistency
}
tag.parentNode.insertBefore(ad, tag.nextSibling);
} // for(tags)
} // addLinksTo(...)
// Adds link to tags section that allows to deactivate all tags.
function addAllLinkTo(tagsSection) {
if (search === "" ) // if search field is empty
return;
var da = document.createElement('a');
da.href = "/questions";
da.title = "remove all from active tags";
da.style = "background-color: #ffe6e6; padding: 0 0.3em;";
da.innerHTML = '×';
tagsSection.appendChild(da);
} // addAllLinkTo(...)
var search = document.getElementById('search').firstElementChild.value;
// active tags
var aTag = document.evaluate("//div[@class='tagged']", document, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null)
.snapshotItem(0);
if (aTag !== null) {
var aTags = document.evaluate("//div[@class='tagged']/a[not(starts-with(., 'about'))]",
aTag, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);
addLinksTo(aTags);
addAllLinkTo(aTag);
}
// interesting tags
var iTag = document.getElementById('interestingTags');
if (iTag !== null) {
var iTags = document.evaluate("//div[@id='interestingTags']/a", iTag, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);
addLinksTo(iTags);
addAllLinkTo(iTag);
}
// related tags
var rTag = document.evaluate("//div[contains(@class, 'js-gps-related-tags')]",
document, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null)
.snapshotItem(0);
if (rTag !== null) {
var rTags = document.evaluate( "//div[contains(@class, 'js-gps-related-tags')]/div/a",
rTag, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);
addLinksTo(rTags);
}
//unanswered tags
var uTag = document.evaluate("//div[./h4[@id='h-related-tags']]",
document, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null)
.snapshotItem(0);
if (uTag !== null) {
var uTags = document.evaluate( "//div[./h4[@id='h-related-tags']]/div/a",
uTag, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);
addLinksTo(uTags);
}
// Adds a section with links primarily for removing related tags which are no more in the Related Tags list once active.
function addDeactivateSection(parentElement) {
var active = search.split(" ");
if (active[0] === "" ) // if search field is empty (first element is an empty string after split())
return;
var deactivate = document.createElement('div');
deactivate.style = "margin-bottom: 1.5em;";
var h3 = document.createElement('h3');
h3.style = "margin-top: 1em; font-weight: normal;";
h3.innerHTML = "Remove from active tags";
deactivate.appendChild(h3);
for (var i = 0; i < active.length; i++) {
var r = document.createElement('a');
var rgx = "\-*" + qouteRegexSpecialCharsIn(active[i])/*.replace(/\[/g, "\\\[").replace(/\]/g, "\\\]")*/;
r.href = "/questions/tagged/" +
search.replace(new RegExp(rgx, "g"), "").trim().replace(/ +/g, "+");
r.title = "remove '" + active[i].replace(/[\[\]]/g, "") + "' from active tags";
r.style = "font-size: 12px; background-color: #ffe6e6; margin: 0.2em; padding: 0.2em 0.4em;";
r.innerHTML = active[i].replace(/[\-\[\]]/g, "");
if (active[i].startsWith("-")) {
var n = document.createElement('span');
n.innerHTML = "not";
deactivate.appendChild(n);
}
deactivate.appendChild(r);
} // for(active tags)
parentElement.parentNode.parentNode.insertBefore(deactivate, parentElement.parentNode.nextSibling);
addAllLinkTo(deactivate);
} // addDeactivateSection(...)
addDeactivateSection(iTag);
console.debug("END SEQTAIL.");
})(); // use strict