Skip to content

Commit

Permalink
Merge pull request #144 from Geta/feature/sortable-headers
Browse files Browse the repository at this point in the history
Add sortable headers feature
  • Loading branch information
jevgenijsp authored Nov 26, 2024
2 parents 0f66a17 + 7415b7b commit 467ccab
Show file tree
Hide file tree
Showing 14 changed files with 283 additions and 52 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using Geta.NotFoundHandler.Admin.Areas.GetaNotFoundHandlerAdmin.Pages.Models;
using Microsoft.AspNetCore.Mvc.RazorPages;

namespace Geta.NotFoundHandler.Admin.Areas.GetaNotFoundHandlerAdmin.Pages.Base;

public abstract class AbstractSortablePageModel : PageModel
{
public string SortColumn { get; set; }
public SortDirection? SortDirection { get; set; }

public void ApplySort(string sortColumn, SortDirection? sortDirection)
{
SortColumn = sortColumn;
SortDirection = sortDirection;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
@using Geta.NotFoundHandler.Admin.Areas.GetaNotFoundHandlerAdmin.Pages.Models
@model Geta.NotFoundHandler.Admin.Areas.GetaNotFoundHandlerAdmin.Pages.Components.SortableHeaderCell.SortableHeaderCellViewModel

<a href="@Model.GetSortUrl()"
style="white-space: nowrap">
@Model.DisplayName

@if (Model.IsActive() && Model.GetSortDirection() != null)
{
<span data-feather="@(Model.GetSortDirection() == SortDirection.Ascending ? "arrow-up" : "arrow-down")">
</span>
}
</a>
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;

namespace Geta.NotFoundHandler.Admin.Areas.GetaNotFoundHandlerAdmin.Pages.Components.SortableHeaderCell;

public class SortableHeaderCellViewComponent : ViewComponent
{
private readonly IHttpContextAccessor _contextAccessor;

public SortableHeaderCellViewComponent(IHttpContextAccessor contextAccessor)
{
_contextAccessor = contextAccessor;
}

public IViewComponentResult Invoke(string key, string displayName)
{
var context = _contextAccessor.HttpContext;

return View(new SortableHeaderCellViewModel
{
QueryString = context?.Request.QueryString.ToString(),
Key = key,
DisplayName = displayName
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
using System;
using System.Web;
using Geta.NotFoundHandler.Admin.Areas.GetaNotFoundHandlerAdmin.Pages.Base;
using Geta.NotFoundHandler.Admin.Areas.GetaNotFoundHandlerAdmin.Pages.Models;

namespace Geta.NotFoundHandler.Admin.Areas.GetaNotFoundHandlerAdmin.Pages.Components.SortableHeaderCell;

public class SortableHeaderCellViewModel
{
public string DisplayName { get; set; }
public string Key { get; set; }
public string QueryString { get; set; }

public SortDirection? GetNextSortDirection()
{
return GetSortDirection() switch
{
null => SortDirection.Ascending,
SortDirection.Ascending => SortDirection.Descending,
SortDirection.Descending => null,
_ => throw new ArgumentOutOfRangeException()
};
}

public string GetSortColumn()
{
if (GetNextSortDirection() == null)
{
return null;
}

return Key;
}

public string GetSortUrl()
{
var qs = HttpUtility
.ParseQueryString(QueryString);

qs[nameof(AbstractSortablePageModel.SortColumn)] = GetSortColumn();
qs[nameof(AbstractSortablePageModel.SortDirection)] = GetNextSortDirection().ToString();

return $"?{qs}";
}

public bool IsActive()
{
var sortColumn = HttpUtility
.ParseQueryString(QueryString)
.Get(nameof(AbstractSortablePageModel.SortColumn));

return !string.IsNullOrEmpty(sortColumn) && sortColumn == Key;
}

public SortDirection? GetSortDirection()
{
var sortDirection = HttpUtility
.ParseQueryString(QueryString)
.Get(nameof(AbstractSortablePageModel.SortDirection));

return Enum.TryParse(sortDirection, out SortDirection sort) ? sort : null;
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
@page "{handler?}"
@using Geta.NotFoundHandler.Admin.Areas.GetaNotFoundHandlerAdmin.Pages.Components.SortableHeaderCell
@using Geta.NotFoundHandler.Core.Providers.RegexRedirects
@using Microsoft.AspNetCore.Mvc.TagHelpers
@model Geta.NotFoundHandler.Admin.Pages.Geta.NotFoundHandler.Admin.DeletedModel

@await Component.InvokeAsync("Card", new { message = Model.Message })
Expand All @@ -7,42 +10,44 @@
<div class="table-responsive mt-3">
<table class="table table-hover table-sm" aria-label="Deleted redirects">
<thead>
<tr>
<th>URL</th>
<th class="col-1"></th>
</tr>
<tr>
<th>
<vc:sortable-header-cell key="@nameof(RegexRedirect.OldUrlRegex)" display-name="Old URL Regex"/>
</th>
<th class="col-1"></th>
</tr>
</thead>
<tbody>
<tr>
<td>
<input type="text" class="form-control" asp-for="DeletedRedirect.OldUrl">
<span asp-validation-for="DeletedRedirect.OldUrl" class="text-danger"></span>
</td>
<tr>
<td>
<input type="text" class="form-control" asp-for="DeletedRedirect.OldUrl">
<span asp-validation-for="DeletedRedirect.OldUrl" class="text-danger"></span>
</td>
<td>
<div class="d-grid gap-2">
<button type="submit" class="btn btn-primary"
asp-page-handler="create">
<span data-feather="plus"></span> add
</button>
</div>
</td>
</tr>
@foreach (var item in Model.Items)
{
<tr class="align-middle">
<td>@item.OldUrl</td>
<td>
<div class="d-grid gap-2">
<button type="submit" class="btn btn-primary"
asp-page-handler="create">
<span data-feather="plus"></span> add
<button type="submit" class="btn btn-danger"
asp-page-handler="delete" asp-route-oldurl="@item.OldUrl">
<span data-feather="trash-2"></span> delete
</button>
</div>
</td>
</tr>
@foreach (var item in Model.Items)
{
<tr class="align-middle">
<td>@item.OldUrl</td>
<td>
<div class="d-grid gap-2">
<button type="submit" class="btn btn-danger"
asp-page-handler="delete" asp-route-oldurl="@item.OldUrl">
<span data-feather="trash-2"></span> delete
</button>
</div>
</td>
</tr>
}
}
</tbody>
</table>
@await Component.InvokeAsync(typeof(Geta.NotFoundHandler.Admin.Pages.Geta.NotFoundHandler.Admin.Components.Pager.PagerViewComponent), new { Model.Items })
</div>
</form>
</form>
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
using System.Collections.Generic;
using System.Linq;
using Geta.NotFoundHandler.Admin.Areas.GetaNotFoundHandlerAdmin.Pages.Base;
using Geta.NotFoundHandler.Admin.Areas.GetaNotFoundHandlerAdmin.Pages.Extensions;
using Geta.NotFoundHandler.Admin.Areas.GetaNotFoundHandlerAdmin.Pages.Models;
using Geta.NotFoundHandler.Admin.Pages.Geta.NotFoundHandler.Admin.Models;
using Geta.NotFoundHandler.Core.Redirects;
using Geta.NotFoundHandler.Infrastructure;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using X.PagedList;

namespace Geta.NotFoundHandler.Admin.Pages.Geta.NotFoundHandler.Admin;

[Authorize(Constants.PolicyName)]
public class DeletedModel : PageModel
public class DeletedModel : AbstractSortablePageModel
{
private readonly IRedirectsService _redirectsService;

Expand All @@ -29,8 +32,10 @@ public DeletedModel(IRedirectsService redirectsService)
[BindProperty(SupportsGet = true)]
public Paging Paging { get; set; }

public void OnGet()
public void OnGet(string sortColumn, SortDirection? sortDirection)
{
ApplySort(sortColumn, sortDirection);

Load();
}

Expand All @@ -55,9 +60,16 @@ public IActionResult OnPostDelete(string oldUrl)

private void Load()
{
var items = _redirectsService.GetDeleted().ToPagedList(Paging.PageNumber, Paging.PageSize);
var items = FindRedirects().ToPagedList(Paging.PageNumber, Paging.PageSize);
Message =
$"There are currently {items.TotalItemCount} URLs that return a Deleted response. This tells crawlers to remove these URLs from their index.";
Items = items;
}

private IEnumerable<CustomRedirect> FindRedirects()
{
return _redirectsService
.GetDeleted()
.Sort(SortColumn, SortDirection);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text.RegularExpressions;
using Geta.NotFoundHandler.Admin.Areas.GetaNotFoundHandlerAdmin.Pages.Models;

namespace Geta.NotFoundHandler.Admin.Areas.GetaNotFoundHandlerAdmin.Pages.Extensions;

public static class EnumerableExtensions
{
public static IEnumerable<T> Sort<T>(this IEnumerable<T> list, string propertyName, SortDirection? sortDirection)
{
if (!string.IsNullOrEmpty(propertyName))
{
var prop = typeof(T).GetProperty(propertyName);

if (prop != null && sortDirection != null)
{
if (sortDirection == SortDirection.Ascending)
{
list = list.OrderBy(x => GetValue(prop, x));
}
else
{
list = list.OrderByDescending(x => GetValue(prop, x));
}
}
}

return list;
}

private static object GetValue<T>(PropertyInfo prop, T element)
{
var value = prop.GetValue(element);

// Value should be IComparable
if (value is Regex regex)
{
return regex.ToString();
}

return value;
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
@page "{handler?}"
@using Geta.NotFoundHandler.Core.Redirects
@using Microsoft.AspNetCore.Mvc.TagHelpers
@model Geta.NotFoundHandler.Admin.Pages.Geta.NotFoundHandler.Admin.IgnoredModel

@await Component.InvokeAsync("Card", new { message = Model.Message })
Expand All @@ -8,7 +10,9 @@
<table class="table table-hover table-sm" aria-label="Ignored redirects">
<thead>
<tr>
<th>URL</th>
<th>
<vc:sortable-header-cell key="@nameof(CustomRedirect.OldUrl)" display-name="URL"/>
</th>
<th class="col-1"></th>
</tr>
</thead>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
using System.Collections.Generic;
using System.Linq;
using Geta.NotFoundHandler.Admin.Areas.GetaNotFoundHandlerAdmin.Pages.Base;
using Geta.NotFoundHandler.Admin.Areas.GetaNotFoundHandlerAdmin.Pages.Extensions;
using Geta.NotFoundHandler.Admin.Areas.GetaNotFoundHandlerAdmin.Pages.Models;
using Geta.NotFoundHandler.Admin.Pages.Geta.NotFoundHandler.Admin.Models;
using Geta.NotFoundHandler.Core.Redirects;
using Geta.NotFoundHandler.Infrastructure;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using X.PagedList;

namespace Geta.NotFoundHandler.Admin.Pages.Geta.NotFoundHandler.Admin;

[Authorize(Constants.PolicyName)]
public class IgnoredModel : PageModel
public class IgnoredModel : AbstractSortablePageModel
{
private readonly IRedirectsService _redirectsService;

Expand All @@ -26,8 +29,10 @@ public IgnoredModel(IRedirectsService redirectsService)
[BindProperty(SupportsGet = true)]
public Paging Paging { get; set; }

public void OnGet()
public void OnGet(string sortColumn, SortDirection? sortDirection)
{
ApplySort(sortColumn, sortDirection);

Load();
}

Expand All @@ -40,8 +45,15 @@ public IActionResult OnPostUnignore(string oldUrl)

private void Load()
{
var items = _redirectsService.GetIgnored().ToPagedList(Paging.PageNumber, Paging.PageSize);
var items = FindRedirects().ToPagedList(Paging.PageNumber, Paging.PageSize);
Message = $"There are currently {items.TotalItemCount} ignored suggestions stored.";
Items = items;
}

private IEnumerable<CustomRedirect> FindRedirects()
{
return _redirectsService
.GetIgnored()
.Sort(SortColumn, SortDirection);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,18 @@
<table class="table table-hover table-sm" aria-label="Redirects">
<thead>
<tr>
<th>Old URL</th>
<th>New URL</th>
<th class="col-1 text-center">Wildcard</th>
<th class="col-1">Redirect Type</th>
<th>
<vc:sortable-header-cell key="@nameof(CustomRedirect.NewUrl)" display-name="New URL"/>
</th>
<th>
<vc:sortable-header-cell key="@nameof(CustomRedirect.OldUrl)" display-name="Old URL"/>
</th>
<th class="col-1 text-center">
<vc:sortable-header-cell key="@nameof(CustomRedirect.WildCardSkipAppend)" display-name="Wildcard"/>
</th>
<th class="col-1">
<vc:sortable-header-cell key="@nameof(CustomRedirect.RedirectType)" display-name="Redirect Type"/>
</th>
<th class="col-1"></th>
</tr>
</thead>
Expand Down
Loading

0 comments on commit 467ccab

Please sign in to comment.