diff --git a/src/Blogifier.Admin/Pages/Blog/CategoriesView.razor b/src/Blogifier.Admin/Pages/Blog/CategoriesView.razor index 166a11186..8c978d023 100644 --- a/src/Blogifier.Admin/Pages/Blog/CategoriesView.razor +++ b/src/Blogifier.Admin/Pages/Blog/CategoriesView.razor @@ -6,8 +6,6 @@ @inject IJSRuntime _jsruntime @inject IToaster _toaster @inject NavigationManager _navigation -@inject BlogStateProvider _stateprovider -@implements IDisposable @@ -19,9 +17,9 @@ { CheckAll(EventArgs.Value); }" class="list-check-input form-check-input"> - @_localizer["New Category"] + @*@_localizer["New Category"]*@ - @if (Posts != null && Posts.Count > 0) + @if (Categories != null && Categories.Count > 0) { await RunAction(GroupAction.Delete))" class="btn btn-link text-danger" title="@_localizer["delete"]"> @@ -41,29 +39,26 @@ - await SearchPosts())" class="list-search-button" type="button"> + await SearchCategories())" class="list-search-button" type="button"> @_localizer["search"] - @if (Posts != null && Posts.Count > 0) + @if (Categories != null && Categories.Count > 0) { - - - @{ - string pubDate = post.Published > DateTime.MinValue ? post.Published.ToFriendlyShortDateString() : "Draft"; - string pubStatus = post.Published > DateTime.MinValue ? "published" : "draft"; - } - + - + - - @post.Title + await ShowEdit(category.Id)" @onclick:preventDefault> + @category.Category - + + @category.PostCount + + @@ -85,167 +80,117 @@ } + @if(IsEdit && CurrentCategory != null) + { - @_localizer["New Category"] + @_localizer["Edit Category"] @_localizer["title"] - + - + @* @_localizer["description"] - + *@ - @_localizer["save"] - @_localizer["cancel"] + @_localizer["save"] + @_localizer["cancel"] + } + @code { - protected List Posts { get; set; } - protected Author Author { get; set; } - - protected string SearchTerm { get; set; } - - protected string FilterLabel { get; set; } - protected PublishedStatus FilterValue { get; set; } - - protected string PostTypeLabel { get; set; } - protected string PostTypeButton { get; set; } - - protected override async Task OnInitializedAsync() - { - Author = await _http.GetFromJsonAsync("api/author/getcurrent"); - - FilterLabel = _localizer["all"]; - PostTypeLabel = _localizer["posts"]; - PostTypeButton = _localizer["post"]; - - _stateprovider.OnChange += StateHasChanged; - _stateprovider.PostType = PostType.Post; - - await Load(); - } - - protected async Task Load() - { - Posts = await _http.GetFromJsonAsync>($"api/post/list/{FilterValue}/{PostType.Page}"); - } - - public void CheckAll(object checkValue) - { - bool isChecked = (bool)checkValue; - Posts.ForEach(p => p.Selected = isChecked); - StateHasChanged(); - } - - public async Task RunAction(GroupAction action) - { - string confirm = _localizer["confirm-delete"]; - bool confirmed = false; - - if (action == GroupAction.Delete) - { - confirmed = await _jsruntime.InvokeAsync("confirm", confirm); - if (!confirmed) - return; - } - - foreach (var post in Posts) - { - if (post.Selected) - { - switch (action) - { - case GroupAction.Publish: - await _http.PutAsJsonAsync($"api/post/publish/{post.Id}", true); - break; - case GroupAction.Unpublish: - await _http.PutAsJsonAsync($"api/post/publish/{post.Id}", false); - break; - case GroupAction.Delete: - await _http.DeleteAsync($"api/post/{post.Id}"); - break; - } - } - } - await Load(); - } - - protected async Task SearchKeyPress(KeyboardEventArgs e) - { - if (e.Key == "Enter") - await SearchPosts(); - } - - protected async Task SearchPosts() - { - if (string.IsNullOrEmpty(SearchTerm)) - SearchTerm = "*"; - - Posts = await _http.GetFromJsonAsync>($"api/post/list/search/{SearchTerm}"); - SearchTerm = ""; - } - - protected async Task GetPages() - { - PostTypeLabel = _localizer["pages"]; - PostTypeButton = _localizer["page"]; - _stateprovider.SetPostType(PostType.Page); - await Load(); - } - - public async Task Filter(PublishedStatus filter) - { - FilterValue = filter; - switch (filter) - { - case PublishedStatus.Published: - FilterLabel = _localizer["published"]; - break; - case PublishedStatus.Drafts: - FilterLabel = _localizer["draft", true]; - break; - default: - FilterLabel = _localizer["all"]; - break; - } - await Load(); - } - - public async Task Publish(Post post) - { - Toast(await _http.PutAsJsonAsync($"api/post/publish/{post.Id}", (post.Published == DateTime.MinValue))); - await Load(); - } - - protected override async Task OnAfterRenderAsync(bool firstRender) - { - if (firstRender) - { - await _jsruntime.InvokeVoidAsync("commonJsFunctions.setTooltip"); - } - } - - protected void Toast(HttpResponseMessage msg) - { - if (msg.IsSuccessStatusCode) - _toaster.Success(_localizer["completed"]); - else - _toaster.Error(_localizer["generic-error"]); - } - - protected void Open() - { - } - - public void Dispose() - { - _stateprovider.OnChange -= StateHasChanged; - } + protected ToasterComponent Toaster; + protected List Categories { get; set; } + protected Category CurrentCategory { get; set; } + protected string SearchTerm { get; set; } + protected bool IsEdit = false; + + protected override async Task OnInitializedAsync() + { + await Load(); + } + + protected async Task Load() + { + Categories = await _http.GetFromJsonAsync>($"api/category"); + } + + protected async Task ShowEdit(int categoryId) + { + CurrentCategory = await _http.GetFromJsonAsync($"api/category/byId/{categoryId}"); + IsEdit = true; + } + + protected void CancelEdit() + { + CurrentCategory = null; + IsEdit = false; + } + + protected async Task SaveEdit() + { + Toaster.Toast(await _http.PutAsJsonAsync("api/category", CurrentCategory)); + CurrentCategory = null; + IsEdit = false; + await Load(); + } + + public void CheckAll(object checkValue) + { + bool isChecked = (bool)checkValue; + Categories.ForEach(p => p.Selected = isChecked); + StateHasChanged(); + } + + public async Task RunAction(GroupAction action) + { + string confirm = _localizer["confirm-delete"]; + bool confirmed = false; + + if (action == GroupAction.Delete) + { + confirmed = await _jsruntime.InvokeAsync("confirm", confirm); + if (!confirmed) + return; + } + + foreach (var category in Categories) + { + if (category.Selected) + { + await _http.DeleteAsync($"api/category/{category.Id}"); + } + } + await Load(); + } + + protected async Task SearchKeyPress(KeyboardEventArgs e) + { + if (e.Key == "Enter") + await SearchCategories(); + } + + protected async Task SearchCategories() + { + if (string.IsNullOrEmpty(SearchTerm)) + SearchTerm = "*"; + + Categories = await _http.GetFromJsonAsync>($"api/category/{SearchTerm}"); + SearchTerm = ""; + } + + protected override async Task OnAfterRenderAsync(bool firstRender) + { + if (firstRender) + { + await _jsruntime.InvokeVoidAsync("commonJsFunctions.setTooltip"); + } + } } diff --git a/src/Blogifier.Core/Blogifier.Core.csproj b/src/Blogifier.Core/Blogifier.Core.csproj index 3ba3999d0..b16773e08 100644 --- a/src/Blogifier.Core/Blogifier.Core.csproj +++ b/src/Blogifier.Core/Blogifier.Core.csproj @@ -2,7 +2,7 @@ net5.0 - 2.9.0.9 + 2.9.1.0 blogifierdotnet Blogifier is an open-source publishing platform Written in .NET 5.0 and Blazor WebAssembly. Blogifier.Net diff --git a/src/Blogifier.Core/Providers/CategoryProvider.cs b/src/Blogifier.Core/Providers/CategoryProvider.cs index ae375c53a..e9fda6047 100644 --- a/src/Blogifier.Core/Providers/CategoryProvider.cs +++ b/src/Blogifier.Core/Providers/CategoryProvider.cs @@ -10,15 +10,20 @@ namespace Blogifier.Core.Providers { public interface ICategoryProvider { - Task> Categories(); - Task> GetPostCategories(int postId); + Task> Categories(); + Task> SearchCategories(string term); + + Task GetCategory(int categoryId); + Task> GetPostCategories(int postId); + + Task SaveCategory(Category category); Task SaveCategory(string tag); Task AddPostCategory(int postId, string tag); Task SavePostCategories(int postId, List categories); - Task RemoveCategory(int postId, int categoryId); - } + Task RemoveCategory(int categoryId); + } public class CategoryProvider : ICategoryProvider { @@ -29,17 +34,24 @@ public CategoryProvider(AppDbContext db) _db = db; } - public async Task> Categories() - { - var cats = new List(); + public async Task> Categories() + { + var cats = new List(); - if (_db.Posts != null && _db.Posts.Count() > 0) - { - foreach (var pc in _db.PostCategories.Include(pc => pc.Category).AsNoTracking()) - { + if (_db.Posts != null && _db.Posts.Count() > 0) + { + foreach (var pc in _db.PostCategories.Include(pc => pc.Category).AsNoTracking()) + { if (!cats.Exists(c => c.Category.ToLower() == pc.Category.Content.ToLower())) { - cats.Add(new CategoryItem { Category = pc.Category.Content.ToLower(), PostCount = 1 }); + cats.Add(new CategoryItem + { + Selected = false, + Id = pc.CategoryId, + Category = pc.Category.Content.ToLower(), + PostCount = 1, + DateCreated = pc.Category.DateCreated + }); } else { @@ -49,10 +61,27 @@ public async Task> Categories() } } } - return await Task.FromResult(cats); - } + return await Task.FromResult(cats); + } + + public async Task> SearchCategories(string term) + { + var cats = await Categories(); + + if (term == "*") + return cats; - public async Task> GetPostCategories(int postId) + return cats.Where(c => c.Category.ToLower().Contains(term.ToLower())).ToList(); + } + + public async Task GetCategory(int categoryId) + { + return await _db.Categories.AsNoTracking() + .Where(c => c.Id == categoryId) + .FirstOrDefaultAsync(); + } + + public async Task> GetPostCategories(int postId) { return await _db.PostCategories.AsNoTracking() .Where(pc => pc.PostId == postId) @@ -60,6 +89,24 @@ public async Task> GetPostCategories(int postId) .ToListAsync(); } + public async Task SaveCategory(Category category) + { + Category existing = await _db.Categories.AsNoTracking() + .Where(c => c.Content.ToLower() == category.Content.ToLower()).FirstOrDefaultAsync(); + + if (existing != null) + return false; // already exists category with the same title + + Category dbCategory = await _db.Categories.Where(c => c.Id == category.Id).FirstOrDefaultAsync(); + if (dbCategory == null) + return false; + + dbCategory.Content = category.Content; + dbCategory.DateUpdated = DateTime.UtcNow; + + return await _db.SaveChangesAsync() > 0; + } + public async Task SaveCategory(string tag) { Category category = await _db.Categories @@ -119,10 +166,7 @@ public async Task SavePostCategories(int postId, List categories List existingPostCategories = await _db.PostCategories.AsNoTracking() .Where(pc => pc.PostId == postId).ToListAsync(); - foreach (var pc in existingPostCategories) - { - await RemoveCategory(postId, pc.CategoryId); - } + _db.PostCategories.RemoveRange(existingPostCategories); await _db.SaveChangesAsync(); @@ -134,33 +178,20 @@ public async Task SavePostCategories(int postId, List categories return await _db.SaveChangesAsync() > 0; } - public async Task RemoveCategory(int postId, int categoryId) - { - PostCategory postCategory = await _db.PostCategories + public async Task RemoveCategory(int categoryId) + { + List postCategories = await _db.PostCategories .AsNoTracking() .Where(pc => pc.CategoryId == categoryId) - .Where(pc => pc.PostId == postId) - .FirstOrDefaultAsync(); - - if(postCategory != null) - { - _db.PostCategories.Remove(postCategory); - - int postCount = await _db.PostCategories.AsNoTracking() - .Where(pc => pc.CategoryId == categoryId).CountAsync(); + .ToListAsync(); + _db.PostCategories.RemoveRange(postCategories); - if(postCount == 1) - { - Category category = _db.Categories + Category category = _db.Categories .Where(c => c.Id == categoryId) .FirstOrDefault(); - _db.Categories.Remove(category); - } + _db.Categories.Remove(category); - return await _db.SaveChangesAsync() > 0; - } - - return true; - } - } + return await _db.SaveChangesAsync() > 0; + } + } } diff --git a/src/Blogifier.Shared/Models/CategoryItem.cs b/src/Blogifier.Shared/Models/CategoryItem.cs index 186c1dde9..3500c8c3d 100644 --- a/src/Blogifier.Shared/Models/CategoryItem.cs +++ b/src/Blogifier.Shared/Models/CategoryItem.cs @@ -1,15 +1,18 @@ -using System; +using System; namespace Blogifier.Shared { - public class CategoryItem : IComparable - { - public string Category { get; set; } - public int PostCount { get; set; } + public class CategoryItem : IComparable + { + public bool Selected { get; set; } + public int Id { get; set; } + public string Category { get; set; } + public int PostCount { get; set; } + public DateTime DateCreated { get; set; } - public int CompareTo(CategoryItem other) - { - return Category.ToLower().CompareTo(other.Category.ToLower()); - } - } + public int CompareTo(CategoryItem other) + { + return Category.ToLower().CompareTo(other.Category.ToLower()); + } + } } diff --git a/src/Blogifier/Controllers/CategoryController.cs b/src/Blogifier/Controllers/CategoryController.cs index 2af2ee778..d4504da9c 100644 --- a/src/Blogifier/Controllers/CategoryController.cs +++ b/src/Blogifier/Controllers/CategoryController.cs @@ -24,13 +24,38 @@ public async Task> GetPostCategories(int postId) return await _categoryProvider.GetPostCategories(postId); } - [Authorize] + [HttpGet("byId/{categoryId:int}")] + public async Task GetCategory(int categoryId) + { + return await _categoryProvider.GetCategory(categoryId); + } + + [HttpGet] + public async Task> GetCategories() + { + return await _categoryProvider.Categories(); + } + + [HttpGet("{term}")] + public async Task> SearchCategories(string term = "*") + { + return await _categoryProvider.SearchCategories(term); + } + + [Authorize] [HttpPost("{postId:int}/{tag}")] public async Task> AddPostCategory(int postId, string tag) { return await _categoryProvider.AddPostCategory(postId, tag); } + [Authorize] + [HttpPut] + public async Task> SaveCategory(Category category) + { + return await _categoryProvider.SaveCategory(category); + } + [Authorize] [HttpPut("{postId:int}")] public async Task> SavePostCategories(int postId, List categories) @@ -39,10 +64,10 @@ public async Task> SavePostCategories(int postId, List> RemoveCategory(int postId, int categoryId) - { - return await _categoryProvider.RemoveCategory(postId, categoryId); - } - } + [HttpDelete("{categoryId:int}")] + public async Task> RemoveCategory(int categoryId) + { + return await _categoryProvider.RemoveCategory(categoryId); + } + } }