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

[dw-free] Option to view top level comments only

[commit: http://hg.dwscoalition.org/dw-free/rev/2259a0756378]

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

Allow users to view only top-level comments (along with a link to expand the
thread / view the thread). Also adds a link to view=flat. Also, also adds
the ability to collapse expanded comments.

Patch by [personal profile] allen.

Files modified:
  • bin/upgrading/en.dat
  • bin/upgrading/s2layers/core2.s2
  • cgi-bin/LJ/S2.pm
  • cgi-bin/LJ/S2/EntryPage.pm
  • cgi-bin/LJ/S2Theme.pm
  • cgi-bin/LJ/Talk.pm
  • htdocs/js/jquery.threadexpander.js
  • htdocs/talkread.bml
  • htdocs/talkread.bml.text
--------------------------------------------------------------------------------
diff -r fecd2b3c1a0b -r 2259a0756378 bin/upgrading/en.dat
--- a/bin/upgrading/en.dat	Fri Sep 09 14:51:13 2011 +0800
+++ b/bin/upgrading/en.dat	Fri Sep 09 16:57:23 2011 +0800
@@ -3774,6 +3774,8 @@
 
 talk.frozen=Replies frozen
 
+talk.hide=Hide [[num]] [[?num|comment|comments]]
+
 talk.parentlink=Parent
 
 talk.readsimilar=Read similar journal entries:
@@ -3792,6 +3794,8 @@
 
 talk.threadrootlink=Thread from start
 
+talk.unhide=Show [[num]] [[?num|comment|comments]]
+
 time.ago.day=[[num]] [[?num|day|days]] ago
 
 time.ago.hour=[[num]] [[?num|hour|hours]] ago
diff -r fecd2b3c1a0b -r 2259a0756378 bin/upgrading/s2layers/core2.s2
--- a/bin/upgrading/s2layers/core2.s2	Fri Sep 09 14:51:13 2011 +0800
+++ b/bin/upgrading/s2layers/core2.s2	Fri Sep 09 16:57:23 2011 +0800
@@ -341,6 +341,9 @@
 
     var string[] link_keyseq "An array of keys which you should pass to [method[EntryLite.get_link(string key)]] to produce an entry 'toolbar'. Does not contain nav_next and nav_prev for entries; you should retrieve those separately and put them somewhere appropriate for your layout.";
 
+    var readonly bool hidden_child "Indicates if the child is hidden by default.";
+    var readonly bool hide_children "Indicates we are hiding the children of this comment";
+
     function print_wrapper_start() "Start the wrapper for this entry or comment; includes anchor and classes";
     function print_wrapper_end() "End the wrapper for this entry or comment.";
     function print_metatypes() "Print the metatype icons for this entry (security, age restriction) or comment (subject icon)";
@@ -448,6 +451,11 @@
 
     function builtin print_expand_link () : string "Prints a link to expand a collapsed comment. Uses the value of the 'text_comment_expand' property as the text.";
     function builtin print_expand_link (string{} opts) : string "Prints a link to expand a collapsed comment. Can pass options 'text', 'title', 'class', and 'img_url' (and other 'img_*' options).";
+
+    function builtin print_hide_link () : string "Prints a link to hide a comment thread. Uses the value of the 'text_comment_hide' property as the text.";
+    function builtin print_hide_link (string{} opts) : string "Prints a link to hide a comment. Can pass options 'text', 'title', 'class', and 'img_url' (and other 'img_*' options).";
+    function builtin print_unhide_link () : string "Prints a link to unhide a comment thread. Uses the value of the 'text_comment_unhide' property as the text.";
+    function builtin print_unhide_link (string{} opts) : string "Prints a link to hide a comment. Can pass options 'text', 'title', 'class', and 'img_url' (and other 'img_*' options).";
     function print_time (string datefmt, string timefmt, bool edittime) "Same as EntryLite::print_time, except can pass in if we want the edit time or not.";
     function time_display (string datefmt, string timefmt, bool edittime) : string "Same as EntryLite::time_display, except can pass in if we want the edit time or not.";
     function print_edittime () "Print the time that this comment was edited, with most useful information for user.  Empty string if the comment hasn't been edited.";
@@ -474,6 +482,16 @@
     function print() "Prints the icon";
 }
 
+class CommentNav
+"Represents the comment navigation bar."
+{
+  var string view_mode "The current view mode (threaded, flat, top-only).";
+  var string url "The url for this entry page, complete with style argument if present.";
+  var bool filter "True if the current view is filtered.";
+
+  function print () "Prints the nav bar, including the comment_pages bar";
+}
+
 
 ### Userinfo
 
@@ -858,6 +876,8 @@
 {
     var Entry entry "Journal entry being viewed";
     var ItemRange comment_pages "Represents what comment page is being displayed.";
+    var CommentNav comment_nav "The comment navigation bar";
+
     var Comment[] comments "Comments to journal entry, or at least some of them.";
     var bool viewing_thread "True if viewing a specific sub-thread of the comments.  Style may which to hide the journal entry at this point, since the focus is the comments.";
 
@@ -2481,6 +2501,16 @@
     example = "Expand";
     maxlength = "50";
 }
+property string text_comment_hide {
+    des = "Text to hide a comment thread";
+    example = "Hide 1 comment // Hide # comments";
+    maxlength = "50";
+}
+property string text_comment_unhide {
+    des = "Text to unhide a comment thread";
+    example = "Show 1 comment // Show # comments";
+    maxlength = "50";
+}
 set text_comment_reply = "Reply";
 set text_comment_frozen = "Frozen";
 set text_comment_parent = "Parent";
@@ -2488,6 +2518,8 @@
 set text_comment_thread = "Thread";
 set text_comment_threadroot = "Thread from start";
 set text_comment_expand = "Expand";
