Janine (janinedog) wrote in changelog,
Janine
janinedog
changelog

[livejournal] r12954: Bug ID: 60422

Committer: janine
Bug ID: 60422

Comment editing for paid users. This is almost but not quite finished. Here's how it works:

* Only paid users can edit comments.
* Only the comment poster can edit a comment.
* A comment cannot be edited if it has been replied to or frozen.
* Editing a comment does not change the screening status of either the comment itself or its parent.
* When a comment is edited, a small note is put at the bottom of the comment text with a timestamp of when it was last edited.
* When a comment is edited from an IP address different from the original IP address, and IP logging is on, both are shown (most recent and original IP).
* There is no way to view past versions of a comment. Only the most recent version can be viewed.
* Anyone who would normally receive a comment notification when a new comment is posted to a thread will receive a notification when a comment has been edited (no separate subscription).

Still need to do:

* The timestamp should be based on database time, not webserver time.
* Make it so the subject and body of a comment can be edited (waiting for the APIs for this).

U   trunk/bin/upgrading/en.dat
U   trunk/bin/upgrading/proplists.dat
U   trunk/bin/upgrading/s2layers/core1.s2
U   trunk/bin/upgrading/s2layers/haven/layout.s2
U   trunk/cgi-bin/Apache/LiveJournal.pm
U   trunk/cgi-bin/LJ/Comment.pm
U   trunk/cgi-bin/LJ/Event/JournalNewComment.pm
U   trunk/cgi-bin/LJ/S2/EntryPage.pm
U   trunk/cgi-bin/LJ/S2/ReplyPage.pm
U   trunk/cgi-bin/LJ/S2.pm
U   trunk/cgi-bin/LJ/User.pm
U   trunk/cgi-bin/imageconf.pl
U   trunk/cgi-bin/talklib.pl
U   trunk/htdocs/js/talkpost.js
U   trunk/htdocs/stc/esn.css
U   trunk/htdocs/stc/lj_base.css
U   trunk/htdocs/talkpost.bml
U   trunk/htdocs/talkpost.bml.text
U   trunk/htdocs/talkpost_do.bml
U   trunk/htdocs/talkread.bml
U   trunk/htdocs/talkread.bml.text
Modified: trunk/bin/upgrading/en.dat
===================================================================
--- trunk/bin/upgrading/en.dat	2007-11-01 17:59:37 UTC (rev 12953)
+++ trunk/bin/upgrading/en.dat	2007-11-01 21:56:24 UTC (rev 12954)
@@ -1235,6 +1235,8 @@
 
 img.btn_up=Up
 
+img.editcomment=Edit
+
 img.editentry=Edit Entry
 
 img.edittags=Edit Tags
@@ -1662,6 +1664,18 @@
 
 talk.error.bogusargs=Bogus arguments
 
+talk.error.cantedit=You cannot edit comments.
+
+talk.error.cantedit.haschildren=You cannot edit this comment because someone has replied to it.
+
+talk.error.cantedit.isdeleted=You cannot edit this comment because it has been deleted.
+
+talk.error.cantedit.isfrozen=You cannot edit this comment because it has been frozen.
+
+talk.error.cantedit.notvisible=You cannot edit this comment because it is not visible to you.
+
+talk.error.cantedit.notyours=You cannot edit this comment because you did not post it.
+
 talk.error.comm_deleted=This comment has been deleted.
 
 talk.error.deleted=This journal is deleted.

Modified: trunk/bin/upgrading/proplists.dat
===================================================================
--- trunk/bin/upgrading/proplists.dat	2007-11-01 17:59:37 UTC (rev 12953)
+++ trunk/bin/upgrading/proplists.dat	2007-11-01 21:56:24 UTC (rev 12954)
@@ -1086,6 +1086,11 @@
   multihomed: 0
   prettyname: ZIP code
 
+talkproplist.edit_time:
+  datatype: num
+  des: Unix time of the last edit.  undef if never edited.
+  prettyname: Edit Time
+
 talkproplist.deleted_poster:
   datatype: char
   des: If the comment poster's account is deleted, this field gets added to all of their posts, so the UI can show something besides 'anonymous' when posterid gets set to 0

Modified: trunk/bin/upgrading/s2layers/core1.s2
===================================================================
--- trunk/bin/upgrading/s2layers/core1.s2	2007-11-01 17:59:37 UTC (rev 12953)
+++ trunk/bin/upgrading/s2layers/core1.s2	2007-11-01 21:56:24 UTC (rev 12954)
@@ -327,7 +327,7 @@
     function builtin get_tags_text () : string "Returns a string containing a div of class 'ljtags' with the tags for the entry.  If there are no tags on the entry, returns a blank string.  The string is formatted according to the 'text_tags' property.";
     function print_linkbar() "Print the link bar for this entry or comment.";
     function time_display () : string "Show the time of this post, with most useful information for user, and with tooltip for more.";
-    function time_display (string datefmt, string timefmt) : string "time_post, with customized date/time formats.";
+    function time_display (string datefmt, string timefmt) : string "time_display, with customized date/time formats.";
 }
 
 class Entry extends EntryLite
@@ -371,11 +371,16 @@
     var readonly bool deleted "True if comment has been deleted. Deleted comments still show up if they are the parent of a thread.";
     var readonly string anchor "Direct link to comment, via HTML name anchors";
     var readonly bool comment_posted "True if comment was just posted by the current user.";
+    var readonly bool edited "True if the comment has been edited.";
 
     var readonly DateTime time_remote "The local time the comment appeared, in the remote user's (reader's) timezone.  Or undef if no remote user, or remote user hasn't set their timezone.";
-    var readonly DateTime time_poster "The local time the comment appeared, in the commenter's timezone.  Or undef if no anonymous comment, or commenter's timezone is unknown.";
-    var readonly int seconds_since_entry "The number of elapsed seconds from the time of the journal entry until he comment was made.";
+    var readonly DateTime time_poster "The local time the comment appeared, in the commenter's timezone.  Or undef if anonymous comment, or commenter's timezone is unknown.";
+    var readonly int seconds_since_entry "The number of elapsed seconds from the time of the journal entry until the comment was initially posted.";
 
+    var readonly DateTime edittime "The GMT time the comment was edited.  Or undef if the comment hasn't been edited.";
+    var readonly DateTime edittime_remote "The local time the comment was edited, in the remote user's (reader's) timezone.  Or undef if no remote user, or remote user hasn't set their timezone, or the comment hasn't been edited.";
+    var readonly DateTime edittime_poster "The local time the comment was edited, in the commenter's timezone.  Or undef if anonymous comment, or commenter's timezone is unknown, or the comment hasn't been edited.";
+
     function builtin print_multiform_check "Prints the select checkbox in CSS class 'ljcomsel' with DOM id 'ljcomsel_\$talkid' for a multi-action form started with [method[EntryPage.print_multiform_start()]].";
 
     function builtin print_reply_link(string{} opts) "Prints a link to reply to the comment. You may specify the link text in the 'linktext' option, and the link CSS class in 'class'. You may also specify the url of an image to use as a button in 'img_url'.";
@@ -384,6 +389,11 @@
 
     function builtin print_reply_container(string{} opts) "Prints the area in which the quickreply box will go. You may 'class' which will be the CSS class used by the container. If no container is available, quickreply will not work.";
 
+    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 edittime_display () : string "Show the time that this comment was edited, with most useful information for user.  Empty string if the comment hasn't been edited.";
+    function edittime_display (string datefmt, string timefmt) : string "edittime_display, with customized date/time formats.";
+
+    function print_edit_text () "Print the text that says when this comment was edited.";
 }
 
 ### Userinfo
@@ -1361,6 +1371,11 @@
 }
 set text_multiform_opt_untrack = "Untrack This";
 
+property string text_multiform_opt_edit {
+    des = "Text for the comment editing action";
+}
+set text_multiform_opt_edit = "Edit";
+
 property string text_comment_posted {
     des = "Text to display when a comment has just been posted by the user";
 }
@@ -1425,9 +1440,15 @@
     example = "IP Address:";
     maxlength = "20";
 }
+property string text_comment_edittime {
+    des = "Text of the 'Edited at' string at the bottom of edited comments";
+    example = "Edited at:";
+    maxlength = "20";
+}
 set text_comment_from = "From:";
 set text_comment_date = "Date:";
 set text_comment_ipaddr = "IP Address:";
