From: Dylan William Hardison Date: Tue, 20 Nov 2018 21:30:56 +0000 (-0500) Subject: Bug 1484892 - Modify EditComments extension to let anyone use it conditionally and... X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=c6700101c34409fb1e694f54a8d39eb64a2baed7;p=thirdparty%2Fbugzilla.git Bug 1484892 - Modify EditComments extension to let anyone use it conditionally and support inline editing --- diff --git a/Bugzilla/Template.pm b/Bugzilla/Template.pm index a72a440f9..827ffa910 100644 --- a/Bugzilla/Template.pm +++ b/Bugzilla/Template.pm @@ -39,7 +39,8 @@ use File::Spec; use IO::Dir; use List::MoreUtils qw(firstidx); use Scalar::Util qw(blessed); -use JSON::XS qw(encode_json); +use Mojo::JSON qw(encode_json); +use Encode qw(encode decode); use parent qw(Template); @@ -888,7 +889,7 @@ sub create { }, json_encode => sub { - return encode_json($_[0]); + return decode('UTF-8', encode_json($_[0]), Encode::FB_DEFAULT); }, # Function to create date strings diff --git a/extensions/BugModal/template/en/default/bug_modal/activity_stream.html.tmpl b/extensions/BugModal/template/en/default/bug_modal/activity_stream.html.tmpl index 33a38209f..05e8833f1 100644 --- a/extensions/BugModal/template/en/default/bug_modal/activity_stream.html.tmpl +++ b/extensions/BugModal/template/en/default/bug_modal/activity_stream.html.tmpl @@ -141,17 +141,23 @@ [% Hook.process('comment_action', 'bug_modal/activity_stream.html.tmpl') %] [% IF user.id %] [% IF user.can_tag_comments %] - + [% END %] - + [% END %] - + @@ -166,6 +172,7 @@
[% INCLUDE bug_modal/rel_time.html.tmpl ts=comment.creation_ts %]
+ [% Hook.process('comment_meta', 'bug_modal/activity_stream.html.tmpl') %] @@ -193,8 +200,13 @@ Comment hidden ([% comment.collapsed_reason FILTER html %]) - @@ -225,7 +237,12 @@ [% Hook.process('user', 'bug/changes.html.tmpl') %] - + diff --git a/extensions/BugModal/web/bug_modal.css b/extensions/BugModal/web/bug_modal.css index dbf949802..b7ce55d19 100644 --- a/extensions/BugModal/web/bug_modal.css +++ b/extensions/BugModal/web/bug_modal.css @@ -594,7 +594,7 @@ td.flag-requestee { } -.change-name, .change-time, .comment-private { +.change-name, .change-time { display: inline; } @@ -604,11 +604,57 @@ h3.change-name { } .comment-actions { + display: flex; + align-items: center; white-space: nowrap; vertical-align: top; padding: 2px 2px 0 0 !important; } +.comment-private { + display: inline-block; + margin: 0 8px; +} + +.comment-actions button { + outline: 0; + margin: 0; +} + +.comment-actions button:not(:first-of-type) { + border-top-left-radius: 0; + border-bottom-left-radius: 0; +} + +.comment-actions button:not(:last-of-type) { + border-top-right-radius: 0; + border-bottom-right-radius: 0; +} + +.comment-actions button.iconic:not(:disabled) { + color: #555; +} + +.comment-actions button.iconic .icon::before { + font-family: 'Material Icons'; +} + +.comment-actions .tag-btn .icon::before { + content: '\E54E'; +} + +.comment-actions .reply-btn .icon::before { + content: '\E15E'; +} + +.comment-actions .change-spinner[aria-expanded="true"] .icon::before { + content: '\E15B'; +} + +.comment-actions .change-spinner[aria-expanded="false"] .icon::before { + content: '\E145'; +} + .change-spinner { width: 29px; } diff --git a/extensions/BugModal/web/comments.js b/extensions/BugModal/web/comments.js index b09cd851f..45ee0890d 100644 --- a/extensions/BugModal/web/comments.js +++ b/extensions/BugModal/web/comments.js @@ -10,6 +10,16 @@ $(function() { // comment collapse/expand + const update_spinner = (spinner, expanded) => { + const str = spinner.data('strings'); + + spinner.attr({ + 'title': expanded ? str.collapse_tooltip : str.expand_tooltip, + 'aria-label': expanded ? str.collapse_label : str.expand_label, + 'aria-expanded': expanded, + }); + }; + function toggleChange(spinner, forced) { var spinnerID = spinner.attr('id'); var id = spinnerID.substring(spinnerID.indexOf('-') + 1); @@ -23,24 +33,24 @@ $(function() { changeSet.find(activitySelector).hide(); changeSet.find('.gravatar').css('width', '16px').css('height', '16px'); $('#ar-' + id).hide(); - spinner.text('+'); + update_spinner(spinner, false); } else if (forced == 'show' || forced == 'reset') { changeSet.find(activitySelector).show(); changeSet.find('.gravatar').css('width', '32px').css('height', '32px'); $('#ar-' + id).show(); - spinner.text('-'); + update_spinner(spinner, true); } else { changeSet.find(activitySelector).slideToggle('fast', function() { $('#ar-' + id).toggle(); if (changeSet.find(activitySelector + ':visible').length) { changeSet.find('.gravatar').css('width', '32px').css('height', '32px'); - spinner.text('-'); + update_spinner(spinner, true); } else { changeSet.find('.gravatar').css('width', '16px').css('height', '16px'); - spinner.text('+'); + update_spinner(spinner, false); } }); } @@ -72,7 +82,7 @@ $(function() { $('#c' + id).find('.comment-tags').hide(); $('#c' + id).find('.gravatar').css('width', '16px').css('height', '16px'); $('#cr-' + id).hide(); - realSpinner.text('+'); + update_spinner(realSpinner, false); } else if (forced == 'show') { if (defaultCollapsed) { @@ -87,14 +97,14 @@ $(function() { $('#c' + id).find('.comment-tags').show(); $('#c' + id).find('.gravatar').css('width', '32px').css('height', '32px'); $('#cr-' + id).show(); - realSpinner.text('-'); + update_spinner(realSpinner, true); } else { $('#ct-' + id).slideToggle('fast', function() { $('#c' + id).find(activitySelector).toggle(); if ($('#ct-' + id + ':visible').length) { $('#c' + id).find('.comment-tags').show(); - realSpinner.text('-'); + update_spinner(realSpinner, true); $('#cr-' + id).show(); if (BUGZILLA.user.id !== 0) $('#ctag-' + id).show(); @@ -106,7 +116,7 @@ $(function() { } else { $('#c' + id).find('.comment-tags').hide(); - realSpinner.text('+'); + update_spinner(realSpinner, false); $('#cr-' + id).hide(); if (BUGZILLA.user.id !== 0) $('#ctag-' + id).hide(); diff --git a/extensions/EditComments/Extension.pm b/extensions/EditComments/Extension.pm index 1aa0efcfe..0dd531b9a 100644 --- a/extensions/EditComments/Extension.pm +++ b/extensions/EditComments/Extension.pm @@ -70,20 +70,12 @@ sub install_update_db { sub page_before_template { my ($self, $args) = @_; - return if $args->{'page_id'} ne 'editcomments.html'; + return if $args->{'page_id'} ne 'comment-revisions.html'; my $vars = $args->{'vars'}; my $user = Bugzilla->user; my $params = Bugzilla->input_params; - # validate group membership - my $edit_comments_group = Bugzilla->params->{edit_comments_group}; - if (!$edit_comments_group || !$user->in_group($edit_comments_group)) { - ThrowUserError('auth_failure', { group => $edit_comments_group, - action => 'view', - object => 'editcomments' }); - } - my $bug_id = $params->{bug_id}; my $bug = Bugzilla::Bug->check($bug_id); @@ -133,8 +125,9 @@ sub _get_activity { my $dbh = Bugzilla->dbh; my $query = 'SELECT longdescs_activity.comment_id AS id, profiles.userid, ' . - $dbh->sql_date_format('longdescs_activity.change_when', '%Y.%m.%d %H:%i:%s') . ' - AS time, longdescs_activity.old_comment AS old + $dbh->sql_date_format('longdescs_activity.change_when', '%Y-%m-%d %H:%i:%s') . ' + AS time, longdescs_activity.old_comment AS old, + longdescs_activity.is_hidden as is_hidden FROM longdescs_activity INNER JOIN profiles ON profiles.userid = longdescs_activity.who @@ -148,21 +141,19 @@ sub _get_activity { # body that the comment was before the edit, not the actual new version # of the comment. my @activity; - my $new_comment; - my $last_old_comment; + my $prev_rev; my $count = 0; - while (my $change_ref = $sth->fetchrow_hashref()) { - my %change = %$change_ref; - $change{'author'} = Bugzilla::User->new({ id => $change{'userid'}, cache => 1 }); - if ($count == 0) { - $change{new} = $self->body; - } - else { - $change{new} = $new_comment; - } - $new_comment = $change{old}; - $last_old_comment = $change{old}; - push (@activity, \%change); + while (my $revision = $sth->fetchrow_hashref()) { + my $current = $count == 0; + push (@activity, { + author => Bugzilla::User->new({ id => $revision->{userid}, cache => 1 }), + created_time => $revision->{time}, + old => $revision->{old}, + revised_time => $current ? undef : $prev_rev->{time}, + new => $current ? $self->body : $prev_rev->{old}, + is_hidden => $current ? 0 : $prev_rev->{is_hidden}, + }); + $prev_rev = $revision; $count++; } @@ -171,10 +162,11 @@ sub _get_activity { # Store the original comment as the first or last entry # depending on sort order push(@activity, { - author => $self->author, - body => $last_old_comment, - time => $self->creation_ts, - original => 1 + author => $self->author, + created_time => $self->creation_ts, + revised_time => $prev_rev->{time}, + new => $prev_rev->{old}, + is_hidden => $prev_rev->{is_hidden}, }); $activity_sort_order @@ -208,7 +200,7 @@ sub bug_end_of_update { # or if editing comments is disabled my $user = Bugzilla->user; my $edit_comments_group = Bugzilla->params->{edit_comments_group}; - return if (!$edit_comments_group || !$user->in_group($edit_comments_group)); + return unless $user->is_insider || $edit_comments_group && $user->in_group($edit_comments_group); my $bug = $args->{bug}; my $timestamp = $args->{timestamp}; @@ -224,11 +216,18 @@ sub bug_end_of_update { my ($comment_obj) = grep($_->id == $comment_id, @{ $bug->comments}); next if (!$comment_obj || ($comment_obj->is_private && !$user->is_insider)); + # Insiders can edit any comment while unprivileged users can only edit their own comments + next unless $user->is_insider || $comment_obj->author->id == $user->id; + my $new_comment = $comment_obj->_check_thetext($params->{$param}); my $old_comment = $comment_obj->body; next if $old_comment eq $new_comment; + # Insiders can hide comment revisions where needed + my $is_hidden = ($user->is_insider && defined $params->{"edit_comment_checkbox_$comment_id"} + && $params->{"edit_comment_checkbox_$comment_id"} == 'on') ? 1 : 0; + trick_taint($new_comment); $dbh->do("UPDATE longdescs SET thetext = ?, edit_count = edit_count + 1 WHERE comment_id = ?", @@ -238,9 +237,9 @@ sub bug_end_of_update { # Log old comment to the longdescs activity table $timestamp ||= $dbh->selectrow_array("SELECT NOW()"); $dbh->do("INSERT INTO longdescs_activity " . - "(comment_id, who, change_when, old_comment) " . - "VALUES (?, ?, ?, ?)", - undef, ($comment_id, $user->id, $timestamp, $old_comment)); + "(comment_id, who, change_when, old_comment, is_hidden) " . + "VALUES (?, ?, ?, ?, ?)", + undef, ($comment_id, $user->id, $timestamp, $old_comment, $is_hidden)); $comment_obj->{thetext} = $new_comment; @@ -256,7 +255,7 @@ sub config_modify_panels { name => 'edit_comments_group', type => 's', choices => \&get_all_group_names, - default => 'admin', + default => 'editbugs', checker => \&check_group }; } diff --git a/extensions/EditComments/lib/WebService.pm b/extensions/EditComments/lib/WebService.pm index 6969ca742..d9ebbc7a8 100644 --- a/extensions/EditComments/lib/WebService.pm +++ b/extensions/EditComments/lib/WebService.pm @@ -13,12 +13,18 @@ use warnings; use base qw(Bugzilla::WebService); +use Bugzilla; +use Bugzilla::Comment; +use Bugzilla::Constants; use Bugzilla::Error; -use Bugzilla::Util qw(trim); +use Bugzilla::Template; +use Bugzilla::Util qw(trick_taint trim); use Bugzilla::WebService::Util qw(validate); use constant PUBLIC_METHODS => qw( comments + update_comment + modify_revision ); sub comments { @@ -59,6 +65,109 @@ sub comments { return { comments => \%comments }; } +# See Bugzilla::Extension::EditComments->bug_end_of_update for the original implementation. +# This should be migrated to the standard API method at /rest/bug/comment/(comment_id) +sub update_comment { + my ($self, $params) = @_; + my $user = Bugzilla->login(LOGIN_REQUIRED); + my $edit_comments_group = Bugzilla->params->{edit_comments_group}; + + # Validate group membership + ThrowUserError('auth_failure', { group => $edit_comments_group, action => 'view', object => 'editcomments' }) + unless $user->is_insider || $edit_comments_group && $user->in_group($edit_comments_group); + + my $comment_id = (defined $params->{comment_id} && $params->{comment_id} =~ /^(\d+)$/) ? $1 : undef; + + # Validate parameters + ThrowCodeError('param_required', { function => 'EditComments.update_comment', param => 'comment_id' }) + unless defined $comment_id; + ThrowCodeError('param_required', { function => 'EditComments.update_comment', param => 'new_comment' }) + unless defined $params->{new_comment} && trim($params->{new_comment}) ne ''; + + my $comment = Bugzilla::Comment->new($comment_id); + + # Validate comment visibility + ThrowUserError('comment_id_invalid', { id => $comment_id }) + unless $comment; + ThrowUserError('comment_is_private', { id => $comment->id }) + unless $user->is_insider || !$comment->is_private; + + # Insiders can edit any comment while unprivileged users can only edit their own comments + ThrowUserError('auth_failure', { group => 'insidergroup', action => 'view', object => 'editcomments' }) + unless $user->is_insider || $comment->author->id == $user->id; + + my $bug = $comment->bug; + my $old_comment = $comment->body; + my $new_comment = $comment->_check_thetext($params->{new_comment}); + + # Validate bug visibility + $bug->check_is_visible(); + + # Make sure there is any change in the comment + ThrowCodeError('param_no_changes', { function => 'EditComments.update_comment', param => 'new_comment' }) + if $old_comment eq $new_comment; + + my $dbh = Bugzilla->dbh; + my $change_when = $dbh->selectrow_array('SELECT NOW()'); + + # Insiders can hide comment revisions where needed + my $is_hidden = ($user->is_insider && defined $params->{is_hidden} && $params->{is_hidden} == 1) ? 1 : 0; + + # Update the `longdescs` (comments) table + trick_taint($new_comment); + $dbh->do('UPDATE longdescs SET thetext = ?, edit_count = edit_count + 1 WHERE comment_id = ?', + undef, $new_comment, $comment_id); + Bugzilla->memcached->clear({ table => 'longdescs', id => $comment_id }); + + # Log old comment to the `longdescs_activity` (comment revisions) table + $dbh->do('INSERT INTO longdescs_activity (comment_id, who, change_when, old_comment, is_hidden) + VALUES (?, ?, ?, ?, ?)', undef, ($comment_id, $user->id, $change_when, $old_comment, $is_hidden)); + + $comment->{thetext} = $new_comment; + $bug->_sync_fulltext( update_comments => 1 ); + + # Respond with the updated comment and number of revisions + return { + text => $self->type('string', $new_comment), + html => $self->type('string', Bugzilla::Template::quoteUrls($new_comment, $bug)), + count => $self->type('int', $dbh->selectrow_array('SELECT COUNT(*) FROM longdescs_activity + WHERE comment_id = ?', undef, ($comment_id))), + }; +} + +sub modify_revision { + my ($self, $params) = @_; + my $user = Bugzilla->login(LOGIN_REQUIRED); + + # Only allow insiders to modify revisions + ThrowUserError('auth_failure', { group => 'insidergroup', action => 'view', object => 'editcomments' }) + unless $user->is_insider; + + my $comment_id = (defined $params->{comment_id} + && $params->{comment_id} =~ /^(\d+)$/) ? $1 : undef; + my $change_when = (defined $params->{change_when} + && $params->{change_when} =~ /^(\d{4}-\d{2}-\d{2}\ \d{2}:\d{2}:\d{2})$/) ? $1 : undef; + my $is_hidden = defined $params->{is_hidden} && $params->{is_hidden} == 1 ? 1 : 0; + + # Validate parameters + ThrowCodeError('param_required', { function => 'EditComments.modify_revision', param => 'comment_id' }) + unless defined $comment_id; + ThrowCodeError('param_required', { function => 'EditComments.modify_revision', param => 'change_when' }) + unless defined $change_when; + + my $dbh = Bugzilla->dbh; + + # Update revision visibility + $dbh->do('UPDATE longdescs_activity SET is_hidden = ? WHERE comment_id = ? AND change_when = ?', + undef, ($is_hidden, $comment_id, $change_when)); + + # Respond with updated revision info + return { + change_when => $self->type('dateTime', $change_when), + is_hidden => $self->type('boolean', $is_hidden), + }; +} + sub rest_resources { return [ qr{^/editcomments/comment/(\d+)$}, { @@ -68,12 +177,23 @@ sub rest_resources { return { comment_ids => $_[0] }; }, }, + PUT => { + method => 'update_comment', + params => sub { + return { comment_id => $_[0] }; + }, + }, }, qr{^/editcomments/comment$}, { GET => { method => 'comments', }, }, + qr{^/editcomments/revision$}, { + PUT => { + method => 'modify_revision', + }, + }, ]; }; diff --git a/extensions/EditComments/template/en/default/hook/admin/params/editparams-current_panel.html.tmpl b/extensions/EditComments/template/en/default/hook/admin/params/editparams-current_panel.html.tmpl index 01ca7bbb7..c35dc0d98 100644 --- a/extensions/EditComments/template/en/default/hook/admin/params/editparams-current_panel.html.tmpl +++ b/extensions/EditComments/template/en/default/hook/admin/params/editparams-current_panel.html.tmpl @@ -8,6 +8,7 @@ [% IF panel.name == "groupsecurity" %] [% panel.param_descs.edit_comments_group = - 'The name of the group of users who can edit comments. Leave blank to disable comment editing.' + 'The name of the group of users who can edit their own comments. Leave it blank to disable comment editing. ' _ + 'Insiders can always edit any comment and hide revisions where needed.' %] [% END -%] diff --git a/extensions/EditComments/template/en/default/hook/bug_modal/activity_stream-comment_action.html.tmpl b/extensions/EditComments/template/en/default/hook/bug_modal/activity_stream-comment_action.html.tmpl index d5028c4a1..76208017e 100644 --- a/extensions/EditComments/template/en/default/hook/bug_modal/activity_stream-comment_action.html.tmpl +++ b/extensions/EditComments/template/en/default/hook/bug_modal/activity_stream-comment_action.html.tmpl @@ -8,7 +8,8 @@ [% RETURN IF comment.body == ''; - RETURN UNLESS Param('edit_comments_group') && user.in_group(Param('edit_comments_group')); + RETURN UNLESS user.is_insider + || Param('edit_comments_group') && user.in_group(Param('edit_comments_group')) && comment.author.id == user.id; RETURN UNLESS comment.type == constants.CMT_NORMAL || comment.type == constants.CMT_DUPE_OF @@ -16,11 +17,6 @@ || comment.type == constants.CMT_ATTACHMENT_UPDATED; %] -[% IF comment.edit_count %] - (Edited) -[% END %] - - + diff --git a/extensions/EditComments/template/en/default/hook/bug_modal/activity_stream-comment_meta.html.tmpl b/extensions/EditComments/template/en/default/hook/bug_modal/activity_stream-comment_meta.html.tmpl new file mode 100644 index 000000000..a6ea7ce58 --- /dev/null +++ b/extensions/EditComments/template/en/default/hook/bug_modal/activity_stream-comment_meta.html.tmpl @@ -0,0 +1,17 @@ +[%# This Source Code Form is subject to the terms of the Mozilla Public + # License, v. 2.0. If a copy of the MPL was not distributed with this + # file, You can obtain one at http://mozilla.org/MPL/2.0/. + # + # This Source Code Form is "Incompatible With Secondary Licenses", as + # defined by the Mozilla Public License, v. 2.0. + #%] + +[% RETURN UNLESS comment.edit_count %] + +• +
+ Edited +
diff --git a/extensions/EditComments/template/en/default/hook/bug_modal/header-end.html.tmpl b/extensions/EditComments/template/en/default/hook/bug_modal/header-end.html.tmpl index 68597bcb0..b48547785 100644 --- a/extensions/EditComments/template/en/default/hook/bug_modal/header-end.html.tmpl +++ b/extensions/EditComments/template/en/default/hook/bug_modal/header-end.html.tmpl @@ -7,8 +7,11 @@ #%] [% - IF Param('edit_comments_group') && user.in_group(Param('edit_comments_group')); - style_urls.push('extensions/EditComments/web/styles/editcomments.css'); - javascript_urls.push('extensions/EditComments/web/js/editcomments.js'); - END; + # Always load CSS to style the "Edited" revision link + style_urls.push('extensions/EditComments/web/styles/inline-editor.css'); + + RETURN UNLESS user.is_insider + || Param('edit_comments_group') && user.in_group(Param('edit_comments_group')); + + javascript_urls.push('extensions/EditComments/web/js/inline-editor.js'); %] diff --git a/extensions/EditComments/template/en/default/hook/global/header-start.html.tmpl b/extensions/EditComments/template/en/default/hook/global/header-start.html.tmpl new file mode 100644 index 000000000..58cd77983 --- /dev/null +++ b/extensions/EditComments/template/en/default/hook/global/header-start.html.tmpl @@ -0,0 +1,29 @@ +[%# This Source Code Form is subject to the terms of the Mozilla Public + # License, v. 2.0. If a copy of the MPL was not distributed with this + # file, You can obtain one at http://mozilla.org/MPL/2.0/. + # + # This Source Code Form is "Incompatible With Secondary Licenses", as + # defined by the Mozilla Public License, v. 2.0. + #%] + +[% + RETURN UNLESS template.name == 'bug/show-modal.html.tmpl'; + RETURN UNLESS user.is_insider + || Param('edit_comments_group') && user.in_group(Param('edit_comments_group')); + + # Expose strings used in JavaScript + js_BUGZILLA.string.InlineCommentEditor = { + cancel => 'Cancel', + cancel_tooltip => 'Discard the changes', + edited => 'Edited', + fetch_error => 'Raw comment could not be loaded. Please try again later.', + hide_revision => 'Hide This Revision', + loading => 'Loading…', + revision_count => [ '%d revision', '%d revisions' ], + save => 'Update Comment', + save_error => 'Updated comment could not be saved. Please try again later.', + save_tooltip => 'Save the changes', + saving => 'Saving…', + toolbar => 'Comment Editor Toolbar', + }; +%] diff --git a/extensions/EditComments/template/en/default/pages/comment-revisions.html.tmpl b/extensions/EditComments/template/en/default/pages/comment-revisions.html.tmpl new file mode 100644 index 000000000..d4cc4e0b8 --- /dev/null +++ b/extensions/EditComments/template/en/default/pages/comment-revisions.html.tmpl @@ -0,0 +1,58 @@ +[%# This Source Code Form is subject to the terms of the Mozilla Public + # License, v. 2.0. If a copy of the MPL was not distributed with this + # file, You can obtain one at http://mozilla.org/MPL/2.0/. + # + # This Source Code Form is "Incompatible With Secondary Licenses", as + # defined by the Mozilla Public License, v. 2.0. + #%] + +[% + PROCESS global/variables.none.tmpl; + PROCESS global/header.html.tmpl + title = "$terms.Bug $bug.id Comment $comment.count Edit History" + style_urls = ['extensions/EditComments/web/styles/revisions.css'] + javascript_urls = (user.is_insider ? ['extensions/EditComments/web/js/revisions.js'] : []) + generate_api_token = 1 +%] + +

[% "$terms.Bug $bug.id Comment $comment.count" FILTER none %] Edit History

+ +

+ Note: The actual edited comment in the [% terms.bug %] view + page will always show the original commentor’s name and original timestamp. +

+ +[% SET rev_count = 0 %] +[% FOREACH a = comment.activity %] +
+
+ + [% IF user.is_insider && a.revised_time %] +
+ +
+ [% END %] +
+
+ [% IF !user.is_insider && a.is_hidden %] +
(Hidden by Administrator)
+ [% ELSE %] +
[% a.new FILTER quoteUrls(bug) %]
+ [% END %] +
+
+ [% rev_count = rev_count + 1 %] +[% END %] + +[% IF !comment.activity.size %] +

No changes have been made to this comment.

+[% END %] + +

[% "Back to $terms.Bug $bug.id Comment $comment.count" + FILTER bug_link(bug, { comment_num => comment.count }) FILTER none %]

+ +[% PROCESS global/footer.html.tmpl %] diff --git a/extensions/EditComments/template/en/default/pages/editcomments.html.tmpl b/extensions/EditComments/template/en/default/pages/editcomments.html.tmpl deleted file mode 100644 index 13364f5b1..000000000 --- a/extensions/EditComments/template/en/default/pages/editcomments.html.tmpl +++ /dev/null @@ -1,49 +0,0 @@ -[%# This Source Code Form is subject to the terms of the Mozilla Public - # License, v. 2.0. If a copy of the MPL was not distributed with this - # file, You can obtain one at http://mozilla.org/MPL/2.0/. - # - # This Source Code Form is "Incompatible With Secondary Licenses", as - # defined by the Mozilla Public License, v. 2.0. - #%] - -[% - PROCESS global/variables.none.tmpl; - PROCESS global/header.html.tmpl - title = "$bug.id comment $comment.count Activity" - style_urls = ['extensions/EditComments/web/styles/editcomments.css'] -%] - -

- Comment changes made to - [%= "$terms.bug $bug.id comment $comment.count" - FILTER bug_link(bug, { comment_num => comment.count }) - FILTER none %] -

- -

- Note: The actual edited comment in the [% terms.bug %] view - page will always show the original commentor's name and original timestamp. -

- -[% FOREACH a = comment.activity %] -
-
- [% a.original ? "Original comment" : "Revision" %] - by [% INCLUDE bug_modal/user.html.tmpl u=a.author %] - on [% a.time FILTER time %] -
-
-
-    [%- a.original ? a.body : a.new FILTER quoteUrls(bug) -%]
-  
-[% END %] - -[% IF comment.activity.size %] -

- [%= "Back to $terms.bug $bug.id comment $comment.count" - FILTER bug_link(bug, { comment_num => comment.count }) - FILTER none %] -

-[% END %] - -[% PROCESS global/footer.html.tmpl %] diff --git a/extensions/EditComments/web/js/editcomments.js b/extensions/EditComments/web/js/editcomments.js deleted file mode 100644 index 04e4a2f88..000000000 --- a/extensions/EditComments/web/js/editcomments.js +++ /dev/null @@ -1,64 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this file, - * You can obtain one at http://mozilla.org/MPL/2.0/. - * - * This Source Code Form is "Incompatible With Secondary Licenses", as - * defined by the Mozilla Public License, v. 2.0. - */ - -$(function() { - $('.edit-comment-btn') - .click(function(event) { - event.preventDefault(); - var that = $(this); - var id = that.data('id'); - var no = that.data('no'); - - // cancel editing - if (that.data('editing')) { - that.data('editing', false).text('Edit'); - $('#edit_comment_textarea_' + id).remove(); - $('#ct-' + no).show(); - return; - } - that.text('Unedit'); - - // replace comment
 with loading message
-            $('#ct-' + no)
-                .hide()
-                .after(
-                    $('
')
-                        .attr('id', 'edit-comment-loading-' + id)
-                        .addClass('edit-comment-loading')
-                        .text('Loading...')
-                );
-
-            // load original comment text
-            bugzilla_ajax(
-                {
-                    url: `${BUGZILLA.config.basepath}rest/editcomments/comment/${id}`,
-                    hideError: true
-                },
-                function(data) {
-                    // create editing textarea
-                    $('#edit-comment-loading-' + id).remove();
-                    that.data('editing', true);
-                    $('#ct-' + no)
-                        .after(
-                            $('`);
+    this.$textarea = this.$body.nextElementSibling;
+    this.$textarea.style.height = `${this.$textarea.scrollHeight}px`;
+
+    // Insert a toolbar that provides the Save and Cancel buttons as well as the Hide This Revision checkbox for admin
+    this.$textarea.insertAdjacentHTML('afterend',
+      `
+      
+      `
+    );
+    this.$toolbar = this.$textarea.nextElementSibling;
+
+    this.$save_button = this.$toolbar.querySelector('button[data-action="save"]');
+    this.$cancel_button = this.$toolbar.querySelector('button[data-action="cancel"]');
+    this.$is_hidden_checkbox = this.$toolbar.querySelector('input[type="checkbox"]');
+
+    this.$textarea.addEventListener('input', event => this.textarea_oninput(event));
+    this.$textarea.addEventListener('keydown', event => this.textarea_onkeydown(event));
+    this.$save_button.addEventListener('click', () => this.save());
+    this.$cancel_button.addEventListener('click', () => this.finish());
+
+    // Retrieve the raw comment text
+    bugzilla_ajax({
+      url: `${BUGZILLA.config.basepath}rest/editcomments/comment/${this.comment_id}`,
+      hideError: true,
+    }, data => {
+      this.fetch_onload(data);
+    }, message => {
+      this.fetch_onerror(message);
+    });
+  }
+
+  /**
+   * Called whenever the comment `