[dw-free] jQuerify comment management (screening, etc)
[commit: http://hg.dwscoalition.org/dw-free/rev/97e54a4c0a87]
http://bugs.dwscoalition.org/show_bug.cgi?id=3536
jQuery version of comment screening, deletion, etc. Includes tweaked
styling, and (reuasble) method of doing the tooltip-like popups for future
use.
Patch by
fu.
Files modified:
http://bugs.dwscoalition.org/show_bug.cgi?id=3536
jQuery version of comment screening, deletion, etc. Includes tweaked
styling, and (reuasble) method of doing the tooltip-like popups for future
use.
Patch by
![[personal profile]](https://www.dreamwidth.org/img/silk/identity/user.png)
Files modified:
- cgi-bin/LJ/S2/EntryPage.pm
- htdocs/delcomment.bml
- htdocs/img/ajax-loader.gif
- htdocs/js/commentmanage.js
- htdocs/js/jquery.ajaxtip.js
- htdocs/js/jquery.commentmanage.js
- htdocs/js/tooltip.dynamic.js
- htdocs/js/tooltip.dynamic.min.js
- htdocs/js/tooltip.js
- htdocs/js/tooltip.min.js
- htdocs/stc/ajaxtip.css
- htdocs/stc/popup-form.css
- htdocs/talkscreen.bml
- views/dev/tests/commentmanage.html
- views/dev/tests/commentmanage.js
-------------------------------------------------------------------------------- diff -r 1c0749606568 -r 97e54a4c0a87 cgi-bin/LJ/S2/EntryPage.pm --- a/cgi-bin/LJ/S2/EntryPage.pm Mon Mar 28 14:40:04 2011 +0800 +++ b/cgi-bin/LJ/S2/EntryPage.pm Mon Mar 28 16:25:31 2011 +0800 @@ -372,9 +372,16 @@ sub EntryPage $p->{'head_content'} .= $js; } - LJ::need_res(qw( - js/commentmanage.js - )); + LJ::need_res( "js/commentmanage.js" ); + LJ::need_res( { group => "jquery" }, qw( + js/jquery/jquery.ui.widget.js + js/jquery.ajaxtip.js + js/jquery.commentmanage.js + js/tooltip.min.js + js/tooltip.dynamic.min.js + stc/ajaxtip.css + stc/popup-form.css + ) ); $p->{'_picture_keyword'} = $get->{'prop_picture_keyword'}; diff -r 1c0749606568 -r 97e54a4c0a87 htdocs/delcomment.bml --- a/htdocs/delcomment.bml Mon Mar 28 14:40:04 2011 +0800 +++ b/htdocs/delcomment.bml Mon Mar 28 16:25:31 2011 +0800 @@ -20,13 +20,21 @@ _info?><?_code use vars qw(%GET %POST); use vars qw($body); + BML::set_language_scope("/delcomment.bml"); + my $jsmode = $GET{mode} eq "js"; $body = ""; my $error = sub { if ($jsmode) { BML::finish(); - return "alert('" . LJ::ejs($_[0]) . "'); 0;"; + # FIXME: remove once we've switched over completely to jquery + if ( !!$GET{json} ) { + sleep 1 if $LJ::IS_DEV_SERVER; + return JSON::objToJson( { error => $_[0] } ); + } else { + return "alert('" . LJ::ejs($_[0]) . "'); 0;"; + } } $body = "<?h1 $ML{'Error'} h1?><?p $_[0] p?>"; return; @@ -116,7 +124,6 @@ _info?><?_code ### perform actions if (LJ::did_post() && $POST{'confirm'}) { return $error->($ML{'error.invalidform'}) unless LJ::check_form_auth(); - # mark this as spam? LJ::Talk::mark_comment_as_spam($u, $tp->{talkid}) if $POST{spam} && $can_spam; @@ -129,7 +136,6 @@ _info?><?_code # delete single comment... LJ::Talk::delete_comment($u, $tp->{'itemid'}, $tpid, $tp->{'state'}); } - # ban the user, if selected my $msg; if ($POST{'ban'} && $can_ban) { @@ -138,11 +144,16 @@ _info?><?_code $msg = BML::ml('.success.andban', { 'user' => LJ::ljuser($tp->{'userpost'}) }); } $msg ||= $ML{'.success.noban'}; - $msg .= "<?p $ML{'.success.spam'} p?>" if $POST{spam} && $can_spam; + $msg .= " <p>$ML{'.success.spam'}</p>" if $POST{spam} && $can_spam; if ($jsmode) { BML::finish(); - return "1;"; + if ( !!$GET{json} ) { + sleep 1 if $LJ::IS_DEV_SERVER; + return JSON::objToJson( { msg => LJ::strip_html( $msg ) } ); + } else { + return "1;"; + } } else { $body = "<?h1 $ML{'.success.head'} h1?><?p $msg p?>"; return; diff -r 1c0749606568 -r 97e54a4c0a87 htdocs/img/ajax-loader.gif Binary file htdocs/img/ajax-loader.gif has changed diff -r 1c0749606568 -r 97e54a4c0a87 htdocs/js/commentmanage.js --- a/htdocs/js/commentmanage.js Mon Mar 28 14:40:04 2011 +0800 +++ b/htdocs/js/commentmanage.js Mon Mar 28 16:25:31 2011 +0800 @@ -575,7 +575,7 @@ function createModerationFunction (ae, d var rpcRes; if (xtr.status == 200) { - var resObj = eval(xtr.responseText); + var resObj = eval("resObj = " + xtr.responseText + ";"); if (resObj) { poofAt(clickPos); updateLink(ae, resObj, imgTarget); diff -r 1c0749606568 -r 97e54a4c0a87 htdocs/js/jquery.ajaxtip.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/htdocs/js/jquery.ajaxtip.js Mon Mar 28 16:25:31 2011 +0800 @@ -0,0 +1,114 @@ +(function($) { +$.widget("dw.ajaxtip", { + options: { + content: undefined + }, + _namespace: function() { + return this.options.namespace ? "."+this.options.namespace : ""; + }, + _create: function() { + var self = this; + var ns = self._namespace(); + + var tipcontainer = $("<div class='ajaxtooltip' style='display: none'></div>") + .click(function(e) {e.stopPropagation()}) + + self.element + .after(tipcontainer) + .bind("ajaxresult"+ns, function(e) { + self.element.data("tooltip").getTip() + .addClass("ajaxresult-" + e.ajaxresult.status) + .text(e.ajaxresult.message) + }) + .tooltip({ + predelay: 0, + delay: 1500, + events: { + def: "ajaxstart"+ns+",ajaxresult"+ns, + tooltip: "mouseover,mouseleave" + }, + position: "bottom center", + relative: true, + effect: "fade", + + onBeforeShow: function(e) { + var tip = this.getTip(); + tip.removeClass("ajaxresult ajaxresult-success ajaxresult-error"); + + if ( self.options.content && ! this.inprogress ){ + tip.html(self.options.content) + } else { + tip.empty().append($("<img />", { src: Site.imgprefix + "/ajax-loader.gif" })) + .addClass("ajaxresult") + } + } + }) + .dynamic({ classNames: "tip-top tip-right tip-bottom tip-left" }); + }, + _init: function() { + if(this.options.content) + this.element.data("tooltip").show() + }, + widget: function() { + return this.element.data("tooltip").getTip(); + }, + cancel: function() { + var tip = this.element.data("tooltip"); + if( tip && tip.isShown() ) tip.hide(); + }, + load: function(opts) { + var self = this; + + var tip = self.element.data("tooltip"); + if( tip ) { + if( tip.inprogress ) return; + if( tip.isShown() ) tip.hide(); + } + + tip.inprogress = true; + self.element.trigger("ajaxstart" + self._namespace()); + + $.ajax({ + type: "POST", + url : opts.url, + data: opts.data, + + dataType: "json", + complete: function() { + self.element.data("tooltip").inprogress = false; + }, + success: opts.success, + error: opts.error + }); + }, + success: function(msg) { + this.element.trigger({ type: "ajaxresult"+this._namespace(), + ajaxresult: { message: msg, status: "success" } }) + }, + error: function(msg) { + this.element.trigger({ type: "ajaxresult"+this._namespace(), + ajaxresult: { message: msg, status: "error" } }) + }, + abort: function(msg) { + this.element.data("tooltip").show(); + this.element.trigger({ type: "ajaxresult"+this._namespace(), + ajaxresult: { message: msg, status: "error" } }) + } +}); + +$.extend( $.dw.ajaxtip, { + closeall: function() { + $(".ajaxtooltip:visible").each( + function(){ + var tip = $(this).prev().data("tooltip"); + if ( !tip.inprogress ) tip.hide() + }) + } +}) +})(jQuery); + +jQuery(function($) { + $(document).click(function() { + $.dw.ajaxtip.closeall(); + }); +}); diff -r 1c0749606568 -r 97e54a4c0a87 htdocs/js/jquery.commentmanage.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/htdocs/js/jquery.commentmanage.js Mon Mar 28 16:25:31 2011 +0800 @@ -0,0 +1,305 @@ +(function($) { +$.extractParams = function(url) { + if ( ! $.extractParams.cache ) + $.extractParams.cache = {}; + + if ( url in $.extractParams.cache ) + return $.extractParams.cache[url]; + + var search = url.indexOf( "?" ); + if ( search == -1 ) { + $.extractParams.cache[url] = {}; + return $.extractParams.cache[url]; + } + + var params = decodeURI( url.substring( search + 1 ) ); + if ( ! params ) { + $.extractParams.cache[url] = {}; + return $.extractParams.cache[url]; + } + + var paramsArray = params.split("&"); + var params = {}; + for( var i = 0; i < paramsArray.length; i++ ) { + var p = paramsArray[i].split("="); + var key = p[0]; + var value = p.length < 2 ? undefined : p[1]; + params[key] = value; + } + + $.extractParams.cache[url] = params; + return params; +}; + +$.widget("dw.moderate", { + options: { + journal: undefined, + form_auth: undefined, + + endpoint: "__rpc_talkscreen", + }, + _updateLink: function(newData) { + this.element.attr("href", newData.newurl); + + var params = $.extractParams(newData.newurl); + this.linkdata = { + id: params.talkid, + action: params.mode, + journal: params.journal + }; + + var image = this.element.find('img[src="'+newData.oldimage+'"]'); + + if ( image.length == 0 ) { + this.element.text(newData.newalt); + } else { + image.attr({ + title: newData.newalt, + alt: newData.newalt, + src: newData.newimage + }); + } + }, + + _abort: function(reason, ditemid) { + ditemid = ditemid || this.linkdata.id; + this.element.ajaxtip({namespace:"moderate"}) + .ajaxtip( "abort", "Error moderating comment #" + ditemid + ". " + reason); + }, + + _create: function() { + var self = this; + + var params = $.extractParams(this.element.attr("href")); + this.linkdata = { + id: params.talkid || "", + action: params.mode, + journal: params.journal + }; + + this.element.click(function(e) { + e.preventDefault(); + e.stopPropagation(); + + if (!self.options.form_auth || ! self.options.journal + || !self.linkdata.id || !self.linkdata.action || !self.linkdata.journal) { + self._abort( "Not enough context available." ); + return; + } + if ( self.linkdata.journal != self.options.journal ) { + self._abort( "Journal in link does not match expected journal."); + return; + } + var tomod = $("#cmt" + self.linkdata.id); + if( tomod.length == 0 ) { + self._abort("Cannot moderate comment which is not visible on this page."); + return; + } + + var posturl = "/" + self.options.journal + "/" + self.options.endpoint + + "?jsmode=1&json=1&mode=" + self.linkdata.action; + + self.element + .ajaxtip({ + namespace: "moderate", + }) + .ajaxtip("load", { + url: posturl, + data: { + talkid : self.linkdata.id, + journal : self.options.journal, + + confirm : "Y", + lj_form_auth: self.options.form_auth + }, + success: function( data, status, jqxhr ) { + if ( data.error ) { + self.element.ajaxtip( "error", "Error while trying to " + self.linkdata.action + ": " + data.error ) + } else { + self.element.ajaxtip("success",data.msg); + self._updateLink(data); + } + self._trigger( "complete" ); + }, + error: function( jqxhr, status, error ) { + self.element.ajaxtip( "error", "Error contacting server. " + error); + self._trigger( "complete" ); + } + }); + }); + } +}); + +$.widget("dw.delcomment", { + options: { + cmtinfo: undefined, + journal: undefined, + form_auth: undefined, + + endpoint: "__rpc_delcomment" + }, + + _abort: function(reason, ditemid) { + ditemid = ditemid || this.linkdata.id; + this.element.ajaxtip({namespace:"delcomment"}) + .ajaxtip( "abort", "Error deleting comment #" + ditemid + ". " + reason); + }, + + _create: function() { + var self = this; + + var params = $.extractParams(this.element.attr("href")); + this.linkdata = { + journal: params.journal || "", + id: params.id || "" + }; + + var cmtinfo = self.options.cmtinfo; + var cmtdata = cmtinfo ? cmtinfo[this.linkdata.id] : undefined; + var remote = cmtinfo ? cmtinfo["remote"] : undefined; + + function deletecomment() { + var todel = self.linkdata.id ? $("#cmt" + self.linkdata.id) : []; + if( todel.length == 0 ) { + self._abort("Comment is not visible on this page."); + return; + } + + var posturl = "/" + self.options.journal + "/" + self.options.endpoint + +"?"+$.param({ mode: "js", json: 1, journal: self.options.journal, id: self.linkdata.id}); + + var postdata = { confirm: 1 }; + if($("#popdel"+self.linkdata.id+"ban").is(":checked")) postdata["ban"] = 1; + if($("#popdel"+self.linkdata.id+"spam").is(":checked")) postdata["spam"] = 1; + if($("#popdel"+self.linkdata.id+"thread").is(":checked")) postdata["delthread"] = 1; + if(self.options.form_auth) postdata["lj_form_auth"] = self.options.form_auth; + + self.element + .ajaxtip("load", { + url: posturl, + data: postdata, + success: function( data, status, jqxhr ) { + if ( data.error ) { + self.element.ajaxtip( "error", "Error while trying to delete comment: " + data.error ) + } else { + self.element.ajaxtip("success",data.msg); + removecomment(self.linkdata.id, postdata["delthread"]); + } + self._trigger( "complete" ); + }, + error: function( jqxhr, status, error ) { + self.element.ajaxtip( "error", "Error contacting server. " + error); + self._trigger( "complete" ); + } + }) + } + + function removecomment(ditemid,killchildren) { + var todel = $("#cmt" + ditemid); + if ( todel.length > 0 ) { + todel.fadeOut(2500); + + if ( killchildren ) { + var com = cmtinfo[ditemid]; + for ( var i = 0; i < com.rc.length; i++ ) { + removecomment(com.rc[i], true); + } + } + } else { + self._abort( "Child comment is not available on this page", ditemid); + } + } + + this.element.click(function(e) { + e.preventDefault(); + e.stopPropagation(); + + if (!cmtinfo || !remote || !self.options.form_auth || !self.options.journal) { + self._abort( "Not enough context available." ); + return; + } + if ( !cmtdata ) { + self._abort( "Comment is not visible on this page." ); + return; + } + if ( self.linkdata.journal != self.options.journal ) { + self._abort( "Journal in link does not match expected journal."); + return; + } + + if ( e.shiftKey ) { + self.element.ajaxtip({ namespace: "delcomment" }) + deletecomment(); + return; + } + + self.element + .ajaxtip({ + namespace: "delcomment", + content: function() { + var canAdmin = cmtinfo["canAdmin"]; + var canSpam = cmtinfo["canSpam"]; + + var form = $("<form class='popup-form'><fieldset><legend>Delete comment?</legend></fieldset></form>"); + var ul = $("<ul>").appendTo(form.find("fieldset")); + + if(remote != "" && cmtdata.u != "" && cmtdata.u != remote && canAdmin) { + var id = "popdel"+self.linkdata.id+"ban"; + ul.append($("<li>").append( + $("<input>", { type: "checkbox", value: "ban", id: id}), + $("<label>", { for: id }).html("Ban <strong>"+cmtdata.u+"</strong> from commenting") + )); + } + + if(remote != "" && cmtdata.u != remote && canSpam) { + var id = "popdel"+self.linkdata.id+"spam"; + ul.append($("<li>").append( + $("<input>", { type: "checkbox", value: "spam", id: id}), + $("<label>", { for: id }).text("Mark this comment as spam") + )); + } + + if(cmtdata.rc && cmtdata.rc.length && canAdmin){ + var id = "popdel"+self.linkdata.id+"thread"; + ul.append($("<li>").append( + $("<input>", { type: "checkbox", value: "thread", id: id}), + $("<label>", { for: id }).text("Delete thread (all subcomments)") + )); + } + + ul.append($("<li>", { class: "submit" }).append( + $("<input>", { type: "button", value: "Delete"}) + .click(deletecomment), + + $("<input>", { type: "button", value: "Cancel" }) + .click(function(){self.element.ajaxtip("cancel")}), + + $("<div class='note'>shift-click to delete without options</div>") + )); + + return form; + } + }); + }); + } +}); + +})(jQuery); + +jQuery(function($) { + if ( ! $.isEmptyObject( window.LJ_cmtinfo ) ) { + $('a') + .filter("[href^='"+Site.siteroot+"/talkscreen']") + .moderate({ + journal: LJ_cmtinfo.journal, + form_auth: LJ_cmtinfo.form_auth + }) + .end() + .filter("[href^='"+Site.siteroot+"/delcomment']") + .delcomment({ + journal: LJ_cmtinfo.journal, + form_auth: LJ_cmtinfo.form_auth, + cmtinfo: LJ_cmtinfo + }) + } +}); diff -r 1c0749606568 -r 97e54a4c0a87 htdocs/js/tooltip.dynamic.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/htdocs/js/tooltip.dynamic.js Mon Mar 28 16:25:31 2011 +0800 @@ -0,0 +1,150 @@ +/** + * @license + * jQuery Tools 1.2.5 / Tooltip Dynamic Positioning + * + * NO COPYRIGHTS OR LICENSES. DO WHAT YOU LIKE. + * + * http://flowplayer.org/tools/tooltip/dynamic.html + * + * Since: July 2009 + * Date: Wed Sep 22 06:02:10 2010 +0000 + */ +(function($) { + + // version number + var t = $.tools.tooltip; + + t.dynamic = { + conf: { + classNames: "top right bottom left" + } + }; + + /* + * See if element is on the viewport. Returns an boolean array specifying which + * edges are hidden. Edges are in following order: + * + * [top, right, bottom, left] + * + * For example following return value means that top and right edges are hidden + * + * [true, true, false, false] + * + */ + function getCropping(el) { + + var w = $(window); + var right = w.width() + w.scrollLeft(); + var bottom = w.height() + w.scrollTop(); + + return [ + el.offset().top <= w.scrollTop(), // top + right <= el.offset().left + el.width(), // right + bottom <= el.offset().top + el.height(), // bottom + w.scrollLeft() >= el.offset().left // left + ]; + } + + /* + Returns true if all edges of an element are on viewport. false if not + + @param crop the cropping array returned by getCropping function + */ + function isVisible(crop) { + var i = crop.length; + while (i--) { + if (crop[i]) { return false; } + } + return true; + } + + // dynamic plugin + $.fn.dynamic = function(conf) { + + if (typeof conf == 'number') { conf = {speed: conf}; } + + conf = $.extend({}, t.dynamic.conf, conf); + + var cls = conf.classNames.split(/\s/), orig; + + this.each(function() { + + var api = $(this).tooltip().onBeforeShow(function(e, pos) { + + // get nessessary variables + var tip = this.getTip(), tipConf = this.getConf(); + + /* + We store the original configuration and use it to restore back to the original state. + */ + if (!orig) { + orig = [ + tipConf.position[0], + tipConf.position[1], + tipConf.offset[0], + tipConf.offset[1], + $.extend({}, tipConf) + ]; + } + + /* + display tip in it's default position and by setting visibility to hidden. + this way we can check whether it will be on the viewport + */ + $.extend(tipConf, orig[4]); + tipConf.position = [orig[0], orig[1]]; + tipConf.offset = [orig[2], orig[3]]; + + tip.css({ + visibility: 'hidden', + position: 'absolute', + top: pos.top, + left: pos.left + }).show(); + + // now let's see for hidden edges + var crop = getCropping(tip); + + // possibly alter the configuration + if (!isVisible(crop)) { + + // change the position and add class + if (crop[2]) { $.extend(tipConf, conf.top); tipConf.position[0] = 'top'; tip.addClass(cls[0]); } + if (crop[3]) { $.extend(tipConf, conf.right); tipConf.position[1] = 'right'; tip.addClass(cls[1]); } + if (crop[0]) { $.extend(tipConf, conf.bottom); tipConf.position[0] = 'bottom'; tip.addClass(cls[2]); } + if (crop[1]) { $.extend(tipConf, conf.left); tipConf.position[1] = 'left'; tip.addClass(cls[3]); } + + // vertical offset + if (crop[0] || crop[2]) { tipConf.offset[0] *= -1; } + + // horizontal offset + if (crop[1] || crop[3]) { tipConf.offset[1] *= -1; } + } + + tip.css({visibility: 'visible'}).hide(); + + }); + + // restore positioning as soon as possible + api.onBeforeShow(function() { + var c = this.getConf(), tip = this.getTip(); + setTimeout(function() { + c.position = [orig[0], orig[1]]; + c.offset = [orig[2], orig[3]]; + }, 0); + }); + + // remove custom class names and restore original effect + api.onHide(function() { + var tip = this.getTip(); + tip.removeClass(conf.classNames); + }); + + ret = api; + + }); + + return conf.api ? ret : this; + }; + +}) (jQuery); diff -r 1c0749606568 -r 97e54a4c0a87 htdocs/js/tooltip.dynamic.min.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/htdocs/js/tooltip.dynamic.min.js Mon Mar 28 16:25:31 2011 +0800 @@ -0,0 +1,14 @@ +/* + + jQuery Tools 1.2.5 / Tooltip Dynamic Positioning + + NO COPYRIGHTS OR LICENSES. DO WHAT YOU LIKE. + + http://flowplayer.org/tools/tooltip/dynamic.html + + Since: July 2009 + Date: Wed Sep 22 06:02:10 2010 +0000 +*/ +(function(g){function j(a){var c=g(window),d=c.width()+c.scrollLeft(),h=c.height()+c.scrollTop();return[a.offset().top<=c.scrollTop(),d<=a.offset().left+a.width(),h<=a.offset().top+a.height(),c.scrollLeft()>=a.offset().left]}function k(a){for(var c=a.length;c--;)if(a[c])return false;return true}var i=g.tools.tooltip;i.dynamic={conf:{classNames:"top right bottom left"}};g.fn.dynamic=function(a){if(typeof a=="number")a={speed:a};a=g.extend({},i.dynamic.conf,a);var c=a.classNames.split(/\s/),d;this.each(function(){var h= +g(this).tooltip().onBeforeShow(function(e,f){e=this.getTip();var b=this.getConf();d||(d=[b.position[0],b.position[1],b.offset[0],b.offset[1],g.extend({},b)]);g.extend(b,d[4]);b.position=[d[0],d[1]];b.offset=[d[2],d[3]];e.css({visibility:"hidden",position:"absolute",top:f.top,left:f.left}).show();f=j(e);if(!k(f)){if(f[2]){g.extend(b,a.top);b.position[0]="top";e.addClass(c[0])}if(f[3]){g.extend(b,a.right);b.position[1]="right";e.addClass(c[1])}if(f[0]){g.extend(b,a.bottom);b.position[0]="bottom";e.addClass(c[2])}if(f[1]){g.extend(b, +a.left);b.position[1]="left";e.addClass(c[3])}if(f[0]||f[2])b.offset[0]*=-1;if(f[1]||f[3])b.offset[1]*=-1}e.css({visibility:"visible"}).hide()});h.onBeforeShow(function(){var e=this.getConf();this.getTip();setTimeout(function(){e.position=[d[0],d[1]];e.offset=[d[2],d[3]]},0)});h.onHide(function(){var e=this.getTip();e.removeClass(a.classNames)});ret=h});return a.api?ret:this}})(jQuery); diff -r 1c0749606568 -r 97e54a4c0a87 htdocs/js/tooltip.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/htdocs/js/tooltip.js Mon Mar 28 16:25:31 2011 +0800 @@ -0,0 +1,340 @@ +/** + * @license + * jQuery Tools 1.2.5 Tooltip - UI essentials + * + * NO COPYRIGHTS OR LICENSES. DO WHAT YOU LIKE. + * + * http://flowplayer.org/tools/tooltip/ + * + * Since: November 2008 + * Date: Wed Sep 22 06:02:10 2010 +0000 + */ +(function($) { + // static constructs + $.tools = $.tools || {version: '1.2.5'}; + + $.tools.tooltip = { + + conf: { + + // default effect variables + effect: 'toggle', + fadeOutSpeed: "fast", + predelay: 0, + delay: 30, + opacity: 1, + tip: 0, + + // 'top', 'bottom', 'right', 'left', 'center' + position: ['top', 'center'], + offset: [0, 0], + relative: false, + cancelDefault: true, + + // type to event mapping + events: { + def: "mouseenter,mouseleave", + input: "focus,blur", + widget: "focus mouseenter,blur mouseleave", + tooltip: "mouseenter,mouseleave" + }, + + // 1.2 + layout: '<div/>', + tipClass: 'tooltip' + }, + + addEffect: function(name, loadFn, hideFn) { + effects[name] = [loadFn, hideFn]; + } + }; + + + var effects = { + toggle: [ + function(done) { + var conf = this.getConf(), tip = this.getTip(), o = conf.opacity; + if (o < 1) { tip.css({opacity: o}); } + tip.show(); + done.call(); + }, + + function(done) { + this.getTip().hide(); + done.call(); + } + ], + + fade: [ + function(done) { + var conf = this.getConf(); + this.getTip().fadeTo(conf.fadeInSpeed, conf.opacity, done); + }, + function(done) { + this.getTip().fadeOut(this.getConf().fadeOutSpeed, done); + } + ] + }; + + + /* calculate tip position relative to the trigger */ + function getPosition(trigger, tip, conf) { + + + // get origin top/left position + var top = conf.relative ? trigger.position().top : trigger.offset().top, + left = conf.relative ? trigger.position().left : trigger.offset().left, + pos = conf.position[0]; + + top -= tip.outerHeight() - conf.offset[0]; + left += trigger.outerWidth() + conf.offset[1]; + + // iPad position fix + if (/iPad/i.test(navigator.userAgent)) { + top -= $(window).scrollTop(); + } + + // adjust Y + var height = tip.outerHeight() + trigger.outerHeight(); + if (pos == 'center') { top += height / 2; } + if (pos == 'bottom') { top += height; } + + + // adjust X + pos = conf.position[1]; + var width = tip.outerWidth() + trigger.outerWidth(); + if (pos == 'center') { left -= width / 2; } + if (pos == 'left') { left -= width; } + + return {top: top, left: left}; + } + + + + function Tooltip(trigger, conf) { + var self = this, + fire = trigger.add(self), + tip, + timer = 0, + pretimer = 0, + title = trigger.attr("title"), + tipAttr = trigger.attr("data-tooltip"), + effect = effects[conf.effect], + shown, + + // get show/hide configuration + isInput = trigger.is(":input"), + isWidget = isInput && trigger.is(":checkbox, :radio, select, :button, :submit"), + type = trigger.attr("type"), + evt = conf.events[type] || conf.events[isInput ? (isWidget ? 'widget' : 'input') : 'def']; + + // check that configuration is sane + if (!effect) { throw "Nonexistent effect \"" + conf.effect + "\""; } + + evt = evt.split(/,\s*/); + if (evt.length != 2) { throw "Tooltip: bad events configuration for " + type; } + + + // trigger --> show + trigger.bind(evt[0], function(e) { + clearTimeout(timer); + if (conf.predelay) { + pretimer = setTimeout(function() { self.show(e); }, conf.predelay); + + } else { + self.show(e); + } + + // trigger --> hide + }).bind(evt[1], function(e) { + clearTimeout(pretimer); + if (conf.delay) { + timer = setTimeout(function() { self.hide(e); }, conf.delay); + + } else { + self.hide(e); + } + + }); + + + // remove default title + if (title && conf.cancelDefault) { + trigger.removeAttr("title"); + trigger.data("title", title); + } + + $.extend(self, { + + show: function(e) { + + // tip not initialized yet + if (!tip) { + + // data-tooltip + if (tipAttr) { + tip = $(tipAttr); + + // single tip element for all + } else if (conf.tip) { + tip = $(conf.tip).eq(0); + + // autogenerated tooltip + } else if (title) { + tip = $(conf.layout).addClass(conf.tipClass).appendTo(document.body) + .hide().append(title); + + // manual tooltip + } else { + tip = trigger.next(); + if (!tip.length) { tip = trigger.parent().next(); } + } + + if (!tip.length) { throw "Cannot find tooltip for " + trigger; } + } + + if (self.isShown()) { return self; } + + // stop previous animation + tip.stop(true, true); + + // get position + var pos = getPosition(trigger, tip, conf); + + // restore title for single tooltip element + if (conf.tip) { + tip.html(trigger.data("title")); + } + + // onBeforeShow + e = e || $.Event(); + e.type = "onBeforeShow"; + fire.trigger(e, [pos]); + if (e.isDefaultPrevented()) { return self; } + + + // onBeforeShow may have altered the configuration + pos = getPosition(trigger, tip, conf); + + // set position + tip.css({position:'absolute', top: pos.top, left: pos.left}); + + shown = true; + + // invoke effect + effect[0].call(self, function() { + e.type = "onShow"; + shown = 'full'; + fire.trigger(e); + }); + + + // tooltip events + var event = conf.events.tooltip.split(/,\s*/); + + if (!tip.data("__set")) { + + tip.bind(event[0], function() { + clearTimeout(timer); + clearTimeout(pretimer); + }); + + if (event[1] && !trigger.is("input:not(:checkbox, :radio), textarea")) { + tip.bind(event[1], function(e) { + + // being moved to the trigger element + if (e.relatedTarget != trigger[0]) { + trigger.trigger(evt[1].split(" ")[0]); + } + }); + } + + tip.data("__set", true); + } + + return self; + }, + + hide: function(e) { + + if (!tip || !self.isShown()) { return self; } + + // onBeforeHide + e = e || $.Event(); + e.type = "onBeforeHide"; + fire.trigger(e); + if (e.isDefaultPrevented()) { return; } + + shown = false; + + effects[conf.effect][1].call(self, function() { + e.type = "onHide"; + fire.trigger(e); + }); + + return self; + }, + + isShown: function(fully) { + return fully ? shown == 'full' : shown; + }, + + getConf: function() { + return conf; + }, + + getTip: function() { + return tip; + }, + + getTrigger: function() { + return trigger; + } + + }); + + // callbacks + $.each("onHide,onBeforeShow,onShow,onBeforeHide".split(","), function(i, name) { + + // configuration + if ($.isFunction(conf[name])) { + $(self).bind(name, conf[name]); + } + + // API + self[name] = function(fn) { + if (fn) { $(self).bind(name, fn); } + return self; + }; + }); + + } + + + // jQuery plugin implementation + $.fn.tooltip = function(conf) { + + // return existing instance + var api = this.data("tooltip"); + if (api) { return api; } + + conf = $.extend(true, {}, $.tools.tooltip.conf, conf); + + // position can also be given as string + if (typeof conf.position == 'string') { + conf.position = conf.position.split(/,?\s/); + } + + // install tooltip for each entry in jQuery object + this.each(function() { + api = new Tooltip($(this), conf); + $(this).data("tooltip", api); + }); + + return conf.api ? api: this; + }; + +}) (jQuery); + + + diff -r 1c0749606568 -r 97e54a4c0a87 htdocs/js/tooltip.min.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/htdocs/js/tooltip.min.js Mon Mar 28 16:25:31 2011 +0800 @@ -0,0 +1,18 @@ +/* + + jQuery Tools 1.2.5 Tooltip - UI essentials + + NO COPYRIGHTS OR LICENSES. DO WHAT YOU LIKE. + + http://flowplayer.org/tools/tooltip/ + + Since: November 2008 + Date: Wed Sep 22 06:02:10 2010 +0000 +*/ +(function(f){function p(a,b,c){var h=c.relative?a.position().top:a.offset().top,d=c.relative?a.position().left:a.offset().left,i=c.position[0];h-=b.outerHeight()-c.offset[0];d+=a.outerWidth()+c.offset[1];if(/iPad/i.test(navigator.userAgent))h-=f(window).scrollTop();var j=b.outerHeight()+a.outerHeight();if(i=="center")h+=j/2;if(i=="bottom")h+=j;i=c.position[1];a=b.outerWidth()+a.outerWidth();if(i=="center")d-=a/2;if(i=="left")d-=a;return{top:h,left:d}}function u(a,b){var c=this,h=a.add(c),d,i=0,j= +0,m=a.attr("title"),q=a.attr("data-tooltip"),r=o[b.effect],l,s=a.is(":input"),v=s&&a.is(":checkbox, :radio, select, :button, :submit"),t=a.attr("type"),k=b.events[t]||b.events[s?v?"widget":"input":"def"];if(!r)throw'Nonexistent effect "'+b.effect+'"';k=k.split(/,\s*/);if(k.length!=2)throw"Tooltip: bad events configuration for "+t;a.bind(k[0],function(e){clearTimeout(i);if(b.predelay)j=setTimeout(function(){c.show(e)},b.predelay);else c.show(e)}).bind(k[1],function(e){clearTimeout(j);if(b.delay)i= +setTimeout(function(){c.hide(e)},b.delay);else c.hide(e)});if(m&&b.cancelDefault){a.removeAttr("title");a.data("title",m)}f.extend(c,{show:function(e){if(!d){if(q)d=f(q);else if(b.tip)d=f(b.tip).eq(0);else if(m)d=f(b.layout).addClass(b.tipClass).appendTo(document.body).hide().append(m);else{d=a.next();d.length||(d=a.parent().next())}if(!d.length)throw"Cannot find tooltip for "+a;}if(c.isShown())return c;d.stop(true,true);var g=p(a,d,b);b.tip&&d.html(a.data("title"));e=e||f.Event();e.type="onBeforeShow"; +h.trigger(e,[g]);if(e.isDefaultPrevented())return c;g=p(a,d,b);d.css({position:"absolute",top:g.top,left:g.left});l=true;r[0].call(c,function(){e.type="onShow";l="full";h.trigger(e)});g=b.events.tooltip.split(/,\s*/);if(!d.data("__set")){d.bind(g[0],function(){clearTimeout(i);clearTimeout(j)});g[1]&&!a.is("input:not(:checkbox, :radio), textarea")&&d.bind(g[1],function(n){n.relatedTarget!=a[0]&&a.trigger(k[1].split(" ")[0])});d.data("__set",true)}return c},hide:function(e){if(!d||!c.isShown())return c; +e=e||f.Event();e.type="onBeforeHide";h.trigger(e);if(!e.isDefaultPrevented()){l=false;o[b.effect][1].call(c,function(){e.type="onHide";h.trigger(e)});return c}},isShown:function(e){return e?l=="full":l},getConf:function(){return b},getTip:function(){return d},getTrigger:function(){return a}});f.each("onHide,onBeforeShow,onShow,onBeforeHide".split(","),function(e,g){f.isFunction(b[g])&&f(c).bind(g,b[g]);c[g]=function(n){n&&f(c).bind(g,n);return c}})}f.tools=f.tools||{version:"1.2.5"};f.tools.tooltip= +{conf:{effect:"toggle",fadeOutSpeed:"fast",predelay:0,delay:30,opacity:1,tip:0,position:["top","center"],offset:[0,0],relative:false,cancelDefault:true,events:{def:"mouseenter,mouseleave",input:"focus,blur",widget:"focus mouseenter,blur mouseleave",tooltip:"mouseenter,mouseleave"},layout:"<div/>",tipClass:"tooltip"},addEffect:function(a,b,c){o[a]=[b,c]}};var o={toggle:[function(a){var b=this.getConf(),c=this.getTip();b=b.opacity;b<1&&c.css({opacity:b});c.show();a.call()},function(a){this.getTip().hide(); +a.call()}],fade:[function(a){var b=this.getConf();this.getTip().fadeTo(b.fadeInSpeed,b.opacity,a)},function(a){this.getTip().fadeOut(this.getConf().fadeOutSpeed,a)}]};f.fn.tooltip=function(a){var b=this.data("tooltip");if(b)return b;a=f.extend(true,{},f.tools.tooltip.conf,a);if(typeof a.position=="string")a.position=a.position.split(/,?\s/);this.each(function(){b=new u(f(this),a);f(this).data("tooltip",b)});return a.api?b:this}})(jQuery); diff -r 1c0749606568 -r 97e54a4c0a87 htdocs/stc/ajaxtip.css --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/htdocs/stc/ajaxtip.css Mon Mar 28 16:25:31 2011 +0800 @@ -0,0 +1,13 @@ +.ajaxtooltip { + padding: .2em .5em; + z-index: 99; + text-align: left; + color: #000; + background: #fff; + border: 2px solid #555; +} +.ajaxresult { + color: #000; + background: #fbf9ee; + border: 2px solid #fcefa1; +} diff -r 1c0749606568 -r 97e54a4c0a87 htdocs/stc/popup-form.css --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/htdocs/stc/popup-form.css Mon Mar 28 16:25:31 2011 +0800 @@ -0,0 +1,49 @@ +.popup-form fieldset { + border-style: none; +} +.popup-form legend { + font-weight: bold; +} +.popup-form ul { + margin: 0; + padding: 0; +} +.popup-form ul li { + background: none !important; + display: block !important; +} +.popup-form .submit { + margin-top: 0.7em; +} +.popup-form .note { + font-size: 0.8em; + font-style: italic; + line-height: 1.3em; +} +.submit input { + margin-right: 1em; + background: #e3e3e3; + border: 1px solid #ccc; + -moz-border-radius: 3px; + border-radius: 3px; + -moz-box-shadow: inset 0 0 1px 1px #f6f6f6; + box-shadow: inset 0 0 1px 1px #f6f6f6; + color: #333; + font-weight: bold; + padding: 8px 3em 9px; + text-shadow: 0 1px 0px #fff; +} + +.submit input:hover { + background: #c9c9c9; + -moz-box-shadow: inset 0 0 1px 1px #eaeaea; + box-shadow: inset 0 0 1px 1px #eaeaea; + color: #222; +} + +.submit input:active { + background: #d0d0d0; + -moz-box-shadow: inset 0 0 1px 1px #e3e3e3; + box-shadow: inset 0 0 1px 1px #e3e3e3; + color: #000; +} diff -r 1c0749606568 -r 97e54a4c0a87 htdocs/talkscreen.bml --- a/htdocs/talkscreen.bml Mon Mar 28 14:40:04 2011 +0800 +++ b/htdocs/talkscreen.bml Mon Mar 28 16:25:31 2011 +0800 @@ -30,7 +30,12 @@ _info?><?_code my $error = sub { if ($jsmode) { BML::finish(); - return "alert('" . LJ::ejs($_[0]) . "'); 0;"; + # FIXME: remove once we've switched over completely to jquery + if ( !!$GET{json} ) { + return JSON::objToJson( { error => $_[0] } ); + } else { + return "alert('" . LJ::ejs($_[0]) . "'); 0;"; + } } $body = "<?h1 $ML{'Error'} h1?><?p $_[0] p?>"; return; @@ -48,7 +53,9 @@ _info?><?_code my $dtalkid = $qtalkid; # display talkid, for use in URL later my $jsres = sub { - my $mode = shift; + use JSON; + + my ( $mode, $message ) = @_; # flip case of 'un' my $newmode = "un$mode"; @@ -63,14 +70,20 @@ _info?><?_code 'unfreeze' => "silk/comments/unfreeze.png", }; - my $res = "rpcRes = {\n mode: \"$mode\", \n" . - " newalt: \"$alttext\", id: $dtalkid, \n" . - " oldimage: \"$LJ::IMGPREFIX/$stockimg->{$mode}\",\n " . - " newimage: '$LJ::IMGPREFIX/$stockimg->{$newmode}',\n " . - " newurl: '$LJ::SITEROOT/talkscreen?mode=$newmode&journal=$journal&talkid=$dtalkid' \n" . - "};\n"; + my %ret = ( + id => $dtalkid, + mode => $mode, + newalt => $alttext, + oldimage => "$LJ::IMGPREFIX/$stockimg->{$mode}", + newimage => "$LJ::IMGPREFIX/$stockimg->{$newmode}", + newurl => "$LJ::SITEROOT/talkscreen?mode=$newmode&journal=$journal&talkid=$dtalkid", + msg => $message, + ); + + sleep 1 if $LJ::IS_DEV_SERVER; + BML::finish(); - return $res; + return JSON::objToJson( \%ret ); }; my $remote = LJ::get_remote(); @@ -81,7 +94,7 @@ _info?><?_code # userpost (username of this comment's author). Then we can check permissions. my $u = LJ::load_user($journal); - return $error->($ML{'.talk.error.bogusargs'}) unless $u; + return $error->($ML{'talk.error.bogusargs'}) unless $u; # if we're on a user vhost, our remote was authed using that vhost, # so let's let them only modify the journal that their session @@ -148,7 +161,7 @@ _info?><?_code LJ::Talk::screen_comment($u, $qitemid, $qtalkid); } # FIXME: no error checking? - return $jsres->($mode) if $jsmode; + return $jsres->($mode, $ML{'.screened.body'}) if $jsmode; $body = "<?h1 $ML{'.screened.title'} h1?><?p $ML{'.screened.body'} $linktext p?>"; return; } @@ -173,7 +186,7 @@ _info?><?_code LJ::Talk::unscreen_comment($u, $qitemid, $qtalkid); } # FIXME: no error checking? - return $jsres->($mode) if $jsmode; + return $jsres->($mode, $ML{'.unscreened.body'}) if $jsmode; $body = "<?h1 $ML{'.unscreened.title'} h1?><?p $ML{'.unscreened.body'} $linktext p?>"; return; } @@ -201,7 +214,7 @@ _info?><?_code if ($state ne 'F') { LJ::Talk::freeze_thread($u, $qitemid, $qtalkid); } - return $jsres->($mode) if $jsmode; + return $jsres->($mode, $ML{'.frozen.body'}) if $jsmode; $body = "<?h1 $ML{'.frozen.title'} h1?><?p $ML{'.frozen.body'} $linktext p?>"; return; } @@ -227,7 +240,7 @@ _info?><?_code if ($state eq 'F') { LJ::Talk::unfreeze_thread($u, $qitemid, $qtalkid); } - return $jsres->($mode) if $jsmode; + return $jsres->($mode, $ML{'.unfrozen.body'}) if $jsmode; $body = "<?h1 $ML{'.unfrozen.title'} h1?><?p $ML{'.unfrozen.body'} $linktext p?>"; return; } diff -r 1c0749606568 -r 97e54a4c0a87 views/dev/tests/commentmanage.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/views/dev/tests/commentmanage.html Mon Mar 28 16:25:31 2011 +0800 @@ -0,0 +1,57 @@ +<a id="url_with_params_noescape" href="http://blah.com/?noequals&novalue=&key=value&key2=value%202">not escaped</a> +<a id="url_with_params_escaped" href="http://blah.com/?noequals&novalue=&key=value&key2=value%202">not escaped</a> + + +<!-- old JS implementation really really needs the delete link to be the first in this--> +<!-- text --> +<div id="cmt123">comment 123 + + +<a id="delete_link" href="http://localhost/delcomment?journal=test&id=123">Delete</a> + +<a id="freeze_link" href="http://localhost/talkscreen?mode=freeze&journal=test&talkid=123">Freeze</a> +<a id="unfreeze_link" href="http://localhost/talkscreen?mode=unfreeze&journal=test&talkid=123">Unfreeze</a> + +<a id="screen_link" href="http://localhost/talkscreen?mode=screen&journal=test&talkid=123">Screen</a> +<a id="unscreen_link" href="http://localhost/talkscreen?mode=unscreen&journal=test&talkid=123">Unscreen</a> + + +<!-- images --> +<a id="delete_img" href="http://localhost/delcomment?journal=test&id=123"><img border="0" width="16" height="16" alt="Delete" title="Delete" src="http://localhost/img/silk/comments/delete.png" /></a> + +<a id="freeze_img" href="http://localhost/talkscreen?mode=freeze&journal=test&talkid=123"><img border="0" width="16" height="16" alt="Freeze" title="Freeze" src="http://localhost/img/silk/comments/freeze.png" /></a> +<a id="unfreeze_img" href="http://localhost/talkscreen?mode=unfreeze&journal=test&talkid=123"><img border="0" width="16" height="16" alt="Unfreeze" title="Unfreeze" src="http://localhost/img/silk/comments/unfreeze.png" /></a> + +<a id="screen_img" href="http://localhost/talkscreen?mode=screen&journal=test&talkid=123"><img border="0" width="16" height="16" alt="Screen" title="Screen" src="http://localhost/img/silk/comments/screen.png" /></a> +<a id="unscreen_img" href="http://localhost/talkscreen?mode=unscreen&journal=test&talkid=123"><img border="0" width="16" height="16" alt="Unscreen" title="Unscreen" src="http://localhost/img/silk/comments/unscreen.png" /></a> +</div> + +<div id="cmt456">comment 456; child comment +<a id="child_delete_link" href="http://localhost/delcomment?journal=test&id=456">Delete</a> + +<a id="child_freeze_link" href="http://localhost/talkscreen?mode=freeze&journal=test&talkid=456">Freeze</a> +<a id="child_unfreeze_link" href="http://localhost/talkscreen?mode=unfreeze&journal=test&talkid=456">Unfreeze</a> + +<a id="child_screen_link" href="http://localhost/talkscreen?mode=screen&journal=test&talkid=456">Screen</a> +<a id="child_unscreen_link" href="http://localhost/talkscreen?mode=unscreen&journal=test&talkid=456">Unscreen</a> + +<!-- images --> +<a id="child_delete_img" href="http://localhost/delcomment?journal=test&id=456"><img border="0" width="16" height="16" alt="Delete" title="Delete" src="http://localhost/img/silk/comments/delete.png" /></a> + +<a id="child_freeze_img" href="http://localhost/talkscreen?mode=freeze&journal=test&talkid=456"><img border="0" width="16" height="16" alt="Freeze" title="Freeze" src="http://localhost/img/silk/comments/freeze.png" /></a> +<a id="child_unfreeze_img" href="http://localhost/talkscreen?mode=unfreeze&journal=test&talkid=456"><img border="0" width="16" height="16" alt="Unfreeze" title="Unfreeze" src="http://localhost/img/silk/comments/unfreeze.png" /></a> + +<a id="child_screen_img" href="http://localhost/talkscreen?mode=screen&journal=test&talkid=456"><img border="0" width="16" height="16" alt="Screen" title="Screen" src="http://localhost/img/silk/comments/screen.png" /></a> +<a id="child_unscreen_img" href="http://localhost/talkscreen?mode=unscreen&journal=test&talkid=456"><img border="0" width="16" height="16" alt="Unscreen" title="Unscreen" src="http://localhost/img/silk/comments/unscreen.png" /></a> +</div> + +<div id="invalidlink">invalid links here +<a id="invalid_delete_link" href="http://localhost/delcomment">Delete</a> +<a id="invalid_moderate_link" href="http://localhost/talkscreen">Moderate</a> +</div> + +<a id="mismatched_delete_link" href="http://localhost/delcomment?journal=test&id=999">Delete</a> +<a id="mismatched_moderate_link" href="http://localhost/talkscreen?mode=freeze&journal=test&talkid=999">Freeze</a> + +<a id="mismatched_journal_delete_link" href="http://localhost/delcomment?journal=untest&id=123">Delete</a> +<a id="mismatched_journal_moderate_link" href="http://localhost/talkscreen?mode=freeze&journal=untest&talkid=123">Freeze</a> diff -r 1c0749606568 -r 97e54a4c0a87 views/dev/tests/commentmanage.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/views/dev/tests/commentmanage.js Mon Mar 28 16:25:31 2011 +0800 @@ -0,0 +1,545 @@ +/* INCLUDE: + +old: js/commentmanage.js +jquery: js/jquery/jquery.ui.widget.js +jquery: js/jquery.ajaxtip.js +jquery: js/jquery.commentmanage.js +jquery: js/tooltip.js +jquery: js/tooltip.dynamic.js +*/ + +var lifecycle = { + setup: function() { + var p = { + "freeze": { + "mode": "freeze", + "text": "Freeze", + "img": "http://localhost/img/silk/comments/freeze.png", + "url": "http://localhost/talkscreen?mode=freeze&journal=test&talkid=123", + "msg": "thread was frozen" + }, + + "unfreeze": { + "mode": "unfreeze", + "text": "Unfreeze", + "img": "http://localhost/img/silk/comments/unfreeze.png", + "url": "http://localhost/talkscreen?mode=unfreeze&journal=test&talkid=123", + "msg": "thread was unfrozen" + }, + + "screen": { + "mode": "screen", + "text": "Screen", + "img": "http://localhost/img/silk/comments/screen.png", + "url": "http://localhost/talkscreen?mode=screen&journal=test&talkid=123", + "msg": "comment was screened" + }, + + "unscreen": { + "mode": "unscreen", + "text": "Unscreen", + "img": "http://localhost/img/silk/comments/unscreen.png", + "url": "http://localhost/talkscreen?mode=unscreen&journal=test&talkid=123", + "msg": "comment was unscreened" + }, + + "delete": { + "mode": "delete", + "text": "Delete", + "img": "http://localhost/img/silk/comments/delete.png", + "url": "http://localhost/delcomment?journal=test&id=123", + "msg": "comment deleted" + } + }; + this.linkprops = p; + + this.del_args = { + cmtinfo: { + "form_auth": "authauthauth", + "remote": "test", + "journal": "test", + + "canSpam": 1, + "canAdmin": 1, + + "123": { "parent": "", + "u": "test", + "rc": [ "456" ], + "full": 1 + }, + "456": { + "parent": "123", + "u": "test", + "rc": [], + "full": 1 + } + }, + journal: "test", + form_auth: "authauthauth" + }; + + this.mod_args = { + journal: "test", + form_auth: "authauthauth" + }; + + this.server = sinon.sandbox.useFakeServer(); + this.server.respondWith( /mode=freeze/, [ + 200, + {}, + '{\ + "mode": "freeze",\ + "id": 123,\ + "newalt": "'+p.unfreeze.text+'",\ + "oldimage": "'+p.freeze.img+'",\ + "newimage": "'+p.unfreeze.img+'",\ + "newurl": "'+p.unfreeze.url+'",\ + "msg": "'+p.unfreeze.msg+'"\ + }' + ] ); + + this.server.respondWith( /mode=unfreeze/, [ + 200, + {}, + '{\ + "mode": "unfreeze",\ + "id": 123,\ + "newalt": "'+p.freeze.text+'",\ + "oldimage": "'+p.unfreeze.img+'",\ + "newimage": "'+p.freeze.img+'",\ + "newurl": "'+p.freeze.url+'",\ + "msg": "'+p.freeze.msg+'"\ + }' + ] ); + + this.server.respondWith( /mode=screen/, [ + 200, + {}, + '{\ + "mode": "screen",\ + "id": 123,\ + "newalt": "'+p.unscreen.text+'",\ + "oldimage": "'+p.screen.img+'",\ + "newimage": "'+p.unscreen.img+'",\ + "newurl": "'+p.unscreen.url+'",\ + "msg": "'+p.unscreen.msg+'"\ + }' + ] ); + + this.server.respondWith( /mode=unscreen/, [ + 200, + {}, + '{\ + "mode": "unscreen",\ + "id": 123,\ + "newalt": "'+p.screen.text+'",\ + "oldimage": "'+p.unscreen.img+'",\ + "newimage": "'+p.screen.img+'",\ + "newurl": "'+p.screen.url+'",\ + "msg": "'+p.screen.msg+'"\ + }' + ] ); + + this.server.respondWith( /delforcefail/ [ + 200, + {}, + '{ "error": "fail!" }' + ] ); + + this.server.respondWith( /delcomment/, [ + 200, + {}, + '{ "msg": "'+p["delete"].msg+'" }' + ] ); + + this.server.respondWith( [ + 200, + {}, + '{ "error": "error!" }' + ] ); + }, + + teardown: function() { + this.server.restore(); + } +}; + + +module( "jquery", lifecycle ); +function _check_link(linkid, oldstate, newstate) { + var $link = $("#"+linkid); + + equal($link.attr("href"), oldstate.url, linkid + " - original url" ); + equal($link.text(), oldstate.text, linkid + " - original text" ); + + $link + .moderate(this.mod_args) + .one( "moderatecomplete", function( event, data ) { + equal($link.attr("href"), newstate.url, linkid + " - new url" ); + equal($link.text(), newstate.text, linkid + " - new text" ); + + equals($link.ajaxtip("widget").html(), newstate.msg, linkid + " - did action"); + }) + .trigger("click"); + this.server.respond(); + + $link + .one("moderatecomplete", function( event, data ) { + equal($link.attr("href"), oldstate.url, linkid + " - changed back to old url"); + equal($link.text(), oldstate.text, linkid + " - changed back to old text"); + + equals($link.ajaxtip("widget").html(), oldstate.msg, linkid + " - did action"); + }) + .trigger("click"); + this.server.respond(); +} + +function _check_link_with_image(linkid, oldstate, newstate) { + var $link = $("#"+linkid); + var $img = $link.find("img"); + + equal($link.attr("href"), oldstate.url, linkid + " - original url" ); + equal($img.attr("alt"), oldstate.text, linkid + " - original alt" ); + equal($img.attr("title"), oldstate.text, linkid + " - original title" ); + + $link + .moderate(this.mod_args) + .one( "moderatecomplete", function(event, data) { + equal($link.attr("href"), newstate.url, linkid + " - new url" ); + equal($img.attr("alt"), newstate.text, linkid + " - new alt" ); + equal($img.attr("title"), newstate.text, linkid + " - new title" ); + + equals($link.ajaxtip("widget").html(), newstate.msg, linkid + " - did action"); + }) + .trigger("click"); + this.server.respond(); + + $link + .one("moderatecomplete", function(event, data) { + equal($link.attr("href"), oldstate.url, linkid + " - changed back to old url"); + equal($img.attr("alt"), oldstate.text, linkid + " - changed back to old alt"); + equal($img.attr("title"), oldstate.text, linkid + " - changed back to old title" ); + + equals($link.ajaxtip("widget").html(), oldstate.msg, linkid + " - did action"); + }) + .trigger("click"); + this.server.respond(); +} + +test( "freeze / unfreeze", 38, function() { + _check_link.call(this, "freeze_link", this.linkprops.freeze, this.linkprops.unfreeze); + _check_link.call(this, "unfreeze_link", this.linkprops.unfreeze, this.linkprops.freeze); + + _check_link_with_image.call(this, "freeze_img", this.linkprops.freeze, this.linkprops.unfreeze); + _check_link_with_image.call(this, "unfreeze_img", this.linkprops.unfreeze, this.linkprops.freeze); +} ); + +test( "screen / unscreen", 38, function() { + _check_link.call(this, "screen_link", this.linkprops.screen, this.linkprops.unscreen); + _check_link.call(this, "unscreen_link", this.linkprops.unscreen, this.linkprops.screen); + + _check_link_with_image.call(this, "screen_img", this.linkprops.screen, this.linkprops.unscreen); + _check_link_with_image.call(this, "unscreen_img", this.linkprops.unscreen, this.linkprops.screen); +} ); + +test( "delete with shift", 4, function() { + var parent = $("#cmt123"); + var child = $("#cmt456"); + ok( parent.is(":visible"), "Parent comment started out visible" ); + ok( child.is(":visible"), "Child comment started out visible" ); + + $("#delete_link") + .delcomment(this.del_args) + .one( "delcommentcomplete", function(event, data) { + // finish animation early + parent.stop(true, true); + child.stop(true, true); + + ok( ! parent.is(":visible"), "Parent comment successfully hidden after delete" ); + ok( child.is(":visible"), "Child comment not deleted, still visible" ); + + }) + .trigger({type: "click", shiftKey: true}); + this.server.respond(); +} ); + +test( "delete all children (has children)", 4, function() { + var parent = $("#cmt123"); + var child = $("#cmt456"); + ok( parent.is(":visible"), "Parent comment started out visible" ); + ok( child.is(":visible"), "Child comment started out visible" ); + + $("#delete_link") + .delcomment(this.del_args) + .one( "delcommentcomplete", function(event, data) { + // finish animation early + parent.stop(true, true); + child.stop(true, true); + + ok( ! parent.is(":visible"), "Parent comment successfully hidden after delete" ); + ok( ! child.is(":visible"), "Child comment successfully hidden after delete" ); + }) + .trigger("click") + .ajaxtip("widget") + .find("input[value='thread']") + .attr("checked", "checked") + .end() + .find("input[value='Delete']") + .click(); + + this.server.respond(); +} ); + +test( "delete all children (has no children)", 4, function() { + var parent = $("#cmt123"); + var child = $("#cmt456"); + ok( parent.is(":visible"), "Parent comment started out visible" ); + ok( child.is(":visible"), "Child comment started out visible" ); + + $("#child_delete_link") + .delcomment(this.del_args) + .one( "delcommentcomplete", function(event, data) { + // finish animation early + parent.stop(true, true); + child.stop(true, true); + + ok( parent.is(":visible"), "Parent comment not deleted" ); + ok( ! child.is(":visible"), "Child comment successfully hidden after delete" ); + }) + .trigger("click") + .ajaxtip("widget") + .find("input[value='thread']") + .attr("checked", "checked") + .end() + .find("input[value='Delete']") + .click(); + this.server.respond(); +} ); + +test( "delete no children (has children)", 4, function() { + var parent = $("#cmt123"); + var child = $("#cmt456"); + ok( parent.is(":visible"), "Parent comment started out visible" ); + ok( child.is(":visible"), "Child comment started out visible" ); + + $("#delete_link") + .delcomment(this.del_args) + .one( "delcommentcomplete", function(event, data) { + // finish animation early + parent.stop(true, true); + child.stop(true, true); + + ok( ! parent.is(":visible"), "Parent comment successfully hidden after delete" ); + ok( child.is(":visible"), "Child comment not deleted, still visible" ); + }) + .trigger("click") + .ajaxtip("widget") + .find("input[value='Delete']") + .click(); + + this.server.respond(); +} ); + +test( "delete no children (has no children)", 4, function() { + var parent = $("#cmt123"); + var child = $("#cmt456"); + ok( parent.is(":visible"), "Parent comment started out visible" ); + ok( child.is(":visible"), "Child comment started out visible" ); + + $("#child_delete_link") + .delcomment(this.del_args) + .one( "delcommentcomplete", function(event, data) { + // finish animation early + parent.stop(true, true); + child.stop(true, true); + + ok( parent.is(":visible"), "Parent comment not deleted, still visible" ); + ok( ! child.is(":visible"), "Child comment successfully hidden after delete" ); + }) + .trigger("click") + .ajaxtip("widget") + .find("input[value='Delete']") + .click(); + + this.server.respond(); +} ); + +test( "failed delete: no hiding", 4, function() { + var parent = $("#cmt123"); + var child = $("#cmt456"); + ok( parent.is(":visible"), "Parent comment started out visible" ); + ok( child.is(":visible"), "Child comment started out visible" ); + + this.del_args["endpoint"] = "/delforcefail"; + + $("#delete_link") + .delcomment(this.del_args) + .one( "delcommentcomplete", function(event, data) { + // finish animation early + parent.stop(true, true); + child.stop(true, true); + + ok( parent.is(":visible"), "Parent comment not deleted, still visible" ); + ok( child.is(":visible"), "Child comment not deleted, still visible" ); + }) + .trigger("click") + .ajaxtip("widget") + .find("input[value='Delete']") + .click(); + + this.server.respond(); + +} ); + + +test( "invalid moderate link", 1, function() { + $("#invalid_moderate_link") + .moderate(this.mod_args) + .trigger("click") + this.server.respond() + + equals($("#invalid_moderate_link").ajaxtip("widget").text(), + "Error moderating comment #. Not enough context available."); +}); + +test( "invalid delete link", 1, function() { + $("#invalid_delete_link") + .delcomment(this.del_args) + .trigger({ type: "click", shiftKey: true }) + this.server.respond(); + + equals($("#invalid_delete_link").ajaxtip("widget").text(), + "Error deleting comment #. Comment is not visible on this page."); +} ); + + +test( "no such comment for moderation", 1, function() { + $("#mismatched_moderate_link") + .moderate(this.mod_args) + .trigger("click") + this.server.respond(); + + equals($("#mismatched_moderate_link").ajaxtip("widget").text(), + "Error moderating comment #999. Cannot moderate comment which is not visible on this page.") +} ); + +test( "mismatched journal for deletion", 1, function() { + $("#mismatched_journal_delete_link") + .delcomment(this.del_args) + .trigger({ type: "click", shiftKey: true }) + this.server.respond(); + + equals($("#mismatched_journal_delete_link").ajaxtip("widget").text(), + "Error deleting comment #123. Journal in link does not match expected journal.") +} ); + +test( "no such comment for moderation", 1, function() { + $("#mismatched_journal_moderate_link") + .moderate(this.mod_args) + .trigger("click") + this.server.respond(); + + equals($("#mismatched_journal_moderate_link").ajaxtip("widget").text(), + "Error moderating comment #123. Journal in link does not match expected journal.") +} ); + +test( "no such comment for deletion", 1, function() { + $("#mismatched_delete_link") + .delcomment(this.del_args) + .trigger({ type: "click", shiftKey: true }) + this.server.respond(); + + equals($("#mismatched_delete_link").ajaxtip("widget").text(), + "Error deleting comment #999. Comment is not visible on this page.") +} ); + +test( "lacking arguments for moderate: form_auth", 1, function() { + delete this.mod_args["form_auth"] + $("#freeze_link") + .moderate(this.mod_args) + .trigger("click"); + this.server.respond(); + + equals($("#freeze_link").ajaxtip("widget").text(), + "Error moderating comment #123. Not enough context available.") +} ); + +test( "lacking arguments for moderate: journal", 1, function() { + delete this.mod_args["journal"] + $("#freeze_link") + .moderate(this.mod_args) + .trigger("click"); + this.server.respond(); + + equals($("#freeze_link").ajaxtip("widget").text(), + "Error moderating comment #123. Not enough context available.") +} ); + +test( "lacking arguments for delete: cmtinfo", 1, function() { + delete this.del_args["cmtinfo"] + $("#delete_link") + .delcomment(this.del_args) + .trigger({ type: "click", shiftKey: true }) + this.server.respond(); + + equals($("#delete_link").ajaxtip("widget").text(), + "Error deleting comment #123. Not enough context available.") +} ); + +test( "lacking arguments for delete: journal", 1, function() { + delete this.del_args["journal"]; + $("#delete_link") + .delcomment(this.del_args) + .trigger({ type: "click", shiftKey: true }) + this.server.respond(); + + equals($("#delete_link").ajaxtip("widget").text(), + "Error deleting comment #123. Not enough context available.") +} ); + +test( "lacking arguments for delete: form_auth", 1, function() { + delete this.del_args["form_auth"] + $("#delete_link") + .delcomment(this.del_args) + .trigger({ type: "click", shiftKey: true }) + this.server.respond(); + + equals($("#delete_link").ajaxtip("widget").text(), + "Error deleting comment #123. Not enough context available.") +} ); + + +module( "jquery util" ); +test( "extract params", 18, function() { + var params; + + params = $.extractParams("http://blah.com/"); + deepEqual( params, {}, "no params" ); + + params = $.extractParams("http://blah.com/?"); + deepEqual( params, {}, "has ?, but no params" ); + + params = $.extractParams("http://blah.com/?noequals&novalue=&key=value&key2=value 2"); + equal( params["key"], "value", "extract url params: key" ); + equal( params["noequals"], undefined, "extract url params: noequals" ); + equal( params["novalue"], "", "extract url params: novalue" ); + equal( params["key2"], "value 2", "extract url params: key2" ); + + params = $.extractParams("http://blah.com/?noequals&novalue=&key=value&key2=value%202"); + equal( params["key"], "value", "extract url params URI-escaped: key" ); + equal( params["noequals"], undefined, "extract url params URI-escaped: noequals" ); + equal( params["novalue"], "", "extract url params URI-escaped: novalue" ); + equal( params["key2"], "value 2", "extract url params: key2" ); + + params = $.extractParams($("#url_with_params_noescape").attr("href")); + equal( params["key"], "value", "url from dom: key" ); + equal( params["noequals"], undefined, "url from dom: noequals" ); + equal( params["novalue"], "", "url from dom: novalue" ); + equal( params["key2"], "value 2", "url from dom: key2" ); + + params = $.extractParams($("#url_with_params_escaped").attr("href")); + equal( params["key"], "value", "url from dom, escaped: key" ); + equal( params["noequals"], undefined, "url from dom, escaped: noequals" ); + equal( params["novalue"], "", "url from dom, escaped: novalue" ); + equal( params["key2"], "value 2", "url from dom, escaped: key2" ); +}); --------------------------------------------------------------------------------
no subject