fu: Close-up of Fu, bringing a scoop of water to her mouth (Default)
fu ([personal profile] fu) wrote in [site community profile] changelog2011-04-01 06:09 am

[dw-free] jquerify quick reply

[commit: http://hg.dwscoalition.org/dw-free/rev/9b2eb5944a03]

http://bugs.dwscoalition.org/show_bug.cgi?id=3580

jQuery version of the quick reply functionality.

Patch by [personal profile] fu.

Files modified:
  • cgi-bin/DW/BetaFeatures/journaljquery.pm
  • cgi-bin/LJ/S2.pm
  • cgi-bin/LJ/S2/EntryPage.pm
  • cgi-bin/weblib.pl
  • htdocs/js/jquery.quickreply.js
  • views/dev/tests/quickreply.html
  • views/dev/tests/quickreply.js
--------------------------------------------------------------------------------
diff -r 437cf707d595 -r 9b2eb5944a03 cgi-bin/DW/BetaFeatures/journaljquery.pm
--- a/cgi-bin/DW/BetaFeatures/journaljquery.pm	Fri Apr 01 13:41:19 2011 +0800
+++ b/cgi-bin/DW/BetaFeatures/journaljquery.pm	Fri Apr 01 14:08:45 2011 +0800
@@ -21,11 +21,11 @@ sub args_list {
         "Logging in",
         "Screen/freeze/delete",
         "Control strip injection for non-supporting journals",
+        "Quick reply",
     );
 
     my @notimplemented = (
         "Contextual hover",
-        "Quick reply",
         "Cut expand and collapse",
         "Media embed placeholder expansion",
         "Same-page poll submission",
diff -r 437cf707d595 -r 9b2eb5944a03 cgi-bin/LJ/S2.pm
--- a/cgi-bin/LJ/S2.pm	Fri Apr 01 13:41:19 2011 +0800
+++ b/cgi-bin/LJ/S2.pm	Fri Apr 01 14:08:45 2011 +0800
@@ -3368,7 +3368,7 @@ sub _print_quickreply_link
     my $linktext = LJ::ehtml($opts->{'linktext'}) || "";
 
     my $target = $opts->{target} || '';
-    return unless $target =~ /^\w+$/; # if no target specified bail the fuck out
+    return unless $target =~ /^\w+$/; # if no target specified bail out
 
     my $opt_class = $opts->{class}|| '';
     undef $opt_class unless $opt_class =~ /^[\w\s-]+$/;
@@ -3412,7 +3412,7 @@ sub _print_quickreply_link
         $basesubject =~ s/^(Re:\s*)*//i;
         $basesubject = "Re: $basesubject" if $basesubject;
         $basesubject = LJ::ejs($basesubject);
-        $onclick = "return quickreply(\"$target\", $pid, \"$basesubject\")";
+        $onclick = "return function(that) {return quickreply(\"$target\", $pid, \"$basesubject\",that)}(this)";
         $onclick = "onclick='$onclick'";
     }
 
diff -r 437cf707d595 -r 9b2eb5944a03 cgi-bin/LJ/S2/EntryPage.pm
--- a/cgi-bin/LJ/S2/EntryPage.pm	Fri Apr 01 13:41:19 2011 +0800
+++ b/cgi-bin/LJ/S2/EntryPage.pm	Fri Apr 01 14:08:45 2011 +0800
@@ -85,6 +85,11 @@ sub EntryPage
                     js/browserdetect.js
                     js/thread_expander.js
                     ));
+
+    LJ::need_res( { group => "jquery" }, qw(
+            js/jquery/jquery.ui.widget.min.js
+            js/jquery.quickreply.js
+        ) );
 
     $p->{'entry'} = $s2entry;
     LJ::Hooks::run_hook('notify_event_displayed', $entry);
