Skip to content

Instantly share code, notes, and snippets.

@xqwzts
Last active March 14, 2016 14:04
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save xqwzts/72a77cf0b41a4ae70911 to your computer and use it in GitHub Desktop.
Save xqwzts/72a77cf0b41a4ae70911 to your computer and use it in GitHub Desktop.
Greasemonkey script adding the ability to tag users on Metafilter [http://www.metafilter.com]
// ==UserScript==
// @name Metafilter User Tagger
// @description Add custom tags next to mefite usernames.
// @namespace http://www.xqwzts.com
// @include http://*.metafilter.com/*
// @include https://*.metafilter.com/*
// @version 1.0.0
// @grant GM_setValue
// @grant GM_getValue
// ==/UserScript==
// injecting stylesheet into the header. from: http://commons.oreilly.com/wiki/index.php/Greasemonkey_Hacks/Getting_Started#Alter_a_Page.27s_Style
// tag input field styling adapted from http://tympanus.net/Development/TextInputEffects/
function _injectStyle() {
var head, style;
head = document.getElementsByTagName('head')[0];
if (!head) { return; }
style = document.createElement('style');
style.type = 'text/css';
style.innerHTML = '.utag {\
text-decoration: none;\
background-color: #128A82;\
color: #000 !important;\
padding-right: 1px;\
font-weight: 400 !important;\
border-radius: 2px;\
font-size: 12px;\
}\
\
.utagp {\
color: #128A82;\
position: relative;\
box-sizing: border-box;\
}\
\
.usertaggerpopup {\
display: none;\
position: absolute;\
background-color: #384A52;\
z-index: 1002;\
overflow: auto;\
width: 400px;\
height: 300px;\
padding: 15px;\
border-radius: 5px;\
}\
\
.closebutton {\
float: right;\
font-size: 20px;\
font-weight: bold;\
line-height: 18px;\
color: #fff;\
text-shadow: 0 1px 0 #ffffff;\
opacity: 0.2;\
filter: alpha(opacity=20);\
text-decoration: none;\
}\
\
.closebutton:hover {\
color: white;\
text-decoration: none;\
opacity: 0.4;\
filter: alpha(opacity=40);\
cursor: pointer;\
}\
\
.tagbutton {\
display: block;\
width: 300px;\
border-radius: 15px;\
background-color: #9CC754;\
text-align: center;\
color: #FFFFFF;\
top: 1em;\
left: 2em;\
}\
\
.delbutton {\
display: block;\
color: #CE5E73;\
text-decoration: underline;\
top: 4em;\
float: right;\
}\
\
.tagspanjuro {\
overflow: hidden;\
position: relative;\
z-index: 1;\
display: inline-block;\
margin: 1em;\
max-width: 400px;\
width: calc(100% - 2em);\
vertical-align: top;\
}\
\
.taginputjuro {\
position: absolute;\
z-index: 100;\
padding: 2.15em 0.75em 0;\
width: 100%;\
background: transparent;\
color: #1784cd;\
font-size: 0.85em;\
display: block;\
float: right;\
border: none;\
border-radius: 0;\
font-weight: bold;\
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;\
-webkit-appearance: none;\
}\
\
.taginputjuro:focus {\
outline: none;\
}\
\
.taglabeljuro {\
padding: 0;\
width: 100%;\
height: 100%;\
background: #fff;\
text-align: left;\
display: inline-block;\
float: right;\
color: #6a7989;\
font-weight: bold;\
font-size: 70.25%;\
-webkit-touch-callout: none;\
-webkit-user-select: none;\
-khtml-user-select: none;\
-moz-user-select: none;\
-ms-user-select: none;\
user-select: none;\
}\
\
.taglabelspanjuro {\
padding: 2em 1em;\
-webkit-transform-origin: 0% 50%;\
transform-origin: 0% 50%;\
-webkit-transition: -webkit-transform 0.3s, color 0.3s;\
transition: transform 0.3s, color 0.3s;\
text-rendering: geometricPrecision;\
position: relative;\
display: block;\
width: 100%;\
}\
\
.taglabeljuro::before {\
content: "";\
position: absolute;\
top: 0;\
left: 0;\
width: 100%;\
height: 100%;\
border: 0px solid transparent;\
-webkit-transition: border-width 0.3s, border-color 0.3s;\
transition: border-width 0.3s, border-color: 0.3s;\
}\
\
.taginputjuro:focus + .taglabeljuro::before,\
.tagspanjurofilled .taglabeljuro::before {\
border-width: 8px;\
border-color: #1784cd;\
border-top-width: 2em;\
}\
\
.taginputjuro:focus + .taglabeljuro .taglabelspanjuro,\
.tagspanjurofilled .taglabeljuro .taglabelspanjuro {\
color: #fff;\
-webkit-transform: translate3d(0, -1.5em, 0) scale3d(0.75, 0.75, 1);\
transform: translate3d(0, -1.5em, 0) scale3d(0.75, 0.75, 1) translateZ(1px);\
}\
\
.colorinput {\
width: 200px;\
border: 0;\
background-color: transparent;\
top: 0.2em;\
left: 1em;\
}\
\
.colorspan {\
left: 1em;\
}\
';
head.appendChild(style);
}
function _clearTags() {
GM_setValue('mefiusertags', '{}');
}
// load from greasemonkey's sqlite db.
function _loadFromMemory() {
var storedObject = JSON.parse(GM_getValue('mefiusertags', '{}'));
return storedObject;
}
// save back into the db overwriting the old value.
function _saveToMemory(newObject) {
var objAsStr = JSON.stringify(newObject);
GM_setValue('mefiusertags', objAsStr);
}
// get a user tag from the loaded values, if it exists.
function _getUserTag(userid) {
if (usertags[userid]) {
return usertags[userid];
}
return null;
}
// convert rgb color to hex color
// since element.style.backgroundColor returns an rgb value, but input type color expects a hex value.
// from: http://stackoverflow.com/a/3627747
function _rgb2hex(rgb) {
rgb = rgb.match(/^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/);
function hex(x) {
return ("0" + parseInt(x).toString(16)).slice(-2);
}
return "#" + hex(rgb[1]) + hex(rgb[2]) + hex(rgb[3]);
}
// build user tag's dom node
function _buildUserTagElement(userid, usertag) {
var a = document.createElement('a');
a.className = 'utag';
a.style = 'background-color:' + usertag.color;
a.href = '#';
a.title = 'Edit user tag';
var openspan = document.createElement('span');
openspan.class = 'utagp';
openspan.style = 'color:' + usertag.color;
openspan.appendChild(document.createTextNode('('));
var closespan = document.createElement('span');
closespan.class = 'utagp';
closespan.style = 'color:' + usertag.color;
closespan.appendChild(document.createTextNode(')'));
a.appendChild(openspan);
a.appendChild(document.createTextNode(usertag.tag));
a.appendChild(closespan);
a.addEventListener('click', function(e) {
e.preventDefault();
var usertag = {
tag: this.childNodes[1].textContent,
color: _rgb2hex(this.style.backgroundColor)
};
openPopup(e.clientX, e.clientY, userid, usertag);
}, false);
return a;
};
function _buildAddTagElement(userid) {
var a = document.createElement('a');
a.className = 'addtag';
a.href = '#';
a.title = 'Add a tag for this user';
a.addEventListener('click', function(e) {
e.preventDefault();
openPopup(e.clientX, e.clientY, userid);
}, false);
a.appendChild(document.createTextNode('+'));
return a;
}
function openPopup(posx, posy, userid, usertag) {
var popupdiv = document.querySelector('#usertaggerpopup');
// clear the fields.
// clear the fields
popupdiv.querySelector('#popupuserid').value = '';
popupdiv.querySelector('#popupusertag').value = '';
popupdiv.querySelector('#popupusercolor').value = '#61CAED';
// set the hidden userid
popupdiv.querySelector('#popupuserid').value = userid;
// if we have tag data then prefill it
if (usertag) {
popupdiv.querySelector('#popupusertag').value = usertag.tag;
popupdiv.querySelector('#popupusercolor').value = usertag.color;
popupdiv.querySelector('.delbutton').style.display = 'block';
} else {
popupdiv.querySelector('.delbutton').style.display = 'none';
}
if (popupdiv.querySelector('#popupusertag').value.trim() === '') {
popupdiv.querySelector('#tagspan').classList.remove('tagspanjurofilled');
} else {
popupdiv.querySelector('#tagspan').classList.add('tagspanjurofilled');
}
// display the popup at this position
popupdiv.style.top = posy + window.scrollY - 50 + "px";
popupdiv.style.left = posx + window.scrollX + 20 + "px";
popupdiv.style.display = 'block';
}
function closePopup(e) {
e.preventDefault();
var popupdiv = document.querySelector('#usertaggerpopup');
// hide the popup
popupdiv.style.display = 'none';
// clear the fields
popupdiv.querySelector('#popupuserid').value = '';
popupdiv.querySelector('#popupusertag').value = '';
popupdiv.querySelector('#popupusercolor').value = '#128A82';
}
function addTag(e) {
e.preventDefault();
var popupdiv = document.querySelector('#usertaggerpopup');
// get the inputs, ignore if empty
var tagtext = popupdiv.querySelector('#popupusertag').value;
var tagcolor = popupdiv.querySelector('#popupusercolor').value;
var userid = popupdiv.querySelector('#popupuserid').value;
var usertag = {
'tag': tagtext,
'color': tagcolor
};
usertags[userid] = usertag;
// update the tags db.
_saveToMemory(usertags);
var usernodes = document.querySelectorAll('.smallcopy a[href*=\'/user/' + userid +'\']');
// now add tags to all instances of the user so we don't have to refresh just to see them.
for (var i = 0; i < usernodes.length; i++) {
var usernode = usernodes[i];
// check if this user is already tagged.
var userTagElement = usernode.parentElement.querySelector('.utag');
if (userTagElement) {
// edit the existing tag
userTagElement.childNodes[1].textContent = usertag.tag;
userTagElement.style.backgroundColor = usertag.color;
userTagElement.children[0].style.color = usertag.color;
userTagElement.children[1].style.color = usertag.color;
} else {
// not yet tagged, tag it
userTagElement = _buildUserTagElement(userid, usertag);
// replace the addtag button with the tag
var addTagElement = usernode.parentNode.querySelector('.addtag');
usernode.parentNode.replaceChild(userTagElement, addTagElement);
}
}
closePopup(e);
}
function delTag(e) {
e.preventDefault();
var popupdiv = document.querySelector('#usertaggerpopup');
var userid = popupdiv.querySelector('#popupuserid').value;
// remove the tag from the db.
delete usertags[userid];
_saveToMemory(usertags);
// remove the tags from the page without refreshing
var usernodes = document.querySelectorAll('.smallcopy a[href*=\'/user/' + userid +'\']');
for (var i = 0; i < usernodes.length; i++) {
var usernode = usernodes[i];
var userTagElement = usernode.parentElement.querySelector('.utag');
if (userTagElement) {
userTagElement.parentNode.replaceChild(_buildAddTagElement(userid), userTagElement);
}
}
closePopup(e);
}
function _onTagInputFocus(e) {
document.querySelector('#tagspan').classList.add('tagspanjurofilled');
}
function _onTagInputBlur(e) {
if (e.target.value.trim() === '') {
document.querySelector('#tagspan').classList.remove('tagspanjurofilled');
}
}
function _createPopup() {
// add our popup div to the body
var popupdiv = document.createElement('div');
popupdiv.id = 'usertaggerpopup';
popupdiv.className = 'usertaggerpopup';
var cbutton = document.createElement('a');
cbutton.href = '#';
cbutton.className = 'closebutton';
cbutton.addEventListener('click', closePopup, false);
cbutton.appendChild(document.createTextNode('×'));
popupdiv.appendChild(cbutton);
var tagspan = document.createElement('span');
tagspan.className = 'tagspanjuro';
tagspan.id = 'tagspan';
var taginput = document.createElement('input');
taginput.id = 'popupusertag';
taginput.type = 'text';
taginput.className = 'taginputjuro'
taginput.value = '';
taginput.addEventListener('focus', _onTagInputFocus);
taginput.addEventListener('blur', _onTagInputBlur);
var taglabel = document.createElement('label');
taglabel.className = 'taglabeljuro';
taglabel.for = 'popupusertag';
var taglabelspan = document.createElement('span');
taglabelspan.className = 'taglabelspanjuro';
taglabelspan.appendChild(document.createTextNode('Tag'));
taglabel.appendChild(taglabelspan);
tagspan.appendChild(taginput);
tagspan.appendChild(taglabel);
popupdiv.appendChild(tagspan);
var colorinput = document.createElement('input');
colorinput.id = 'popupusercolor';
colorinput.type = 'color';
colorinput.className = 'colorinput';
colorinput.value = '#128A82';
var colorlabel = document.createElement('label');
colorlabel.className = 'colorlabel';
colorlabel.for = 'popupusercolor';
colorlabel.appendChild(document.createTextNode('Color:'));
var colorspan = document.createElement('span');
colorspan.className = 'colorspan';
colorspan.appendChild(colorlabel);
colorspan.appendChild(colorinput);
popupdiv.appendChild(colorspan);
var idinput = document.createElement('input');
idinput.id = 'popupuserid';
idinput.style = 'display:none;'
idinput.text = '';
popupdiv.appendChild(idinput);
var abutton = document.createElement('a');
abutton.href = '#';
abutton.addEventListener('click', addTag, false);
abutton.appendChild(document.createTextNode('tag!'));
abutton.className = 'tagbutton';
popupdiv.appendChild(abutton);
var dbutton = document.createElement('a');
dbutton.href = '#';
dbutton.addEventListener('click', delTag, false);
dbutton.appendChild(document.createTextNode('delete'));
dbutton.className = 'delbutton';
popupdiv.appendChild(dbutton);
document.body.appendChild(popupdiv);
}
// all helpers defined, do stuff now:
// inject our css classes into the page
_injectStyle();
// create the popup div
_createPopup();
// load tags from memory
var usertags = _loadFromMemory();
// get all username nodes
var usernodes = document.querySelectorAll('.smallcopy a[href*=\'/user/\']');
// loop on each user
for (var i = 0; i < usernodes.length; i++) {
var usernode = usernodes[i];
// extract the userid
var userid = usernode.href.split('/').pop();
if (!userid) {
return;
}
// get the tag if any
var usertag = _getUserTag(userid);
// add tag node
if (usertag) {
var userTagElement = _buildUserTagElement(userid, usertag);
usernode.parentNode.insertBefore(userTagElement, usernode.nextSibling);
userTagElement.parentNode.insertBefore(document.createTextNode(' '), userTagElement);
} else {
// this user isn't tagged but add a small button to tag with
var addTagElement = _buildAddTagElement(userid);
usernode.parentNode.insertBefore(addTagElement, usernode.nextSibling);
addTagElement.parentNode.insertBefore(document.createTextNode(' '), addTagElement);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment