Added ability for authors to choose their reviewer if there are multiple

This commit is contained in:
Mia Rose Winter 2024-03-21 10:38:51 +01:00
parent 3b64009ada
commit afe9627438
Signed by: miawinter
GPG key ID: 4B6F6A83178F595E
7 changed files with 100 additions and 29 deletions

View file

@ -81,12 +81,12 @@
</section> </section>
} }
<div class="flex gap-2 flex-wrap"> <div class="flex gap-2 flex-wrap w-full">
@if (string.IsNullOrWhiteSpace(Article.Author.AboutTheAuthor)) { @if (string.IsNullOrWhiteSpace(Article.Author.AboutTheAuthor)) {
<ProfilePill Profile="Article.Author" RoleTag="@Localizer["Author"]"/> <ProfilePill Profile="Article.Author" RoleTag="@Localizer["Author"]"/>
} }
@if (Article.Reviewer is not null && Article.Reviewer.Id != Article.Author.Id) { @if (Article.Reviewer is not null && Article.Reviewer.Id != Article.Author.Id) {
<ProfilePill Profile="Article.Reviewer" RoleTag="@Localizer["Reviewer"]"/> <ProfilePill Profile="Article.Reviewer" RoleTag="@Localizer["Reviewer"]" DisableProfileLink="true" />
} }
</div> </div>

View file