diff -r 437cf707d595 -r 9b2eb5944a03 cgi-bin/weblib.pl
--- a/cgi-bin/weblib.pl	Fri Apr 01 13:41:19 2011 +0800
+++ b/cgi-bin/weblib.pl	Fri Apr 01 14:08:45 2011 +0800
@@ -890,6 +890,7 @@ sub create_qr_div {
     $qrhtml .= LJ::ljuser($remote->{'user'});
     $qrhtml .= "</td><td align='center'>";
 
+    my $beta_jquery = LJ::BetaFeatures->user_in_beta( $remote => "journaljquery" );
     # Userpic selector
     {
         my %res;
@@ -918,7 +919,8 @@ sub create_qr_div {
                 <input type="button" id="lj_userpicselect" value="Browse" />
                 } if $remote->can_use_userpic_select;
 
-            $qrhtml .= "<a href='javascript:void(0)' onclick='randomicon();' id='randomicon'>" . BML::ml('/talkpost.bml.userpic.random') . "</a>";
+            my $onclick = $beta_jquery ? "" : "onclick='randomicon();'";
+            $qrhtml .= "<a href='javascript:void(0)' $onclick id='randomicon'>" . BML::ml('/talkpost.bml.userpic.random') . "</a>";
 
             $qrhtml .= LJ::help_icon_html("userpics", " ");
         }
@@ -944,19 +946,19 @@ sub create_qr_div {
 
     $qrhtml .= LJ::html_submit('submitpost', BML::ml('/talkread.bml.button.post'),
                                { 'id' => 'submitpost',
-                                 'raw' => 'onclick="if (checkLength()) {submitform();}"'
+                                 'raw' => $beta_jquery ? "" : 'onclick="if (checkLength()) {submitform();}"'
                                  });
 
     $qrhtml .="&nbsp;" . LJ::html_submit('submitpview', BML::ml('talk.btn.preview'),
                                { 'id' => 'submitpview',
-                                 'raw' => 'onclick="preview()"'
+                                 'raw' => $beta_jquery ? "" : 'onclick="preview()"'
                                  });
 
     $qrhtml .= LJ::html_hidden('submitpreview', '0');
 
     $qrhtml .= "&nbsp;" . LJ::html_submit('submitmoreopts', BML::ml('/talkread.bml.button.more'),
                                           { 'id' => 'submitmoreopts',
-                                            'raw' => 'onclick="if (moreopts()) {submitform();}"'
+                                            'raw' => $beta_jquery ? "" : 'onclick="if (moreopts()) {submitform();}"'
                                             });
     if ($LJ::SPELLER) {
         $qrhtml .= "&nbsp;<input type='checkbox' name='do_spellcheck' value='1' id='do_spellcheck' /> <label for='do_spellcheck'>";
@@ -976,16 +978,15 @@ sub create_qr_div {
 
     $qrhtml .= "</td></tr></table>";
     $qrhtml .= "</form></div>";
+    $qrhtml = LJ::ejs( $qrhtml );
 
     my $ret;
     $ret = "<script language='JavaScript'>\n";
 
-    $qrhtml = LJ::ejs($qrhtml);
 
     # here we create some separate fields for saving the quickreply entry
     # because the browser will not save to a dynamically-created form.
-
-    my $qrsaveform .= LJ::ejs(LJ::html_hidden(
+    my $qrsaveform = LJ::ejs(LJ::html_hidden(
                                       {'name' => 'saved_subject', 'id' => 'saved_subject'},
                                       {'name' => 'saved_body', 'id' => 'saved_body'},
                                       {'name' => 'saved_spell', 'id' => 'saved_spell'},
@@ -994,7 +995,14 @@ sub create_qr_div {
                                       {'name' => 'saved_ptid', 'id' => 'saved_ptid'},
                                       ));
 
-    $ret .= qq{
+    if ( LJ::BetaFeatures->user_in_beta( $remote => "journaljquery" ) ) {
+        # FIXME: figure out how to fix the saving of the qr entry stuff
+        $ret .= qq{jQuery(function(jQ){
+                jQ("body").append(jQ("<div id='qrdiv'></div>").html("$qrhtml").hide());
+            });
+        };
+    } else {
+        $ret .= qq{
                var de;
                if (document.createElement && document.body.insertBefore && !(xMac && xIE4Up)) {
                    document.write("$qrsaveform");
@@ -1010,6 +1018,7 @@ sub create_qr_div {
                    }
                }
            };
+    }
 
     # quick quote button
     $ret .= LJ::Talk::js_quote_button( 'body' );
diff -r 437cf707d595 -r 9b2eb5944a03 htdocs/js/jquery.quickreply.js
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/htdocs/js/jquery.quickreply.js	Fri Apr 01 14:08:45 2011 +0800
@@ -0,0 +1,136 @@
+(function($) {
+var _ok;
+var qrdiv;
+var customsubject;
+var previous;
+
+function can_continue() {
+    if ( _ok == undefined )
+        _ok =
+            ($("#parenttalkid,#replyto,#dtid,#qrdiv,#qrformdiv,#qrform,#subject").length == 7);
+    return _ok;
+}
+
+function update(data,widget) {
+    $("#parenttalkid, #replyto").val(data.pid);
+    $("#dtid").val(data.dtid);
+    
+    var old_subject;
+    if ( previous ) {
+        old_subject = previous.subject;
+        previous.widget.hide();
+    }
+    
+    var subject = $("#subject");
+    var cur_subject = subject.val();
+
+    if ( old_subject != undefined && cur_subject != old_subject ) 
+        customsubject = true;
+    if ( ! customsubject || cur_subject == "" )
+        subject.val(data.subject);
+
+    $("#qrdiv").show().appendTo(widget);
+    widget.show();
+    $("#body").focus();
+    
+    previous = {
+        subject: data.subject,
+        widget: widget
+    };
+};
+
+$.widget("dw.quickreply", {
+    options: {
+        dtid: undefined,
+        pid: undefined,
+        subject: undefined
+    },
+    _create: function() {
+        var self = this;
+        self.element.click(function(e){
+            if ( ! can_continue() ) return;
+            e.stopPropagation();
+            e.preventDefault();
+            update(self.options, self.widget())
+        }).click();
+    },
+    widget: function() {
+        return this.options.dtid ? $("#ljqrt"+this.options.dtid) : [];
+    }
+});
+
+$.extend( $.dw.quickreply, {
+    can_continue: function() { return _ok; }
+} );
+
+})(jQuery);
+
+jQuery(document).ready(function($) {
+    function submitform(e) {
+        e.preventDefault();
+        e.stopPropagation();
+
+        $("#submitmoreopts, #submitpview, #submitpost").attr("disabled", "disabled");
+
+        var dtid = $("#dtid");
+        if ( ! Number(dtid.val()) )
+            dtid.val("0");
+
+        $("#qrform")
+            .attr("action", Site.siteroot + "/talkpost_do" )
+            .submit();
+    }
+
+    $("#submitpview").live("click", function(e){
+        $("#qrform input[name='submitpreview']").val(1);
+        submitform(e);
+    });
+    $("#submitpost").live("click", function(e){
+        var maxlength = 16000;
+        var length = $("#body").val().length;
+        if ( length > maxlength ) {
+            alert("Sorry, but your comment of " + length + " characters exceeds the maximum character length of " + maxlength + ". Please try shortening it and then post again");
+
+            e.stopPropagation();
+            e.preventDefault();
+        } else {
+            submitform(e);
+        }
+    });
+    $("#submitmoreopts").live("click", function(e) {
+        var replyto = Number($("#dtid").val());
+        var pid = Number($("#parenttalkid").val());
+        var basepath = $("#basepath").val();
+
+        e.stopPropagation();
+        e.preventDefault();
+
+        if(replyto > 0 && pid > 0) {
+            $("#qrform").attr("action", basepath + "replyto=" + replyto );
+        } else {
+            $("#qrform").attr("action", basepath + "mode=reply" );
+        }
+
+        $("#qrform").submit();
+    });
+
+    $("#randomicon").live("click", function(e){
+        e.stopPropagation();
+        e.preventDefault();
+
+        var iconslist = $("#prop_picture_keyword").get(0);
+        if ( !iconslist ) return;
+
+        // take a random number, ignoring the "(default)" option
+        var randomnumber = Math.floor(Math.random() * (iconslist.length-1) ) + 1;
+        iconslist.selectedIndex = randomnumber;
+    });
+});
+
+
+function quickreply(dtid, pid, newsubject, trigger) {
+    trigger = trigger || document;
+    $(trigger).quickreply({ dtid: dtid, pid: pid, subject: newsubject })
+            .attr("onclick", null);
+    return ! $.dw.quickreply.can_continue();
+}
diff -r 437cf707d595 -r 9b2eb5944a03 views/dev/tests/quickreply.html
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/views/dev/tests/quickreply.html	Fri Apr 01 14:08:45 2011 +0800
@@ -0,0 +1,74 @@
+<input type="hidden" name="saved_subject" id="saved_subject">
+<input type="hidden" name="saved_body" id="saved_body">
+<input type="hidden" name="saved_spell" id="saved_spell">
+<input type="hidden" name="saved_upic" id="saved_upic">
+<input type="hidden" name="saved_dtid" id="saved_dtid">
+<input type="hidden" name="saved_ptid" id="saved_ptid">
+
+<div id="qrdiv"><div id="qrformdiv">
+<form id="qrform" name="qrform" method="POST" action="/talkpost_do">
+    <input type="hidden" name="lj_form_auth" value="authauthauth">
+    <input type="hidden" name="replyto" value="0" id="replyto">
+    <input type="hidden" name="parenttalkid" value="0" id="parenttalkid">
+    <input type="hidden" name="journal" value="test" id="journal">
+    <input type="hidden" name="itemid" value="123" id="itemid">
+    <input type="hidden" name="usertype" value="cookieuser" id="usertype">
+    <input type="hidden" name="qr" value="1" id="qr">
+    <input type="hidden" name="cookieuser" value="test" id="cookieuser">
+    <input type="hidden" name="dtid" value="" id="dtid">
+    <input type="hidden" name="basepath" value="http://test.dreamwidth.org/123.html?" id="basepath">
+    <input type="hidden" name="viewing_thread" value="" id="viewing_thread">
+    <input type="hidden" name="chrp1" value="chrpchrpchrp">
+    <table>
+        <tbody>
+            <tr valign="center">
+                <td align="right"><b>From:</b></td>
+                <td align="left"><span class="ljuser" lj:user="test" style="white-space: nowrap;"><a href="http://test.dream.fu/profile"><img src="http://www.dream.fu/img/silk/identity/user.png" alt="[personal profile] " width="17" height="17" style="vertical-align: text-bottom; border: 0; padding-right: 1px;"></a><a href="http://test.dream.fu/"><b>test</b></a></span></td>
+                <td align="center"><span id="quotebuttonspan"></span></td>
+            </tr>
+            <tr>
+                <td align="right"><b>Subject:</b></td>
+                <td colspan="2" align="left"><input class="textbox" type="text" size="50" maxlength="100" name="subject" id="subject" value=""></td>
+            </tr>
+            <tr valign="top">
+                <td align="right"><b>Message:</b></td>
+                <td colspan="3" style="width: 90%">
+                    <textarea class="textbox" rows="10" cols="50" wrap="soft" name="body" id="body" style="width: 99%"></textarea>
+                </td>
+            </tr>
+            <tr>
+                <td>&nbsp;</td>
+                <td colspan="3" align="left">
+                    <input type="submit" name="submitpost" value="Post Comment" id="submitpost" onclick="/*if (checkLength()) {submitform();}*/"> <!-- TODO: -->
+                    &nbsp;
+                    <input type="submit" name="submitpview" value="Preview" id="submitpview" onclick="/*preview()*/"> <!-- TODO -->
+                    <input type="hidden" name="submitpreview" value="0">
+                    &nbsp;
+                    <input type="submit" name="submitmoreopts" value="More Options..." id="submitmoreopts" onclick="/*if (moreopts()) {submitform();}*/"> <!-- TODO -->
+                    <br><span class="de"><b>Notice!</b> This user has turned on the option that logs your IP address when posting.</span>
+                </td>
+            </tr>
+        </tbody>
+    </table>
+</form>
+</div></div>
+
+
+<a id="replyto_entry_top" onclick="return function(that) {return quickreply(&quot;topcomment&quot;, 0, &quot;&quot;,that)}(this)" href="http://test.dreamwidth.org/123.html?mode=reply">Reply</a>
+<div id="ljqrttopcomment" style="display: none"></div>
+
+
+<a id="existing_comment" onclick="return function(that) {return quickreply(&quot;1&quot;, 1, &quot;&quot;, that)}(this)" href="http://test.dreamwidth.org/123.html?replyto=1">Reply</a>
+<div id="ljqrt1" style="display: none"></div>
+
+<a id="child_of_existing_comment" onclick="return function(that) {return quickreply(&quot;2&quot;, 2, &quot;&quot;,that)}(this)" href="http://test.dreamwidth.org/123.html?replyto=2">Reply</a>
+<div id="ljqrt2" style="display: none"></div>
+
+<a id="hassubject" onclick="return function(that) {return quickreply(&quot;3&quot;, 3, &quot;Re: has subject&quot;,that)}(this)" href="http://test.dreamwidth.org/123.html?replyto=3">Reply</a>
+<div id="ljqrt3" style="display: none"></div>
+
+<a id="hasclass" onclick="return function(that) {return quickreply(&quot;4&quot;, 4, &quot;&quot;,that)}(this)" href="http://test.dreamwidth.org/123.html?replyto=4">Reply</a>
+<div id="ljqrt4" class='container_class' style="display: none"></div>
+
+<a id="replyto_entry_bottom" onclick="return function(that) {return quickreply(&quot;bottomcomment&quot;, 0, &quot;&quot;,that)}(this)" href="http://test.dreamwidth.org/123.html?mode=reply">Reply</a>
+<div id="ljqrtbottomcomment" style="display: none"></div>
diff -r 437cf707d595 -r 9b2eb5944a03 views/dev/tests/quickreply.js
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/views/dev/tests/quickreply.js	Fri Apr 01 14:08:45 2011 +0800
@@ -0,0 +1,299 @@
+/* INCLUDE:
+old: js/x_core.js
+old: js/quickreply.js
+jquery: js/jquery/jquery.ui.widget.js
+jquery: js/jquery.quickreply.js
+*/
+
+var lifecycle = {
+    setup: function() {
+        this.links = {
+            replyto_entry_top: {
+                pid     : 0,
+                replyto : 0,
+                dtid    : "topcomment",
+                subject : ""
+            },
+            replyto_entry_bottom: {
+                pid     : 0,
+                replyto : 0,
+                dtid    : "bottomcomment",
+                subject : ""
+            },
+            existing_comment: {
+                pid     : 1,
+                replyto : 1,
+                dtid    : 1,
+                subject : ""                
+            },
+            child_of_existing_comment: {
+                pid     : 2,
+                replyto : 2,
+                dtid    : 2,
+                subject : ""                
+            },
+            hassubject : {
+                pid     : 3,
+                replyto : 3,
+                dtid    : 3,
+                subject : "Re: has subject"
+            }
+        }
+
+        if ( QUnit.config.current.module == "old" ) {
+            this.has_qr = function(selector) {
+                var prev_sibling = $("qrdiv").previousElementSibling || $("qrdiv").previousSibling;
+                if ( prev_sibling )
+                    return prev_sibling.id == selector;
+
+                return false;
+            };
+            this.check_values = function(values) {
+                for( var element_id in values ) {
+                    equals( $(element_id).value, values[element_id][0], values[element_id][1] );
+                }
+            }
+        } else if ( QUnit.config.current.module == "jquery" ) {
+            this.has_qr = function(selector) {
+                return $("#ljqrt"+this.links[selector].dtid).find("#qrdiv").length > 0;
+            };
+            this.get_qr_container = function(selector) {
+                return $("#ljqrt"+this.links[selector].dtid);
+            };
+            this.check_values = function(values) {
+                for( var element_id in values ) {
+                    equals( $("#"+element_id).val(), values[element_id][0], values[element_id][1] );
+                }
+            }
+        }
+    }
+}
+
+module( "old", lifecycle );
+test( "quickreply to entry", 20, function() {
+    ok( ! this.has_qr("replyto_entry_top"), "no qrdiv here yet" );
+    this.check_values({
+        subject : ["", "Empty subject"],
+        body    : ["", "Empty body"],
+        parenttalkid: ["0", "No parent"],
+        dtid    : ["", "No dtid"],
+        replyto : ["0", "No replyto"]
+    });
+
+    QUnit.triggerEvent($("replyto_entry_top"), "click");
+    ok(   this.has_qr("replyto_entry_top"), "qrdiv shows up when clicking link to reply to entry (top)" );
+    $("body").value = "foo";
+    this.check_values({
+        subject : ["", "Empty subject"],
+        body    : ["foo", "Contains body"],
+        parenttalkid: ["0", "No parent"],
+        dtid    : ["topcomment", "No dtid"],
+        replyto : ["0", "No replyto"]
+    });
+
+
+    ok( ! this.has_qr("replyto_entry_bottom"), "no qrdiv here yet" );
+
+    QUnit.triggerEvent($("replyto_entry_bottom"), "click");
+    ok(   this.has_qr("replyto_entry_bottom"), "qrdiv shows up after clicking link to reply to entry (bottom)" );
+    ok( ! this.has_qr("replyto_entry_top"), "previous qr container no longer has contains the qr" );
+    this.check_values({
+        subject : ["", "Empty subject"],
+        body    : ["foo", "Keep existing body"],
+        parenttalkid: ["0", "No parent"],
+        dtid    : ["bottomcomment", "No dtid"],
+        replyto : ["0", "No replyto"]
+    });
+
+});
+
+test( "quickreply to comments", 20, function() {
+    ok( ! this.has_qr("existing_comment"), "no qrdiv here yet" );
+    this.check_values({
+        subject : ["", "Empty subject"],
+        body    : ["", "Empty body"],
+        parenttalkid: ["0", "No parent"],
+        dtid    : ["", "No dtid"],
+        replyto : ["0", "No replyto"]
+    });
+
+
+    QUnit.triggerEvent($("existing_comment"), "click");
+    ok(   this.has_qr("existing_comment"), "qrdiv shows up when clicking link to reply to existing toplevel comment" );
+    $("body").value = "bar";
+    this.check_values({
+        subject : ["", "Empty subject"],
+        body    : ["bar", "Contains body"],
+        parenttalkid: ["1", "Parent is existing comment"],
+        dtid    : ["1", "Dtid is existing comment"],
+        replyto : ["1", "Replyto is existing comment"]
+    });
+
+
+    ok( ! this.has_qr("child_of_existing_comment"), "no qrdiv here yet" );
+
+    QUnit.triggerEvent($("child_of_existing_comment"), "click");
+    ok(   this.has_qr("child_of_existing_comment"), "qrdiv shows up after clicking link to reply to existing secondlevel comment" );
+    ok( ! this.has_qr("existing_comment"), "previous qr container no longer has contains the qr" );
+    this.check_values({
+        subject : ["", "Empty subject"],
+        body    : ["bar", "Keep existing body"],
+        parenttalkid: ["2", "Parent is existing secondlevel comment"],
+        dtid    : ["2", "Dtid is existing secondlevel comment"],
+        replyto : ["2", "Replyto is existing secondlevel comment"]
+    });
+});
+
+test( "class names", 1, function() {
+    QUnit.triggerEvent($("hasclass"), "click");
+    // this puts the container class onto #qrdiv
+    ok( $("qrformdiv").className == "container_class", "#qrdiv is now assigned the container class");
+});
+
+
+
+module( "jquery", lifecycle );
+test( "quickreply to entry", 25, function() {
+    ok( ! this.has_qr("replyto_entry_top"), "no qrdiv here yet" );
+    ok( ! this.get_qr_container("replyto_entry_top").is(":visible"), "qr container starts out invisible");
+    this.check_values({
+        subject : ["", "Empty subject"],
+        body    : ["", "Empty body"],
+        parenttalkid: ["0", "No parent"],
+        dtid    : ["", "No dtid"],
+        replyto : ["0", "No replyto"]
+    });
+
+    $("#replyto_entry_top").click();
+    ok(   this.has_qr("replyto_entry_top"), "qrdiv shows up when clicking link to reply to entry (top)" );
+    ok(   this.get_qr_container("replyto_entry_top").is(":visible"), "qr container becomes visible when we add qr to it");
+    $("#body").val("foo");
+    this.check_values({
+        subject : ["", "Empty subject"],
+        body    : ["foo", "Contains body"],
+        parenttalkid: ["0", "No parent"],
+        dtid    : ["topcomment", "No dtid"],
+        replyto : ["0", "No replyto"]
+    });
+
+
+    ok( ! this.has_qr("replyto_entry_bottom"), "no qrdiv here yet" );
+    ok( ! this.get_qr_container("replyto_entry_bottom").is(":visible"), "qr container starts out invisible");
+
+    $("#replyto_entry_bottom").click();
+    ok(   this.has_qr("replyto_entry_bottom"), "qrdiv shows up after clicking link to reply to entry (bottom)" );
+    ok(   this.get_qr_container("replyto_entry_bottom").is(":visible"), "qr container becomes visible when we add qr to it");
+    ok( ! this.has_qr("replyto_entry_top"), "previous qr container no longer has contains the qr" );
+    ok( ! this.get_qr_container("replyto_entry_top").is(":visible"), "previous qr container no longer visible");
+    this.check_values({
+        subject : ["", "Empty subject"],
+        body    : ["foo", "Keep existing body"],
+        parenttalkid: ["0", "No parent"],
+        dtid    : ["bottomcomment", "No dtid"],
+        replyto : ["0", "No replyto"]
+    });
+});
+
+test( "quickreply to comments", 25, function() {
+    ok( ! this.has_qr("existing_comment"), "no qrdiv here yet" );
+    ok( ! this.get_qr_container("existing_comment").is(":visible"), "qr container starts out invisible");
+    this.check_values({
+        subject : ["", "Empty subject"],
+        body    : ["", "Empty body"],
+        parenttalkid: ["0", "No parent"],
+        dtid    : ["", "No dtid"],
+        replyto : ["0", "No replyto"]
+    });
+
+    $("#existing_comment").click();
+    ok(   this.has_qr("existing_comment"), "qrdiv shows up when clicking link to reply to existing toplevel comment" );
+    ok(   this.get_qr_container("existing_comment").is(":visible"), "qr container becomes visible when we add qr to it");
+    $("#body").val("bar");
+    this.check_values({
+        subject : ["", "Empty subject"],
+        body    : ["bar", "Contains body"],
+        parenttalkid: ["1", "Parent is existing comment"],
+        dtid    : ["1", "Dtid is existing comment"],
+        replyto : ["1", "Replyto is existing comment"]
+    });
+
+
+    ok( ! this.has_qr("child_of_existing_comment"), "no qrdiv here yet" );
+    ok( ! this.get_qr_container("child_of_existing_comment").is(":visible"), "qr container starts out invisible");
+
+    $("#child_of_existing_comment").click();
+    ok(   this.has_qr("child_of_existing_comment"), "qrdiv shows up after clicking link to reply to existing second-level comment" );
+    ok(   this.get_qr_container("child_of_existing_comment").is(":visible"), "qr container becomes visible when we add qr to it");
+    ok( ! this.has_qr("existing_comment"), "previous qr container no longer has contains the qr" );
+    ok( ! this.get_qr_container("existing_comment").is(":visible"), "previous qr container no longer visible");
+    this.check_values({
+        subject : ["", "Empty subject"],
+        body    : ["bar", "Keep existing body"],
+        parenttalkid: ["2", "Parent is existing secondlevel comment"],
+        dtid    : ["2", "Dtid is existing secondlevel comment"],
+        replyto : ["2", "Replyto is existing secondlevel comment"]
+    });
+});
+
+test( "reply to comment which has subject", 17, function() {
+    $("#existing_comment").click();
+    ok(   this.has_qr("existing_comment"), "reply to existing_comment" );
+    this.check_values({
+        subject : ["", "Empty subject"],
+        body    : ["", "Empty body"],
+    });
+
+    $("#hassubject").click();
+    ok(   this.has_qr("hassubject"), "reply to comment which has a subject" );
+    $("#body").val("whee");
+    this.check_values({
+        subject : ["Re: has subject", "Use existing comment subject"],
+        body    : ["whee", "Contains body"],
+    });
+
+    $("#existing_comment").click();
+    ok(   this.has_qr("existing_comment"), "reply to existing_comment again" );
+    this.check_values({
+        subject : ["", "Clear comment subject if it matches previous / hasn't been customized"],
+        body    : ["whee", "Keep old body"],
+    });
+
+
+    $("#subject").val("some custom subject");
+    this.check_values({
+        subject : ["some custom subject", "Using custom subject"],
+        body    : ["whee", "Contains body"],
+    });
+
+
+    $("#hassubject").click();
+    ok(   this.has_qr("hassubject"), "reply to something with a subject again, but this time with a custom subject" );
+    this.check_values({
+        subject : ["some custom subject", "Custom subject overrides original comment subject"],
+        body    : ["whee", "Contains body"],
+    });
+
+    $("#existing_comment").click();
+    ok(   this.has_qr("existing_comment"), "switch back to existing_comment" );
+    this.check_values({
+        subject : ["some custom subject", "Still using custom subject"],
+        body    : ["whee", "Keep old body"],
+    });
+
+});
+
+test( "class names", 1, function() {
+    $("#hasclass").click();
+    // not the same as in old style; this puts #qrformdiv within .container_class
+    ok( $(".container_class").find("#qrformdiv").length == 1, "#qrdiv is contained within quick reply container which has a class");
+});
+
+test( "submit post", 0, function() {
+    // FIXME: add test
+});
+test( "submit preview", 0, function() {
+    // FIXME: add test
+});
+test( "submit more options", 0, function() {
+    // FIXME: add test
+});
--------------------------------------------------------------------------------