+set text_comment_hide = "Hide 1 comment // Hide # comments";
+set text_comment_unhide = "Show 1 comment // Show # comments";
 
 ##===============================
 ## Text - icons
@@ -4993,6 +5025,23 @@
         $this->print_expand_link();
         """</li>\n""";
     }
+    var Link hide_link = $this->get_link("hide_comments");
+    if (defined $hide_link) {
+         var string show_hide = "";
+         if (not $this.hide_children) {
+             $show_hide = " cmt_show_hide_default";
+         }
+        """<li class="link cmt_hide$show_hide" style="display:none;" id="cmt${this.talkid}_hide">""";
+        $this->print_hide_link();
+        """</li>\n""";
+    }
+    var Link unhide_link = $this->get_link("unhide_comments");
+    if (defined $unhide_link) {
+        var string show_hide = "";
+        """<li class="link cmt_unhide$show_hide" style="display: none;" id="cmt${this.talkid}_unhide">""";
+        $this->print_unhide_link();
+        """</li>\n""";
+    }
     """</ul>""";
 }
 
@@ -5731,6 +5780,7 @@
        """<div class='comments-message'>$*text_comments_disabled_maintainer</div>""";
    }
    if ($.comment_pages.total_subitems > 0) {
+       $.comment_nav->print();
        $this->print_multiform_start();
    }
    $this->print_comments($.comments);
@@ -5753,7 +5803,11 @@
         var string parity = $c.depth % 2 ? "odd" : "even";
         var int indent = ($c.depth - 1) * 25;
         "<div class='comment-thread comment-depth-$parity comment-depth-$c.depth'>\n";
-        "<div id='$c.dom_id' style='margin-left: ${indent}px; margin-top: 5px'>\n";
+        "<div id='$c.dom_id' style='margin-left: ${indent}px; margin-top: 5px;";
+        if ($c.hidden_child) {
+            " display: none;";
+        }
+        "'>\n";
         if ($c.full) {
             $this->print_comment($c);
         } else {
@@ -5828,9 +5882,39 @@
 
 function EntryPage::print_comment_partial (Comment c) {
     $c->print_wrapper_start();
-    if ($c.deleted) { print $*text_deleted; }
-    elseif ($c.fromsuspended) { print $*text_fromsuspended; }
-    elseif ($c.screened_noshow) { print $*text_screened; }
+    if ($c.deleted) {
+        print $*text_deleted;
+        if ($c.hide_children) {
+            var Link expand_link = $c->get_link("expand_comments");
+            if (defined $expand_link) {
+                print " (";
+                $c->print_expand_link();
+                print ")";
+            }
+        }
+    }
+    elseif ($c.fromsuspended) {
+        print $*text_fromsuspended;
+        if ($c.hide_children) {
+            var Link expand_link = $c->get_link("expand_comments");
+            if (defined $expand_link) {
+                print " (";
+                $c->print_expand_link();
+                print ")";
+            }
+        }
+    }
+    elseif ($c.screened_noshow) {
+        print $*text_screened;
+        if ($c.hide_children) {
+            var Link expand_link = $c->get_link("expand_comments");
+            if (defined $expand_link) {
+                print " (";
+                $c->print_expand_link();
+                print ")";
+            }
+        }
+    }
     else {
         var string poster = defined $c.poster ? $c.poster->as_string() : "<i>$*text_poster_anonymous</i>";
         $c->print_subject();
@@ -5878,6 +5962,29 @@
     "</div>";
 }
 
+function CommentNav::print
+{
+    var string sep = $.url->contains( "?" ) ? "&" : "?";
+
+    print "<div class='comment-pages'>";
+
+    var string{} links = {
+        "flat"      => """<a href="$.url${sep}view=flat#comments">Flat</a>""",
+        "threaded"  => """<a href="$.url#comments">Threaded</a>""",
+        "top-only"  => """<a href="$.url${sep}view=top-only#comments">Top-Level Comments Only</a>"""
+    };
+
+    if ( $.view_mode == "threaded" ) {
+        print "$links{"flat"} | $links{"top-only"}";
+    } elseif ( $.view_mode == "flat" ) {
+        print "$links{"threaded"} | $links{"top-only"}";
+    } elseif ( $.view_mode == "top-only" ) {
+        print "$links{"threaded"} | $links{"flat"}";
+    }
+
+    print "</div>";
+}
+
 function EntryPage::print_body
 {
     var Entry e = $.entry;
diff -r fecd2b3c1a0b -r 2259a0756378 cgi-bin/LJ/S2.pm
--- a/cgi-bin/LJ/S2.pm	Fri Sep 09 14:51:13 2011 +0800
+++ b/cgi-bin/LJ/S2.pm	Fri Sep 09 16:57:23 2011 +0800
@@ -2421,6 +2421,14 @@
     return $h;
 }
 
+sub CommentNav
+{
+    my $h = shift;
+    $h->{'_type'} = "CommentNav";
+
+    return $h;
+}
+
 sub User
 {
     my ($u) = @_;
@@ -3368,6 +3376,26 @@
         return LJ::S2::Link("#",        ## actual link is javascript: onclick='....'
                             $ctx->[S2::PROPS]->{"text_comment_expand"});
     }
+    if ($key eq "hide_comments") {
+        ## show "Hide/Show" link if the comment has any children
+        # only show hide/show comments if using jquery
+        if  ( LJ::BetaFeatures->user_in_beta( $remote => "journaljquery" ) && @{ $this->{replies} } > 0 ) {
+            return LJ::S2::Link("#",        ## actual link is javascript: onclick='....'
+                                $ctx->[S2::PROPS]->{"text_comment_hide"});
+        } else {
+            return $null_link;
+        }
+    }
+    if ($key eq "unhide_comments") {
+        ## show "Hide/Unhide" link if the comment has any children
+        # only show hide/show comments if using jquery
+        if  ( LJ::BetaFeatures->user_in_beta( $remote => "journaljquery" ) && @{ $this->{replies} } > 0 ) {
+            return LJ::S2::Link("#",        ## actual link is javascript: onclick='....'
+                                $ctx->[S2::PROPS]->{"text_comment_unhide"});
+        } else {
+            return $null_link;
+        }
+    }
 }
 
 sub Comment__print_multiform_check
@@ -3536,7 +3564,25 @@
     my $title = $opts->{title} ? " title='" . LJ::ehtml($opts->{title}) . "'" : "";
     my $class = $opts->{class} ? " class='" . LJ::ehtml($opts->{class}) . "'" : "";
 
-    return "<a href='$this->{expand_url}'$title$class onClick=\"Expander.make(this,'$this->{expand_url}','$this->{talkid}'); return false;\">$text</a>";
+    my $onclick = "";
+    # if we're in top-only mode, then we display the expand link as
+    # the unhide ('show x comments') message
+
+    if ( $this->{"hide_children"} ) {
+        my $comment_count = $this->{'showable_children'};
+
+        $text = LJ::ehtml( get_plural_phrase( $ctx, $comment_count, "text_comment_unhide" ) );
+        my $remote = LJ::get_remote();
+
+        # unhide only works with jquery; if the user isn't in the jquery
+        # beta, drop back down to the no-javascript option
+        if  ( LJ::BetaFeatures->user_in_beta( $remote => "journaljquery" ) ) {
+            $onclick = " onClick=\"Expander.make(this,'$this->{expand_url}','$this->{talkid}', false, true); return false;\"";
+        }
+    } else {
+        $onclick = " onClick=\"Expander.make(this,'$this->{expand_url}','$this->{talkid}', false); return false;\"";
+    }
+    return"<a href='$this->{expand_url}'$title$class$onclick>$text</a>";
 }
 
 sub Comment__print_expand_link
