Difference between revisions of "User talk:Void Singer/SL Forum Ignore"
(→Design) |
Void Singer (talk | contribs) m (→Current status / support (Mar 2010): response) |
||
(22 intermediate revisions by 3 users not shown) | |||
Line 4: | Line 4: | ||
P.S. The onclick attribute has fallen out of favor. addEventListener is the approved method of registering events. | P.S. The onclick attribute has fallen out of favor. addEventListener is the approved method of registering events. | ||
:originally I had been injecting the whole script, but didn't know about the changes to GM_*Value context or some of the newer methods (haven't played with javascript for awhile since I quite doing web work)... took me awhile to dig up a useful example for grabbing variables back to the sandbox context... current version is only beta while I make sure the features I have, work, then I'll rewrite for max efficiency... | |||
:There is a more recent version on userscripts [http://userscripts.org/scripts/show/68846] that adds jive version testing and wrappers the the GM_*Value functions for safe degrading to localStorage w/ JSON for Chrome/Opera/Safari/IE8 (although I'm not sure all the other methods will pass... I can't get a good idea what level of XPATH support they have, for instance). My understanding was that FF didn't have support for GM_* before v3 though? But that was just a matter of quick reading out of date resources... | |||
:Oh and I didn't know you could "or" the classes like that... nice, I was reading the MSDN for XPATH and must've missed it. (I also didn't realize we have javascript code wrappers on the wiki). '''I'd more than welcome input/suggestion/assistance on the script''', I've got a few planned feature tweaks to make it nicer (listed on the new forum under beta testers), but some of them run in different directories on the new forum, so I haven't decided whether to make them separate scripts or not to cut down on page parsing... | |||
:The page layout on the Blogorums is... messy. and the wysiwyg editor loves to overuse &nbsp; and there's lots of garbage spacing every... plus I forgot to convert childNodes to getElementByTagName since in both cases there's only 1 html element in the container, and it's an anchor. anyways, I'm rambling so I'll stop here <br/>-- '''[[User:Void_Singer|Void]]''' <sup><small>([[User_talk:Void_Singer|talk]]|[[Special:Contributions/Void_Singer|contribs]])</small></sup> 00:33, 14 February 2010 (UTC) | |||
I tried to tweak it to be better... but it turned out to be easier to rewrite it from scratch. I redid just about everything with fuzzy xpath (I love "ancestor"). This final version is a lot similar than my previous version (which instead of mod'ing the post classes on the fly, it generated custom style elements on the fly; it did not scale). It looks like there is more CSS than code. | |||
As to debugging JS in firefox, I recommend: | |||
*[https://addons.mozilla.org/en-US/firefox/addon/11900 FireXPath] - XPath tab in Firebug, easier to use than XPather. | |||
*[http://xpath.alephzarro.com/ XPather] - Has an excellent concise XPath help window. | |||
<javascript> | |||
// ==UserScript== | |||
// @name SL Forum Ignore | |||
// @namespace http://home.comcast.net/~mailerdaemon | |||
// @description Adds Ignore Links to User Posts on SL Forum Discussions/Questions (And Make Linden Names RED) | |||
// @include https://blogs.secondlife.com/thread/* | |||
// @include https://blogs.secondlife.com/message/* | |||
// @version 1.1 | |||
// ==/UserScript== | |||
var names = {}; | |||
GM_addStyle([ | |||
".GM-ignore-link-wrapper { cursor:pointer; }", | |||
"", | |||
".GM-ignore-link-wrapper .ignore { display:block;}", | |||
".GM-ignore-link-wrapper .unignore { display:none;}", | |||
".ignored-user .GM-ignore-link-wrapper .ignore {display:none;}", | |||
".ignored-user .GM-ignore-link-wrapper .unignore {display:block;}", | |||
"", | |||
".ignored-user .jive-author { padding-top:0; padding-bottom:0; }", | |||
".ignored-user .jive-author-avatar-container { display:none!important; }", | |||
".ignored-user .jive-author-avatar-container { background-image: none !important; }", | |||
".ignored-user .jive-author > em { display:none; }", | |||
"", | |||
".ignored-user .jive-thread-post-body-container { min-height:0; background-image: none !important; padding-bottom:8px; }", | |||
".ignored-user .jive-thread-post-subject { float: left; width:auto; }", | |||
".ignored-user .jive-thread-post-subject h2 { display:none; }", | |||
".ignored-user .jive-thread-post-message { display:none; }", | |||
".ignored-user .jive-thread-post-details { background-image: none !important; padding-top:0; margin-top:0; width:auto;}", | |||
".ignored-user .jive-thread-post-subject-content { background-image: none !important; padding-bottom:0; margin-bottom:0; }", | |||
".ignored-user .jive-thread-post-subject-content .jive-thread-post-reply { display:none; }", | |||
"", | |||
".ignored-user .jive-thread-reply-body-container { min-height:0; padding-bottom:6px; }", | |||
".ignored-user .jive-thread-reply-subject { float: left; width:auto; padding-bottom:0!important;}", | |||
".ignored-user .jive-thread-reply-subject strong { display:none!important; }", | |||
".ignored-user .jive-thread-reply-message { display:none; }", | |||
".ignored-user .jive-content-controls { text-align:right; width:auto; padding-top:0; padding-bottom:0;}", | |||
].join("\n")); | |||
var box; | |||
$Z("//div[@class='jive-thread-post-body' or @class='jive-thread-reply-body']", function(r,i){ | |||
var linkwrapper = $X("./div[@class='jive-author']/div[@class='jive-username-link-wrapper']", r); | |||
var fullname = $X("./a", linkwrapper).href.replace(/https?:\/\/blogs\.secondlife\.com\/people\/([^;\/?]+).*/, "$1"); | |||
var name = fullname.split("."); | |||
if(name.slice(-1)[0] != "Linden") | |||
{//this all could be made part of the original selector but... that would be ugly to say the least. | |||
var hidden = names[fullname] = Boolean(GM_getValue(fullname, false)); | |||
r.parentNode.setAttribute("username", fullname);//didn't want to pollute the body css | |||
if(!box) | |||
{ | |||
box = document.createElement("div"); | |||
box.className = "GM-ignore-link-wrapper"; | |||
{ | |||
var span = document.createElement("span"); | |||
span.appendChild(document.createTextNode("Ignore")); | |||
span.className="ignore"; | |||
box.appendChild(span); | |||
} | |||
{ | |||
var span = document.createElement("span"); | |||
span.appendChild(document.createTextNode("Unignore")); | |||
span.className="unignore"; | |||
box.appendChild(span); | |||
} | |||
} | |||
if(hidden) | |||
QuickHide(r.parentNode); | |||
var boxy = box.cloneNode(true); | |||
insertAfter(boxy, linkwrapper); | |||
boxy.addEventListener("click", toggleIgnore, false); | |||
} | |||
}); | |||
function QuickHide(r){ r.className = r.className + " ignored-user"; } | |||
function QuickShow(r){ r.className = r.className.split(" ").filter(function(v){ return v != "ignored-user";}).join(" "); } | |||
function toggleIgnore(event){ | |||
var base = $X("./ancestor::div[@username]", event.currentTarget); | |||
var fullname = base.getAttribute("username"); | |||
var hidden = names[fullname] = !names[fullname]; | |||
if(hidden) | |||
GM_setValue(fullname, true); | |||
else | |||
GM_deleteValue(fullname); | |||
$Z("//div[@username='"+fullname+"']", hidden?QuickHide:QuickShow, base); | |||
} | |||
function insertAfter(insert, after){return after.parentNode.insertBefore(insert, after.nextSibling);} | |||
function insertBefore(insert, before){return before.parentNode.insertBefore(insert, before);} | |||
function $X(_xpath, node){//to search in a frame, you must traverse the .contentDocument or .contentWindow attribute. | |||
var doc = (node)?(node.ownerDocument || node):(node = document); | |||
return doc.evaluate(_xpath, node, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null).snapshotItem(0); | |||
} | |||
function $Y(_xpath, node){ | |||
var doc = (node)?(node.ownerDocument || node):(node = document); | |||
return doc.evaluate(_xpath, node, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); | |||
} | |||
function $Z(_xpath, func, node){ | |||
var doc = (node)?(node.ownerDocument || node):(node = document); | |||
var res = doc.evaluate(_xpath, node, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); | |||
var args = Array.prototype.slice.call(arguments, 3); | |||
var i = 0; | |||
for (; i < res.snapshotLength; ++i) | |||
func.apply(func, [res.snapshotItem(i), i].concat(args)); | |||
return i; | |||
} | |||
</javascript> | |||
-- '''[[User:Strife_Onizuka|Strife]]''' <sup><small>([[User talk:Strife_Onizuka|talk]]|[[Special:Contributions/Strife_Onizuka|contribs]])</small></sup> 07:47, 14 February 2010 (UTC) | |||
:lol, I should know better than to ask you for suggestions... you anticipated most of the niceties that I was going to add (kill every but the the name and unignore buttons)... and then did them all =P | |||
:I did notice one slight bug in these three lines '''var fullname = $X("./a", linkwrapper).textContent; var name = fullname.split(" "); if(name[1] != "Linden")'''. won't work 100% because we've got at least one linden with a space in their first name (Kona linden Linden) and also, any user that sets the preference "hide last name" in their profile (as I have), will have a text name that matches their system name... including Lindens (Making that Linden ignorable... the system format is "first.last", with other spaces collapsed)... hence the reason why I was parsing their system name from the .href, rather than from the .textContent which could lead to a user taking themselves off ignore by changing that setting (at least until they were added again under the new name as well, and also means it's harder to take them off ignore)... I haven't found any Lindens that have used this setting, but people with this setting on are near impossible to find via people search on the site, and google stubbornly refuses to follow a strict match on punctuation marks =/ | |||
:I'm assuming names is being used as a backup GM_*Value... and only good per session? assuming GM_ functions fail, won't GM_addStyle fail too? | |||
:I'm not going to pretend to automatically understand all the references at a glance, but the $Z function is a quite confusing... '''func.apply(func, [res.snapshotItem(i), i].concat(args)''', your essentially appending _xpath, func, and node (when it's called) to array of things to apply to, which would seem to be an infinitely expanding loop of calls to quickHide or quickShow.... (including calls of those functions to those functions) I'm not seeing how it's breaking out, or what the "3" argument to .call in "args" is doing.... | |||
:ps thanks for the link to FireXPath, it's funny but I was actually considering using getElementsByClass in the rewrite if it turned out faster... believe it or not I only recently installed FireBug, as I've had no want to do web work in a while... it's quite the fun tool though and I've been wandering around the SL site crying at the markup it uses | |||
:pps I am NOT suggesting YOU make those changes... although at this point your script is so drastically different and improved from my own, I wouldn't want to take any credit on it's creation <br/>-- '''[[User:Void_Singer|Void]]''' <sup><small>([[User_talk:Void_Singer|talk]]|[[Special:Contributions/Void_Singer|contribs]])</small></sup> 03:33, 15 February 2010 (UTC) | |||
::I didn't know that about the names and thats a really silly oversight with Kona's name (has it been reported as a bug?). I understand now why you had it reading the href. GM_[sg]etValue's persist across sessions and between tabs; they are stored in the users preferences (but are only accessible to the GM script that stored them). | |||
::I'm proud of the design of $Z, but not entirely happy with the implementation. | |||
<javascript> | |||
function $Z(_xpath, func, node){ | |||
//When working with frames we cannot assume the node we are parsing is in the script's document. | |||
//So we try to find the document that owns 'node', which is conveniently in the 'ownerDocument' property. | |||
//While Document objects do have an 'ownerDocument' property, it's always null. | |||
//If 'node' is null, than we set 'node' and 'doc' to the scripts 'document' ('node' should not be null). | |||
var doc = (node)?(node.ownerDocument || node):(node = document); | |||
//Using 'ownerDocument' this is brittle. | |||
//I spent a sizable chunk of time trying to find a better way, i didn't find one; | |||
//"instanceof" is unusable on XPCNativeWrapper'ed objects (unwrapping does not help). | |||
//Next we evalute the xpath in our node, in it's document | |||
var res = doc.evaluate(_xpath, node, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); | |||
//Later we will want an array of any extra arguments that were passed this function. | |||
//'arguments' may look like an array but it is not; it lacks array function methods. | |||
//But that is irrelevant. We can get at the function another way. | |||
var args = Array.prototype.slice.call(arguments, 3); | |||
//'call' btw is a default method of all functions (functions are objects too). | |||
//The 'this' value in the function is set equal to the first parameter, any following parameters are passed along to the function. | |||
//'call' can be defined as: | |||
//function call(targetThis){ this.apply(targetThis, Array.prototype.slice.call(arguments, 1)); } | |||
//We now loop through the xpath results. | |||
var i = 0; | |||
for (; i < res.snapshotLength; ++i) { | |||
//We want to call func, passing along the found node, the index of the node, and the any extra arguments. | |||
//So we build the partial argument array and append the extra arguments. | |||
func.apply(func, [res.snapshotItem(i), i].concat(args)); | |||
} | |||
//Because we can, lets return the number of nodes found. | |||
return i; | |||
} | |||
</javascript> | |||
:You may notice I edited the script, I had partial Window support but it was incomplete (hadn't realized at the time of posting); full support wasn't worth the extra code, and only marginally useful. | |||
:Jive uses jQuery and loads it into it's pages, if you wanted, you could port the script to jQuery and free it from using XPath; jQuery supports more browsers than those that implement XPath. -- '''[[User:Strife_Onizuka|Strife]]''' <sup><small>([[User talk:Strife_Onizuka|talk]]|[[Special:Contributions/Strife_Onizuka|contribs]])</small></sup> 17:53, 15 February 2010 (UTC) | |||
::understood on the persistence of GM_*Value, was more curious about if the '''name''' array was supposed to back it up for browsers that didn't support it, and if so, does '''name''' persist across sessions (wouldn't seem like it, but greasemonkey and related environments are new toys for me). If it is backup support for those 3 functions (on other Grease* platforms), it would seem there needs to be a backup for GM_addStyle too as I'm pretty sure none of the other Grease* products implement them separately (chrome, opera, safari). although I couldn't attest 100% to that. it's why I wrote conditional JSON alternatives for the GM_*Value functions in the update of mine the other day on userscripts (all recent browsers are supposed to have native JSON support, though I haven't had any one test them yet)... I'll look into a conditional alternative for GM_addStyle when I have a moment, for supporting other platforms | |||
::PS no clue if it's been reported, I found it accidentally while looking for Lindens that may have set "hide last name". AFAIK it's the only instance of a first name that has a space in it ANYWHERE in SL, and was obviously a case of bad data entry checking on LL's end... shouldn't have been possible =| <br/>-- '''[[User:Void_Singer|Void]]''' <sup><small>([[User_talk:Void_Singer|talk]]|[[Special:Contributions/Void_Singer|contribs]])</small></sup> 03:00, 16 February 2010 (UTC) | |||
:::GM_addStyle does not persist (I knew there was a question I forgot to answer), but neither does the script state. A new instance of the script is created for each page that matches the rule, for each page the script is executed from scratch. If you want something a little more intelligent you may want to look at Jetpack (which IMHO is too immature to be used; I'm giving them feedback so they can the design right for the Reboot). -- '''[[User:Strife_Onizuka|Strife]]''' <sup><small>([[User talk:Strife_Onizuka|talk]]|[[Special:Contributions/Strife_Onizuka|contribs]])</small></sup> 08:09, 16 February 2010 (UTC) | |||
::Added some tweaks to yours (was faster than rebuilding mine), linden names in red, OP thread edit button on post, conditional replacements for GM_* for other browsers (untested, should work), namespace back to private (for compatibility with previous saves of mine) and jive version checking. still intend to do a less artful version myself, but I think you should release yours... I don't feel comfortable releasing it under my account unless you want me too (w/ credit, 'natch)... anyways, here 'tis below <br/>-- '''[[User:Void_Singer|Void]]''' <sup><small>([[User_talk:Void_Singer|talk]]|[[Special:Contributions/Void_Singer|contribs]])</small></sup> 07:37, 17 February 2010 (UTC) | |||
'''removed intermediate version''' | |||
---- | |||
hmmm | |||
<javascript> | |||
// ==UserScript== | |||
// @name SL Forum Ignore | |||
// @namespace Private | |||
// @description +Ignore Links on User Posts on SL Forum Discussions/Questions, +Make Linden Names RED, +OP Edit fix | |||
// @include https://blogs.secondlife.com/thread/* | |||
// @include https://blogs.secondlife.com/message/* | |||
// @version 1.2 | |||
// ==/UserScript== | |||
var names = {}; | |||
//--VS +compatibility replacements for Chrome4+, Opera10.5+, Safari4+ | |||
if (typeof GM_addStyle == 'undefined'){ | |||
function GM_addStyle( vCss ){ | |||
var vStyle = document.createElement( 'style' ); | |||
style.textContent = vCss; | |||
document.getElementsByTagName( 'head' )[0].appendChild( vStyle ); | |||
} | |||
//-- native JSON and localStorage for GM_*Value replacement | |||
GM_getValue = function( vKey, vDefault ){ | |||
return (JSON.parse( window.localStorage.getItem( vKey ) ) || vDefault); | |||
} | |||
GM_setValue = function( vKey, vValue ){ | |||
window.localStorage.setItem( vKey, JSON.stringify( vValue ) ); | |||
} | |||
GM_deleteValue = function( vKey ){ | |||
window.localStorage.removeItem( vKey ); | |||
} | |||
} | |||
//--VS +jive version change protection | |||
if ($X( "//div[@class='jiveVersion']", document ).textContent.indexOf( '80211' ) != -1 ){ | |||
//--VS +Thread Edit Link Fix | |||
var vGetEdit = document.getElementsByClassName( 'jive-link-edit' ).item( 0 ); | |||
if (vGetEdit != null){ | |||
vCloneEdit = vGetEdit.cloneNode( false ); | |||
document.getElementsByClassName( 'jive-thread-post-details' ).item( 0 ).appendChild( vCloneEdit ); | |||
vCloneEdit.className= "GM-Fix-Thread-Edit"; | |||
} | |||
//--VS +.GM-fix-thread-edit | |||
GM_addStyle([ | |||
".GM-Fix-Thread-Edit {", | |||
" background: url( '../images/jive-icon-edit-16x16.gif' ) no-repeat scroll left center transparent;", | |||
" font-weight: bold;", | |||
" padding-left: 17px;", | |||
" margin-right: 0.5em;", | |||
" float: right;}", | |||
"", | |||
".GM-ignore-link-wrapper { cursor:pointer; }", | |||
"", | |||
".GM-ignore-link-wrapper .ignore { display:block;}", | |||
".GM-ignore-link-wrapper .unignore { display:none;}", | |||
".ignored-user .GM-ignore-link-wrapper .ignore {display:none;}", | |||
".ignored-user .GM-ignore-link-wrapper .unignore {display:block;}", | |||
"", | |||
".ignored-user .jive-author { padding-top:0; padding-bottom:0; }", | |||
".ignored-user .jive-author-avatar-container { display:none!important; }", | |||
".ignored-user .jive-author-avatar-container { background-image: none !important; }", | |||
".ignored-user .jive-author > em { display:none; }", | |||
"", | |||
".ignored-user .jive-thread-post-body-container { min-height:0; background-image: none !important; padding-bottom:8px; }", | |||
".ignored-user .jive-thread-post-subject { float: left; width:auto; }", | |||
".ignored-user .jive-thread-post-subject h2 { display:none; }", | |||
".ignored-user .jive-thread-post-message { display:none; }", | |||
".ignored-user .jive-thread-post-details { background-image: none !important; padding-top:0; margin-top:0; width:auto;}", | |||
".ignored-user .jive-thread-post-subject-content { background-image: none !important; padding-bottom:0; margin-bottom:0; }", | |||
".ignored-user .jive-thread-post-subject-content .jive-thread-post-reply { display:none; }", | |||
"", | |||
".ignored-user .jive-thread-reply-body-container { min-height:0; padding-bottom:6px; }", | |||
".ignored-user .jive-thread-reply-subject { float: left; width:auto; padding-bottom:0!important;}", | |||
".ignored-user .jive-thread-reply-subject strong { display:none!important; }", | |||
".ignored-user .jive-thread-reply-message { display:none; }", | |||
".ignored-user .jive-content-controls { text-align:right; width:auto; padding-top:0; padding-bottom:0;}", | |||
"", | |||
"div[username$='.Linden'] div.jive-author div.jive-username-link-wrapper > a { color:red; }", | |||
].join("\n")); | |||
var box; | |||
$Z("//div[@class='jive-thread-post-body' or @class='jive-thread-reply-body']", function(r,i){ | |||
var linkwrapper = $X("./div[@class='jive-author']/div[@class='jive-username-link-wrapper']", r); | |||
var fullname = $X("./a", linkwrapper).href.replace(/https?:\/\/blogs\.secondlife\.com\/people\/([^;\/?]+).*/, "$1"); | |||
var name = fullname.split("."); | |||
r.parentNode.setAttribute("username", fullname); | |||
if(name.slice(-1)[0] != "Linden") | |||
{//this all could be made part of the original selector but... that would be ugly to say the least. | |||
var hidden = names[fullname] = Boolean(GM_getValue(fullname, false)); | |||
if(!box) | |||
{ | |||
box = document.createElement("div"); | |||
box.className = "GM-ignore-link-wrapper"; | |||
{ | |||
var span = document.createElement("span"); | |||
span.appendChild(document.createTextNode("Ignore")); | |||
span.className="ignore"; | |||
box.appendChild(span); | |||
} | |||
{ | |||
var span = document.createElement("span"); | |||
span.appendChild(document.createTextNode("Unignore")); | |||
span.className="unignore"; | |||
box.appendChild(span); | |||
} | |||
} | |||
if(hidden) | |||
QuickHide(r.parentNode); | |||
var boxy = box.cloneNode(true); | |||
insertAfter(boxy, linkwrapper); | |||
boxy.addEventListener("click", toggleIgnore, false); | |||
} | |||
}); | |||
} | |||
function QuickHide(r){ r.className = r.className + " ignored-user"; } | |||
function QuickShow(r){ r.className = r.className.split(" ").filter(function(v){ return v != "ignored-user";}).join(" "); } | |||
function toggleIgnore(event){ | |||
var base = $X("./ancestor::div[@username]", event.currentTarget); | |||
var fullname = base.getAttribute("username"); | |||
var hidden = names[fullname] = !names[fullname]; | |||
if(hidden) | |||
GM_setValue(fullname, true); | |||
else | |||
GM_deleteValue(fullname); | |||
$Z("//div[@username='"+fullname+"']", hidden?QuickHide:QuickShow, base); | |||
} | |||
function insertAfter(insert, after){return after.parentNode.insertBefore(insert, after.nextSibling);} | |||
function insertBefore(insert, before){return before.parentNode.insertBefore(insert, before);} | |||
function $X(_xpath, node){//to search in a frame, you must traverse the .contentDocument or .contentWindow attribute. | |||
var doc = (node)?(node.ownerDocument || node):(node = document); | |||
return doc.evaluate(_xpath, node, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null).snapshotItem(0); | |||
} | |||
function $Y(_xpath, node){ | |||
var doc = (node)?(node.ownerDocument || node):(node = document); | |||
return doc.evaluate(_xpath, node, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); | |||
} | |||
function $Z(_xpath, func, node){ | |||
var doc = (node)?(node.ownerDocument || node):(node = document); | |||
var res = doc.evaluate(_xpath, node, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); | |||
var args = Array.prototype.slice.call(arguments, 3); | |||
var i = 0; | |||
for (; i < res.snapshotLength; ++i) | |||
func.apply(func, [res.snapshotItem(i), i].concat(args)); | |||
return i; | |||
} | |||
</javascript> | |||
=== Last Update? === | |||
: removed the intermediate version, this is my non-xpath rewrite using getElementsByClassName (wanted to see if it's really as much faster as claimed). fixed a bit of bugginess that I introduced int the replacement functions, and a logic error in the handling of OP edit buttons (actions bar retains an edit link on following pages in flat view) | |||
: I had to convert the username attributes in most cases to class additions, to avoid searching for multiple classes on quickhide/show. and then I cheated by using those instead in the ignore button box, to grab it for class find since I don't see a simple solution to 'ancestor' support for that. having to make two calls for the original button inserts (because there is no "OR" support in that function) is a bit ugly... but it's damn fast. stuff got renamed/reformatted/collapsed/inlined to fit my personal style for consistency (hope you don't mind) but the structure is basically yours. ''with your permission (and assuming you don't find any obvious bugs) I'd like to release it as shown below.'' <br/>-- '''[[User:Void_Singer|Void]]''' <sup><small>([[User_talk:Void_Singer|talk]]|[[Special:Contributions/Void_Singer|contribs]])</small></sup> 01:27, 19 February 2010 (UTC) | |||
<javascript>// ==UserScript== | |||
// @name SL Forum Ignore | |||
// @namespace Private | |||
// @description +Ignore Links on User Posts on SL Forum Discussions/Questions, +Make Linden Names RED, +OP Edit fix | |||
// @include https://blogs.secondlife.com/thread/* | |||
// @include https://blogs.secondlife.com/message/* | |||
// @match https://blogs.secondlife.com/thread/* | |||
// @match https://blogs.secondlife.com/message/* | |||
// @version 1.3 | |||
// @copyright Void Singer 2009 - 2010 [ https://wiki.secondlife.com/wiki/User:Void_Singer ] & BlindWanderer [ http://userscripts.org/users/4223 ] | |||
// @license CC-BY [ http://creativecommons.org/licenses/by/3.0 ] | |||
// @licesne.ext Free to copy, use, modify, distribute, or sell, with attribution. | |||
// ==/UserScript== | |||
/*//-- added match statements for compatibility with chromium 'content scripts' --//*/ | |||
/*//-- IE7pro unsupported: lacks CSS inheritance & getElementsByClassName --//*/ | |||
//-- compatibility replacements for Chrome4+, Opera10.5+, Safari4+, I hope | |||
if (typeof GM_addStyle == 'undefined'){ | |||
function GM_addStyle( vCss ){ | |||
var vStyle = document.createElement( 'style' ); | |||
vStyle.textContent = vCss; | |||
document.getElementsByTagName( 'head' )[0].appendChild( vStyle ); | |||
} | |||
} //-- "it's possible that *this* GM_ function is supported in chromium, but the others are not. can't find docs" | |||
//-- compatibility replacements for Chrome4+, Opera10.5+, Safari4+, I hope | |||
if (typeof GM_setValue == 'undefined'){ | |||
//-- native JSON and localStorage for GM_*Value replacement | |||
GM_getValue = function( vKey, vDefault ){ | |||
var vReturn = JSON.parse( window.localStorage.getItem( vKey ) ); | |||
return ((vReturn == null)? vDefault: vReturn); | |||
} | |||
GM_setValue = function( vKey, vValue ){ | |||
window.localStorage.setItem( vKey, JSON.stringify( vValue ) ); | |||
} | |||
GM_deleteValue = function( vKey ){ | |||
window.localStorage.removeItem( vKey ); | |||
} | |||
} | |||
//-- function contributed by BlindWanderer | |||
function uHide( vPostNode ){ | |||
vPostNode.className += ' ignored-user'; | |||
} | |||
//-- function contributed by BlindWanderer | |||
function uShow( vPostNode ){ | |||
vPostNode.className = vPostNode.className.split( ' ' ).filter( function( v ){ return v != 'ignored-user';} ).join( ' ' ); | |||
} | |||
//-- function contributed by BlindWanderer, modifed by Void Singer | |||
function uToggleIgnore(){ | |||
//-- no ancestor support outside of xpath? | |||
var vName = this.getAttribute( 'username' ); | |||
//-- ?is there really any savings to caching instead of a direct call to GM_getValue? | |||
var vAction = (vIgnoreCache[vName] = !vIgnoreCache[vName])? uHide : uShow; | |||
if (vIgnoreCache[vName]){ | |||
GM_setValue( vName, true ); | |||
} | |||
else{ | |||
GM_deleteValue( vName ); | |||
} | |||
var vSwaps = document.getElementsByClassName( vName ); | |||
var vCount = 0; | |||
for (vCount; vCount < vSwaps.length; ++vCount){ | |||
vAction.apply( vAction, [vSwaps.item( vCount )] ); | |||
} | |||
} | |||
//-- adds styles, ignore toggles, and marks post containers | |||
function uTweakPage(){ | |||
//-- use style to reduce load, contributed by BlindWanderer, modifed by Void Singer | |||
GM_addStyle( [ | |||
'.GM-Fix-Thread-Edit {', | |||
' background: url( "../images/jive-icon-edit-16x16.gif" ) no-repeat scroll left center transparent;', | |||
' font-weight: bold;', | |||
' padding-left: 17px;', | |||
' margin-right: 0.5em;', | |||
' float: right;}', | |||
'', | |||
'.GM-ignore-link-wrapper { cursor:pointer; }', | |||
'', | |||
'.GM-ignore-link-wrapper .ignore { display:block;}', | |||
'.GM-ignore-link-wrapper .unignore { display:none;}', | |||
'.ignored-user .GM-ignore-link-wrapper .ignore {display:none;}', | |||
'.ignored-user .GM-ignore-link-wrapper .unignore {display:block;}', | |||
'', | |||
'.ignored-user .jive-author { padding-top:0; padding-bottom:0; }', | |||
'.ignored-user .jive-author-avatar-container { display:none!important; }', | |||
'.ignored-user .jive-author-avatar-container { background-image: none !important; }', | |||
'.ignored-user .jive-author > em { display:none; }', | |||
'', | |||
'.ignored-user .jive-thread-post-body-container { min-height:0; background-image: none !important; padding-bottom:8px; }', | |||
'.ignored-user .jive-thread-post-subject { float: left; width:auto; }', | |||
'.ignored-user .jive-thread-post-subject h2 { display:none; }', | |||
'.ignored-user .jive-thread-post-message { display:none; }', | |||
'.ignored-user .jive-thread-post-details { background-image: none !important; padding-top:0; margin-top:0; width:auto;}', | |||
'.ignored-user .jive-thread-post-subject-content { background-image: none !important; padding-bottom:0; margin-bottom:0; }', | |||
'.ignored-user .jive-thread-post-subject-content .jive-thread-post-reply { display:none; }', | |||
'', | |||
'.ignored-user .jive-thread-reply-body-container { min-height:0; padding-bottom:6px; }', | |||
'.ignored-user .jive-thread-reply-subject { float: left; width:auto; padding-bottom:0!important;}', | |||
'.ignored-user .jive-thread-reply-subject strong { display:none!important; }', | |||
'.ignored-user .jive-thread-reply-message { display:none; }', | |||
'.ignored-user .jive-content-controls { text-align:right; width:auto; padding-top:0; padding-bottom:0;}', | |||
'', | |||
'div[class$=".Linden"] div.jive-author div.jive-username-link-wrapper > a { color:#BB0000; }', | |||
].join( '\n' ) ); | |||
//-- clone edit thread link to OP | |||
var vGetOP = document.getElementsByClassName( 'jive-thread-post-details' ).item( 0 ); | |||
var vGetEdit = document.getElementsByClassName( 'jive-link-edit' ).item( 0 ); | |||
if ((null != vGetEdit) && (null != vGetOP)){ //-- need to text both for flat paged views (edit still shows on follow-up pages) | |||
var vCloneEdit = vGetEdit.cloneNode( true ); | |||
vGetOP.appendChild( vCloneEdit ); | |||
vCloneEdit.className = 'GM-Fix-Thread-Edit'; | |||
} | |||
//-- create generic toggle for ignore buttons, contributed by BlindWanderer | |||
var vToggleBox = document.createElement( 'div' ); | |||
vToggleBox.className = 'GM-ignore-link-wrapper'; | |||
{ | |||
var span = document.createElement( 'span' ); | |||
span.appendChild( document.createTextNode( 'Ignore' ) ); | |||
span.className = 'ignore'; | |||
vToggleBox.appendChild(span); | |||
} | |||
{ | |||
var span = document.createElement( 'span' ); | |||
span.appendChild( document.createTextNode( 'Unignore' ) ); | |||
span.className = 'unignore'; | |||
vToggleBox.appendChild(span); | |||
} | |||
function uInsertToggles( vPostNodes ){ //-- contributed by BlindWanderer, modifed by Void Singer | |||
var vCount = 0; | |||
//-- loop through posts | |||
for (vCount; vCount < vPostNodes.length; ++vCount){ | |||
//-- get user profile link for position, and grab their system name | |||
var vUserLink = vPostNodes.item( vCount ).getElementsByClassName( 'jive-username-link' ).item( 0 ); | |||
var vName = vUserLink.href.substring( vUserLink.href.lastIndexOf( '/' ) + 1 ); | |||
//-- tag post for ignore handling | |||
vPostNodes.item( vCount ).parentNode.className += ' ' + vName; //-- changed to class vs custom attribute | |||
if (vName.split( '.' ).slice( -1 )[0] != 'Linden'){ | |||
//-- check and ignore post if user already on ignore | |||
//-- ?is there really any savings to caching instead of a direct call to GM_getValue? | |||
var vIsIgnored = vIgnoreCache[vName] = GM_getValue( vName, false ); | |||
if (vIsIgnored){ | |||
uHide( vPostNodes.item( vCount ).parentNode ); | |||
} | |||
//-- clone ignore toggle into user info box, and tie it the switch function | |||
var vIgnoreToggle = vToggleBox.cloneNode( true ); | |||
//-- ?no ancestor support outside of xpath? | |||
vIgnoreToggle.setAttribute( 'username', vName ); | |||
vUserLink.parentNode.insertBefore( vIgnoreToggle, vUserLink.nextSibling ); //--contributed by Blind Wanderer | |||
vIgnoreToggle.addEventListener( 'click', uToggleIgnore, false ); | |||
} | |||
} | |||
} | |||
//-- insert Toggle in OP | |||
uInsertToggles( document.getElementsByClassName( 'jive-thread-post-body' ) ); | |||
//-- insert Toggle in replies | |||
uInsertToggles( document.getElementsByClassName( 'jive-thread-reply-body' ) ); | |||
//-- not happy with the separate inserts for OP and reply, but it wasn't worth a custom function to grab both | |||
} | |||
//-- ?is there really any savings to caching instead of a direct call to GM_getValue? | |||
var vIgnoreCache = {}; //-- contributed by BlindWanderer | |||
//-- jive version checking | |||
if (document.getElementsByClassName( 'jiveVersion' ).item( 0 ).textContent.indexOf( '80211' ) != -1){ | |||
uTweakPage(); | |||
}</javascript> | |||
EDIT: forgot to tweak the linden name css selector when I switch from username to class addition <br/>-- '''[[User:Void_Singer|Void]]''' <sup><small>([[User_talk:Void_Singer|talk]]|[[Special:Contributions/Void_Singer|contribs]])</small></sup> 01:34, 19 February 2010 (UTC) | |||
EDIT: Tweaked comments/header<br/>-- '''[[User:Void_Singer|Void]]''' <sup><small>([[User_talk:Void_Singer|talk]]|[[Special:Contributions/Void_Singer|contribs]])</small></sup> 08:55, 21 February 2010 (UTC) | |||
:Looks good. -- '''[[User:Strife_Onizuka|Strife]]''' <sup><small>([[User talk:Strife_Onizuka|talk]]|[[Special:Contributions/Strife_Onizuka|contribs]])</small></sup> 19:00, 21 February 2010 (UTC) | |||
I'll take that as approval to use your bits in the current v. I could still go through and get the micro tweaks... and I found one more needed tweak for compatibility with Cerise's High Contrast Style (!important on linden names color), but other than that, I'm posting now =) <br/>-- '''[[User:Void_Singer|Void]]''' <sup><small>([[User_talk:Void_Singer|talk]]|[[Special:Contributions/Void_Singer|contribs]])</small></sup> 02:40, 22 February 2010 (UTC) | |||
:You have gone out of your way to credit me, above and beyond what was needed. I would be happy with just being mentioned in the @copyright/@author tag. *tweak* -- '''[[User:Strife_Onizuka|Strife]]''' <sup><small>([[User talk:Strife_Onizuka|talk]]|[[Special:Contributions/Strife_Onizuka|contribs]])</small></sup> 15:28, 22 February 2010 (UTC) | |||
credit were credit is due =) Comment/author lines are cheap, recognizing and crediting other's contributions to your goals are priceless (cue credit card commercial lol). Seriously though, I've lived on the corporate ladder long enough to prefer dragging people up with me that help me, even if it doesn't get me as high as stepping on their backs. hope you don't mind I used your userscripts name rather than your SL name. I've seen from some of the things you've written that you prefer not to encourage connections between online identities ;) | |||
PS I also didn't catch your comment about getElementsByClassName on the edit summary until after I'd written that version... go go powers of observation neh?<br/>-- '''[[User:Void_Singer|Void]]''' <sup><small>([[User_talk:Void_Singer|talk]]|[[Special:Contributions/Void_Singer|contribs]])</small></sup> 22:00, 23 February 2010 (UTC) | |||
== Current status / support (Mar 2010) == | |||
* Does this really require Firefox 2.0 as stated? What about Firefox 3.x? | |||
* I should do further testing about this, but it looks like the script works with Google Chrome. Apparently, you can install Greasemonkey scripts natively in Google Chrome. Confirmed on: | |||
** 5.0.360.0 dev on Windows | |||
* The script did NOT work in these pages (note it's a different page template) | |||
** https://blogs.secondlife.com/docs/DOC-1331 | |||
THANKS for your efforts! --[[User:Opensource Obscure|oobscure]] 14:09, 30 March 2010 (UTC) | |||
:Firefox 2.0 is probably the min version it supports, it will work fine in 3.x and beyond (barring any breaking changes to javascript and css; very unlikely). | |||
:Should work on google chrome, nothing too special about the JS and CSS at this point. | |||
:Thanks for the tip about 'docs' i'll look into it when I have some time. | |||
:-- '''[[User:Strife_Onizuka|Strife]]''' <sup><small>([[User talk:Strife_Onizuka|talk]]|[[Special:Contributions/Strife_Onizuka|contribs]])</small></sup> 05:47, 2 April 2010 (UTC) | |||
sorry for the confusion/slow response, I probably should have stated "2.0+" for the FF version, and I'd been waiting to hear back on other platforms supported. I didn't catch the doc pages, but I was already aware that it did not work on the blog comments either. currently it's filtered to only message/thread directories, which use a different format and contain only discussion threads (apparently). I'll probably extend support to those when I've got time to update, although I may institute those as separate scripts to reduce overhead for parsing the different structure... haven't decided. | |||
I also intend to add the CSS tags used by the LSL_Portal for LSL highlighting so that copied scripts from there will show in the same manner as the wiki (eventually)... or if possible I'll try to convince Nyx to just insert the identical CSS tag names directly when the lsl script highlighting component is done. and thanks for the main page updates <br/>-- '''[[User:Void_Singer|Void]]''' <sup><small>([[User_talk:Void_Singer|talk]]|[[Special:Contributions/Void_Singer|contribs]])</small></sup> 20:36, 3 April 2010 (UTC) |
Latest revision as of 12:37, 3 April 2010
Design
I'm curious why you are injecting code, it seems to complicate things. You could put everything in the uPersist function (I'd rename the function). I'm a pretty experienced GM script writer and I wouldn't mind making the changes. If you haven't already, consider posting it on UserScripts.org (it's got version tracking and makes script installation easy, oh and it's free). -- Strife (talk|contribs) 18:51, 13 February 2010 (UTC)
P.S. The onclick attribute has fallen out of favor. addEventListener is the approved method of registering events.
- originally I had been injecting the whole script, but didn't know about the changes to GM_*Value context or some of the newer methods (haven't played with javascript for awhile since I quite doing web work)... took me awhile to dig up a useful example for grabbing variables back to the sandbox context... current version is only beta while I make sure the features I have, work, then I'll rewrite for max efficiency...
- There is a more recent version on userscripts [1] that adds jive version testing and wrappers the the GM_*Value functions for safe degrading to localStorage w/ JSON for Chrome/Opera/Safari/IE8 (although I'm not sure all the other methods will pass... I can't get a good idea what level of XPATH support they have, for instance). My understanding was that FF didn't have support for GM_* before v3 though? But that was just a matter of quick reading out of date resources...
- Oh and I didn't know you could "or" the classes like that... nice, I was reading the MSDN for XPATH and must've missed it. (I also didn't realize we have javascript code wrappers on the wiki). I'd more than welcome input/suggestion/assistance on the script, I've got a few planned feature tweaks to make it nicer (listed on the new forum under beta testers), but some of them run in different directories on the new forum, so I haven't decided whether to make them separate scripts or not to cut down on page parsing...
- The page layout on the Blogorums is... messy. and the wysiwyg editor loves to overuse and there's lots of garbage spacing every... plus I forgot to convert childNodes to getElementByTagName since in both cases there's only 1 html element in the container, and it's an anchor. anyways, I'm rambling so I'll stop here
-- Void (talk|contribs) 00:33, 14 February 2010 (UTC)
I tried to tweak it to be better... but it turned out to be easier to rewrite it from scratch. I redid just about everything with fuzzy xpath (I love "ancestor"). This final version is a lot similar than my previous version (which instead of mod'ing the post classes on the fly, it generated custom style elements on the fly; it did not scale). It looks like there is more CSS than code.
As to debugging JS in firefox, I recommend:
- FireXPath - XPath tab in Firebug, easier to use than XPather.
- XPather - Has an excellent concise XPath help window.
<javascript> // ==UserScript== // @name SL Forum Ignore // @namespace http://home.comcast.net/~mailerdaemon // @description Adds Ignore Links to User Posts on SL Forum Discussions/Questions (And Make Linden Names RED) // @include https://blogs.secondlife.com/thread/* // @include https://blogs.secondlife.com/message/* // @version 1.1 // ==/UserScript==
var names = {};
GM_addStyle([
".GM-ignore-link-wrapper { cursor:pointer; }", "", ".GM-ignore-link-wrapper .ignore { display:block;}", ".GM-ignore-link-wrapper .unignore { display:none;}", ".ignored-user .GM-ignore-link-wrapper .ignore {display:none;}", ".ignored-user .GM-ignore-link-wrapper .unignore {display:block;}", "", ".ignored-user .jive-author { padding-top:0; padding-bottom:0; }", ".ignored-user .jive-author-avatar-container { display:none!important; }", ".ignored-user .jive-author-avatar-container { background-image: none !important; }", ".ignored-user .jive-author > em { display:none; }", "", ".ignored-user .jive-thread-post-body-container { min-height:0; background-image: none !important; padding-bottom:8px; }", ".ignored-user .jive-thread-post-subject { float: left; width:auto; }", ".ignored-user .jive-thread-post-subject h2 { display:none; }", ".ignored-user .jive-thread-post-message { display:none; }", ".ignored-user .jive-thread-post-details { background-image: none !important; padding-top:0; margin-top:0; width:auto;}", ".ignored-user .jive-thread-post-subject-content { background-image: none !important; padding-bottom:0; margin-bottom:0; }", ".ignored-user .jive-thread-post-subject-content .jive-thread-post-reply { display:none; }", "", ".ignored-user .jive-thread-reply-body-container { min-height:0; padding-bottom:6px; }", ".ignored-user .jive-thread-reply-subject { float: left; width:auto; padding-bottom:0!important;}", ".ignored-user .jive-thread-reply-subject strong { display:none!important; }", ".ignored-user .jive-thread-reply-message { display:none; }", ".ignored-user .jive-content-controls { text-align:right; width:auto; padding-top:0; padding-bottom:0;}", ].join("\n"));
var box;
$Z("//div[@class='jive-thread-post-body' or @class='jive-thread-reply-body']", function(r,i){
var linkwrapper = $X("./div[@class='jive-author']/div[@class='jive-username-link-wrapper']", r); var fullname = $X("./a", linkwrapper).href.replace(/https?:\/\/blogs\.secondlife\.com\/people\/([^;\/?]+).*/, "$1"); var name = fullname.split("."); if(name.slice(-1)[0] != "Linden") {//this all could be made part of the original selector but... that would be ugly to say the least. var hidden = names[fullname] = Boolean(GM_getValue(fullname, false)); r.parentNode.setAttribute("username", fullname);//didn't want to pollute the body css if(!box) { box = document.createElement("div"); box.className = "GM-ignore-link-wrapper"; { var span = document.createElement("span"); span.appendChild(document.createTextNode("Ignore")); span.className="ignore"; box.appendChild(span); } { var span = document.createElement("span"); span.appendChild(document.createTextNode("Unignore")); span.className="unignore"; box.appendChild(span); } } if(hidden) QuickHide(r.parentNode); var boxy = box.cloneNode(true); insertAfter(boxy, linkwrapper); boxy.addEventListener("click", toggleIgnore, false); } });
function QuickHide(r){ r.className = r.className + " ignored-user"; } function QuickShow(r){ r.className = r.className.split(" ").filter(function(v){ return v != "ignored-user";}).join(" "); }
function toggleIgnore(event){
var base = $X("./ancestor::div[@username]", event.currentTarget); var fullname = base.getAttribute("username"); var hidden = names[fullname] = !names[fullname]; if(hidden) GM_setValue(fullname, true); else GM_deleteValue(fullname); $Z("//div[@username='"+fullname+"']", hidden?QuickHide:QuickShow, base);
}
function insertAfter(insert, after){return after.parentNode.insertBefore(insert, after.nextSibling);} function insertBefore(insert, before){return before.parentNode.insertBefore(insert, before);}
function $X(_xpath, node){//to search in a frame, you must traverse the .contentDocument or .contentWindow attribute.
var doc = (node)?(node.ownerDocument || node):(node = document); return doc.evaluate(_xpath, node, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null).snapshotItem(0);
} function $Y(_xpath, node){
var doc = (node)?(node.ownerDocument || node):(node = document); return doc.evaluate(_xpath, node, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
} function $Z(_xpath, func, node){
var doc = (node)?(node.ownerDocument || node):(node = document); var res = doc.evaluate(_xpath, node, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); var args = Array.prototype.slice.call(arguments, 3); var i = 0; for (; i < res.snapshotLength; ++i) func.apply(func, [res.snapshotItem(i), i].concat(args)); return i;
} </javascript>
-- Strife (talk|contribs) 07:47, 14 February 2010 (UTC)
- lol, I should know better than to ask you for suggestions... you anticipated most of the niceties that I was going to add (kill every but the the name and unignore buttons)... and then did them all =P
- I did notice one slight bug in these three lines var fullname = $X("./a", linkwrapper).textContent; var name = fullname.split(" "); if(name[1] != "Linden"). won't work 100% because we've got at least one linden with a space in their first name (Kona linden Linden) and also, any user that sets the preference "hide last name" in their profile (as I have), will have a text name that matches their system name... including Lindens (Making that Linden ignorable... the system format is "first.last", with other spaces collapsed)... hence the reason why I was parsing their system name from the .href, rather than from the .textContent which could lead to a user taking themselves off ignore by changing that setting (at least until they were added again under the new name as well, and also means it's harder to take them off ignore)... I haven't found any Lindens that have used this setting, but people with this setting on are near impossible to find via people search on the site, and google stubbornly refuses to follow a strict match on punctuation marks =/
- I'm assuming names is being used as a backup GM_*Value... and only good per session? assuming GM_ functions fail, won't GM_addStyle fail too?
- I'm not going to pretend to automatically understand all the references at a glance, but the $Z function is a quite confusing... func.apply(func, [res.snapshotItem(i), i].concat(args), your essentially appending _xpath, func, and node (when it's called) to array of things to apply to, which would seem to be an infinitely expanding loop of calls to quickHide or quickShow.... (including calls of those functions to those functions) I'm not seeing how it's breaking out, or what the "3" argument to .call in "args" is doing....
- ps thanks for the link to FireXPath, it's funny but I was actually considering using getElementsByClass in the rewrite if it turned out faster... believe it or not I only recently installed FireBug, as I've had no want to do web work in a while... it's quite the fun tool though and I've been wandering around the SL site crying at the markup it uses
- pps I am NOT suggesting YOU make those changes... although at this point your script is so drastically different and improved from my own, I wouldn't want to take any credit on it's creation
-- Void (talk|contribs) 03:33, 15 February 2010 (UTC)
- I didn't know that about the names and thats a really silly oversight with Kona's name (has it been reported as a bug?). I understand now why you had it reading the href. GM_[sg]etValue's persist across sessions and between tabs; they are stored in the users preferences (but are only accessible to the GM script that stored them).
- I'm proud of the design of $Z, but not entirely happy with the implementation.
<javascript> function $Z(_xpath, func, node){
//When working with frames we cannot assume the node we are parsing is in the script's document. //So we try to find the document that owns 'node', which is conveniently in the 'ownerDocument' property. //While Document objects do have an 'ownerDocument' property, it's always null. //If 'node' is null, than we set 'node' and 'doc' to the scripts 'document' ('node' should not be null). var doc = (node)?(node.ownerDocument || node):(node = document); //Using 'ownerDocument' this is brittle. //I spent a sizable chunk of time trying to find a better way, i didn't find one; //"instanceof" is unusable on XPCNativeWrapper'ed objects (unwrapping does not help).
//Next we evalute the xpath in our node, in it's document var res = doc.evaluate(_xpath, node, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
//Later we will want an array of any extra arguments that were passed this function. //'arguments' may look like an array but it is not; it lacks array function methods. //But that is irrelevant. We can get at the function another way. var args = Array.prototype.slice.call(arguments, 3); //'call' btw is a default method of all functions (functions are objects too). //The 'this' value in the function is set equal to the first parameter, any following parameters are passed along to the function. //'call' can be defined as: //function call(targetThis){ this.apply(targetThis, Array.prototype.slice.call(arguments, 1)); }
//We now loop through the xpath results. var i = 0; for (; i < res.snapshotLength; ++i) { //We want to call func, passing along the found node, the index of the node, and the any extra arguments. //So we build the partial argument array and append the extra arguments. func.apply(func, [res.snapshotItem(i), i].concat(args)); }
//Because we can, lets return the number of nodes found. return i;
} </javascript>
- You may notice I edited the script, I had partial Window support but it was incomplete (hadn't realized at the time of posting); full support wasn't worth the extra code, and only marginally useful.
- Jive uses jQuery and loads it into it's pages, if you wanted, you could port the script to jQuery and free it from using XPath; jQuery supports more browsers than those that implement XPath. -- Strife (talk|contribs) 17:53, 15 February 2010 (UTC)
- understood on the persistence of GM_*Value, was more curious about if the name array was supposed to back it up for browsers that didn't support it, and if so, does name persist across sessions (wouldn't seem like it, but greasemonkey and related environments are new toys for me). If it is backup support for those 3 functions (on other Grease* platforms), it would seem there needs to be a backup for GM_addStyle too as I'm pretty sure none of the other Grease* products implement them separately (chrome, opera, safari). although I couldn't attest 100% to that. it's why I wrote conditional JSON alternatives for the GM_*Value functions in the update of mine the other day on userscripts (all recent browsers are supposed to have native JSON support, though I haven't had any one test them yet)... I'll look into a conditional alternative for GM_addStyle when I have a moment, for supporting other platforms
- PS no clue if it's been reported, I found it accidentally while looking for Lindens that may have set "hide last name". AFAIK it's the only instance of a first name that has a space in it ANYWHERE in SL, and was obviously a case of bad data entry checking on LL's end... shouldn't have been possible =|
-- Void (talk|contribs) 03:00, 16 February 2010 (UTC)
- PS no clue if it's been reported, I found it accidentally while looking for Lindens that may have set "hide last name". AFAIK it's the only instance of a first name that has a space in it ANYWHERE in SL, and was obviously a case of bad data entry checking on LL's end... shouldn't have been possible =|
- GM_addStyle does not persist (I knew there was a question I forgot to answer), but neither does the script state. A new instance of the script is created for each page that matches the rule, for each page the script is executed from scratch. If you want something a little more intelligent you may want to look at Jetpack (which IMHO is too immature to be used; I'm giving them feedback so they can the design right for the Reboot). -- Strife (talk|contribs) 08:09, 16 February 2010 (UTC)
- Added some tweaks to yours (was faster than rebuilding mine), linden names in red, OP thread edit button on post, conditional replacements for GM_* for other browsers (untested, should work), namespace back to private (for compatibility with previous saves of mine) and jive version checking. still intend to do a less artful version myself, but I think you should release yours... I don't feel comfortable releasing it under my account unless you want me too (w/ credit, 'natch)... anyways, here 'tis below
-- Void (talk|contribs) 07:37, 17 February 2010 (UTC)
- Added some tweaks to yours (was faster than rebuilding mine), linden names in red, OP thread edit button on post, conditional replacements for GM_* for other browsers (untested, should work), namespace back to private (for compatibility with previous saves of mine) and jive version checking. still intend to do a less artful version myself, but I think you should release yours... I don't feel comfortable releasing it under my account unless you want me too (w/ credit, 'natch)... anyways, here 'tis below
removed intermediate version
hmmm <javascript> // ==UserScript== // @name SL Forum Ignore // @namespace Private // @description +Ignore Links on User Posts on SL Forum Discussions/Questions, +Make Linden Names RED, +OP Edit fix // @include https://blogs.secondlife.com/thread/* // @include https://blogs.secondlife.com/message/* // @version 1.2 // ==/UserScript==
var names = {};
//--VS +compatibility replacements for Chrome4+, Opera10.5+, Safari4+ if (typeof GM_addStyle == 'undefined'){ function GM_addStyle( vCss ){ var vStyle = document.createElement( 'style' ); style.textContent = vCss; document.getElementsByTagName( 'head' )[0].appendChild( vStyle ); }
//-- native JSON and localStorage for GM_*Value replacement GM_getValue = function( vKey, vDefault ){ return (JSON.parse( window.localStorage.getItem( vKey ) ) || vDefault); }
GM_setValue = function( vKey, vValue ){ window.localStorage.setItem( vKey, JSON.stringify( vValue ) ); }
GM_deleteValue = function( vKey ){ window.localStorage.removeItem( vKey ); } }
//--VS +jive version change protection if ($X( "//div[@class='jiveVersion']", document ).textContent.indexOf( '80211' ) != -1 ){ //--VS +Thread Edit Link Fix var vGetEdit = document.getElementsByClassName( 'jive-link-edit' ).item( 0 ); if (vGetEdit != null){ vCloneEdit = vGetEdit.cloneNode( false ); document.getElementsByClassName( 'jive-thread-post-details' ).item( 0 ).appendChild( vCloneEdit ); vCloneEdit.className= "GM-Fix-Thread-Edit"; }
//--VS +.GM-fix-thread-edit GM_addStyle([ ".GM-Fix-Thread-Edit {", " background: url( '../images/jive-icon-edit-16x16.gif' ) no-repeat scroll left center transparent;", " font-weight: bold;", " padding-left: 17px;", " margin-right: 0.5em;", " float: right;}", "", ".GM-ignore-link-wrapper { cursor:pointer; }", "", ".GM-ignore-link-wrapper .ignore { display:block;}", ".GM-ignore-link-wrapper .unignore { display:none;}", ".ignored-user .GM-ignore-link-wrapper .ignore {display:none;}", ".ignored-user .GM-ignore-link-wrapper .unignore {display:block;}", "", ".ignored-user .jive-author { padding-top:0; padding-bottom:0; }", ".ignored-user .jive-author-avatar-container { display:none!important; }", ".ignored-user .jive-author-avatar-container { background-image: none !important; }", ".ignored-user .jive-author > em { display:none; }", "", ".ignored-user .jive-thread-post-body-container { min-height:0; background-image: none !important; padding-bottom:8px; }", ".ignored-user .jive-thread-post-subject { float: left; width:auto; }", ".ignored-user .jive-thread-post-subject h2 { display:none; }", ".ignored-user .jive-thread-post-message { display:none; }", ".ignored-user .jive-thread-post-details { background-image: none !important; padding-top:0; margin-top:0; width:auto;}", ".ignored-user .jive-thread-post-subject-content { background-image: none !important; padding-bottom:0; margin-bottom:0; }", ".ignored-user .jive-thread-post-subject-content .jive-thread-post-reply { display:none; }", "", ".ignored-user .jive-thread-reply-body-container { min-height:0; padding-bottom:6px; }", ".ignored-user .jive-thread-reply-subject { float: left; width:auto; padding-bottom:0!important;}", ".ignored-user .jive-thread-reply-subject strong { display:none!important; }", ".ignored-user .jive-thread-reply-message { display:none; }", ".ignored-user .jive-content-controls { text-align:right; width:auto; padding-top:0; padding-bottom:0;}", "", "div[username$='.Linden'] div.jive-author div.jive-username-link-wrapper > a { color:red; }", ].join("\n"));
var box;
$Z("//div[@class='jive-thread-post-body' or @class='jive-thread-reply-body']", function(r,i){ var linkwrapper = $X("./div[@class='jive-author']/div[@class='jive-username-link-wrapper']", r); var fullname = $X("./a", linkwrapper).href.replace(/https?:\/\/blogs\.secondlife\.com\/people\/([^;\/?]+).*/, "$1"); var name = fullname.split("."); r.parentNode.setAttribute("username", fullname); if(name.slice(-1)[0] != "Linden") {//this all could be made part of the original selector but... that would be ugly to say the least. var hidden = names[fullname] = Boolean(GM_getValue(fullname, false)); if(!box) { box = document.createElement("div"); box.className = "GM-ignore-link-wrapper"; { var span = document.createElement("span"); span.appendChild(document.createTextNode("Ignore")); span.className="ignore"; box.appendChild(span); } { var span = document.createElement("span"); span.appendChild(document.createTextNode("Unignore")); span.className="unignore"; box.appendChild(span); } } if(hidden) QuickHide(r.parentNode);
var boxy = box.cloneNode(true); insertAfter(boxy, linkwrapper); boxy.addEventListener("click", toggleIgnore, false); } }); }
function QuickHide(r){ r.className = r.className + " ignored-user"; } function QuickShow(r){ r.className = r.className.split(" ").filter(function(v){ return v != "ignored-user";}).join(" "); }
function toggleIgnore(event){ var base = $X("./ancestor::div[@username]", event.currentTarget); var fullname = base.getAttribute("username"); var hidden = names[fullname] = !names[fullname]; if(hidden) GM_setValue(fullname, true); else GM_deleteValue(fullname); $Z("//div[@username='"+fullname+"']", hidden?QuickHide:QuickShow, base); }
function insertAfter(insert, after){return after.parentNode.insertBefore(insert, after.nextSibling);} function insertBefore(insert, before){return before.parentNode.insertBefore(insert, before);}
function $X(_xpath, node){//to search in a frame, you must traverse the .contentDocument or .contentWindow attribute.
var doc = (node)?(node.ownerDocument || node):(node = document); return doc.evaluate(_xpath, node, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null).snapshotItem(0);
} function $Y(_xpath, node){
var doc = (node)?(node.ownerDocument || node):(node = document); return doc.evaluate(_xpath, node, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
} function $Z(_xpath, func, node){
var doc = (node)?(node.ownerDocument || node):(node = document); var res = doc.evaluate(_xpath, node, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); var args = Array.prototype.slice.call(arguments, 3); var i = 0; for (; i < res.snapshotLength; ++i) func.apply(func, [res.snapshotItem(i), i].concat(args)); return i;
} </javascript>
Last Update?
- removed the intermediate version, this is my non-xpath rewrite using getElementsByClassName (wanted to see if it's really as much faster as claimed). fixed a bit of bugginess that I introduced int the replacement functions, and a logic error in the handling of OP edit buttons (actions bar retains an edit link on following pages in flat view)
- I had to convert the username attributes in most cases to class additions, to avoid searching for multiple classes on quickhide/show. and then I cheated by using those instead in the ignore button box, to grab it for class find since I don't see a simple solution to 'ancestor' support for that. having to make two calls for the original button inserts (because there is no "OR" support in that function) is a bit ugly... but it's damn fast. stuff got renamed/reformatted/collapsed/inlined to fit my personal style for consistency (hope you don't mind) but the structure is basically yours. with your permission (and assuming you don't find any obvious bugs) I'd like to release it as shown below.
-- Void (talk|contribs) 01:27, 19 February 2010 (UTC)
<javascript>// ==UserScript== // @name SL Forum Ignore // @namespace Private // @description +Ignore Links on User Posts on SL Forum Discussions/Questions, +Make Linden Names RED, +OP Edit fix // @include https://blogs.secondlife.com/thread/* // @include https://blogs.secondlife.com/message/* // @match https://blogs.secondlife.com/thread/* // @match https://blogs.secondlife.com/message/* // @version 1.3 // @copyright Void Singer 2009 - 2010 [ https://wiki.secondlife.com/wiki/User:Void_Singer ] & BlindWanderer [ http://userscripts.org/users/4223 ] // @license CC-BY [ http://creativecommons.org/licenses/by/3.0 ] // @licesne.ext Free to copy, use, modify, distribute, or sell, with attribution. // ==/UserScript==
/*//-- added match statements for compatibility with chromium 'content scripts' --//*/ /*//-- IE7pro unsupported: lacks CSS inheritance & getElementsByClassName --//*/
//-- compatibility replacements for Chrome4+, Opera10.5+, Safari4+, I hope
if (typeof GM_addStyle == 'undefined'){ function GM_addStyle( vCss ){ var vStyle = document.createElement( 'style' ); vStyle.textContent = vCss; document.getElementsByTagName( 'head' )[0].appendChild( vStyle ); } } //-- "it's possible that *this* GM_ function is supported in chromium, but the others are not. can't find docs"
//-- compatibility replacements for Chrome4+, Opera10.5+, Safari4+, I hope
if (typeof GM_setValue == 'undefined'){ //-- native JSON and localStorage for GM_*Value replacement GM_getValue = function( vKey, vDefault ){ var vReturn = JSON.parse( window.localStorage.getItem( vKey ) ); return ((vReturn == null)? vDefault: vReturn); }
GM_setValue = function( vKey, vValue ){ window.localStorage.setItem( vKey, JSON.stringify( vValue ) ); }
GM_deleteValue = function( vKey ){ window.localStorage.removeItem( vKey ); } }
//-- function contributed by BlindWanderer
function uHide( vPostNode ){ vPostNode.className += ' ignored-user'; }
//-- function contributed by BlindWanderer
function uShow( vPostNode ){ vPostNode.className = vPostNode.className.split( ' ' ).filter( function( v ){ return v != 'ignored-user';} ).join( ' ' ); }
//-- function contributed by BlindWanderer, modifed by Void Singer
function uToggleIgnore(){ //-- no ancestor support outside of xpath? var vName = this.getAttribute( 'username' ); //-- ?is there really any savings to caching instead of a direct call to GM_getValue? var vAction = (vIgnoreCache[vName] = !vIgnoreCache[vName])? uHide : uShow; if (vIgnoreCache[vName]){ GM_setValue( vName, true ); } else{ GM_deleteValue( vName ); } var vSwaps = document.getElementsByClassName( vName ); var vCount = 0; for (vCount; vCount < vSwaps.length; ++vCount){ vAction.apply( vAction, [vSwaps.item( vCount )] ); } }
//-- adds styles, ignore toggles, and marks post containers function uTweakPage(){ //-- use style to reduce load, contributed by BlindWanderer, modifed by Void Singer GM_addStyle( [ '.GM-Fix-Thread-Edit {', ' background: url( "../images/jive-icon-edit-16x16.gif" ) no-repeat scroll left center transparent;', ' font-weight: bold;', ' padding-left: 17px;', ' margin-right: 0.5em;', ' float: right;}', , '.GM-ignore-link-wrapper { cursor:pointer; }', , '.GM-ignore-link-wrapper .ignore { display:block;}', '.GM-ignore-link-wrapper .unignore { display:none;}', '.ignored-user .GM-ignore-link-wrapper .ignore {display:none;}', '.ignored-user .GM-ignore-link-wrapper .unignore {display:block;}', , '.ignored-user .jive-author { padding-top:0; padding-bottom:0; }', '.ignored-user .jive-author-avatar-container { display:none!important; }', '.ignored-user .jive-author-avatar-container { background-image: none !important; }', '.ignored-user .jive-author > em { display:none; }', , '.ignored-user .jive-thread-post-body-container { min-height:0; background-image: none !important; padding-bottom:8px; }', '.ignored-user .jive-thread-post-subject { float: left; width:auto; }', '.ignored-user .jive-thread-post-subject h2 { display:none; }', '.ignored-user .jive-thread-post-message { display:none; }', '.ignored-user .jive-thread-post-details { background-image: none !important; padding-top:0; margin-top:0; width:auto;}', '.ignored-user .jive-thread-post-subject-content { background-image: none !important; padding-bottom:0; margin-bottom:0; }', '.ignored-user .jive-thread-post-subject-content .jive-thread-post-reply { display:none; }', , '.ignored-user .jive-thread-reply-body-container { min-height:0; padding-bottom:6px; }', '.ignored-user .jive-thread-reply-subject { float: left; width:auto; padding-bottom:0!important;}', '.ignored-user .jive-thread-reply-subject strong { display:none!important; }', '.ignored-user .jive-thread-reply-message { display:none; }', '.ignored-user .jive-content-controls { text-align:right; width:auto; padding-top:0; padding-bottom:0;}', , 'div[class$=".Linden"] div.jive-author div.jive-username-link-wrapper > a { color:#BB0000; }', ].join( '\n' ) );
//-- clone edit thread link to OP var vGetOP = document.getElementsByClassName( 'jive-thread-post-details' ).item( 0 ); var vGetEdit = document.getElementsByClassName( 'jive-link-edit' ).item( 0 ); if ((null != vGetEdit) && (null != vGetOP)){ //-- need to text both for flat paged views (edit still shows on follow-up pages) var vCloneEdit = vGetEdit.cloneNode( true ); vGetOP.appendChild( vCloneEdit ); vCloneEdit.className = 'GM-Fix-Thread-Edit'; }
//-- create generic toggle for ignore buttons, contributed by BlindWanderer var vToggleBox = document.createElement( 'div' ); vToggleBox.className = 'GM-ignore-link-wrapper'; { var span = document.createElement( 'span' ); span.appendChild( document.createTextNode( 'Ignore' ) ); span.className = 'ignore'; vToggleBox.appendChild(span); } { var span = document.createElement( 'span' ); span.appendChild( document.createTextNode( 'Unignore' ) ); span.className = 'unignore'; vToggleBox.appendChild(span); }
function uInsertToggles( vPostNodes ){ //-- contributed by BlindWanderer, modifed by Void Singer var vCount = 0; //-- loop through posts for (vCount; vCount < vPostNodes.length; ++vCount){ //-- get user profile link for position, and grab their system name var vUserLink = vPostNodes.item( vCount ).getElementsByClassName( 'jive-username-link' ).item( 0 ); var vName = vUserLink.href.substring( vUserLink.href.lastIndexOf( '/' ) + 1 ); //-- tag post for ignore handling vPostNodes.item( vCount ).parentNode.className += ' ' + vName; //-- changed to class vs custom attribute if (vName.split( '.' ).slice( -1 )[0] != 'Linden'){ //-- check and ignore post if user already on ignore //-- ?is there really any savings to caching instead of a direct call to GM_getValue? var vIsIgnored = vIgnoreCache[vName] = GM_getValue( vName, false ); if (vIsIgnored){ uHide( vPostNodes.item( vCount ).parentNode ); } //-- clone ignore toggle into user info box, and tie it the switch function var vIgnoreToggle = vToggleBox.cloneNode( true ); //-- ?no ancestor support outside of xpath? vIgnoreToggle.setAttribute( 'username', vName ); vUserLink.parentNode.insertBefore( vIgnoreToggle, vUserLink.nextSibling ); //--contributed by Blind Wanderer vIgnoreToggle.addEventListener( 'click', uToggleIgnore, false ); } } } //-- insert Toggle in OP uInsertToggles( document.getElementsByClassName( 'jive-thread-post-body' ) ); //-- insert Toggle in replies uInsertToggles( document.getElementsByClassName( 'jive-thread-reply-body' ) ); //-- not happy with the separate inserts for OP and reply, but it wasn't worth a custom function to grab both }
//-- ?is there really any savings to caching instead of a direct call to GM_getValue? var vIgnoreCache = {}; //-- contributed by BlindWanderer
//-- jive version checking
if (document.getElementsByClassName( 'jiveVersion' ).item( 0 ).textContent.indexOf( '80211' ) != -1){
uTweakPage();
}</javascript>
EDIT: forgot to tweak the linden name css selector when I switch from username to class addition
-- Void (talk|contribs) 01:34, 19 February 2010 (UTC)
EDIT: Tweaked comments/header
-- Void (talk|contribs) 08:55, 21 February 2010 (UTC)
I'll take that as approval to use your bits in the current v. I could still go through and get the micro tweaks... and I found one more needed tweak for compatibility with Cerise's High Contrast Style (!important on linden names color), but other than that, I'm posting now =)
-- Void (talk|contribs) 02:40, 22 February 2010 (UTC)
- You have gone out of your way to credit me, above and beyond what was needed. I would be happy with just being mentioned in the @copyright/@author tag. *tweak* -- Strife (talk|contribs) 15:28, 22 February 2010 (UTC)
credit were credit is due =) Comment/author lines are cheap, recognizing and crediting other's contributions to your goals are priceless (cue credit card commercial lol). Seriously though, I've lived on the corporate ladder long enough to prefer dragging people up with me that help me, even if it doesn't get me as high as stepping on their backs. hope you don't mind I used your userscripts name rather than your SL name. I've seen from some of the things you've written that you prefer not to encourage connections between online identities ;)
PS I also didn't catch your comment about getElementsByClassName on the edit summary until after I'd written that version... go go powers of observation neh?
-- Void (talk|contribs) 22:00, 23 February 2010 (UTC)
Current status / support (Mar 2010)
- Does this really require Firefox 2.0 as stated? What about Firefox 3.x?
- I should do further testing about this, but it looks like the script works with Google Chrome. Apparently, you can install Greasemonkey scripts natively in Google Chrome. Confirmed on:
- 5.0.360.0 dev on Windows
- The script did NOT work in these pages (note it's a different page template)
THANKS for your efforts! --oobscure 14:09, 30 March 2010 (UTC)
- Firefox 2.0 is probably the min version it supports, it will work fine in 3.x and beyond (barring any breaking changes to javascript and css; very unlikely).
- Should work on google chrome, nothing too special about the JS and CSS at this point.
- Thanks for the tip about 'docs' i'll look into it when I have some time.
- -- Strife (talk|contribs) 05:47, 2 April 2010 (UTC)
sorry for the confusion/slow response, I probably should have stated "2.0+" for the FF version, and I'd been waiting to hear back on other platforms supported. I didn't catch the doc pages, but I was already aware that it did not work on the blog comments either. currently it's filtered to only message/thread directories, which use a different format and contain only discussion threads (apparently). I'll probably extend support to those when I've got time to update, although I may institute those as separate scripts to reduce overhead for parsing the different structure... haven't decided.
I also intend to add the CSS tags used by the LSL_Portal for LSL highlighting so that copied scripts from there will show in the same manner as the wiki (eventually)... or if possible I'll try to convince Nyx to just insert the identical CSS tag names directly when the lsl script highlighting component is done. and thanks for the main page updates
-- Void (talk|contribs) 20:36, 3 April 2010 (UTC)