Implemented update and delete for posts
This commit is contained in:
@@ -45,6 +45,7 @@ public class PostService
|
|||||||
{
|
{
|
||||||
post.Title = title;
|
post.Title = title;
|
||||||
post.Content = content;
|
post.Content = content;
|
||||||
|
post.UpdatedAt = DateTime.UtcNow;
|
||||||
|
|
||||||
PostRepository.Update(post);
|
PostRepository.Update(post);
|
||||||
|
|
||||||
@@ -53,6 +54,30 @@ public class PostService
|
|||||||
|
|
||||||
public async Task Delete(Post post)
|
public async Task Delete(Post post)
|
||||||
{
|
{
|
||||||
|
var postWithData = PostRepository
|
||||||
|
.Get()
|
||||||
|
.Include(x => x.Comments)
|
||||||
|
.Include(x => x.Likes)
|
||||||
|
.First(x => x.Id == post.Id);
|
||||||
|
|
||||||
|
// Cache relational data to delete later on
|
||||||
|
var likes = postWithData.Likes.ToArray();
|
||||||
|
var comments = postWithData.Comments.ToArray();
|
||||||
|
|
||||||
|
// Clear relations
|
||||||
|
postWithData.Comments.Clear();
|
||||||
|
postWithData.Likes.Clear();
|
||||||
|
|
||||||
|
PostRepository.Update(postWithData);
|
||||||
|
|
||||||
|
// Delete relational data
|
||||||
|
foreach (var like in likes)
|
||||||
|
PostLikeRepository.Delete(like);
|
||||||
|
|
||||||
|
foreach (var comment in comments)
|
||||||
|
PostCommentRepository.Delete(comment);
|
||||||
|
|
||||||
|
// Now delete the post itself
|
||||||
PostRepository.Delete(post);
|
PostRepository.Delete(post);
|
||||||
await Events.OnPostDeleted.InvokeAsync(post);
|
await Events.OnPostDeleted.InvokeAsync(post);
|
||||||
}
|
}
|
||||||
@@ -67,7 +92,7 @@ public class PostService
|
|||||||
if (content.Length > 1024)
|
if (content.Length > 1024)
|
||||||
throw new DisplayException("Comment content cannot be longer than 1024 characters");
|
throw new DisplayException("Comment content cannot be longer than 1024 characters");
|
||||||
|
|
||||||
if (!Regex.IsMatch(content, "^[a-zA-Z0-9äöüßÄÖÜẞ,.;_\\n\\t-]+$"))
|
if (!Regex.IsMatch(content, "^[ a-zA-Z0-9äöüßÄÖÜẞ,.;_\\n\\t-]+$"))
|
||||||
throw new DisplayException("Illegal characters in comment content");
|
throw new DisplayException("Illegal characters in comment content");
|
||||||
|
|
||||||
//TODO: Swear word filter
|
//TODO: Swear word filter
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
@using Moonlight.App.Database.Entities.Community
|
@using Moonlight.App.Database.Entities.Community
|
||||||
@using Ganss.Xss
|
@using Ganss.Xss
|
||||||
@using Microsoft.EntityFrameworkCore
|
@using Microsoft.EntityFrameworkCore
|
||||||
|
@using Moonlight.App.Models.Enums
|
||||||
@using Moonlight.App.Repositories
|
@using Moonlight.App.Repositories
|
||||||
@using Moonlight.App.Services
|
@using Moonlight.App.Services
|
||||||
@using Moonlight.App.Services.Community
|
@using Moonlight.App.Services.Community
|
||||||
@@ -8,6 +9,7 @@
|
|||||||
@inject Repository<Post> PostRepository
|
@inject Repository<Post> PostRepository
|
||||||
@inject IdentityService IdentityService
|
@inject IdentityService IdentityService
|
||||||
@inject PostService PostService
|
@inject PostService PostService
|
||||||
|
@inject ToastService ToastService
|
||||||
|
|
||||||
<div class="card card-flush">
|
<div class="card card-flush">
|
||||||
<div class="card-header pt-9">
|
<div class="card-header pt-9">
|
||||||
@@ -16,19 +18,29 @@
|
|||||||
<img src="/api/bucket/avatars/@(Post.Author.Avatar)" class="" alt="">
|
<img src="/api/bucket/avatars/@(Post.Author.Avatar)" class="" alt="">
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-grow-1">
|
<div class="flex-grow-1">
|
||||||
<a href="#" class="text-gray-800 text-hover-primary fs-4 fw-bold">@(Post.Author.Username)</a>
|
<span class="text-gray-800 text-hover-primary fs-4 fw-bold">@(Post.Author.Username)</span>
|
||||||
<span class="text-gray-500 fw-semibold d-block">@(Formatter.FormatAgoFromDateTime(Post.CreatedAt))</span>
|
<span class="text-gray-500 fw-semibold d-block">@(Formatter.FormatAgoFromDateTime(Post.CreatedAt))</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-toolbar">
|
|
||||||
|
|
||||||
</div>
|
@if (Post.Author.Id == IdentityService.CurrentUser.Id || IdentityService.Permissions[Permission.AdminCommunity])
|
||||||
|
{
|
||||||
|
<div class="card-toolbar">
|
||||||
|
<a @onclick="DeletePost" @onclick:preventDefault href="#" class="text-danger fw-semibold d-block">Remove post</a>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="fs-6 fw-normal text-gray-700">
|
<div class="fs-6 fw-normal text-gray-700">
|
||||||
@{
|
@if (IsEditing)
|
||||||
|
{
|
||||||
|
<TextEditor @bind-Value="EditContent" InitialContent="@Post.Content" />
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
var sanitizer = new HtmlSanitizer();
|
var sanitizer = new HtmlSanitizer();
|
||||||
var content = sanitizer.Sanitize(Post.Content);
|
var content = sanitizer.Sanitize(Post.Content);
|
||||||
|
|
||||||
@((MarkupString)content)
|
@((MarkupString)content)
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
@@ -49,6 +61,25 @@
|
|||||||
@(LikesCount) Like(s)
|
@(LikesCount) Like(s)
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
@if (Post.Author.Id == IdentityService.CurrentUser.Id || IdentityService.Permissions[Permission.AdminCommunity])
|
||||||
|
{
|
||||||
|
<li class="nav-item">
|
||||||
|
<div class="nav-link pt-0">
|
||||||
|
<a @onclick="() => ToggleEdit()" @onclick:preventDefault href="#" class="btn btn-sm btn-color-gray-600 btn-active-color-warning fw-bold px-4 pe-0 @(IsEditing ? "active" : "")">
|
||||||
|
<i class="bx bx-edit fs-2"></i>
|
||||||
|
@(IsEditing ? "Save" : "Edit")
|
||||||
|
</a>
|
||||||
|
|
||||||
|
@if (IsEditing)
|
||||||
|
{
|
||||||
|
<a @onclick="() => ToggleEdit(true)" @onclick:preventDefault href="#" class="btn btn-sm btn-color-gray-600 btn-active-color-warning fw-bold px-4 ps-2 @(IsEditing ? "active" : "")">
|
||||||
|
<i class="bx bx-x fs-2"></i>
|
||||||
|
Cancel
|
||||||
|
</a>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
}
|
||||||
</ul>
|
</ul>
|
||||||
<div class="separator separator-solid mb-1"></div>
|
<div class="separator separator-solid mb-1"></div>
|
||||||
@if (ShowComments)
|
@if (ShowComments)
|
||||||
@@ -66,6 +97,11 @@
|
|||||||
<div class="d-flex align-items-center flex-wrap mb-0">
|
<div class="d-flex align-items-center flex-wrap mb-0">
|
||||||
<a href="#" class="text-gray-800 text-hover-primary fw-bold me-6">@(comment.Author.Username)</a>
|
<a href="#" class="text-gray-800 text-hover-primary fw-bold me-6">@(comment.Author.Username)</a>
|
||||||
<span class="text-gray-500 fw-semibold fs-7 me-5">@(Formatter.FormatAgoFromDateTime(comment.CreatedAt))</span>
|
<span class="text-gray-500 fw-semibold fs-7 me-5">@(Formatter.FormatAgoFromDateTime(comment.CreatedAt))</span>
|
||||||
|
|
||||||
|
@if (comment.Author.Id == IdentityService.CurrentUser.Id || IdentityService.Permissions[Permission.AdminCommunity])
|
||||||
|
{
|
||||||
|
<a @onclick="() => DeleteComment(comment)" @onclick:preventDefault href="#" class="text-danger fw-semibold fs-7">Remove comment</a>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
<span class="text-gray-800 fs-6 fw-normal pt-1">
|
<span class="text-gray-800 fs-6 fw-normal pt-1">
|
||||||
@(Formatter.FormatLineBreaks(comment.Content))
|
@(Formatter.FormatLineBreaks(comment.Content))
|
||||||
@@ -90,8 +126,8 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="position-relative w-100">
|
<div class="position-relative w-100">
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<textarea @bind="Comment" type="text" class="form-control form-control-solid border ps-5" placeholder="Write your comment.."></textarea>
|
<textarea @bind="Comment" type="text" class="form-control form-control-solid border ps-5" placeholder="Write your comment.." style="height: 1vh"></textarea>
|
||||||
<WButton OnClick="CreateComment" Text="Comment" CssClasses="btn btn-primary" />
|
<WButton OnClick="CreateComment" Text="Comment" CssClasses="btn btn-primary"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -103,6 +139,9 @@
|
|||||||
[Parameter]
|
[Parameter]
|
||||||
public Post Post { get; set; }
|
public Post Post { get; set; }
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public Func<Task>? OnUpdate { get; set; }
|
||||||
|
|
||||||
private int CommentsCount = -1;
|
private int CommentsCount = -1;
|
||||||
private int LikesCount = -1;
|
private int LikesCount = -1;
|
||||||
private bool HasLiked = false;
|
private bool HasLiked = false;
|
||||||
@@ -111,6 +150,10 @@
|
|||||||
private PostComment[] Comments = Array.Empty<PostComment>();
|
private PostComment[] Comments = Array.Empty<PostComment>();
|
||||||
private string Comment = "";
|
private string Comment = "";
|
||||||
|
|
||||||
|
private bool IsEditing = false;
|
||||||
|
private string EditTitle = "";
|
||||||
|
private string EditContent = "";
|
||||||
|
|
||||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||||
{
|
{
|
||||||
if (firstRender)
|
if (firstRender)
|
||||||
@@ -161,6 +204,7 @@
|
|||||||
|
|
||||||
Comment = "";
|
Comment = "";
|
||||||
ShowComments = true;
|
ShowComments = true;
|
||||||
|
await LoadComments(null!);
|
||||||
await InvokeAsync(StateHasChanged);
|
await InvokeAsync(StateHasChanged);
|
||||||
await UpdateCounts();
|
await UpdateCounts();
|
||||||
}
|
}
|
||||||
@@ -169,8 +213,11 @@
|
|||||||
{
|
{
|
||||||
await PostService.DeleteComment(Post, comment);
|
await PostService.DeleteComment(Post, comment);
|
||||||
|
|
||||||
|
await LoadComments(null!);
|
||||||
await InvokeAsync(StateHasChanged);
|
await InvokeAsync(StateHasChanged);
|
||||||
await UpdateCounts();
|
await UpdateCounts();
|
||||||
|
|
||||||
|
await ToastService.Success("Successfully deleted comment");
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task ToggleComments()
|
private async Task ToggleComments()
|
||||||
@@ -187,4 +234,31 @@
|
|||||||
await PostService.ToggleLike(Post, IdentityService.CurrentUser);
|
await PostService.ToggleLike(Post, IdentityService.CurrentUser);
|
||||||
await UpdateCounts();
|
await UpdateCounts();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task ToggleEdit(bool preventSaving = false)
|
||||||
|
{
|
||||||
|
IsEditing = !IsEditing;
|
||||||
|
|
||||||
|
if (IsEditing)
|
||||||
|
{
|
||||||
|
EditTitle = Post.Title;
|
||||||
|
EditContent = Post.Content;
|
||||||
|
}
|
||||||
|
else if (!preventSaving)
|
||||||
|
{
|
||||||
|
await PostService.Update(Post, EditTitle, EditContent);
|
||||||
|
await ToastService.Success("Successfully saved post");
|
||||||
|
}
|
||||||
|
|
||||||
|
await InvokeAsync(StateHasChanged);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task DeletePost()
|
||||||
|
{
|
||||||
|
await PostService.Delete(Post);
|
||||||
|
await ToastService.Success("Successfully deleted post");
|
||||||
|
|
||||||
|
if (OnUpdate != null)
|
||||||
|
await OnUpdate.Invoke();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -8,10 +8,10 @@
|
|||||||
@inject Repository<Post> PostRepository
|
@inject Repository<Post> PostRepository
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<LazyLoader Load="Load">
|
<LazyLoader @ref="LazyLoader" Load="Load">
|
||||||
@foreach (var post in Posts)
|
@foreach (var post in Posts)
|
||||||
{
|
{
|
||||||
<PostView Post="post" />
|
<PostView Post="post" OnUpdate="() => LazyLoader.Reload()" />
|
||||||
<div class="mb-10"></div>
|
<div class="mb-10"></div>
|
||||||
}
|
}
|
||||||
</LazyLoader>
|
</LazyLoader>
|
||||||
@@ -19,6 +19,7 @@
|
|||||||
|
|
||||||
@code
|
@code
|
||||||
{
|
{
|
||||||
|
private LazyLoader LazyLoader;
|
||||||
private Post[] Posts;
|
private Post[] Posts;
|
||||||
|
|
||||||
private Task Load(LazyLoader _)
|
private Task Load(LazyLoader _)
|
||||||
|
|||||||
Reference in New Issue
Block a user