new sorting option

This commit is contained in:
Hamid Reza Mohammadi 2026-08-04 20:58:05 +03:30
parent 1e4527591c
commit 2fcf7241f2
14 changed files with 72 additions and 50 deletions

View File

@ -131,11 +131,11 @@ namespace GanjooRazor.Pages
}
/// <summary>
/// re-fetch a poem's comments sorted by rating or by date (AJAX, called from sortComments() in bk.js)
/// re-fetch a poem's comments sorted by rating, oldest, or newest (AJAX, called from sortComments() in bk.js)
/// </summary>
public async Task<IActionResult> OnGetCommentsPartialAsync(int poemId, bool sortByRanking = true)
public async Task<IActionResult> OnGetCommentsPartialAsync(int poemId, GanjoorCommentSortOrder sortOrder = GanjoorCommentSortOrder.TopRated)
{
var response = await _httpClient.GetAsync($"{APIRoot.Url}/api/ganjoor/poem/{poemId}/comments?sortByRanking={sortByRanking}");
var response = await _httpClient.GetAsync($"{APIRoot.Url}/api/ganjoor/poem/{poemId}/comments?sortOrder={sortOrder}");
if (!response.IsSuccessStatusCode)
{
return BadRequest(await ReadErrorMessageAsync(response));

View File

@ -23,7 +23,7 @@
font-family: 'Vazirmatn'
}
</style>
<script src="/js/bk.js?version=86"></script>
<script src="/js/bk.js?version=87"></script>
<script src="~/js/jquery-1.10.2.min.js"></script>
<script src="~/js/leaflet.js"></script>
<script src="~/js/leaflet-providers.js"></script>

View File

@ -15,7 +15,7 @@
<link rel="shortcut icon" href="/favicon.ico?version=1" type="image/x-icon" />
<link rel="search" type="application/opensearchdescription+xml" title="گنجور" href="https://ganjoor.net/search.xml" />
<script src="/lib/jquery/dist/jquery.min.js"></script>
<script src="/js/bk.js?version=86"></script>
<script src="/js/bk.js?version=87"></script>
<script>
var cookieValue = getCookie("playerScrollLock");
var playerScrollLock = cookieValue === "true";

View File

@ -876,8 +876,9 @@
{
<p class="comments-sort-toggle">
مرتب‌سازی حاشیه‌ها:
<a role="button" class="cursor-pointer" onclick="sortComments(@Model.GanjoorPage.Id, true)">محبوب‌ترین</a> |
<a role="button" class="cursor-pointer" onclick="sortComments(@Model.GanjoorPage.Id, false)">تازه‌ترین</a>
<a role="button" class="cursor-pointer" onclick="sortComments(@Model.GanjoorPage.Id, 'TopRated')">محبوب‌ترین</a> |
<a role="button" class="cursor-pointer" onclick="sortComments(@Model.GanjoorPage.Id, 'Newest')">جدیدترین</a> |
<a role="button" class="cursor-pointer" onclick="sortComments(@Model.GanjoorPage.Id, 'Oldest')">قدیمی‌ترین</a>
</p>
}
<div id="comments-list-wrapper">

View File

@ -150,7 +150,7 @@
</p>
<script src="~/lib/jquery/dist/jquery.min.js"></script>
<script src="~/js/bk.js?version=86"></script>
<script src="~/js/bk.js?version=87"></script>
<script src="~/js/jplayersupport.js"></script>
<script src="~/js/vaabd.js?version=10"></script>
<script src="~/dist/jplayer/jquery.jplayer.min.js"></script>

View File

@ -238,7 +238,7 @@
<script src="~/js/bk.js?version=86"></script>
<script src="~/js/bk.js?version=87"></script>
<script src="~/js/jplayersupport.js"></script>
<script src="~/js/vaabd.js?version=10"></script>
<script src="~/dist/jplayer/jquery.jplayer.min.js"></script>

View File

@ -35,7 +35,7 @@
});
</script>
<script src="/js/bk.js?version=86"></script>
<script src="/js/bk.js?version=87"></script>
<script>
function editBanner(bannerId) {
var url = '?handler=Edit';

View File

@ -34,7 +34,7 @@
<link rel="shortcut icon" href="/favicon.ico?version=1" type="image/x-icon" />
<link rel="search" type="application/opensearchdescription+xml" title="گنجور" href="https://ganjoor.net/search.xml" />
<script src="/lib/jquery/dist/jquery.min.js"></script>
<script src="/js/bk.js?version=86"></script>
<script src="/js/bk.js?version=87"></script>
<script>
var cookieValue = getCookie("playerScrollLock");
var playerScrollLock = cookieValue === "true";

View File

@ -32,7 +32,7 @@
});
</script>
<script src="/lib/diff.js"></script>
<script src="/js/bk.js?version=86"></script>
<script src="/js/bk.js?version=87"></script>
<script>
function compareCorrectionBlocks(originalElemenyId, editedElementId, resultElementId) {
var a = document.getElementById(originalElemenyId);

View File

@ -504,13 +504,13 @@ function switchBookmarkInternal(poemId, coupletIndex, divSuffix) {
});
}
function sortComments(poemId, sortByRanking) {
function sortComments(poemId, sortOrder) {
var wrapper = document.getElementById('comments-list-wrapper');
if (wrapper == null) return;
$.ajax({
type: "GET",
url: '?handler=CommentsPartial&poemId=' + String(poemId) + '&sortByRanking=' + String(sortByRanking),
url: '?handler=CommentsPartial&poemId=' + String(poemId) + '&sortOrder=' + sortOrder,
error: function (err) {
alert('خطا در بارگذاری حاشیه‌ها: ' + err.responseText);
},
@ -1066,13 +1066,6 @@ function copyCommentUrl(commentId) {
if (url.indexOf('#') != -1) {
url = url.substring(0, url.indexOf('#'));
}
if (url.indexOf('?') != -1) {
url += '&';
}
else {
url += '?'
}
url += 'tab=discussions';
url += ('#comment-' + String(commentId));
navigator.clipboard.writeText(url);
alert('نشانی در حافظه رونوشت شد.');

View File

@ -835,7 +835,7 @@ namespace RMuseum.Controllers
/// </summary>
/// <param name="url"></param>
/// <param name="catPoems"></param>
/// <param name="commentsSortByRanking">true: order comments by rating then date, false: order by date. Only applies to poem pages.</param>
/// <param name="commentSortOrder">TopRated, Oldest, or Newest. Only applies to poem pages.</param>
/// <returns></returns>
[HttpGet]
[Route("page")]
@ -843,10 +843,10 @@ namespace RMuseum.Controllers
[ProducesResponseType((int)HttpStatusCode.OK, Type = typeof(GanjoorPageCompleteViewModel))]
[ProducesResponseType((int)HttpStatusCode.BadRequest, Type = typeof(string))]
[ProducesResponseType((int)HttpStatusCode.NotFound)]
public async Task<IActionResult> GetPageByUrl(string url, bool catPoems = false, bool commentsSortByRanking = true)
public async Task<IActionResult> GetPageByUrl(string url, bool catPoems = false, GanjoorCommentSortOrder commentSortOrder = GanjoorCommentSortOrder.TopRated)
{
RServiceResult<GanjoorPageCompleteViewModel> res =
await _ganjoorService.GetPageByUrl(url, catPoems, commentsSortByRanking);
await _ganjoorService.GetPageByUrl(url, catPoems, commentSortOrder);
if (!string.IsNullOrEmpty(res.ExceptionString))
return BadRequest(res.ExceptionString);
if (res.Result == null)
@ -1043,7 +1043,7 @@ namespace RMuseum.Controllers
/// <param name="verseDetails"></param>
/// <param name="navigation">next/previous</param>
/// <param name="relatedpoems"></param>
/// <param name="sortByRanking"></param>
/// <param name="commentSortOrder"></param>
/// <returns></returns>
[HttpGet]
[Route("poem/{id}")]
@ -1051,10 +1051,10 @@ namespace RMuseum.Controllers
[ProducesResponseType((int)HttpStatusCode.OK, Type = typeof(GanjoorPoemCompleteViewModel))]
[ProducesResponseType((int)HttpStatusCode.BadRequest, Type = typeof(string))]
[ProducesResponseType((int)HttpStatusCode.NotFound)]
public async Task<IActionResult> GetPoemById(int id, bool catInfo = true, bool catPoems = false, bool rhymes = true, bool recitations = true, bool images = true, bool songs = true, bool comments = true, bool verseDetails = true, bool navigation = true, bool relatedpoems = true, bool sortByRanking = true)
public async Task<IActionResult> GetPoemById(int id, bool catInfo = true, bool catPoems = false, bool rhymes = true, bool recitations = true, bool images = true, bool songs = true, bool comments = true, bool verseDetails = true, bool navigation = true, bool relatedpoems = true, GanjoorCommentSortOrder commentSortOrder = GanjoorCommentSortOrder.TopRated)
{
RServiceResult<GanjoorPoemCompleteViewModel> res =
await _ganjoorService.GetPoemById(id, catInfo, catPoems, rhymes, recitations, images, songs, comments, verseDetails, navigation, relatedpoems, true, sortByRanking );
await _ganjoorService.GetPoemById(id, catInfo, catPoems, rhymes, recitations, images, songs, comments, verseDetails, navigation, relatedpoems, true, commentSortOrder );
if (!string.IsNullOrEmpty(res.ExceptionString))
return BadRequest(res.ExceptionString);
if (res.Result == null)
@ -1200,7 +1200,7 @@ namespace RMuseum.Controllers
/// </summary>
/// <param name="id"></param>
/// <param name="coupletIndex"></param>
/// <param name="sortByRanking">true: order by rating then date, false: order by date</param>
/// <param name="sortOrder">TopRated, Oldest, or Newest</param>
/// <returns></returns>
[HttpGet]
@ -1208,10 +1208,10 @@ namespace RMuseum.Controllers
[AllowAnonymous]
[ProducesResponseType((int)HttpStatusCode.OK, Type = typeof(GanjoorCommentSummaryViewModel[]))]
[ProducesResponseType((int)HttpStatusCode.BadRequest, Type = typeof(string))]
public async Task<IActionResult> GetPoemComments(int id, int? coupletIndex, bool sortByRanking = true)
public async Task<IActionResult> GetPoemComments(int id, int? coupletIndex, GanjoorCommentSortOrder sortOrder = GanjoorCommentSortOrder.TopRated)
{
RServiceResult<GanjoorCommentSummaryViewModel[]> res =
await _ganjoorService.GetPoemComments(id, Guid.Empty, coupletIndex, sortByRanking);
await _ganjoorService.GetPoemComments(id, Guid.Empty, coupletIndex, sortOrder);
if (!string.IsNullOrEmpty(res.ExceptionString))
return BadRequest(res.ExceptionString);
return Ok(res.Result);

View File

@ -0,0 +1,23 @@
namespace RMuseum.Models.Ganjoor
{
/// <summary>
/// how to sort a poem's comments
/// </summary>
public enum GanjoorCommentSortOrder
{
/// <summary>
/// highest rating first (ties broken by oldest first)
/// </summary>
TopRated = 0,
/// <summary>
/// oldest comment first
/// </summary>
Oldest = 1,
/// <summary>
/// newest comment first
/// </summary>
Newest = 2,
}
}

View File

@ -93,8 +93,9 @@ namespace RMuseum.Services
/// </summary>
/// <param name="url"></param>
/// <param name="catPoems"></param>
/// <param name="commentSortOrder"></param>
/// <returns></returns>
Task<RServiceResult<GanjoorPageCompleteViewModel>> GetPageByUrl(string url, bool catPoems = false, bool commentsSortByRanking = true);
Task<RServiceResult<GanjoorPageCompleteViewModel>> GetPageByUrl(string url, bool catPoems = false, GanjoorCommentSortOrder commentSortOrder = GanjoorCommentSortOrder.TopRated);
/// <summary>
/// get page url by id
@ -139,9 +140,9 @@ namespace RMuseum.Services
/// <param name="navigation"></param>
/// <param name="relatedpoems"></param>
/// <param name="sections"></param>
/// <param name="sortByRanking"></param>
/// <param name="commentSortOrder"></param>
/// <returns></returns>
Task<RServiceResult<GanjoorPoemCompleteViewModel>> GetPoemById(int id, bool catInfo = true, bool catPoems = false, bool rhymes = true, bool recitations = true, bool images = true, bool songs = true, bool comments = true, bool verseDetails = true, bool navigation = true, bool relatedpoems = true, bool sections = true, bool sortByRanking = true);
Task<RServiceResult<GanjoorPoemCompleteViewModel>> GetPoemById(int id, bool catInfo = true, bool catPoems = false, bool rhymes = true, bool recitations = true, bool images = true, bool songs = true, bool comments = true, bool verseDetails = true, bool navigation = true, bool relatedpoems = true, bool sections = true, GanjoorCommentSortOrder commentSortOrder = GanjoorCommentSortOrder.TopRated);
/// <summary>
/// get poem verses
@ -212,9 +213,9 @@ namespace RMuseum.Services
/// <param name="poemId"></param>
/// <param name="userId"></param>
/// <param name="coupletIndex"></param>
/// <param name="sortByRanking">true: order by rating SortKey then CommentDate, false: order by CommentDate</param>
/// <param name="sortOrder">TopRated, Oldest, or Newest</param>
/// <returns></returns>
Task<RServiceResult<GanjoorCommentSummaryViewModel[]>> GetPoemComments(int poemId, Guid userId, int? coupletIndex, bool sortByRanking = false);
Task<RServiceResult<GanjoorCommentSummaryViewModel[]>> GetPoemComments(int poemId, Guid userId, int? coupletIndex, GanjoorCommentSortOrder sortOrder = GanjoorCommentSortOrder.TopRated);
/// <summary>
/// get a single comment information (replies are not included)

View File

@ -582,8 +582,9 @@ namespace RMuseum.Services.Implementation
/// </summary>
/// <param name="url"></param>
/// <param name="catPoems"></param>
/// <param name="commentSortOrder"></param>
/// <returns></returns>
public async Task<RServiceResult<GanjoorPageCompleteViewModel>> GetPageByUrl(string url, bool catPoems = false, bool commentsSortByRanking = true)
public async Task<RServiceResult<GanjoorPageCompleteViewModel>> GetPageByUrl(string url, bool catPoems = false, GanjoorCommentSortOrder commentSortOrder = GanjoorCommentSortOrder.TopRated)
{
if (url.IndexOf('?') != -1)
{
@ -599,9 +600,10 @@ namespace RMuseum.Services.Implementation
url = url.Replace("//", "/"); //duplicated slashes would be merged
var cachKey = $"GanjoorService::GetPageByUrl::{url}";
// only the default (rating) sort order is cached, so a caller asking for date-sorted
// comments never reads or overwrites the shared cache entry used by everyone else
if (!commentsSortByRanking || !_memoryCache.TryGetValue(cachKey, out GanjoorPageCompleteViewModel page))
// only the default (TopRated) sort order is cached, so a caller asking for a
// different comment order never reads or overwrites the shared cache entry
// used by everyone else
if (commentSortOrder != GanjoorCommentSortOrder.TopRated || !_memoryCache.TryGetValue(cachKey, out GanjoorPageCompleteViewModel page))
{
var dbPage = await _context.GanjoorPages.Where(p => p.FullUrl == url).AsNoTracking().SingleOrDefaultAsync();
if (dbPage == null)
@ -648,7 +650,7 @@ namespace RMuseum.Services.Implementation
{
case GanjoorPageType.PoemPage:
{
var poemRes = await GetPoemById((int)dbPage.PoemId, sortByRanking: commentsSortByRanking);
var poemRes = await GetPoemById((int)dbPage.PoemId, commentSortOrder: commentSortOrder);
if (!string.IsNullOrEmpty(poemRes.ExceptionString))
{
return new RServiceResult<GanjoorPageCompleteViewModel>(null, poemRes.ExceptionString);
@ -864,9 +866,9 @@ namespace RMuseum.Services.Implementation
/// <param name="poemId"></param>
/// <param name="userId"></param>
/// <param name="coupletIndex"></param>
/// <param name="sortByRanking"></param>
/// <param name="sortOrder"></param>
/// <returns></returns>
public async Task<RServiceResult<GanjoorCommentSummaryViewModel[]>> GetPoemComments(int poemId, Guid userId, int? coupletIndex, bool sortByRanking)
public async Task<RServiceResult<GanjoorCommentSummaryViewModel[]>> GetPoemComments(int poemId, Guid userId, int? coupletIndex, GanjoorCommentSortOrder sortOrder)
{
var query = _context.GanjoorComments
.Include(c => c.User)
@ -880,10 +882,12 @@ namespace RMuseum.Services.Implementation
&&
(coupletIndex == null || comment.CoupletIndex == coupletIndex));
query = sortByRanking
? query.OrderByDescending(c => c.SortKey)
.ThenBy(c => c.CommentDate)
: query.OrderBy(c => c.CommentDate);
query = sortOrder switch
{
GanjoorCommentSortOrder.TopRated => query.OrderByDescending(c => c.SortKey).ThenBy(c => c.CommentDate),
GanjoorCommentSortOrder.Newest => query.OrderByDescending(c => c.CommentDate),
_ => query.OrderBy(c => c.CommentDate), // Oldest
};
var source =
from comment in query
@ -1814,11 +1818,11 @@ namespace RMuseum.Services.Implementation
/// <param name="navigation"></param>
/// <param name="relatedpoems"></param>
/// <param name="sections">sections</param>
/// <param name="sortByRanking"></param>
/// <param name="commentSortOrder"></param>
/// <returns></returns>
public async Task<RServiceResult<GanjoorPoemCompleteViewModel>> GetPoemById(int id, bool catInfo = true, bool catPoems = false, bool rhymes = true, bool recitations = true, bool images = true, bool songs = true, bool comments = true, bool verseDetails = true, bool navigation = true, bool relatedpoems = true, bool sections = true, bool sortByRanking = true)
public async Task<RServiceResult<GanjoorPoemCompleteViewModel>> GetPoemById(int id, bool catInfo = true, bool catPoems = false, bool rhymes = true, bool recitations = true, bool images = true, bool songs = true, bool comments = true, bool verseDetails = true, bool navigation = true, bool relatedpoems = true, bool sections = true, GanjoorCommentSortOrder commentSortOrder = GanjoorCommentSortOrder.TopRated)
{
var cachKey = $"GetPoemById({id}, {catInfo}, {catPoems}, {rhymes}, {recitations}, {images}, {songs}, {comments}, {verseDetails}, {navigation}, {relatedpoems}, {sections}, {sortByRanking})";
var cachKey = $"GetPoemById({id}, {catInfo}, {catPoems}, {rhymes}, {recitations}, {images}, {songs}, {comments}, {verseDetails}, {navigation}, {relatedpoems}, {sections}, {commentSortOrder})";
if (!_memoryCache.TryGetValue(cachKey, out GanjoorPoemCompleteViewModel poemViewModel))
{
var poem = await _context.GanjoorPoems.Where(p => p.Id == id).AsNoTracking().SingleOrDefaultAsync();
@ -1954,7 +1958,7 @@ namespace RMuseum.Services.Implementation
if (comments)
{
var commentsRes = await GetPoemComments(id, Guid.Empty, null, sortByRanking);
var commentsRes = await GetPoemComments(id, Guid.Empty, null, commentSortOrder);
if (!string.IsNullOrEmpty(commentsRes.ExceptionString))
return new RServiceResult<GanjoorPoemCompleteViewModel>(null, commentsRes.ExceptionString);
poemComments = commentsRes.Result;