@ -60,10 +60,37 @@
<a class="btn btn-info w-full sm:btn-wide" href="article/@Article!.Id/edit" <a class="btn btn-info w-full sm:btn-wide" href="article/@Article!.Id/edit"
data-enhance-nav="false">@Localizer["Edit"]</a> data-enhance-nav="false">@Localizer["Edit"]</a>
} }
@if (article.AllowedToDelete(HttpContext.User)) {
<a class="btn btn-error w-full sm:btn-wide" href="/article/@article.Id/delete">
@Localizer["Delete_Submit"]
</a>
}
@if (article.AllowedToSubmitForReview(HttpContext.User)) { @if (article.AllowedToSubmitForReview(HttpContext.User)) {
<form @formname="submit-for-review" method="post" @onsubmit="SubmitForReview" class="max-sm:w-full"> <form @formname="submit-for-review" method="post" @onsubmit="SubmitForReview" class="max-sm:w-full">
<AntiforgeryToken/> <AntiforgeryToken/>
<button type="submit" class="btn btn-primary w-full sm:btn-wide">@Localizer["Review_Submit"]</button>
@if (Reviewers.Count > 1) {
<div class="join join-vertical md:join-horizontal w-full">
<button type="submit" class="btn btn-primary flex-1 sm:btn-wide join-item">
@Localizer["Review_Submit"]
</button>
<InputSelect @bind-Value="ReviewerId" class="select select-bordered select-primary join-item">
<option value="" selected>@Localizer["Review_Reviewer_Any"]</option>
@foreach (var reviewer in Reviewers) {
if (reviewer.FullName is null) continue;
<option value="@reviewer.Id">
@reviewer.Name
</option>
}
</InputSelect>
</div>
} else {
<button type="submit" class="btn btn-primary w-full sm:btn-wide">
@Localizer["Review_Submit"]
</button>
}
</form> </form>
} }
@if (article.AllowedToPublish(HttpContext.User)) { @if (article.AllowedToPublish(HttpContext.User)) {
@ -80,11 +107,6 @@
} }
</form> </form>
} }
@if (article.AllowedToDelete(HttpContext.User)) {
<a class="btn btn-error w-full sm:btn-wide" href="/article/@article.Id/delete">
@Localizer["Delete_Submit"]
</a>
}
</div> </div>
} }
</ChildContent> </ChildContent>
@ -127,9 +149,14 @@
[Parameter, SupplyParameterFromForm(FormName = "submit-for-publish")] [Parameter, SupplyParameterFromForm(FormName = "submit-for-publish")]
public bool PublishSilently { get; set; } public bool PublishSilently { get; set; }
[Parameter, SupplyParameterFromForm(FormName = "submit-for-review")]
public string ReviewerId { get; set; } = string.Empty;
[CascadingParameter] [CascadingParameter]
public HttpContext HttpContext { get; set; } = default!; public HttpContext HttpContext { get; set; } = default!;
private List<ApplicationUser> Reviewers { get; } = [];
private Article GetArticle(ClaimsPrincipal principal) { private Article GetArticle(ClaimsPrincipal principal) {
if (Article.AllowedToRead(principal)) return Article!; if (Article.AllowedToRead(principal)) return Article!;
@ -159,24 +186,45 @@
} }
} }
protected override async Task OnInitializedAsync() {
Reviewers.AddRange([
.. await UserManager.GetUsersInRoleAsync("Reviewer"),
.. await UserManager.GetUsersInRoleAsync("Admin")
]);
}
private async Task SubmitForReview() { private async Task SubmitForReview() {
if (Article.AllowedToSubmitForReview(HttpContext.User) is false) return; if (Article.AllowedToSubmitForReview(HttpContext.User) is false) return;
await using var context = await ContextFactory.CreateDbContextAsync(); await using var context = await ContextFactory.CreateDbContextAsync();
Article!.Status = ArticleStatus.InReview; Article!.Status = ArticleStatus.InReview;
if (!string.IsNullOrWhiteSpace(ReviewerId)) {
Article.Reviewer = Reviewers.First(r => r.Id == ReviewerId);
}
context.Update(Article); context.Update(Article);
await context.SaveChangesAsync(); await context.SaveChangesAsync();
Message.ShowSuccess(Localizer["Submit_Review_Success"]); Message.ShowSuccess(Localizer["Submit_Review_Success"]);
try { try {
List<ApplicationUser> reviewers = [ if (Reviewers.Count > 0) {
.. await UserManager.GetUsersInRoleAsync("Reviewer"),
.. await UserManager.GetUsersInRoleAsync("Admin")
];
if (reviewers.Count > 0) {
await EmailService.ConnectAsync(CancellationToken.None); await EmailService.ConnectAsync(CancellationToken.None);
foreach (var reviewer in reviewers) { if (!string.IsNullOrWhiteSpace(ReviewerId) && Article.Reviewer is not null) {
if (Article.Reviewer.Id != HttpContext.User.FindFirst("Id")!.Value) {
var email = await Email.CreateDefaultEmail(
Article.Reviewer.Email!,
Article.Reviewer.Name,
"Article submitted for Review",
"Article submitted for Review",
$"<p>The Article '{Article.Title}' by {Article.Author.Name} has been submitted for review.</p>",
$"The Article '{Article.Title}' by {Article.Author.Name} has been submitted for review.");
// TODO check if they enabled email notifications (property currently not implemented)
await EmailService.SendEmailAsync(email);
}
} else {
foreach (var reviewer in Reviewers) {
if (reviewer.Id == HttpContext.User.FindFirst("Id")!.Value) continue; if (reviewer.Id == HttpContext.User.FindFirst("Id")!.Value) continue;
var email = await Email.CreateDefaultEmail( var email = await Email.CreateDefaultEmail(
@ -189,6 +237,7 @@
// TODO check if they enabled email notifications (property currently not implemented) // TODO check if they enabled email notifications (property currently not implemented)
await EmailService.SendEmailAsync(email); await EmailService.SendEmailAsync(email);
} }
}
await EmailService.DisconnectAsync(CancellationToken.None); await EmailService.DisconnectAsync(CancellationToken.None);
} }

View file

@ -1,7 +1,7 @@
@using Wave.Data @using Wave.Data
@if (!DisableProfileLink) { @if (!DisableProfileLink) {
<a href="/profile/@Profile.Id"> <a href="/profile/@Profile.Id" class="w-full sm:w-56">
<div class="rounded bg-base-200 text-base-content flex content-center w-full sm:w-56"> <div class="rounded bg-base-200 text-base-content flex content-center w-full sm:w-56">
<div class="w-16 h-16"><ProfilePictureComponent Size="200" ProfileId="@Profile.Id" /></div> <div class="w-16 h-16"><ProfilePictureComponent Size="200" ProfileId="@Profile.Id" /></div>
<div class="flex flex-col p-2"> <div class="flex flex-col p-2">

View file

@ -149,4 +149,7 @@
<data name="Delete_Warning" xml:space="preserve"> <data name="Delete_Warning" xml:space="preserve">
<value>Ihr Artikel wird runtergenommen. Bitte beachten Sie das Suchmaschinen eventuell noch links zu diesem Artikel haben, welche dann eine Fehlernachricht produzieren. Der Artikel ist womöglich noch in manchen Caches für die nächsten paar Stunden auffindbar. </value> <value>Ihr Artikel wird runtergenommen. Bitte beachten Sie das Suchmaschinen eventuell noch links zu diesem Artikel haben, welche dann eine Fehlernachricht produzieren. Der Artikel ist womöglich noch in manchen Caches für die nächsten paar Stunden auffindbar. </value>
</data> </data>
<data name="Review_Reviewer_Any" xml:space="preserve">
<value>Jeder</value>
</data>
</root> </root>

View file

@ -149,4 +149,7 @@
<data name="Delete_Warning" xml:space="preserve"> <data name="Delete_Warning" xml:space="preserve">
<value>This will take down your Article. Please keep in mind that search engines may still have links to this article, which will produce error messages. The Article may still be accessable in some caches the next couple of hours.</value> <value>This will take down your Article. Please keep in mind that search engines may still have links to this article, which will produce error messages. The Article may still be accessable in some caches the next couple of hours.</value>
</data> </data>
<data name="Review_Reviewer_Any" xml:space="preserve">
<value>Anyone</value>
</data>
</root> </root>

View file

@ -48,8 +48,16 @@ public static class Permissions {
// Reviewers can edit in-review articles // Reviewers can edit in-review articles
if (article.Status is ArticleStatus.InReview && principal.IsInRole("Reviewer")) { if (article.Status is ArticleStatus.InReview && principal.IsInRole("Reviewer")) {
// Nobody is reviewing this article yet
if (article.Reviewer is null || article.Reviewer.Id == article.Author.Id) {
return true; return true;
} }
// This reviewer is the Reviewer of the article
if (article.Reviewer?.Id == principal.FindFirst("Id")!.Value) {
return true;
}
return false;
}
// Moderators can edit published/-ing articles // Moderators can edit published/-ing articles
if (article.Status is ArticleStatus.Published && principal.IsInRole("Moderator")) { if (article.Status is ArticleStatus.Published && principal.IsInRole("Moderator")) {
@ -107,8 +115,16 @@ public static class Permissions {
// Reviewers can reject/delete in-review articles // Reviewers can reject/delete in-review articles
if (article.Status is ArticleStatus.InReview && principal.IsInRole("Reviewer")) { if (article.Status is ArticleStatus.InReview && principal.IsInRole("Reviewer")) {
// Nobody is reviewing this article yet
if (article.Reviewer is null || article.Reviewer.Id == article.Author.Id) {
return true; return true;
} }
// This reviewer is the Reviewer of the article
if (article.Reviewer?.Id == principal.FindFirst("Id")!.Value) {
return true;
}
return false;
}
// Moderators can take down articles // Moderators can take down articles
if (article.Status is ArticleStatus.Published && principal.IsInRole("Moderator")) { if (article.Status is ArticleStatus.Published && principal.IsInRole("Moderator")) {

File diff suppressed because one or more lines are too long