diff --git a/lib/MusicBrainz/Server/Edit.pm b/lib/MusicBrainz/Server/Edit.pm
index 7c19e782b27..c372f1e16d4 100644
--- a/lib/MusicBrainz/Server/Edit.pm
+++ b/lib/MusicBrainz/Server/Edit.pm
@@ -7,7 +7,7 @@ use MusicBrainz::Server::Data::Utils qw( boolean_to_json datetime_to_iso8601 );
use MusicBrainz::Server::Edit::Exceptions;
use MusicBrainz::Server::Edit::Utils qw( edit_status_name );
use MusicBrainz::Server::Entity::Types;
-use MusicBrainz::Server::Entity::Util::JSON qw( to_json_array );
+use MusicBrainz::Server::Entity::Util::JSON qw( add_linked_entity to_json_array );
use MusicBrainz::Server::Constants qw(
:edit_status
:expire_action
@@ -139,19 +139,6 @@ sub no_votes {
scalar shift->_grep_votes(sub { $_->vote == $VOTE_NO && !$_->superseded });
}
-sub votes_for_editor
-{
- my ($self, $editor_id) = @_;
- $self->_grep_votes(sub { $_->editor_id == $editor_id });
-}
-
-sub latest_vote_for_editor
-{
- my ($self, $editor_id) = @_;
- my @votes = $self->votes_for_editor($editor_id) or return;
- return $votes[-1];
-}
-
sub is_open
{
return shift->status == $STATUS_OPEN;
@@ -233,15 +220,6 @@ sub editor_may_cancel {
&& $self->editor_id == $editor->id;
}
-sub was_approved
-{
- my $self = shift;
-
- return 0 if $self->is_open;
-
- return scalar $self->_grep_votes(sub { $_->vote == $VOTE_APPROVE })
-}
-
sub approval_requires_comment {
my ($self, $editor) = @_;
@@ -319,9 +297,12 @@ sub initialize
sub TO_JSON {
my ($self) = @_;
+ add_linked_entity('editor', $self->editor_id, $self->editor);
+
my $can_preview = $self->does('MusicBrainz::Server::Edit::Role::Preview');
my $conditions = $self->edit_conditions;
return {
+ auto_edit => boolean_to_json($self->auto_edit),
close_time => datetime_to_iso8601($self->close_time),
conditions => {
duration => $conditions->{duration} + 0,
@@ -333,6 +314,7 @@ sub TO_JSON {
display_data => $self->display_data,
data => $self->data,
edit_kind => $self->edit_kind,
+ edit_name => $self->edit_name,
edit_notes => to_json_array($self->edit_notes),
edit_type => $self->edit_type + 0,
editor_id => $self->editor_id + 0,
diff --git a/root/components/common-macros.tt b/root/components/common-macros.tt
index 5c6ffcb0277..9482cea8b62 100644
--- a/root/components/common-macros.tt
+++ b/root/components/common-macros.tt
@@ -494,11 +494,6 @@ END -%]
INCLUDE _link_other_entity content=text action=action type='user' default_content=editor.name avatar=image_url image_size=size;
END -%]
-[%~ MACRO editor_type_info(editor) BLOCK; -%]
- [% IF editor.is_limited; '(' _ l('beginner') _ ')'; END %]
- [% IF editor.is_bot; '(' _ l('bot') _ ')'; END %]
-[%- END -%]
-
[%~ MACRO link_edit(edit, action, text) BLOCK; # Converted to React at root/static/scripts/common/components/EditLink.js
INCLUDE _link_other_entity content=text action=action type='edit' default_content=edit.id;
END -%]
@@ -611,7 +606,7 @@ END -%]
CASE 2; l('High');
END -%]
-[%~ MACRO edit_status_class(edit) BLOCK ~%]
+[%~ MACRO edit_status_class(edit) BLOCK # Converted to React as getEditStatusClass at root/utility/edit.js ~%]
[%~ IF edit.status == 1 ~%]
[%- " open" -%]
[%~ ELSIF edit.status == 2 ~%]
@@ -625,7 +620,7 @@ END -%]
[%~ END ~%]
[%~ END ~%]
-[%~ MACRO vote_tally(edit) BLOCK -%]
+[%~ MACRO vote_tally(edit) BLOCK # Converted to React at root/edit/components/VoteTally.js -%]
[%- IF edit.auto_edit; '' _ l('automatically applied') _ '';
ELSE; l('{yes} yes : {no} no',
{ yes => '' _ edit.yes_votes _ '',
@@ -633,7 +628,7 @@ END -%]
END -%]
[%- END -%]
-[%~ MACRO css_class_name(name) BLOCK;
+[%~ MACRO css_class_name(name) BLOCK; # React equivalent is kebabCase at root/static/scripts/common/utility/strings.js
name | lower | replace('[^a-z]+', '-') | remove('^-|-$');
END -%]
diff --git a/root/edit/components/EditHeader.js b/root/edit/components/EditHeader.js
new file mode 100644
index 00000000000..da135784c97
--- /dev/null
+++ b/root/edit/components/EditHeader.js
@@ -0,0 +1,227 @@
+/*
+ * @flow strict-local
+ * Copyright (C) 2021 MetaBrainz Foundation
+ *
+ * This file is part of MusicBrainz, the open internet music database,
+ * and is licensed under the GPL version 2, or (at your option) any
+ * later version: http://www.gnu.org/licenses/gpl-2.0.txt
+ */
+
+import * as React from 'react';
+
+import {EDIT_VOTE_APPROVE} from '../../constants';
+import RequestLogin from '../../components/RequestLogin';
+import VotingPeriod from '../../components/VotingPeriod';
+import linkedEntities from '../../static/scripts/common/linkedEntities';
+import EditLink from '../../static/scripts/common/components/EditLink';
+import EditorLink from '../../static/scripts/common/components/EditorLink';
+import bracketed from '../../static/scripts/common/utility/bracketed';
+import {isBot} from '../../static/scripts/common/utility/privileges';
+import {kebabCase} from '../../static/scripts/common/utility/strings';
+import getVoteName from '../../static/scripts/edit/utility/getVoteName';
+import {
+ editorMayApprove,
+ editorMayCancel,
+ getEditStatusClass,
+ getLatestVoteForEditor,
+} from '../../utility/edit';
+import formatUserDate from '../../utility/formatUserDate';
+import {returnToCurrentPage} from '../../utility/returnUri';
+
+import VoteTally from './VoteTally';
+
+type Props = {
+ +$c: CatalystContextT,
+ +edit: {...EditT, +id: number},
+ +isSummary?: boolean,
+ +voter?: UnsanitizedEditorT,
+};
+
+const EditorTypeInfo = ({editor}: {editor: EditorT}) => (
+ editor.is_limited ? (
+
+ {bracketed(
+
+ {l('beginner')}
+ ,
+ )}
+
+ ) : isBot(editor) ? (
+
+ {bracketed(
+
+ {l('bot')}
+ ,
+ )}
+
+ ) : null
+);
+
+const EditHeader = ({
+ $c,
+ edit,
+ isSummary = false,
+ voter,
+}: Props): React.Element<'div'> => {
+ const user = $c.user;
+ const mayApprove = editorMayApprove(edit, user);
+ const mayCancel = editorMayCancel(edit, user);
+ const editTitle = texp.l(
+ 'Edit #{id} - {name}',
+ {id: edit.id, name: l(edit.edit_name)},
+ );
+ const editEditor = linkedEntities.editor[edit.editor_id];
+ const isEditEditor = user ? user.id === edit.editor_id : false;
+ const isVoter = user && voter && user.id === voter.id;
+ const latestVoteForEditor = user
+ ? getLatestVoteForEditor(edit, user)
+ : null;
+ const latestVoteForEditorName = latestVoteForEditor
+ ? getVoteName(latestVoteForEditor.vote)
+ : null;
+ const latestVoteForVoter = voter
+ ? getLatestVoteForEditor(edit, voter)
+ : null;
+ const latestVoteForVoterName = latestVoteForVoter
+ ? getVoteName(latestVoteForVoter.vote)
+ : null;
+ const editWasApproved = !edit.is_open && edit.votes.some(
+ (vote) => vote.vote === EDIT_VOTE_APPROVE,
+ );
+ const showVoteTally = latestVoteForEditor || isEditEditor || !edit.is_open;
+
+ return (
+
+ {isSummary ? (
+ <>
+
+
+
+
+ {voter && isVoter === false ? (
+
+ {l('Their vote: ')}
+ {nonEmpty(latestVoteForVoterName)
+ ? lp(latestVoteForVoterName, 'vote')
+ : null}
+
+ ) : user ? (
+
+ {l('My vote: ')}
+ {nonEmpty(latestVoteForEditorName) ? (
+ lp(latestVoteForEditorName, 'vote')
+ ) : isEditEditor ? (
+ l('N/A')
+ ) : l('None')}
+
+ ) : null}
+ |
+
+ {showVoteTally ? (
+
+ {user ? null : (
+ <>
+ {addColon(l('Vote tally'))}
+ {' '}
+ >
+ )}
+
+
+ ) : null}
+ |
+
+
+
+ {edit.is_open ? (
+ <>
+ {addColon(l('Voting'))}
+ {' '}
+
+ >
+ ) : editWasApproved ? (
+ <>
+ {addColon(l('Approved'))}
+ {' '}
+ {formatUserDate($c, edit.close_time)}
+ >
+ ) : (
+ <>
+ {addColon(l('Closed'))}
+ {' '}
+ {formatUserDate($c, edit.close_time)}
+ >
+ )}
+ |
+
+
+
+
+
+
+ >
+ ) : (
+ <>
+ {user && (mayApprove || mayCancel) ? (
+
+ ) : null}
+
{editTitle}
+ >
+ )}
+
+
+ {'~'}
+ {user ? (
+ <>
+ {exp.l(
+ 'Edit by {editor}',
+ {editor: },
+ )}
+
+ >
+ ) : (
+ <>
+ {l('Editor hidden')}
+ {' '}
+ {/* Show editor type since knowing it's, say, a bot is useful */}
+
+ {' '}
+ {bracketed(
+ ,
+ )}
+ >
+ )}
+
+
+ );
+};
+
+export default EditHeader;
diff --git a/root/edit/components/VoteTally.js b/root/edit/components/VoteTally.js
new file mode 100644
index 00000000000..d3197651c7b
--- /dev/null
+++ b/root/edit/components/VoteTally.js
@@ -0,0 +1,43 @@
+/*
+ * @flow strict
+ * Copyright (C) 2021 MetaBrainz Foundation
+ *
+ * This file is part of MusicBrainz, the open internet music database,
+ * and is licensed under the GPL version 2, or (at your option) any
+ * later version: http://www.gnu.org/licenses/gpl-2.0.txt
+ */
+
+import {
+ EDIT_VOTE_YES,
+ EDIT_VOTE_NO,
+} from '../../constants';
+
+function countVotes(votes, voteValue): number {
+ return votes.reduce(
+ (count, vote) => {
+ return count + ((vote.vote === voteValue && !vote.superseded) ? 1 : 0);
+ },
+ 0,
+ );
+}
+
+const VoteTally = ({edit}: {edit: EditT}): Expand2ReactOutput => {
+ if (edit.auto_edit) {
+ return {l('automatically applied')};
+ }
+
+ const yesVotes = countVotes(edit.votes, EDIT_VOTE_YES);
+ const noVotes = countVotes(edit.votes, EDIT_VOTE_NO);
+
+ return (
+ exp.l(
+ '{yes} yes : {no} no',
+ {
+ no: {noVotes},
+ yes: {yesVotes},
+ },
+ )
+ );
+};
+
+export default VoteTally;
diff --git a/root/edit/edit_header.tt b/root/edit/edit_header.tt
deleted file mode 100644
index d6edb17d095..00000000000
--- a/root/edit/edit_header.tt
+++ /dev/null
@@ -1,90 +0,0 @@
-
diff --git a/root/edit/index.tt b/root/edit/index.tt
index cfdd8e4089b..9fe1601911b 100644
--- a/root/edit/index.tt
+++ b/root/edit/index.tt
@@ -1,8 +1,12 @@
[%- WRAPPER 'layout.tt' title=l('Edit #{id}', { id => edit.id }) -%]
- [% INCLUDE 'edit/edit_header.tt' %]
[%- edit_json_obj = React.to_json_object(edit) -%]
+ [%- edit_json_obj_no_data = {} -%]
+ [%- edit_json_obj_no_data.import(edit_json_obj) -%]
+ [%- edit_json_obj_no_data.delete('display_data') -%]
+
+ [%~ React.embed(c, "edit/components/EditHeader.js", {edit => edit_json_obj_no_data}) ~%]
[% l('Changes') %]
[% IF edit.data.defined %]
@@ -11,7 +15,6 @@
[% ELSE %]
[%-INCLUDE "edit/details/${edit.edit_template}.tt" %]
[% END %]
- [%- edit_json_obj.delete('display_data') -%]
[% ELSE %]
[% l('An error occurred while loading this edit.') %]
[% link_edit(edit, 'data', l('Raw edit data may be available.')) %]
@@ -32,7 +35,7 @@
[% l('My vote:') %] |
- [% React.embed(c, 'edit/components/Vote', {edit => edit_json_obj}) %]
+ [% React.embed(c, 'edit/components/Vote', {edit => edit_json_obj_no_data}) %]
|
[% END %]
@@ -75,7 +78,7 @@
[% l('Edit notes') %]
[%~ IF c.user_exists ~%]
- [%- React.embed(c, 'edit/components/EditNotes', {edit => edit_json_obj, index => 0, isOnEditPage => 1}) -%]
+ [%- React.embed(c, 'edit/components/EditNotes', {edit => edit_json_obj_no_data, index => 0, isOnEditPage => 1}) -%]
[%- IF edit.editor_may_vote(c.user) -%]
[%- form_submit(l('Submit vote and note')) -%]
[%- ELSIF edit.editor_may_add_note(c.user) -%]
@@ -92,6 +95,6 @@
[%- IF !full_width -%]
- [%- React.embed(c, 'edit/components/EditSidebar', {edit => edit_json_obj}) -%]
+ [%- React.embed(c, 'edit/components/EditSidebar', {edit => edit_json_obj_no_data}) -%]
[%- END -%]
[%- END -%]
diff --git a/root/edit/list.tt b/root/edit/list.tt
index 0cf36d90bd7..03a076f030c 100644
--- a/root/edit/list.tt
+++ b/root/edit/list.tt
@@ -46,16 +46,18 @@