+set text_comment_edittime = "Edited at";
 
 property string text_comment_reply {
     des = "Text to link to reply for comment";
@@ -2107,8 +2128,19 @@
     "</div>\n\n";
 }
 
+function Comment::print_edit_text() {
+    if ($this.edited) {
+        print "<br /><br /><span class='ljedittime'><em>$*text_comment_edittime " + $this->edittime_display() + "</em></span>";
+    }
+}
+
 function EntryLite::print_text() [fixed] {
     print $.text;
+
+    if ($this isa Comment) {
+        var Comment c = $this as Comment;
+        $c->print_edit_text();
+    }
 }
 
 function Entry::print_metadata() {
@@ -2223,7 +2255,12 @@
     return ehtml($ret);
 }
 
-function Comment::time_display (string datefmt, string timefmt) : string {
+# edittime argument is true if we want the get the edit time of the comment
+function Comment::time_display (string datefmt, string timefmt, bool edittime) : string {
+    var DateTime time = ($edittime ? $this.edittime : $this.time);
+    var DateTime time_remote = ($edittime ? $this.edittime_remote : $this.time_remote);
+    var DateTime time_poster = ($edittime ? $this.edittime_poster : $this.time_poster);
+
     if ($datefmt == "") {
         $datefmt = "iso";
     }
@@ -2232,34 +2269,38 @@
     }
 
     var string tooltip = "";
-    var string etime = secs_to_string($this.seconds_since_entry);
-    $tooltip = $etime + " after journal entry";
+    if ($edittime == false) {
+        var string etime = secs_to_string($this.seconds_since_entry);
+        $tooltip = $etime + " after journal entry";
+    }
 
     var string main;
 
     var string display_date;
     var string display_time;
 
-    if ($this.time_remote) {
-        $display_date = $this.time_remote->date_format($datefmt);
+    if ($time_remote) {
+        $display_date = $time_remote->date_format($datefmt);
         if ($timefmt == "none") { $display_date = $display_date + " (local)"; }
-        $display_time = $this.time_remote->time_format($timefmt) + " (local)";
+        $display_time = $time_remote->time_format($timefmt) + " (local)";
     } else {
-        $display_date = $this.time->date_format($datefmt);
+        $display_date = $time->date_format($datefmt);
         if ($timefmt == "none") { $display_date = $display_date + " (UTC)"; }
-        $display_time = $this.time->time_format($timefmt) + " (UTC)";
+        $display_time = $time->time_format($timefmt) + " (UTC)";
     }
 
-    if (defined $this.time_poster and defined $this.poster)
+    if (defined $time_poster and defined $this.poster)
     {
-        var string poster_date = $this.time_poster->date_format($datefmt);
-        $tooltip = $tooltip + ", ";
+        var string poster_date = $time_poster->date_format($datefmt);
+        if ($edittime == false) {
+            $tooltip = $tooltip + ", ";
+        }
 
         if ($poster_date == $display_date and $timefmt != "none") { $poster_date = ""; }
         else { $poster_date = $poster_date + " "; }
 
         if ($timefmt != "none") {
-            $tooltip = $tooltip + $poster_date + $this.time_poster->time_format($timefmt) + " (" + $this.poster.username + "'s time)";
+            $tooltip = $tooltip + $poster_date + $time_poster->time_format($timefmt) + " (" + $this.poster.username + "'s time)";
         } else {
             $tooltip = $tooltip + $poster_date + "(" + $this.poster.username + "'s time)";
         }
@@ -2272,11 +2313,23 @@
     return "<span title=\"" + ehtml($tooltip) + "\">" + ehtml($main) + "</span>";
 }
 
+function Comment::time_display (string datefmt, string timefmt) : string {
+    return $this->time_display($datefmt, $timefmt, false);
+}
+
 function EntryLite::time_display() : string {
     # Let the real function decide on some nice defaults
     return $this->time_display("", "");
 }
 
+function Comment::edittime_display() : string {
+    return $this->time_display("", "", true);
+}
+
+function Comment::edittime_display (string datefmt, string timefmt) : string {
+    return $this->time_display($datefmt, $timefmt, true);
+}
+
 ### Year view
 
 function YearPage::print_body {

Modified: trunk/bin/upgrading/s2layers/haven/layout.s2
===================================================================
--- trunk/bin/upgrading/s2layers/haven/layout.s2	2007-11-01 17:59:37 UTC (rev 12953)
+++ trunk/bin/upgrading/s2layers/haven/layout.s2	2007-11-01 21:56:24 UTC (rev 12954)
@@ -1491,7 +1491,9 @@
     println """<td style="width: 50%" align="right" valign="top"><strong>(<a href="$c.permalink_url">Link</a>)</strong></td></tr>""";
 
     println "</table></td></tr></table></div>";
-    println """<div style="margin-left: 5px">$c.text</div>""";
+    println """<div style="margin-left: 5px">""";
+    $c->print_text();
+    println "</div>";
     println """<div style="margin-top: 3px; font-size: smaller"> """;
     if ($c.frozen) {
         println """(Replies frozen) """;

Modified: trunk/cgi-bin/Apache/LiveJournal.pm
===================================================================
--- trunk/cgi-bin/Apache/LiveJournal.pm	2007-11-01 17:59:37 UTC (rev 12953)
+++ trunk/cgi-bin/Apache/LiveJournal.pm	2007-11-01 21:56:24 UTC (rev 12954)
@@ -559,7 +559,7 @@
                 or return 404;
 
             $ljentry = LJ::Entry->new($u, ditemid => $1);
-            if ($GET{'mode'} eq "reply" || $GET{'replyto'}) {
+            if ($GET{'mode'} eq "reply" || $GET{'replyto'} || $GET{'edit'}) {
                 $mode = "reply";
             } else {
                 $mode = "entry";
@@ -652,7 +652,7 @@
                                     undef, $u->{userid}, $key);
             if ($type eq "L") {
                 $ljentry = LJ::Entry->new($u, ditemid => $nodeid);
-                if ($GET{'mode'} eq "reply" || $GET{'replyto'}) {
+                if ($GET{'mode'} eq "reply" || $GET{'replyto'} || $GET{'edit'}) {
                     $mode = "reply";
                 } else {
                     $mode = "entry";

Modified: trunk/cgi-bin/LJ/Comment.pm
===================================================================
--- trunk/cgi-bin/LJ/Comment.pm	2007-11-01 17:59:37 UTC (rev 12953)
+++ trunk/cgi-bin/LJ/Comment.pm	2007-11-01 21:56:24 UTC (rev 12954)
@@ -278,6 +278,16 @@
         "?journal=$journal&id=$dtalkid";
 }
 
+sub edit_url {
+    my $self    = shift;
+
+    my $dtalkid = $self->dtalkid;
+    my $entry   = $self->entry;
+    my $url     = $entry->url;
+
+    return "$url?edit=$dtalkid";
+}
+
 # return img tag of userpic that the comment poster used
 sub poster_userpic {
     my $self = shift;
@@ -789,6 +799,11 @@
     return '' unless $self->entry->poster;
 
     my $poster = $self->poster ? $self->poster->user : "";
+
+    if ($remote && $remote->can_edit_comment($self)) {
+        $managebtns .= "<a href='" . $self->edit_url . "'>" . LJ::img("editcomment", "", { 'align' => 'absmiddle', 'hspace' => 2, 'vspace' => }) . "</a>";
+    }
+
     if (LJ::Talk::can_delete($remote, $self->journal, $self->entry->poster, $poster)) {
         $managebtns .= "<a href='$LJ::SITEROOT/delcomment.bml?${jargent}id=$dtalkid'>" . LJ::img("btn_del", "", { 'align' => 'absmiddle', 'hspace' => 2, 'vspace' => }) . "</a>";
     }
@@ -854,6 +869,7 @@
     my $parent  = $self->parent;
     my $entry   = $self->entry;
     my $posteru = $self->poster;
+    my $edited  = $self->is_edited;
 
     $Text::Wrap::columns = 76;
 
@@ -870,33 +886,49 @@
             my $parentu = $entry->journal;
 
             $who = $parentu->{name} . " (" . $parentu->{user} . ")";
-            $text .= "You left a comment in a post by $who.  ";
+            $text .= $edited ? "You edited a comment in a post by $who.  " : "You left a comment in a post by $who.  ";
             $text .= "The entry you replied to was:";
         } else {
-            $text .= "You left a comment in reply to another comment.  ";
+            $text .= $edited ? "You edited a comment in reply to another comment.  " : "You left a comment in reply to another comment.  ";
             $text .= "The comment you replied to was:";
         }
     } elsif (LJ::u_equals($targetu, $entry->journal)) {
         # ->parent returns undef/0 if parent is an entry.
         if (! $parent) {
-            $text .= "$who replied to your $LJ::SITENAMESHORT post in which you said:";
+            if ($edited) {
+                $text .= "$who edited their reply to your $LJ::SITENAMESHORT post in which you said:";
+            } else {
+                $text .= "$who replied to your $LJ::SITENAMESHORT post in which you said:";
+            }
         } else {
-            $text .= "$who replied to another comment somebody left in your $LJ::SITENAMESHORT post.  ";
+            if ($edited) {
+                $text .= "$who edited their reply to another comment somebody left in your $LJ::SITENAMESHORT post.  ";
+            } else {
+                $text .= "$who replied to another comment somebody left in your $LJ::SITENAMESHORT post.  ";
+            }
             $text .= "The comment they replied to was:";
         }
     } else {
         if ($parent) {
             my $pwho = $parent->poster ? $parent->poster->user : "somebody else";
-            $text .= "$who replied to a $LJ::SITENAMESHORT comment in which $pwho said:";
+            if ($edited) {
+                $text .= "$who edited a reply to a $LJ::SITENAMESHORT comment in which $pwho said:";
+            } else {
+                $text .= "$who replied to a $LJ::SITENAMESHORT comment in which $pwho said:";
+            }
         } else {
             my $pwho = $entry->poster->user;
-            $text .= "$who replied to a $LJ::SITENAMESHORT post in which $pwho said:";
+            if ($edited) {
+                $text .= "$who edited a reply to a $LJ::SITENAMESHORT post in which $pwho said:";
+            } else {
+                $text .= "$who replied to a $LJ::SITENAMESHORT post in which $pwho said:";
+            }
         }
     }
     $text .= "\n\n";
     $text .= indent($parent ? $parent->body_for_text_email($targetu)
                             : $entry->event_for_text_email($targetu), ">") . "\n\n";
-    $text .= (LJ::u_equals($targetu, $posteru) ? 'Your' : 'Their') . " reply was:\n\n";
+    $text .= (LJ::u_equals($targetu, $posteru) ? 'Your' : 'Their') . ($edited ? ' new' : '') . " reply was:\n\n";
     if (my $subj = $self->subject_for_text_email($targetu)) {
         $text .= Text::Wrap::wrap("  Subject: ", "", $subj) . "\n\n";
     }
@@ -930,6 +962,10 @@
         $opts .= "  - Delete the comment:\n";
         $opts .= "    " . $self->delete_url . "\n";
     }
+    if ($targetu && $targetu->can_edit_comment($self)) {
+        $opts .= "  - Edit the comment:\n";
+        $opts .= "    " . $self->edit_url . "\n";
+    }
 
     return Text::Wrap::wrap("", "", $text) . "\n" . $opts;
 }
@@ -950,6 +986,7 @@
     my $entry   = $self->entry;
     my $posteru = $self->poster;
     my $talkurl = $entry->url;
+    my $edited  = $self->is_edited;
 
     my $who = "Somebody";
     if ($posteru) {
@@ -993,24 +1030,35 @@
         if (! $parent) {
             $who = LJ::ehtml($parentu->{name}) .
                 " (<a href=\"$profile_url\">$parentu->{user}</a>)";
-            $intro = "You replied to <a href=\"$talkurl\">a $LJ::SITENAMESHORT post</a> in which $pwho said:";
+            if ($edited) {
+                $intro = "You edited a reply to <a href=\"$talkurl\">a $LJ::SITENAMESHORT post</a> in which $pwho said:";
+            } else {
+                $intro = "You replied to <a href=\"$talkurl\">a $LJ::SITENAMESHORT post</a> in which $pwho said:";
+            }
         } else {
-            $intro = "You replied to a comment $pwho left in ";
+            $intro = $edited ? "You edited a reply to a comment $pwho left in " : "You replied to a comment $pwho left in ";
             $intro .= "<a href=\"$talkurl\">a $LJ::SITENAMESHORT post</a>.  ";
             $intro .= "The comment you replied to was:";
         }
     } elsif (LJ::u_equals($targetu, $entry->journal)) {
         if (! $parent) {
-            $intro = "$who replied to <a href=\"$talkurl\">your $LJ::SITENAMESHORT post</a> in which $pwho said:";
+            if ($edited) {
+                $intro = "$who edited a reply to <a href=\"$talkurl\">your $LJ::SITENAMESHORT post</a> in which $pwho said:";
+            } else {
+                $intro = "$who replied to <a href=\"$talkurl\">your $LJ::SITENAMESHORT post</a> in which $pwho said:";
+            }
         } else {
-            $intro = "$who replied to another comment $pwho left in ";
+            $intro = $edited ? "$who edited a reply to another comment $pwho left in " : "$who replied to another comment $pwho left in ";
             $intro .= "<a href=\"$talkurl\">your $LJ::SITENAMESHORT post</a>.  ";
             $intro .= "The comment they replied to was:";
         }
     } else {
-        $intro = "$who replied to <a href=\"$talkurl\">a $LJ::SITENAMESHORT " .
-            ($parent ? "comment" : "post") . "</a> ";
-        $intro .= "in which $pwho said:";
+        if ($edited) {
+            $intro = "$who edited a reply to <a href=\"$talkurl\">a $LJ::SITENAMESHORT ";
+        } else {
+            $intro = "$who replied to <a href=\"$talkurl\">a $LJ::SITENAMESHORT ";
+        }
+        $intro .= ($parent ? "comment" : "post") . "</a> in which $pwho said:";
     }
 
     my $pichtml;
@@ -1042,7 +1090,7 @@
     $html .= blockquote($parent ? $parent->body_for_html_email($targetu)
                                 : $entry->event_for_html_email($targetu));
 
-    $html .= "\n\n" . (LJ::u_equals($targetu, $posteru) ? 'Your' : 'Their') . " reply was:\n\n";
+    $html .= "\n\n" . (LJ::u_equals($targetu, $posteru) ? 'Your' : 'Their') . ($edited ? ' new' : '') . " reply was:\n\n";
     my $pics = LJ::Talk::get_subjecticons();
     my $icon = LJ::Talk::show_image($pics, $self->prop('subjecticon'));
 
@@ -1076,7 +1124,10 @@
     if ($self->user_can_delete($targetu)) {
         $html .= "<li><a href=\"" . $self->delete_url . "\">Delete the comment</a></li>";
     }
-   $html .= "</ul></p>";
+    if ($targetu && $targetu->can_edit_comment($self)) {
+        $html .= "<li><a href=\"" . $self->edit_url . "\">Edit the comment</a></li>";
+    }
+    $html .= "</ul></p>";
 
     my $want_form = $self->is_active || $can_unscreen;  # this should probably be a preference, or maybe just always off.
     if ($want_form) {
@@ -1274,4 +1325,40 @@
     return $up->userpic;
 }
 
+sub poster_ip {
+    my $self = shift;
+
+    return "" unless LJ::is_web_context();
+
+    my $current_ip = $self->prop("poster_ip");
+
+    my $new_ip = BML::get_remote_ip();
+    my $forwarded = BML::get_client_header('X-Forwarded-For');
+    $new_ip = "$forwarded, via $new_ip" if $forwarded && $forwarded ne $new_ip;
+
+    return $new_ip if !$current_ip || $new_ip eq $current_ip;
+
+    if ($current_ip =~ /\(originally ([\w\.]+)\)/) {
+        return $new_ip if $new_ip eq $1;
+
+        $new_ip = "$new_ip (originally $1)";
+    } else {
+        $new_ip = "$new_ip (originally $current_ip)";
+    }
+
+    return $new_ip;
+}
+
+sub edit_time {
+    my $self = shift;
+
+    return $self->prop("edit_time");
+}
+
+sub is_edited {
+    my $self = shift;
+
+    return $self->edit_time ? 1 : 0;
+}
+
 1;

Modified: trunk/cgi-bin/LJ/Event/JournalNewComment.pm
===================================================================
--- trunk/cgi-bin/LJ/Event/JournalNewComment.pm	2007-11-01 17:59:37 UTC (rev 12953)
+++ trunk/cgi-bin/LJ/Event/JournalNewComment.pm	2007-11-01 21:56:24 UTC (rev 12954)
@@ -56,6 +56,8 @@
 sub as_email_subject {
     my ($self, $u) = @_;
 
+    my $edited = $self->comment->is_edited;
+
     my $filename = $self->template_file_for(section => 'subject', lang => $u->prop('browselang'));
     if ($filename) {
         # Load template file into template processor
@@ -67,11 +69,19 @@
     if ($self->comment->subject_orig) {
         return LJ::strip_html($self->comment->subject_orig);
     } elsif ($self->comment->parent) {
-        return LJ::u_equals($self->comment->parent->poster, $u) ? 'Reply to your comment...' : 'Reply to a comment...';
+        if ($edited) {
+            return LJ::u_equals($self->comment->parent->poster, $u) ? 'Reply to your comment was edited...' : 'Reply to a comment was edited...';
+        } else {
+            return LJ::u_equals($self->comment->parent->poster, $u) ? 'Reply to your comment...' : 'Reply to a comment...';
+        }
     } elsif (LJ::u_equals($self->comment->poster, $u)) {
-        return 'Comment you posted....';
+        return $edited ? 'Comment you edited...' : 'Comment you posted....';
     } else {
-        return LJ::u_equals($self->comment->entry->poster, $u) ? 'Reply to your entry...' : 'Reply to an entry...';
+        if ($edited) {
+            return LJ::u_equals($self->comment->entry->poster, $u) ? 'Reply to your entry was edited...' : 'Reply to an entry was edited...';
+        } else {
+            return LJ::u_equals($self->comment->entry->poster, $u) ? 'Reply to your entry...' : 'Reply to an entry...';
+        }
     }
 }
 
@@ -114,20 +124,33 @@
         unless $comment->poster;
 
     my $poster = $comment->poster->display_username;
-    return "$poster has posted a new comment in $journal at " . $comment->url;
+    if ($self->comment->is_edited) {
+        return "$poster has edited a comment in $journal at " . $comment->url;
+    } else {
+        return "$poster has posted a new comment in $journal at " . $comment->url;
+    }
 }
 
 sub as_sms {
     my ($self, $u) = @_;
 
     my $user = $self->comment->poster ? $self->comment->poster->display_username : '(Anonymous user)';
+    my $edited = $self->comment->is_edited;
 
     my $msg;
 
     if ($self->comment->parent) {
-        $msg = LJ::u_equals($self->comment->parent->poster, $u) ? "$user replied to your comment: " : "$user replied to a comment: ";
+        if ($edited) {
+            $msg = LJ::u_equals($self->comment->parent->poster, $u) ? "$user edited a reply to your comment: " : "$user edited a reply to a comment: ";
+        } else {
+            $msg = LJ::u_equals($self->comment->parent->poster, $u) ? "$user replied to your comment: " : "$user replied to a comment: ";
+        }
     } else {
-        $msg = LJ::u_equals($self->comment->entry->poster, $u) ? "$user replied to your post: " : "$user replied to a post: ";
+        if ($edited) {
+            $msg = LJ::u_equals($self->comment->entry->poster, $u) ? "$user edited a reply to your post: " : "$user edited a reply to a post: ";
+        } else {
+            $msg = LJ::u_equals($self->comment->entry->poster, $u) ? "$user replied to your post: " : "$user replied to a post: ";
+        }
     }
 
     return $msg . $self->comment->body_text;
@@ -208,9 +231,11 @@
     my $subject = $comment->subject_text ? ' "' . $comment->subject_text . '"' : '';
 
     my $poster = $comment->poster ? "by $pu" : '';
-    my $ret = "New <a href=\"$url\">comment</a> $subject $poster on $in_text in $ju.";
-
-    return $ret;
+    if ($comment->is_edited) {
+        return "Edited <a href=\"$url\">comment</a> $subject $poster on $in_text in $ju.";
+    } else {
+        return "New <a href=\"$url\">comment</a> $subject $poster on $in_text in $ju.";
+    }
 }
 
 sub as_html_actions {
@@ -338,11 +363,13 @@
     return $self->arg1;
 }
 
-# when was this comment left?
+# when was this comment posted or edited?
 sub eventtime_unix {
     my $self = shift;
     my $cmt = $self->comment;
-    return $cmt ? $cmt->unixtime : $self->SUPER::eventtime_unix;
+
+    my $time = $cmt->is_edited ? $cmt->edit_time : $cmt->unixtime;
+    return $cmt ? $time : $self->SUPER::eventtime_unix;
 }
 
 sub comment {

Modified: trunk/cgi-bin/LJ/S2/EntryPage.pm
===================================================================
--- trunk/cgi-bin/LJ/S2/EntryPage.pm	2007-11-01 17:59:37 UTC (rev 12953)
+++ trunk/cgi-bin/LJ/S2/EntryPage.pm	2007-11-01 21:56:24 UTC (rev 12954)
@@ -125,6 +125,18 @@
             my $seconds_since_entry = $com->{'datepost_unix'} - $entry->logtime_unix;
             my $datetime_poster = DateTime_tz($com->{'datepost_unix'}, $pu);
 
+            my ($edited, $edittime, $edittime_remote, $edittime_poster);
+            if ($com->{_loaded}) {
+                my $comment = LJ::Comment->new($u, jtalkid => $com->{talkid});
+
+                $edited = $comment->is_edited;
+                if ($edited) {
+                    $edittime = DateTime_unix($comment->edit_time);
+                    $edittime_remote = $tz_remote ? DateTime_tz($comment->edit_time, $tz_remote) : undef;
+                    $edittime_poster = DateTime_tz($comment->edit_time, $pu);
+                }
+            }
+
             my $subject_icon = undef;
             if (my $si = $com->{'props'}->{'subjecticon'}) {
                 my $pic = $pics->{$si};
@@ -189,6 +201,7 @@
                 'userpic' => $comment_userpic,
                 'time' => $datetime,
                 'system_time' => $datetime, # same as regular time for comments
+                'edittime' => $edittime,
                 'tags' => [],
                 'full' => $com->{'_loaded'} ? 1 : 0,
                 'depth' => $depth,
@@ -200,9 +213,12 @@
                 'anchor' => "t$dtalkid",
                 'dom_id' => "ljcmt$dtalkid",
                 'comment_posted' => $commentposted,
+                'edited' => $edited ? 1 : 0,
                 'time_remote' => $datetime_remote,
                 'time_poster' => $datetime_poster,
                 'seconds_since_entry' => $seconds_since_entry,
+                'edittime_remote' => $edittime_remote,
+                'edittime_poster' => $edittime_poster,
             };
 
             # don't show info from suspended users
@@ -223,6 +239,7 @@
             push @$link_keyseq, "watch_thread" unless $LJ::DISABLED{'esn'};
             push @$link_keyseq, "unwatch_thread" unless $LJ::DISABLED{'esn'};
             push @$link_keyseq, "watching_parent" unless $LJ::DISABLED{'esn'};
+            unshift @$link_keyseq, "edit_comment" if LJ::is_enabled("edit_comments");
 
             if (@{$com->{'children'}}) {
                 $s2com->{'thread_url'} = LJ::Talk::talkargs($permalink, "thread=$dtalkid", $stylemine) . "#t$dtalkid";

Modified: trunk/cgi-bin/LJ/S2/ReplyPage.pm
===================================================================
--- trunk/cgi-bin/LJ/S2/ReplyPage.pm	2007-11-01 17:59:37 UTC (rev 12953)
+++ trunk/cgi-bin/LJ/S2/ReplyPage.pm	2007-11-01 21:56:24 UTC (rev 12954)
@@ -35,10 +35,35 @@
 
     # setup the replying item
     my $replyto = $s2entry;
+    my $editid = $get->{edit} ? $get->{edit} : 0;
+    my $replytoid = $get->{replyto} ? $get->{replyto} : 0;
     my $parpost;
-    if ($get->{'replyto'}) {
-        my $re_talkid = int($get->{'replyto'} >> 8);
-        my $re_anum = $get->{'replyto'} % 256;
+
+    my $comment;
+    my %comment_values;
+    if ($editid) {
+        my $errref;
+        $comment = LJ::Comment->new($u, dtalkid => $editid);
+        unless ($remote && $remote->can_edit_comment($comment, \$errref)) {
+            $opts->{status} = "403 Forbidden";
+            return "<p>$errref</p>";
+        }
+
+        $parpost = $comment->parent;
+        $replytoid = $parpost ? $comment->parent->dtalkid : 0;
+
+        $comment_values{edit} = $editid;
+        $comment_values{replyto} = $replytoid;
+        $comment_values{subject} = $comment->subject_orig;
+        $comment_values{body} = $comment->body_orig;
+        $comment_values{subjecticon} = $comment->prop('subjecticon');
+        $comment_values{prop_picture_keyword} = $comment->prop('picture_keyword');
+        $comment_values{prop_opt_preformatted} = $comment->prop('opt_preformatted');
+    }
+
+    if ($replytoid) {
+        my $re_talkid = int($replytoid >> 8);
+        my $re_anum = $replytoid % 256;
         unless ($re_anum == $entry->anum) {
             $opts->{'handler_return'} = 404;
             return;
@@ -56,7 +81,7 @@
             # FIXME: This is a hack. See below...
 
             $opts->{status} = "404 Not Found";
-            return "<p>This comment has been deleted; you cannot reply to it.";
+            return "<p>This comment has been deleted; you cannot reply to it.</p>";
         }
         if ($parpost->{'state'} eq 'S' && !LJ::Talk::can_unscreen($remote, $u, $s2entry->{'poster'}->{'username'}, undef)) {
             $opts->{'handler_return'} = 403;
@@ -127,6 +152,7 @@
         '_ditemid' => $ditemid,
         '_parpost' => $parpost,
         '_stylemine' => $get->{'style'} eq "mine",
+        '_values' => \%comment_values,
     };
 
     return $p;
@@ -144,6 +170,7 @@
 
     my $r = Apache->request;
     my $post_vars = { $r->content };
+    $post_vars = $form->{_values} unless keys %$post_vars;
 
     $S2::pout->(LJ::Talk::talkform({ 'remote'    => $remote,
                                      'journalu'  => $u,

Modified: trunk/cgi-bin/LJ/S2.pm
===================================================================
--- trunk/cgi-bin/LJ/S2.pm	2007-11-01 17:59:37 UTC (rev 12953)
+++ trunk/cgi-bin/LJ/S2.pm	2007-11-01 21:56:24 UTC (rev 12954)
@@ -2894,6 +2894,8 @@
     my $com_user = $this->{'poster'} ? $this->{'poster'}->{'username'} : undef;
     my $remote = LJ::get_remote();
     my $null_link = { '_type' => 'Link', '_isnull' => 1 };
+    my $dtalkid = $this->{talkid};
+    my $comment = LJ::Comment->new($u, dtalkid => $dtalkid);
 
     if ($key eq "delete_comment") {
         return $null_link unless LJ::Talk::can_delete($remote, $u, $post_user, $com_user);
@@ -2933,9 +2935,6 @@
         return $null_link if $LJ::DISABLED{'esn'};
         return $null_link unless $remote && $remote->can_use_esn;
 
-        my $dtalkid = $this->{talkid};
-        my $comment = LJ::Comment->new($u, dtalkid => $dtalkid);
-
         if ($key eq "unwatch_thread") {
             return $null_link unless $remote->has_subscription(journal => $u, event => "JournalNewComment", arg2 => $comment->jtalkid);
 
@@ -3017,6 +3016,12 @@
         }
         return $null_link;
     }
+    if ($key eq "edit_comment") {
+        return $null_link unless $remote && $remote->can_edit_comment($comment);
+        return LJ::S2::Link($comment->edit_url,
+                            $ctx->[S2::PROPS]->{"text_multiform_opt_edit"},
+                            LJ::S2::Image("$LJ::IMGPREFIX/btn_edit.gif", 22, 20));
+    }
 }
 
 sub Comment__print_multiform_check

Modified: trunk/cgi-bin/LJ/User.pm
===================================================================
--- trunk/cgi-bin/LJ/User.pm	2007-11-01 17:59:37 UTC (rev 12953)
+++ trunk/cgi-bin/LJ/User.pm	2007-11-01 21:56:24 UTC (rev 12954)
@@ -4324,7 +4324,54 @@
     return LJ::Tags::get_usertags($u);
 }
 
+sub can_edit_comment {
+    my $u = shift;
+    my $comment = shift;
+    my $errref = shift;
 
+    return 0 unless $u && $comment;
+
+    # comments must be enabled and the user can't be underage and must have the cap
+    $$errref = LJ::Lang::ml('talk.error.cantedit');
+    return 0 unless LJ::is_enabled("edit_comments");
+    return 0 if $u->underage;
+    return 0 unless $u->get_cap("edit_comments");
+
+    # user must be the poster of the comment
+    unless ($u->equals($comment->poster)) {
+        $$errref = LJ::Lang::ml('talk.error.cantedit.notyours');
+        return 0;
+    }
+
+    # comment cannot have any replies
+    if ($comment->has_children) {
+        $$errref = LJ::Lang::ml('talk.error.cantedit.haschildren');
+        return 0;
+    }
+
+    # comment cannot be deleted
+    if ($comment->is_deleted) {
+        $$errref = LJ::Lang::ml('talk.error.cantedit.isdeleted');
+        return 0;
+    }
+
+    # comment cannot be frozen
+    if ($comment->is_frozen) {
+        $$errref = LJ::Lang::ml('talk.error.cantedit.isfrozen');
+        return 0;
+    }
+
+    # comment must be visible to the user
+    unless ($comment->visible_to($u)) {
+        $$errref = LJ::Lang::ml('talk.error.cantedit.notvisible');
+        return 0;
+    }
+
+    $$errref = "";
+    return 1;
+}
+
+
 package LJ;
 
 use Carp;

Modified: trunk/cgi-bin/imageconf.pl
===================================================================
--- trunk/cgi-bin/imageconf.pl	2007-11-01 17:59:37 UTC (rev 12953)
+++ trunk/cgi-bin/imageconf.pl	2007-11-01 21:56:24 UTC (rev 12954)
@@ -145,6 +145,13 @@
     'alt' => 'img.flag_btn',
 };
 
+$img{'editcomment'} = {
+    'src' => '/btn_edit.gif',
+    'width' => 22,
+    'height' => 20,
+    'alt' => 'img.editcomment',
+};
+
 # load the site-local version, if it's around.
 if (-e "$LJ::HOME/cgi-bin/imageconf-local.pl") {
     require "$LJ::HOME/cgi-bin/imageconf-local.pl";

Modified: trunk/cgi-bin/talklib.pl
===================================================================
--- trunk/cgi-bin/talklib.pl	2007-11-01 17:59:37 UTC (rev 12953)
+++ trunk/cgi-bin/talklib.pl	2007-11-01 21:56:24 UTC (rev 12954)
@@ -1190,6 +1190,13 @@
     my ($remote, $journalu, $parpost, $form) =
         map { $opts->{$_} } qw(remote journalu parpost form);
 
+    my $editid = $form->{edit} ? $form->{edit} : 0;
+    my $comment;
+    if ($editid) {
+        $comment = LJ::Comment->new($journalu, dtalkid => $editid);
+        return "Cannot load comment information." unless $comment;
+    }
+
     my $pics = LJ::Talk::get_subjecticons();
 
     # early bail if the user can't be making comments yet
@@ -1200,15 +1207,17 @@
     BML::set_language_scope('/talkpost.bml');
 
     # make sure journal isn't locked
-    return "Sorry, this journal is locked and comments cannot be posted to it at this time."
+    return "Sorry, this journal is locked and comments cannot be posted to it or edited at this time."
         if $journalu->{statusvis} eq 'L';
 
-    # check max comments
-    my $jitemid = $opts->{'ditemid'} >> 8;
-    return "Sorry, this entry already has the maximum number of comments allowed."
-        if LJ::Talk::Post::over_maxcomments($journalu, $jitemid);
+    # check max comments only if posting a new comment (not when editing)
+    unless ($editid) {
+        my $jitemid = $opts->{'ditemid'} >> 8;
+        return "Sorry, this entry already has the maximum number of comments allowed."
+            if LJ::Talk::Post::over_maxcomments($journalu, $jitemid);
+    }
 
-    if ($parpost->{'state'} eq "S") {
+    if (!$editid && $parpost->{'state'} eq "S") {
         $ret .= "<div class='ljwarnscreened'>$BML::ML{'.warnscreened'}</div>";
     }
     $ret .= "<form method='post' action='$LJ::SITEROOT/talkpost_do.bml' id='postform'>";
@@ -1300,6 +1309,33 @@
     $ret .= "<table>"; # Internal for "From" options
     my $screening = LJ::Talk::screening_level($journalu, $opts->{ditemid} >> 8);
 
+    if ($editid) {
+
+        return "You cannot edit this comment." unless $remote && !defined $oid_identity;
+
+        $ret .= "<tr valign='middle' id='ljuser_row'>";
+        my $logged_in = LJ::ehtml($remote->display_name);
+
+        if (LJ::is_banned($remote, $journalu)) {
+            $ret .= "<td align='center'><img src='$LJ::IMGPREFIX/userinfo.gif' /></td>";
+            $ret .= "<td align='center'>( )</td>";
+            $ret .= "<td align='left'><span class='ljdeem'>" . BML::ml(".opt.loggedin", {'username'=>"<i>$logged_in</i>"}) . "</font>" . BML::ml(".opt.bannedfrom", {'journal'=>$journalu->{'user'}}) . "</td>";
+        } else {
+            $ret .= "<td align='center'><img src='$LJ::IMGPREFIX/userinfo.gif'  onclick='handleRadios(1);' /></td>";
+            $ret .= "<td align='left'><label for='talkpostfromremote'>" . BML::ml(".opt.loggedin", {'username'=>"<i>$logged_in</i>"}) . "</label>\n";
+
+            $ret .= "<input type='hidden' name='usertype' value='cookieuser' />";
+            $ret .= "<input type='hidden' name='cookieuser' value='$remote->{'user'}' id='cookieuser' />\n";
+            if ($screening eq 'A' ||
+                ($screening eq 'F' && !LJ::is_friend($journalu, $remote))) {
+                $ret .= " " . $BML::ML{'.opt.willscreen'};
+            }
+            $ret .= "</td>";
+        }
+        $ret .= "</tr>\n";
+
+    } else { # if not edit
+
     if ($journalu->{'opt_whocanreply'} eq "all") {
         $ret .= "<tr valign='center'>";
         $ret .= "<td align='center'><img src='$LJ::IMGPREFIX/anonymous.gif' onclick='handleRadios(0);'/></td>";
@@ -1476,6 +1512,8 @@
         $ret .= "</span></td></tr>\n";
     }
 
+    } # end edit check
+
     my $basesubject = $form->{subject} || "";
     if ($opts->{replyto} && !$basesubject && $parpost->{'subject'}) {
         $basesubject = $parpost->{'subject'};
@@ -1693,6 +1731,8 @@
         $ret .= '<br />';
     }
 
+    my $submit_btn = $editid ? LJ::Lang::ml('.opt.edit') : LJ::Lang::ml('.opt.submit');
+
     # post and preview buttons
     my $limit = LJ::CMAX_COMMENT; # javascript String.length uses characters
     $ret .= <<LOGIN;
@@ -1712,7 +1752,7 @@
         // -->
     </script>
 
-    <input type='submit' name='submitpost' onclick='return checkLength() && sendForm("postform", "username")' value="$BML::ML{'.opt.submit'}" />
+    <input type='submit' name='submitpost' onclick='return checkLength() && sendForm("postform", "username")' value="$submit_btn" />
      
     <input type='submit' name='submitpreview' onclick='return checkLength() && sendForm("postform", "username")' value="$BML::ML{'talk.btn.preview'}" />
 LOGIN
@@ -1730,6 +1770,7 @@
         $ret .= LJ::help_icon_html("iplogging", " ");
     }
 
+    $ret .= LJ::html_hidden( editid => $editid );
     $ret .= "</td></tr></td></tr></table>\n";
 
     # Some JavaScript to help the UI out
@@ -2252,7 +2293,13 @@
     my $dtalkid = $comment->{talkid}*256 + $item->{anum};
     my $talkurl = LJ::journal_base($journalu) . "/$ditemid.html";
     my $threadurl = LJ::Talk::talkargs($talkurl, "thread=$dtalkid");
+    my $edited = $comment->{editid} ? 1 : 0;
 
+    my $comment_obj;
+    if ($edited) {
+        $comment_obj = LJ::Comment->new($journalu, dtalkid => $dtalkid);
+    }
+
     # check to see if parent post is from a registered livejournal user, and
     # mail them the response
     my $parentcomment = "";
@@ -2340,9 +2387,10 @@
 
                 my $fromname = $comment->{u} ? "$comment->{u}{'user'} - $LJ::SITENAMEABBREV Comment" : "$LJ::SITENAMESHORT Comment";
 
+                my $defaultsubject = $edited ? "Reply to your comment was edited..." : "Reply to your comment...";
                 my $msg =  new MIME::Lite ('From' => "\"$fromname\" <$LJ::BOGUS_EMAIL>",
                                            'To' => $paru->email_raw,
-                                           'Subject' => ($headersubject || "Reply to your comment..."),
+                                           'Subject' => $headersubject || $defaultsubject,
                                            'Type' => 'multipart/alternative',
                                            'Message-Id' => $this_msgid,
                                            'In-Reply-To:' => $par_msgid,
@@ -2355,7 +2403,7 @@
                 $parent->{ispost} = 0;
                 $item->{entryu} = $entryu;
                 $item->{journalu} = $journalu;
-                my $text = format_text_mail($paru, $parent, $comment, $talkurl, $item);
+                my $text = $edited ? $comment_obj->format_text_mail($paru) : format_text_mail($paru, $parent, $comment, $talkurl, $item);
 
                 if ($LJ::UNICODE && $encoding ne "UTF-8") {
                     $text = Unicode::MapUTF8::from_utf8({-string=>$text, -charset=>$encoding});
@@ -2368,7 +2416,7 @@
                     if $LJ::UNICODE;
 
                 if ($paru->{'opt_htmlemail'} eq "Y") {
-                    my $html = format_html_mail($paru, $parent, $comment, $encoding, $talkurl, $item);
+                    my $html = $edited ? $comment_obj->format_html_mail($paru) : format_html_mail($paru, $parent, $comment, $encoding, $talkurl, $item);
                     if ($LJ::UNICODE && $encoding ne "UTF-8") {
                         $html = Unicode::MapUTF8::from_utf8({-string=>$html, -charset=>$encoding});
                     }
@@ -2411,9 +2459,10 @@
         }
 
         my $fromname = $comment->{u} ? "$comment->{u}{'user'} - $LJ::SITENAMEABBREV Comment" : "$LJ::SITENAMESHORT Comment";
+        my $defaultsubject = $edited ? "Reply to your post was edited..." : "Reply to your post...";
         my $msg =  new MIME::Lite ('From' => "\"$fromname\" <$LJ::BOGUS_EMAIL>",
                                    'To' => $entryu->email_raw,
-                                   'Subject' => ($headersubject || "Reply to your post..."),
+                                   'Subject' => $headersubject || $defaultsubject,
                                    'Type' => 'multipart/alternative',
                                    'Message-Id' => $this_msgid,
                                    'In-Reply-To:' => $par_msgid,
@@ -2439,7 +2488,7 @@
         $item->{entryu} = $entryu;
         $item->{journalu} = $journalu;
 
-        my $text = format_text_mail($entryu, $parent, $comment, $talkurl, $item);
+        my $text = $edited ? $comment_obj->format_text_mail($entryu) : format_text_mail($entryu, $parent, $comment, $talkurl, $item);
 
         if ($LJ::UNICODE && $encoding ne "UTF-8") {
             $text = Unicode::MapUTF8::from_utf8({-string=>$text, -charset=>$encoding});
@@ -2452,7 +2501,7 @@
             if $LJ::UNICODE;
 
         if ($entryu->{'opt_htmlemail'} eq "Y") {
-            my $html = format_html_mail($entryu, $parent, $comment, $encoding, $talkurl, $item);
+            my $html = $edited ? $comment_obj->format_html_mail($entryu) : format_html_mail($entryu, $parent, $comment, $encoding, $talkurl, $item);
             if ($LJ::UNICODE && $encoding ne "UTF-8") {
                 $html = Unicode::MapUTF8::from_utf8({-string=>$html, -charset=>$encoding});
             }
@@ -2490,9 +2539,10 @@
             $headersubject = MIME::Words::encode_mimeword($headersubject, 'B', $encoding);
         }
 
+        my $defaultsubject = $edited ? "Comment you edited..." : "Comment you posted...";
         my $msg = new MIME::Lite ('From' => "\"$u->{'user'} - $LJ::SITENAMEABBREV Comment\" <$LJ::BOGUS_EMAIL>",
                                   'To' => $u->email_raw,
-                                  'Subject' => ($headersubject || "Comment you posted..."),
+                                  'Subject' => $headersubject || $defaultsubject,
                                   'Type' => 'multipart/alternative',
                                   'Message-Id' => $this_msgid,
                                   'In-Reply-To:' => $par_msgid,
@@ -2518,7 +2568,7 @@
         $item->{entryu} = $entryu;
         $item->{journalu} = $journalu;
 
-        my $text = format_text_mail($u, $parent, $comment, $talkurl, $item);
+        my $text = $edited ? $comment_obj->format_text_mail($u) : format_text_mail($u, $parent, $comment, $talkurl, $item);
 
         if ($LJ::UNICODE && $encoding ne "UTF-8") {
             $text = Unicode::MapUTF8::from_utf8({-string=>$text, -charset=>$encoding});
@@ -2531,7 +2581,7 @@
             if $LJ::UNICODE;
 
         if ($u->{'opt_htmlemail'} eq "Y") {
-            my $html = format_html_mail($u, $parent, $comment, $encoding, $talkurl, $item);
+            my $html = $edited ? $comment_obj->format_html_mail($u) : format_html_mail($u, $parent, $comment, $encoding, $talkurl, $item);
             if ($LJ::UNICODE && $encoding ne "UTF-8") {
                 $html = Unicode::MapUTF8::from_utf8({-string=>$html, -charset=>$encoding});
             }
@@ -2753,7 +2803,7 @@
     return $bmlerr->('talk.error.nojournal') unless $journalu;
     return $err->($LJ::MSG_READONLY_USER) if LJ::get_cap($journalu, "readonly");
 
-    return $err->("Account is locked, unable to post comment.") if $journalu->{statusvis} eq 'L';
+    return $err->("Account is locked, unable to post or edit a comment.") if $journalu->{statusvis} eq 'L';
 
     my $r = Apache->request;
     $r->notes("journalid" => $journalu->{'userid'});
@@ -3130,6 +3180,7 @@
         preformat       => $form->{'prop_opt_preformatted'},
         picture_keyword => $form->{'prop_picture_keyword'},
         state           => $state,
+        editid          => $form->{editid},
     };
 
     $init->{item} = $item;
@@ -3277,6 +3328,63 @@
     return 1;
 }
 
+# returns 1 on success.  0 on fail (with $$errref set)
+sub edit_comment {
+    my ($entryu, $journalu, $comment, $parent, $item, $errref) = @_;
+
+    my $err = sub {
+        $$errref = join(": ", @_);
+        return 0;
+    };
+
+    my $comment_obj = LJ::Comment->new($journalu, dtalkid => $comment->{editid});
+
+    my $remote = LJ::get_remote();
+    return 0 unless $remote && $remote->can_edit_comment($comment_obj, $errref);
+
+    my %props = (
+        subjecticon => $comment->{subjecticon},
+        picture_keyword => $comment->{picture_keyword},
+        opt_preformatted => $comment->{preformat} ? 1 : 0,
+        edit_time => time(), # TODO: Make this UNIX_TIMESTAMP()
+    );
+
+    my $opt_logcommentips = $comment_obj->journal->prop('opt_logcommentips');
+    if ($opt_logcommentips eq "A" || ($opt_logcommentips eq "S" && $comment->{usertype} ne "user")) {
+        $props{poster_ip} = $comment_obj->poster_ip;
+    }
+
+    $comment_obj->set_props(%props);
+
+    # TODO: Set subject and body text
+
+    # the caller wants to know the comment's talkid.
+    $comment->{talkid} = $comment_obj->jtalkid;
+
+    # cluster tracking
+    LJ::mark_user_active($comment_obj->poster, 'comment');
+
+    # fire events
+    unless ($LJ::DISABLED{esn}) {
+        my @jobs;
+
+        push @jobs, LJ::Event::JournalNewComment->new($comment_obj)->fire_job;
+        push @jobs, LJ::Event::UserNewComment->new($comment_obj)->fire_job
+            if $comment_obj->poster && ! $LJ::DISABLED{'esn-userevents'};
+        push @jobs, LJ::EventLogRecord::NewComment->new($comment_obj)->fire_job;
+
+        my $sclient = LJ::theschwartz();
+        if ($sclient && @jobs) {
+            my @handles = $sclient->insert_jobs(@jobs);
+        }
+    }
+
+    # send some emails
+    mail_comments($entryu, $journalu, $parent, $comment, $item);
+
+    return 1;
+}
+
 # XXXevan:  this function should have its functionality migrated to talkpost.
 # because of that, it's probably not worth the effort to make it not mangle $form...
 sub make_preview {

Modified: trunk/htdocs/js/talkpost.js
===================================================================
--- trunk/htdocs/js/talkpost.js	2007-11-01 17:59:37 UTC (rev 12953)
+++ trunk/htdocs/js/talkpost.js	2007-11-01 21:56:24 UTC (rev 12954)
@@ -17,7 +17,9 @@
     var form = document.getElementById("postform");
 
     var username = form.userpost;
-    username.onfocus = function () { usernameWasFocused = 1; }
+    if (username) {
+        username.onfocus = function () { usernameWasFocused = 1; }
+    }
     var password = form.password;
 
     var oidurl = document.getElementById("oidurl");
@@ -169,14 +171,16 @@
         radio_oidli.onclick = function () {
             handleRadios(4);
         };
-    username.onkeydown = username.onchange = function () {
-        if (radio_remote) {
-            password.disabled = check_login.disabled = 0;
-            if (password.disabled) password.value='';
-        } else {
-            if (radio_user && username.value != "")
-                radio_user.checked = true;
-            handleRadios(2);  // update the form
+    if (username) {
+        username.onkeydown = username.onchange = function () {
+            if (radio_remote) {
+                password.disabled = check_login.disabled = 0;
+                if (password.disabled) password.value='';
+            } else {
+                if (radio_user && username.value != "")
+                    radio_user.checked = true;
+                handleRadios(2);  // update the form
+            }
         }
     }
     form.onsubmit = submitHandler;
@@ -260,4 +264,4 @@
 
 function showMe(e) {
    e.className = '';
-}
\ No newline at end of file
+}

Modified: trunk/htdocs/stc/esn.css
===================================================================
--- trunk/htdocs/stc/esn.css	2007-11-01 17:59:37 UTC (rev 12953)
+++ trunk/htdocs/stc/esn.css	2007-11-01 21:56:24 UTC (rev 12954)
@@ -285,7 +285,7 @@
 
 .inbox .ManageButtons {
     float: right;
-    width: 81px;
+    width: 104px;
     position: relative;
     top: 4px;
     margin: 0 0 .5em 0;

Modified: trunk/htdocs/stc/lj_base.css
===================================================================
--- trunk/htdocs/stc/lj_base.css	2007-11-01 17:59:37 UTC (rev 12953)
+++ trunk/htdocs/stc/lj_base.css	2007-11-01 21:56:24 UTC (rev 12954)
@@ -130,3 +130,7 @@
 .notice {
     color: #f00;
 }
+
+.ljedittime {
+    font-size: smaller;
+}

Modified: trunk/htdocs/talkpost.bml
===================================================================
--- trunk/htdocs/talkpost.bml	2007-11-01 17:59:37 UTC (rev 12953)
+++ trunk/htdocs/talkpost.bml	2007-11-01 21:56:24 UTC (rev 12954)
@@ -11,6 +11,7 @@
 
     LJ::need_res('stc/display_none.css');
 
+    my $remote = LJ::get_remote();
     my $errtxt;
 
     my $pics = LJ::Talk::get_subjecticons();
@@ -20,6 +21,22 @@
 
     my $uri = BML::get_uri();
 
+    my $editid = $FORM{edit} ? $FORM{edit} : 0;
+    my $comment;
+    if ($editid) {
+        $comment = LJ::Comment->new(LJ::load_user($r->notes("_journal")), dtalkid => $editid);
+        return $errtxt unless $remote && $remote->can_edit_comment($comment, \$errtxt);
+
+        my $parent = $comment->parent;
+        $FORM{replyto} = $parent ? $comment->parent->dtalkid : 0;
+
+        $FORM{subject} = $comment->subject_orig;
+        $FORM{body} = $comment->body_orig;
+        $FORM{subjecticon} = $comment->prop('subjecticon');
+        $FORM{prop_picture_keyword} = $comment->prop('picture_keyword');
+        $FORM{prop_opt_preformatted} = $comment->prop('opt_preformatted');
+    }
+
     if ($uri =~ m!/(\d+)\.html$!) {
         $FORM{'itemid'} = $1 unless $FORM{'replyto'} > 0;
         $FORM{'journal'} = $r->notes("_journal");
@@ -57,7 +74,7 @@
     my $parpost;
     my $reply;
 
-    if ($init->{'replyto'})
+    if ($init->{'replyto'} || ($editid && $comment->parenttalkid))
     {
         my $qparentid = $init->{'replyto'};
 
@@ -124,8 +141,6 @@
                                 $parpost->{'posterid'} => \$ur, ],
                               [ $u ]);
 
-    my $remote = LJ::get_remote();
-
     return if LJ::bad_password_redirect();
 
     my @user_props = ("opt_logcommentips", "opt_whoscreened");
@@ -350,7 +365,8 @@
         return $ret;
     }
 
-    $ret .= BML::fill_template("H1", { DATA => $ML{'.postresponse'} });
+    my $h1title = $editid ? $ML{'.editresponse'} : $ML{'.postresponse'};
+    $ret .= BML::fill_template("H1", { DATA => $h1title });
 
     $ret .= LJ::Talk::talkform({ 'remote'    => $remote,
                                  'journalu'  => $u,

Modified: trunk/htdocs/talkpost.bml.text
===================================================================
--- trunk/htdocs/talkpost.bml.text	2007-11-01 17:59:37 UTC (rev 12953)
+++ trunk/htdocs/talkpost.bml.text	2007-11-01 21:56:24 UTC (rev 12954)
@@ -1,6 +1,8 @@
 ;; -*- coding: utf-8 -*-
 .allowedhtml=Allowed HTML
 
+.editresponse=Edit your response:
+
 .error.cannotreplynopost=You cannot reply to a non-existent post 
 
 .error.nocommentsjournal=User has disabled comments in their journal.
@@ -33,6 +35,8 @@
 
 .opt.defpic=(default)
 
+.opt.edit=Edit Comment
+
 .opt.friendsonly=- this user has disabled anonymous and non-friend posting.  You may post here if [[username]] lists you as a friend.
 
 .opt.from=From:

Modified: trunk/htdocs/talkpost_do.bml
===================================================================
--- trunk/htdocs/talkpost_do.bml	2007-11-01 17:59:37 UTC (rev 12953)
+++ trunk/htdocs/talkpost_do.bml	2007-11-01 21:56:24 UTC (rev 12954)
@@ -33,6 +33,8 @@
 
     LJ::need_res('stc/display_none.css');
 
+    my $editid = $POST{editid};
+
     # Set the title to be for an error, it will be changed later
     # upon sucess
     $title = $ML{'Error'};
@@ -179,20 +181,27 @@
     my $comment  = $init->{comment};
     my $item     = $init->{item};
 
-    # check max comments
-    return LJ::bad_input("Sorry, this entry already has the maximum number of comments allowed.")
-        if LJ::Talk::Post::over_maxcomments($journalu, $item->{'jitemid'});
+    # check max comments only if posting a new comment (not when editing)
+    unless ($editid) {
+        return LJ::bad_input("Sorry, this entry already has the maximum number of comments allowed.")
+            if LJ::Talk::Post::over_maxcomments($journalu, $item->{'jitemid'});
+    }
 
     # no replying to frozen comments
     return LJ::bad_input($ML{'/talkpost.bml.error.noreply_frozen'})
         if $parent->{state} eq 'F';
 
-    ## insertion
+    ## insertion or editing
     my $wasscreened = ($parent->{state} eq 'S');
     my $err;
-    unless (LJ::Talk::Post::post_comment($entryu, $journalu,
-                                         $comment, $parent, $item, \$err)) {
-        return LJ::bad_input($err);
+    if ($editid) {
+        unless (LJ::Talk::Post::edit_comment($entryu, $journalu, $comment, $parent, $item, \$err)) {
+            return LJ::bad_input($err);
+        }
+    } else {
+        unless (LJ::Talk::Post::post_comment($entryu, $journalu, $comment, $parent, $item, \$err)) {
+            return LJ::bad_input($err);
+        }
     }
 
     # Yeah, we're done.

Modified: trunk/htdocs/talkread.bml
===================================================================
--- trunk/htdocs/talkread.bml	2007-11-01 17:59:37 UTC (rev 12953)
+++ trunk/htdocs/talkread.bml	2007-11-01 21:56:24 UTC (rev 12954)
@@ -499,6 +499,19 @@
          my $icon = LJ::Talk::show_image($pics, $post->{'props'}->{'subjecticon'});
 
          if ($post->{'_loaded'}) {
+             my $comment = LJ::Comment->new($u, dtalkid => $dtid);
+
+             my $edittime;
+             if ($comment->is_edited) {
+                 my $s2_datetime_edittime = $tz_remote ?
+                     LJ::S2::DateTime_tz($comment->edit_time, $tz_remote) :
+                     LJ::S2::DateTime_unix($comment->edit_time);
+
+                 $edittime = S2::Builtin::LJ::Date__date_format($s2_ctx, $s2_datetime_edittime, "iso") .
+                     " " . S2::Builtin::LJ::DateTime__time_format($s2_ctx, $s2_datetime_edittime, $fmt_time_short) .
+                     ($tz_remote ? " (local)" : " UTC");
+             }
+
              $ret .= "<a name='t$dtid'></a><table id='ljcmt$dtid' width='100%' class='talk-comment'><tr>";
              $ret .= "<td rowspan='2'><img src='$LJ::IMGPREFIX/dot.gif' height='1' width='" . ($opts->{'depth'} * 25) . "'></td>";
              $ret .= "<td id='cmtbar$dtid' bgcolor='$bgcolor' width='100%'>";
@@ -526,6 +539,10 @@
 
              $ret .= " <font size='-1'>(<a href='" . LJ::Talk::talkargs($talkurl, "thread=$dtid", $formatlight) . "#t$dtid'>$T{'link'}</a>)</font> ";
 
+             if ($remote && $remote->can_edit_comment($comment)) {
+                 $ret .= "<a href='" . $comment->edit_url . "'>" . LJ::img("editcomment", "", { 'align' => 'absmiddle', 'hspace' => 2, 'vspace' => }) . "</a>";
+             }
+
              if (LJ::Talk::can_delete($remote, $u, $up, $userpost)) {
                  $ret .= "<a href='$LJ::SITEROOT/delcomment.bml?${jargent}id=$dtid'>" . LJ::img("btn_del", "", { 'align' => 'absmiddle', 'hspace' => 2, 'vspace' => }) . "</a>";
              }
@@ -553,7 +570,6 @@
              if ($remote && $remote->can_use_esn) {
                  my $track_img = 'track';
 
-                 my $comment = LJ::Comment->new($u, dtalkid => $dtid);
                  my $comment_watched = $remote->has_subscription(
                                                                  event   => "JournalNewComment",
                                                                  journal => $u,
@@ -614,7 +630,8 @@
                  # quote all non-LJ tags
                  $event =~ s{<(?!/?lj)(.*?)>} {<$1>}gi;
              }
-             $ret .= $event;
+             my $edit_html = $edittime ? "<br /><br /><span class='ljedittime'><em>" . BML::ml('.edittime', { edittime => $edittime }) . "</em></span>" : "";
+             $ret .= "$event$edit_html";
 
              $ret .= "<p style='margin: 0.7em 0 0.2em 0'><font size='-2'>";
 

Modified: trunk/htdocs/talkread.bml.text
===================================================================
--- trunk/htdocs/talkread.bml.text	2007-11-01 17:59:37 UTC (rev 12953)
+++ trunk/htdocs/talkread.bml.text	2007-11-01 21:56:24 UTC (rev 12954)
@@ -11,6 +11,8 @@
 
 .deleteduser=<i>(Deleted user: [[username]])</i>
 
+.edittime=Edited at [[edittime]]
+
 .fromip=(from [[ip]])
 
 .noreplies=No replies

Subscribe
  • Post a new comment

    Error

    Anonymous comments are disabled in this journal

    default userpic

    Your reply will be screened

    Your IP address will be recorded 

  • 33 comments
Previous
← Ctrl ← Alt
Next
Ctrl → Alt →
Previous
← Ctrl ← Alt
Next
Ctrl → Alt →