Last active
March 14, 2016 14:04
-
-
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]
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ==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