@@ -3544,6 +3590,94 @@
     $S2::pout->(Comment__expand_link(@_));
 }
 
+# creates the (javascript) link that hides comments under this comment.
+sub Comment__print_hide_link
+{
+    my ($ctx, $this, $opts) = @_;
+    $opts ||= {};
+
+    my $comment_count = $this->{'showable_children'};
+
+    my $prop_text = LJ::ehtml( get_plural_phrase( $ctx, $comment_count, "text_comment_hide" ) );
+
+    my $text = LJ::ehtml($opts->{text});
+    $text =~ s/&amp;nbsp;/&nbsp;/gi; # allow &nbsp; in the text
+
+    my $opt_img = LJ::CleanHTML::canonical_url($opts->{img_url});
+
+    # if they want an image change the text link to the image,
+    # and add the text after the image if they specified it as well
+    if ($opt_img) {
+        my $width = $opts->{img_width};
+        my $height = $opts->{img_height};
+        my $border = $opts->{img_border};
+        my $align = LJ::ehtml($opts->{img_align});
+        my $alt = LJ::ehtml($opts->{img_alt}) || $prop_text;
+        my $title = LJ::ehtml($opts->{img_title}) || $prop_text;
+
+        $width  = defined $width  && $width  =~ /^\d+$/ ? " width=\"$width\"" : "";
+        $height = defined $height && $height =~ /^\d+$/ ? " height=\"$height\"" : "";
+        $border = defined $border && $border =~ /^\d+$/ ? " border=\"$border\"" : "";
+
+        $align  = $align =~ /^\w+$/ ? " align=\"$align\"" : "";
+        $alt    = $alt   ? " alt=\"$alt\"" : "";
+        $title  = $title ? " title=\"$title\"" : "";
+
+        $text = "<img src=\"$opt_img\"$width$height$border$align$title$alt />$text";
+    } elsif (!$text) {
+        $text = $prop_text;
+    }
+
+    my $title = $opts->{title} ? " title='" . LJ::ehtml($opts->{title}) . "'" : "";
+    my $class = $opts->{class} ? " class='" . LJ::ehtml($opts->{class}) . "'" : "";
+
+    $S2::pout->("<a href='#cmt$this->{talkid}'$title$class onClick=\"Expander.hideComments(this, '$this->{talkid}'); return false;\">$text</a>");
+}
+
+# creates the (javascript) link that unhides comments under this comment.
+sub Comment__print_unhide_link
+{
+    my ($ctx, $this, $opts) = @_;
+    $opts ||= {};
+
+    my $comment_count = $this->{'showable_children'};
+
+    my $prop_text = LJ::ehtml( get_plural_phrase( $ctx, $comment_count, "text_comment_unhide" ) );
+
+    my $text = LJ::ehtml($opts->{text});
+    $text =~ s/&amp;nbsp;/&nbsp;/gi; # allow &nbsp; in the text
+
+    my $opt_img = LJ::CleanHTML::canonical_url($opts->{img_url});
+
+    # if they want an image change the text link to the image,
+    # and add the text after the image if they specified it as well
+    if ($opt_img) {
+        my $width = $opts->{img_width};
+        my $height = $opts->{img_height};
+        my $border = $opts->{img_border};
+        my $align = LJ::ehtml($opts->{img_align});
+        my $alt = LJ::ehtml($opts->{img_alt}) || $prop_text;
+        my $title = LJ::ehtml($opts->{img_title}) || $prop_text;
+
+        $width  = defined $width  && $width  =~ /^\d+$/ ? " width=\"$width\"" : "";
+        $height = defined $height && $height =~ /^\d+$/ ? " height=\"$height\"" : "";
+        $border = defined $border && $border =~ /^\d+$/ ? " border=\"$border\"" : "";
+
+        $align  = $align =~ /^\w+$/ ? " align=\"$align\"" : "";
+        $alt    = $alt   ? " alt=\"$alt\"" : "";
+        $title  = $title ? " title=\"$title\"" : "";
+
+        $text = "<img src=\"$opt_img\"$width$height$border$align$title$alt />$text";
+    } elsif (!$text) {
+        $text = $prop_text;
+    }
+
+    my $title = $opts->{title} ? " title='" . LJ::ehtml($opts->{title}) . "'" : "";
+    my $class = $opts->{class} ? " class='" . LJ::ehtml($opts->{class}) . "'" : "";
+
+    $S2::pout->("<a href='$this->{expand_url}'$title$class onClick=\"Expander.unhideComments(this, '$this->{talkid}'); return false;\">$text</a>");
+}
+
 sub Page__print_trusted
 {
     my ($ctx, $this, $key) = @_;
diff -r fecd2b3c1a0b -r 2259a0756378 cgi-bin/LJ/S2/EntryPage.pm
--- a/cgi-bin/LJ/S2/EntryPage.pm	Fri Sep 09 14:51:13 2011 +0800
+++ b/cgi-bin/LJ/S2/EntryPage.pm	Fri Sep 09 16:57:23 2011 +0800
@@ -32,8 +32,8 @@
     $p->{'_type'} = "EntryPage";
     $p->{'view'} = "entry";
     $p->{'comment_pages'} = undef;
+    $p->{'comment_navbar'} = undef;
     $p->{'comments'} = [];
-    $p->{'comment_pages'} = undef;
 
     # setup viewall options
     my ($viewall, $viewsome) = (0, 0);
@@ -98,12 +98,14 @@
     # add the comments
     my $view_arg = $get->{'view'} || "";
     my $flat_mode = ($view_arg =~ /\bflat\b/);
+    my $top_only_mode  = ($view_arg =~ /\btop-only\b/);
     my $view_num = ($view_arg =~ /(\d+)/) ? $1 : undef;
 
     my %userpic;
     my %user;
     my $copts = {
         'flat' => $flat_mode,
+        'top-only' => $top_only_mode,
         'thread' => $get->{thread} ? ( $get->{thread} >> 8 ) : 0,
         'page' => $get->{'page'},
         'view' => $view_num,
@@ -165,7 +167,7 @@
                     $edittime_poster = DateTime_tz($comment->edit_time, $pu);
                 }
 
-                $threadroot_url = $comment->threadroot_url( LJ::viewing_style_args( %$get ) ) if $com->{parenttalkid};
+                $threadroot_url = $comment->threadroot_url( $style_arg ) if $com->{parenttalkid};
             }
 
             my $subject_icon = undef;
