[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
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