@@ -248,6 +250,7 @@
                 'subject' => LJ::ehtml($com->{'subject'}),
                 'subject_icon' => $subject_icon,
                 'talkid' => $dtalkid,
+                'ditemid' => $entry->ditemid,
                 'text' => $text,
                 'userpic' => $comment_userpic,
                 'time' => $datetime,
@@ -276,6 +279,9 @@
                 'edittime_poster' => $edittime_poster,
                 'edit_url' => $edit_url,
                 timeformat24 => $remote && $remote->use_24hour_time,
+                'showable_children' => $com->{'showable_children'},
+                'hide_children' => $com->{'hide_children'},
+                'hidden_child' => $com->{'hidden_child'},
             };
 
             # don't show info from suspended users
@@ -409,6 +415,13 @@
         $copts->{'out_itemfirst'} = $copts->{'out_itemlast'} = undef;
     }
 
+    # creates the comment nav bar
+    $p->{'comment_nav'} = CommentNav({
+        'view_mode' => $flat_mode ? "flat" : $top_only_mode ? "top-only" : "threaded",
+        'url' => $entry->url( style_args => LJ::viewing_style_opts( %$get ) ),
+        'current_page' => $copts->{'out_page'},
+    });
+
     $p->{'comment_pages'} = ItemRange({
         'all_subitems_displayed' => ($copts->{'out_pages'} == 1),
         'current' => $copts->{'out_page'},
@@ -418,7 +431,7 @@
         'total' => $copts->{'out_pages'},
         'total_subitems' => $copts->{'out_items'},
         '_url_of' => sub {
-            my $sty = $flat_mode ? "view=flat&" : "";
+            my $sty = $flat_mode ? "view=flat&" : $top_only_mode ? "view=top-only&" : "";
             return "$permalink?${sty}page=" . int($_[0]) .
                 ($style_arg ? "&$style_arg" : '');
         },
diff -r fecd2b3c1a0b -r 2259a0756378 cgi-bin/LJ/S2Theme.pm
--- a/cgi-bin/LJ/S2Theme.pm	Fri Sep 09 14:51:13 2011 +0800
+++ b/cgi-bin/LJ/S2Theme.pm	Fri Sep 09 16:57:23 2011 +0800
@@ -1012,11 +1012,13 @@
         text_comment_expand
         text_comment_from
         text_comment_frozen
+        text_comment_hide
         text_comment_ipaddr
         text_comment_parent
         text_comment_posted
         text_comment_reply
         text_comment_thread
+        text_comment_unhide
         color_comment_title
         color_comment_title_background
         font_comment_title
diff -r fecd2b3c1a0b -r 2259a0756378 cgi-bin/LJ/Talk.pm
--- a/cgi-bin/LJ/Talk.pm	Fri Sep 09 14:51:13 2011 +0800
+++ b/cgi-bin/LJ/Talk.pm	Fri Sep 09 16:57:23 2011 +0800
@@ -920,6 +920,7 @@
 #   userpicref -- hashref to load userpics into, or undef to
 #                 not load them.
 #   userref -- hashref to load users into, keyed by userid
+#   top-only -- boolean; if set, only load the top-level comments
 #
 # returns:
 #   array of hashrefs containing keys:
@@ -940,6 +941,9 @@
 #      - _loaded => 1 (if fully loaded, subject & body)
 #        unknown items will never be _loaded
 #      - _show => {0|1}, if item is to be ideally shown (0 if deleted, screened, or filtered)
+#      - showable_children - count of showable children for this comment
+#      - hidden_child => {0|1}, if this comment is hidden by default
+#      - hide_children => {0|1}, if this comment has its children hidden
 #      - echi (explicit comment hierarchy indicator)
 sub load_comments
 {
@@ -1035,6 +1039,11 @@
             if ($sum) {
                 $showable_children{$post->{'parenttalkid'}} += $sum;
                 unshift @{$children{$post->{'parenttalkid'}}}, $post->{'talkid'};
+                # record the # of showable children for each comment (though
+                # not for the post itself (0))
+                if ( $post->{parenttalkid} ) {
+                    $posts->{$post->{parenttalkid}}->{'showable_children'} = $showable_children{$post->{'parenttalkid'}};
+                }
             }
 
         }
@@ -1151,7 +1160,13 @@
 
     ## expand first reply to top-level comments
     ## %expand_children - list of comments, children of which are to expand
-    my %expand_children = map { $_ => 1 } @top_replies;
+    my %expand_children;
+    unless ( $opts->{'top-only'} ) {
+        ## expand first reply to top-level comments
+        ## %expand_children - list of comments, children of which are to expand
+        %expand_children = map { $_ => 1 } @top_replies;
+    }
+
     my (@subjects_to_load, @subjects_ignored);
 
     while (@check_for_children) {
@@ -1159,14 +1174,14 @@
 
         next unless defined $children{$cfc};
         foreach my $child (@{$children{$cfc}}) {
-            if (@posts_to_load < $page_size || $expand_children{$cfc} || $opts->{expand_all}) {
+            if ( ! $opts->{'top-only'} && ( @posts_to_load < $page_size || $expand_children{$cfc} || $opts->{expand_all} ) ) {
                 push @posts_to_load, $child;
                 ## expand only the first child, then clear the flag
                 delete $expand_children{$cfc};
-            }
-            elsif (@posts_to_load < $page_size) {
-                push @posts_to_load, $child;
             } else {
+                if ( $opts->{'top-only'} ) {
+                    $posts->{$child}->{'hidden_child'} = 1;
+                }
                 if (@subjects_to_load < $max_subjects) {
                     push @subjects_to_load, $child;
                 } else {
@@ -1189,11 +1204,17 @@
     $posts_loaded = LJ::get_talktext2($u, @posts_to_load);
     $subjects_loaded = LJ::get_talktext2($u, {'onlysubjects'=>1}, @subjects_to_load) if @subjects_to_load;
     foreach my $talkid (@posts_to_load) {
+        if ( $opts->{'top-only'} ) {
+            $posts->{$talkid}->{'hide_children'} = 1;
+        }
         next unless $posts->{$talkid}->{'_show'};
         $posts->{$talkid}->{'_loaded'} = 1;
         $posts->{$talkid}->{'subject'} = $posts_loaded->{$talkid}->[0];
         $posts->{$talkid}->{'body'} = $posts_loaded->{$talkid}->[1];
         $users_to_load{$posts->{$talkid}->{'posterid'}} = 1;
+        if ( $opts->{'top-only'} ) {
+            $posts->{$talkid}->{'hide_children'} = 1;
+        }
     }
     foreach my $talkid (@subjects_to_load) {
         next unless $posts->{$talkid}->{'_show'};
diff -r fecd2b3c1a0b -r 2259a0756378 htdocs/js/jquery.threadexpander.js
--- a/htdocs/js/jquery.threadexpander.js	Fri Sep 09 14:51:13 2011 +0800
+++ b/htdocs/js/jquery.threadexpander.js	Fri Sep 09 16:57:23 2011 +0800
@@ -1,3 +1,8 @@
+/*
+ * This handles the thread expansion for comments.  It also handles
+ * the show/hide functionality for comments.
+ */
+
 (function($) {
   // makes the given comment element displayed fully.
   function setFull(commentElement, full) {
@@ -11,12 +16,15 @@
     return $("td.spacer img", element);
   }
 
-  // Returns the given talkid, plus the talkids of all comments that are
-  // replies to this talkid.
-  function getReplies(LJ, talkid) {
-    var returnValue = [ talkid ];
+  // Returns the talkids of all comments that are replies to this talkid,
+  // plus the given talkid if includeSelf is called.
+  function getReplies(LJ, talkid, includeSelf) {
+    var returnValue = [];
+    if (includeSelf) {
+      returnValue.push(talkid);
+    }
     for (var i = 0; i < LJ[talkid].rc.length; i++) {
-      returnValue = returnValue.concat(getReplies(LJ, LJ[talkid].rc[i]));
+      returnValue = returnValue.concat(getReplies(LJ, LJ[talkid].rc[i], true));
     }
     return returnValue;
   }
@@ -27,7 +35,7 @@
   }
 
   // ajax expands the comments for the given talkid
-  $.fn.expandComments = function(LJ, expand_url, talkid, isS1) {
+  $.fn.expandComments = function(LJ, expand_url, talkid, isS1, unhide) {
     element = this;
     // if we've already been clicked, just return.
     if (element.hasClass("disabled")) {
@@ -47,22 +55,22 @@
           datatype: "html",
           timeout: 30000,
           success: function(data) {
-            doJqExpand(LJ, data, talkid, isS1);
-          },
+          element.doJqExpand(LJ, data, talkid, isS1, unhide);
+        },
           error: function(jqXHR, textStatus, errorThrown) {
-            img.remove();
-            element.removeClass("disabled");
-            element.fadeTo("fast", 1.0);
-            showExpanderError($.threadexpander.config.text.error);
-          }
+          img.remove();
+          element.removeClass("disabled");
+          element.fadeTo("fast", 1.0);
+          showExpanderError($.threadexpander.config.text.error);
+        }
       } );
   };
 
   // callback to handle comment expansion
-  function doJqExpand(LJ, data, talkid, isS1) {
+  $.fn.doJqExpand = function(LJ, data, talkid, isS1, unhide) {
     var updateCount = 0;
     // check for matching expansions on the page
-    var replies = getReplies(LJ, talkid);
+    var replies = getReplies(LJ, talkid, true);
     for (var cmtIdCnt = 0; cmtIdCnt < replies.length; cmtIdCnt++) {
       var cmtId = replies[cmtIdCnt];
       // if we're a valid comment, and either the comment is not expanded
@@ -73,11 +81,12 @@
           var newComment = $("#cmt" + cmtId, data);
           if (newComment && newComment.attr('id') == 'cmt' + cmtId) {
             if (isS1) {
-            var oldWidth = getS1SpacerObject(cmtElement).width();
-            getS1SpacerObject(newComment).width(oldWidth);
-          }
+              var oldWidth = getS1SpacerObject(cmtElement).width();
+              getS1SpacerObject(newComment).width(oldWidth);
+            }
             cmtElement.html($(newComment).html())
                 .trigger( "updatedcontent.comment" );
+            $(".cmt_show_hide_default", cmtElement).show();
             LJ[cmtId].full = true;
             if (! isS1) {
               setFull(cmtElement, true);
@@ -91,9 +100,67 @@
     // if we didn't update any comments, something must have gone wrong
     if (updateCount == 0) {
       showExpanderError($.threadexpander.config.text.error_nomatches);
+    } else if (unhide) {
+      this.unhideComments(LJ, talkid, isS1);
     }
   }
 
+  // returns the comment elements for the given talkids.
+  function getReplyElements(replies) {
+    var returnValue = [];
+    for (var cmtIdCnt = 0; cmtIdCnt < replies.length; cmtIdCnt++) {
+      var cmtId = replies[cmtIdCnt];
+      if (/^\d*$/.test(cmtId)) {
+        var cmtElement = $("#cmt" + cmtId);
+        returnValue.push(cmtElement);
+      }
+    }
+    return returnValue;
+  }
+
+  // hides all the comments under this comment.
+  $.fn.hideComments = function(LJ, talkid, isS1) {
+    var replies = getReplies(LJ, talkid, false);
+    var replyElements = getReplyElements(replies);
+    for (var i = 0; i < replyElements.length; i++) {
+      if (isS1) {
+        replyElements[i].hide();
+      } else {
+        replyElements[i].slideUp("fast");
+      }
+    }
+
+    $("#cmt" + talkid + "_hide").hide();
+    $("#cmt" + talkid + "_unhide").show();
+  }
+
+  // shows all the comments under this comment.
+  $.fn.unhideComments = function(LJ, talkid, isS1) {
+    var replies = getReplies(LJ, talkid, false);
+    var replyElements = getReplyElements(replies);
+    for (var i = 0; i < replyElements.length; i++) {
+      // we're revealing the entire tree, so the subcomments should
+      // all show the hide option, not the unhide option.
+      $(".cmt_hide", replyElements[i]).show();
+      $(".cmt_unhide", replyElements[i]).hide();
+      if (isS1) {
+        replyElements[i].show();
+      } else {
+        replyElements[i].slideDown("fast");
+      }
+    }
+
+    // and this comment itself should show hide.
+    $("#cmt" + talkid + "_hide").show();
+    $("#cmt" + talkid + "_unhide").hide();
+  }
+
+  // reveal all hide links on document ready, so we don't have them if
+  // we don't have javascript enabled.
+  $(document).ready(function() {
+      $(".cmt_show_hide_default").show();
+    });
+
   $.threadexpander = {
     config: {
       text: {
@@ -102,10 +169,20 @@
       }
     }
   };
+
 })(jQuery);
 
+// globals for backwards compatibility
 Expander = {
-  make: function(element, url, dtid, isS1) {
-    $(element).expandComments(LJ_cmtinfo, url, dtid, isS1);
+  make: function(element, url, dtid, isS1, unhide) {
+    $(element).expandComments(LJ_cmtinfo, url, dtid, isS1, unhide);
+  },
+
+  hideComments: function(element, dtid, isS1) {
+    $(element).hideComments(LJ_cmtinfo, dtid, isS1);
+  },
+
+  unhideComments: function(element, dtid, isS1) {
+    $(element).unhideComments(LJ_cmtinfo, dtid, isS1);
   }
 };
diff -r fecd2b3c1a0b -r 2259a0756378 htdocs/talkread.bml
--- a/htdocs/talkread.bml	Fri Sep 09 14:51:13 2011 +0800
+++ b/htdocs/talkread.bml	Fri Sep 09 16:57:23 2011 +0800
@@ -70,6 +70,8 @@
                link            talk.commentpermlink
                deleted         .subjectdeleted
                nosubject       .nosubject
+               hide            talk.hide
+               unhide          talk.unhide
                maxcomments     .maxcomments
                );
     foreach (keys %T) { $T{$_} = $ML{$T{$_}}; }
@@ -343,11 +345,13 @@
 
     my $view_arg = $GET{'view'} || "";
     my $flat_mode = ($view_arg =~ /\bflat\b/);
+    my $top_only = ($view_arg =~ /\btop-only\b/);
     my $view_num = ($view_arg =~ /(\d+)/) ? $1 : undef;
 
     my %user;
     my $opts = {
         'flat' => $flat_mode,
+        'top-only' => $top_only,
         'thread' => $thread,
         'page' => $GET{'page'},
         'view' => $view_num,
@@ -416,17 +420,64 @@
 
         my $htmlid = LJ::Talk::comment_htmlid( $dtid );
 
+        my $hidestyle = $post->{'hidden_child'} ? " style=\"display: none;\"" : "";
         if ($post->{'state'} eq "D") {
-            $ret .= "<p><a name='$htmlid'></a><table summary='' class='delcomment'><tr>";
+            $ret .= "<div id='$htmlid'><p><a name='$htmlid'></a><table summary='' class='delcomment'$hidestyle><tr>";
             $ret .= "<td class='spacer'><img src='$LJ::IMGPREFIX/dot.gif' alt='' height='1' width='" . ($opts->{'depth'} * 25) . "'></td>";
-            $ret .= "<td>$ML{'.deletedpost'}</td></tr></table>\n";
+            $ret .= "<td>$ML{'.deletedpost'}</td>";
+            if ($post->{'hide_children'} && $post->{'children'} && @{$post->{'children'}}) {
+                if ( grep {! $_->{_loaded} and !($_->{state} eq "D")} @{$post->{'children'}} ) {
+                    my $url = LJ::Talk::talkargs( $talkurl, "thread=$dtid", $style_args ) . LJ::Talk::comment_anchor( $dtid );
+                    $ret .= "<td>";
+                    # if we're in top-only mode, then we display the
+                    # expand link as the unhide ('show x comments')
+                    # message
+                    if ( LJ::BetaFeatures->user_in_beta( $remote => "journaljquery" ) ) {
+                        $ret .= qq[(<a href='$url' onClick="Expander.make(this,'$url','$dtid',true,true);return false;">];
+                        $ret .= BML::ml( 'talk.unhide', { num => $post->{'showable_children'} } );
+                        $ret .= qq[</a>)</span>];
+                    } else {
+                        # unhide only works with jquery; if the user
+                        # isn't in the jquery beta, drop back down to
+                        # the no-javascript option
+                        $ret .= qq[(<a href='$url'>];
+                        $ret .= BML::ml( 'talk.unhide', { num => $post->{'showable_children'} } );
+                        $ret .= qq[</a>)</span>];
+                    }
+                    $ret .= "</td>";
+                }
+            }
+            $ret .= "</tr></table></div>\n";
         } elsif ($post->{'state'} eq "S" && !$post->{'_loaded'} && !$post->{'_show'}) {
-            $ret .= "<p><a name='$htmlid'></a><table summary='' class='screenedcomment'><tr>";
+            $ret .= "<p><a name='$htmlid'></a><table summary='' class='screenedcomment'$hidestyle><tr>";
             $ret .= "<td class='spacer'><img src='$LJ::IMGPREFIX/dot.gif' alt='' height='1' width='" . ($opts->{'depth'} * 25) . "'></td>";
             my $screenedtext = $ML{'.screenedpost'};
-            $ret .= "<td>$screenedtext</td></tr></table>\n";
+            $ret .= "<td>$screenedtext</td>";
+            if ($post->{'hide_children'} && $post->{'children'} && @{$post->{'children'}}) {
+                if ( grep {! $_->{_loaded} and !($_->{state} eq "D")} @{$post->{'children'}} ) {
+                    my $url = LJ::Talk::talkargs( $talkurl, "thread=$dtid", $style_args ) . LJ::Talk::comment_anchor( $dtid );
+                    $ret .= "<td>";
+                    # if we're in top-only mode, then we display the
+                    # expand link as the unhide ('show x comments')
+                    # message
+                    if ( LJ::BetaFeatures->user_in_beta( $remote => "journaljquery" ) ) {
+                        $ret .= qq[(<a href='$url' onClick="Expander.make(this,'$url','$dtid',true,true);return false;">];
+                        $ret .= BML::ml( 'talk.unhide', { num => $post->{'showable_children'} } );
+                        $ret .= qq[</a>)</span>];
+                    } else {
+                        # unhide only works with jquery; if the user
+                        # isn't in the jquery beta, drop back down to
+                        # the no-javascript option
+                        $ret .= qq[(<a href='$url'>];
+                        $ret .= BML::ml( 'talk.unhide', { num => $post->{'showable_children'} } );
+                        $ret .= qq[</a>)</span>];
+                    }
+                    $ret .= "</td>";
+                }
+            }
+            $ret .= "</tr></table>\n";
         } elsif ($pu && $pu->is_suspended && !$viewsome) {
-            $ret .= "<p><a name='$htmlid'></a><table summary='' class='suspendedcomment'><tr>";
+            $ret .= "<p><a name='$htmlid'></a><table summary='' class='suspendedcomment'$hidestyle><tr>";
             $ret .= "<td class='spacer'><img src='$LJ::IMGPREFIX/dot.gif' alt='' height='1' width='" . ($opts->{'depth'} * 25) . "'></td>";
             $ret .= "<td>$ML{'.replysuspended'}";
             if (LJ::Talk::can_delete($remote, $u, $up, $userpost)) {
@@ -603,8 +654,38 @@
                     $ret .= "(<a href='$url'>$T{'thread'}</a>)&nbsp; ";
 
                     if ((grep {! $_->{_loaded} and !($_->{state} eq "D")} @{$post->{'children'}}) && $show_thread_expander) {
-                       $ret .= qq[(<a href='$url' onClick="Expander.make(this,'$url','$dtid',true);return false;">$T{'expand'}</a>)];
-                   }
+                        if ( $post->{'hide_children'} ) {
+                            # if we're in top-only mode, then we display the
+                            # expand link as the unhide ('show x comments')
+                            # message
+                            if ( LJ::BetaFeatures->user_in_beta( $remote => "journaljquery" ) ) {
+                                $ret .= qq[(<a href='$url' onClick="Expander.make(this,'$url','$dtid',true,true);return false;">];
+                                $ret .= BML::ml( 'talk.unhide', { num => $post->{'showable_children'} } );
+                                $ret .= qq[</a>)</span>];
+                            } else {
+                                # unhide only works with jquery; if the user
+                                # isn't in the jquery beta, drop back down to
+                                # the no-javascript option
+                                $ret .= qq[(<a href='$url'>];
+                                $ret .= BML::ml( 'talk.unhide', { num => $post->{'showable_children'} } );
+                                $ret .= qq[</a>)</span>];
+                            }
+                        }
+                    } else {
+                        $ret .= qq[(<a href='$url' onClick="Expander.make(this,'$url','$dtid',true);return false;">$T{'expand'}</a>)];
+                    }
+                    if ( $show_thread_expander ) {
+                        if ( LJ::BetaFeatures->user_in_beta( $remote => "journaljquery" ) ) {
+                            my $hideclass = $post->{'hide_children'} ? "" : " cmt_show_hide_default";
+
+                            $ret .= qq[ <span id="cmt${dtid}_hide" class="cmt_hide$hideclass" style="display:none;">(<a href='#' onClick="Expander.hideComments(this,'$dtid',true);return false;">];
+                            $ret .= BML::ml( 'talk.hide', { num => $post->{'showable_children'} } );
+                            $ret .= qq[</a>)</span>];
+                            $ret .= qq[ <span id="cmt${dtid}_unhide" style="display: none;" class="cmt_unhide">(<a href='#' onClick="Expander.unhideComments(this,'$dtid',true);return false;">];
+                            $ret .= BML::ml( 'talk.unhide', { num => $post->{'showable_children'} } );
+                            $ret .= qq[</a>)</span>];
+                        }
+                    }
                 }
 
                 $ret .= "</font></p><br />";
@@ -616,7 +697,7 @@
                 # link to message
 
                 my $url = LJ::Talk::talkargs( $talkurl, "thread=$dtid", $style_args ) . LJ::Talk::comment_anchor( $dtid );
-                $ret .= "<div id='$htmlid'><table summary=''><tbody><tr>";
+                $ret .= "<div id='$htmlid'$hidestyle><table summary=''><tbody><tr>";
                 $ret .= "<td class='spacer'><img src='$LJ::IMGPREFIX/dot.gif' alt='' height='1' width='" . ($opts->{'depth'} * 25) . "'></td>";
                 $ret .= "<td  class='cmtpartial'>";
                 if ($post->{'state'} eq 'F') {
@@ -684,6 +765,18 @@
             }
         }
 
+        $ret .= "<br>\n";
+
+        # comment nav
+        my $view_mode = $flat_mode ? "flat" : $top_only ? "top-only" : "threaded";
+
+        if ( $view_mode eq "threaded" ) {
+            $ret .= "(<a href = '" . BML::self_link( { view => 'flat'  } ) . "#comments'>" . BML::ml('.commentnav.flat') . "</a>) (<a href = '" . BML::self_link( { view => 'top-only'  } ) . "#comments'>" . BML::ml('.commentnav.toponly') . "</a>)";
+        } elsif ( $view_mode eq "flat" ) {
+            $ret .= "(<a href = '" . BML::self_link( { view => ''  } ) . "#comments'>" . BML::ml('.commentnav.threaded') . "</a>) (<a href = '" . BML::self_link( { view => 'top-only'  } ) . "#comments'>" . BML::ml('.commentnav.toponly') . "</a>)";
+        } elsif ( $view_mode eq "top-only" ) {
+            $ret .= "(<a href = '" . BML::self_link( { view => ''  } ) . "#comments'>" . BML::ml('.commentnav.threaded') . "</a>) (<a href = '" . BML::self_link( { view => 'flat'  } ) . "#comments'>" . BML::ml('.commentnav.flat') . "</a>)";
+        }
         $ret .= "</b></p>";
 
         $ret .= "<div align='center'>" . LJ::make_qr_target('top') . "</div>" if $remote;
diff -r fecd2b3c1a0b -r 2259a0756378 htdocs/talkread.bml.text
--- a/htdocs/talkread.bml.text	Fri Sep 09 14:51:13 2011 +0800
+++ b/htdocs/talkread.bml.text	Fri Sep 09 16:57:23 2011 +0800
@@ -7,6 +7,12 @@
 
 .comment.screened.subject=(screened) 
 
+.commentnav.flat=Flat
+
+.commentnav.threaded=Threaded
+
+.commentnav.toponly=Top-level comments only
+
 .confirm.action=Are you sure you want to delete the selected comments?
 
 .deletedpost=<b>(Deleted post)</b>
--------------------------------------------------------------------------------
turlough: Reepicheep on Aslan's Table proposing a toast, art by Pauline Baynes from 'Voyage of the Dawn Treader' ((narnia) raise your glass high)

[personal profile] turlough 2011-09-09 03:44 pm (UTC)(link)
Neat!
ninetydegrees: Art: self-portrait (Default)

[personal profile] ninetydegrees 2011-09-10 12:09 pm (UTC)(link)
Three bugs in one! Allen is such a badass dev! *bg*