divan/DiwanRazor/Pages/Search.cshtml.cs
Anas Rashid 020b010483 Urdu public pages, batch 2 (#2): auth, errors, map, FAQ, similar poems, people pages, page titles
- translate remaining public views and page-model strings (titles, messages, relation names)
- Urdu stopword list for word-frequency stats (replaces Persian list)
- error pages: drop list of alternative Ganjoor services

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 00:22:09 +02:00

431 lines
16 KiB
C#
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using DNTPersianUtils.Core;
using DiwanRazor.Utils;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.OutputCaching;
using Microsoft.Extensions.Configuration;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using RMuseum.Models.Diwan.ViewModels;
using RMuseum.Services.Implementation;
using RSecurityBackend.Models.Generic;
namespace DiwanRazor.Pages
{
[OutputCache(PolicyName = "DiwanPublicPage")]
public class SearchModel : LoginPartialEnabledPageModel
{
private readonly PoetCacheService _poetCache;
/// <summary>
/// constructor
/// </summary>
public SearchModel(HttpClient httpClient, IConfiguration configuration, PoetCacheService poetCache) : base(httpClient, configuration)
{
_poetCache = poetCache;
}
public List<DiwanPoetViewModel> Poets { get; set; }
public string Query { get; set; }
public bool QueryIsSingleWord
{
get
{
return !string.IsNullOrEmpty(Query) && Query.IndexOfAny([' ', '‌']) == -1;
}
}
public int PoetId { get; set; }
public int CatId { get; set; }
public string CatFullTitle { get; set; }
public string CatFullUrl { get; set; }
public DiwanPoetCompleteViewModel Poet { get; set; }
public List<DiwanPoemCompleteViewModel> Poems { get; set; }
public PaginationMetadata PaginationMetadata { get; set; }
public string PagingToolsHtml { get; set; }
public string LastError { get; set; }
public int[] ExceptPoetId { get; set; }
public async Task<IActionResult> OnGetPoetInformationAsync(int id)
{
if (id == 0)
{
return new OkObjectResult(null);
}
var (success, poet, error) = await _poetCache.GetPoetAsync(id, EditorCacheBypass);
if (!success)
{
return BadRequest(error);
}
return new OkObjectResult(poet);
}
public string AlternativeSearchPhrase { get; set; }
public string AlternativeSearchPhraseDescription { get; set; }
public bool Quoted { get; set; }
public bool ExactSearch { get; set; }
public string QueryDin
{
get
{
return Query.ApplyCorrectYeKe().Trim().Replace("\"", "");
}
}
public bool StatsAtTop { get; set; }
public async Task<IActionResult> OnGetAsync()
{
var maintenanceResult = TryGetMaintenanceModeResult();
if (maintenanceResult != null)
{
return maintenanceResult;
}
InitializeCommonPageState();
Query = Request.Query["s"].ApplyCorrectYeKe().Trim();
ExactSearch = Request.Query["es"] == "1";
bool quotes = Query.IndexOf("\"") != -1 || ExactSearch;
Query = LanguageUtils.MakeTextSearchable(Query); //replace zwnj with space
if (quotes)
Query = $"\"{Query}\"";
Quoted = quotes && Query.Contains(" ");
AlternativeSearchPhrase = Quoted ? WebUtility.UrlEncode(Query.Replace("\"", "")) : Query.Contains(" ") ? WebUtility.UrlEncode($"\"{Query}\"") : "";
AlternativeSearchPhraseDescription = "";
if (Quoted)
{
AlternativeSearchPhraseDescription = "مطمئن نہیں؟! ";
bool needsComma = false;
var splitteds = Query.Replace("\"", "").Split(' ');
for (int sp = 0; sp < splitteds.Length - 1; sp++)
{
var splitted = splitteds[sp];
if (needsComma)
AlternativeSearchPhraseDescription += "، ";
AlternativeSearchPhraseDescription += $"«{splitted}»";
needsComma = true;
}
AlternativeSearchPhraseDescription += $" و «{splitteds[splitteds.Length - 1]}»";
AlternativeSearchPhraseDescription += " کو الفاظ کی ترتیب کے بغیر تلاش کریں.";
}
else
if (Query.Contains(" "))
{
AlternativeSearchPhraseDescription = $"مطمئن نہیں؟ عبارت «{Query}» کو بالکل اسی طرح تلاش کریں.";
}
PoetId = string.IsNullOrEmpty(Request.Query["author"]) ? 0 : int.Parse(Request.Query["author"]);
CatId = PoetId == 0 ? 0 : string.IsNullOrEmpty(Request.Query["cat"]) ? 0 : int.Parse(Request.Query["cat"]);
if (CatId != 0)
{
var catResponse = await _httpClient.GetAsync($"{APIRoot.Url}/api/diwan/cat/{CatId}?poems=false&mainSections=false");
if (!catResponse.IsSuccessStatusCode)
{
LastError = await ReadErrorMessageAsync(catResponse);
return Page();
}
else
{
var cat = JObject.Parse(await catResponse.Content.ReadAsStringAsync()).ToObject<DiwanPoetCompleteViewModel>();
CatFullUrl = cat.Cat.FullUrl;
CatFullTitle = "";
foreach (var parentCat in cat.Cat.Ancestors)
{
CatFullTitle += parentCat.Title;
CatFullTitle += " »";
}
CatFullTitle += " " + cat.Cat.Title;
}
}
else
{
CatFullUrl = "";
CatFullTitle = "";
}
StatsAtTop = !string.IsNullOrEmpty(Request.Query["stats"]);
//todo: use html master layout or make it partial
// 1. poets
var (poetsOk, poets, poetsError) = await _poetCache.GetPoetsAsync(EditorCacheBypass);
if (!poetsOk)
{
LastError = poetsError;
return Page();
}
Poets = poets;
var poetName = Poets.SingleOrDefault(p => p.Id == PoetId);
if (poetName != null)
{
if (CatFullTitle != "")
{
ViewData["Title"] = $"دیوان » تلاش کے نتائج: {Query} حصے میں {CatFullTitle}";
}
else
{
ViewData["Title"] = $"دیوان » تلاش کے نتائج: {Query} کلام میں: {poetName?.Name}";
}
}
else
{
if (!string.IsNullOrEmpty(Query))
{
ViewData["Title"] = $"دیوان » تلاش کے نتائج: {Query}";
}
else
{
ViewData["Title"] = $"دیوان » تلاش";
}
}
if (PoetId != 0)
{
var (poetOk, poet, poetError) = await _poetCache.GetPoetAsync(PoetId, EditorCacheBypass);
if (!poetOk)
{
LastError = poetError;
return Page();
}
Poet = poet;
}
// 2. search verses
int pageNumber = 1;
if (!string.IsNullOrEmpty(Request.Query["page"]))
{
pageNumber = int.Parse(Request.Query["page"]);
}
if (pageNumber > 1)
{
ViewData["Title"] += $" - صفحہ {pageNumber.ToPersianNumbers()}";
}
HttpResponseMessage searchQueryResponse = null;
List<int> exceptPoetId = new List<int>();
foreach (var e in Request.Query["e"])
{
exceptPoetId.Add(int.Parse(e));
}
ExceptPoetId = exceptPoetId.ToArray();
string exceptUrl = "";
if (!string.IsNullOrEmpty(Query))
{
string url = $"{APIRoot.Url}/api/diwan/poems/search?term={Query}&poetId={PoetId}&catId={CatId}&PageNumber={pageNumber}&PageSize=20";
foreach (var e in exceptPoetId)
{
exceptUrl += $"&e={e}";
}
url += exceptUrl;
searchQueryResponse = await _httpClient.GetAsync(url);
if (!searchQueryResponse.IsSuccessStatusCode)
{
LastError = await ReadErrorMessageAsync(searchQueryResponse);
return Page();
}
Poems = JArray.Parse(await searchQueryResponse.Content.ReadAsStringAsync()).ToObject<List<DiwanPoemCompleteViewModel>>();
}
if (Poems != null && Poems.Count == 0)
{
Poems = null;
}
if (Poems != null)
{
// highlight searched word
string[] queryParts = Query.IndexOf('"') == 0 && Query.LastIndexOf('"') == (Query.Length - 1) ?
new string[] { Query.Replace("\"", "") }
:
Query.Replace("\"", "").Split(' ', StringSplitOptions.RemoveEmptyEntries);
foreach (var poem in Poems)
{
string[] lines = poem.PlainText.Split(Environment.NewLine, StringSplitOptions.RemoveEmptyEntries);
List<int> linesInExcerpt = new List<int>();
for (int i = 0; i < lines.Length; i++)
{
foreach (var queryPart in queryParts)
{
if (lines[i].IndexOf(queryPart) != -1)
{
if (i > 0)
{
if (linesInExcerpt.IndexOf(i - 1) == -1)
{
linesInExcerpt.Add(i - 1);
}
}
if (linesInExcerpt.IndexOf(i) == -1)
{
linesInExcerpt.Add(i);
}
if (i < (lines.Length - 1))
linesInExcerpt.Add(i + 1);
break;
}
}
}
string plainText = "";
for (int i = 0; i < linesInExcerpt.Count; i++)
{
if (linesInExcerpt[i] > 0 && linesInExcerpt.IndexOf(linesInExcerpt[i] - 1) == -1)
plainText += "... ";
plainText += $"{lines[linesInExcerpt[i]]}";
if (linesInExcerpt[i] < (lines.Length - 1) && linesInExcerpt.IndexOf(linesInExcerpt[i] + 1) == -1)
plainText += " ...";
plainText += $"{Environment.NewLine}";
}
string finalPlainText = "";
foreach (string line in plainText.Split(Environment.NewLine, StringSplitOptions.RemoveEmptyEntries))
{
finalPlainText += $"<p>{line}</p>";
}
if (linesInExcerpt.Count > 0)
{
poem.PlainText = finalPlainText;
}
for (int i = 0; i < queryParts.Length; i++)
{
string cssClass = i % 3 == 0 ? "hilite" : i % 3 == 1 ? "hilite2" : "hilite3";
poem.PlainText = Regex.Replace(poem.PlainText, queryParts[i], $"<span class=\"{cssClass}\">{queryParts[i]}</span>", RegexOptions.IgnoreCase | RegexOptions.RightToLeft); ;
}
}
if (searchQueryResponse != null)
{
string paginationMetadataJsonValue = searchQueryResponse.Headers.GetValues("paging-headers").FirstOrDefault();
if (!string.IsNullOrEmpty(paginationMetadataJsonValue))
{
PaginationMetadata = JsonConvert.DeserializeObject<PaginationMetadata>(paginationMetadataJsonValue);
string catQuery = "";
if (!string.IsNullOrEmpty(Request.Query["cat"]))
{
catQuery = $"&cat={Request.Query["cat"]}";
}
PagingToolsHtml = GeneratePagingBarHtml(PaginationMetadata, $"/search?s={WebUtility.UrlEncode(Query)}&amp;author={PoetId}{catQuery}{exceptUrl}");
}
}
}
return Page();
}
private string GeneratePagingBarHtml(PaginationMetadata paginationMetadata, string routeStartWithQueryStrings)
{
string htmlText = $"<div>{Environment.NewLine}";
if (paginationMetadata != null && paginationMetadata.totalPages > 1)
{
if (paginationMetadata.currentPage > 3)
{
htmlText += $"<a href=\"{routeStartWithQueryStrings.Replace("\"", "%22")}&amp;page=1\"><div class=\"circled-number\">۱</div></a>{Environment.NewLine} …";
}
for (int i = paginationMetadata.currentPage - 2; i <= (paginationMetadata.currentPage + 2); i++)
{
if (i >= 1 && i <= paginationMetadata.totalPages)
{
if (i == paginationMetadata.currentPage)
{
htmlText += $"<div class=\"circled-number-diff\">{i.ToPersianNumbers()}</div>";
}
else
{
htmlText += $"<a href=\"{routeStartWithQueryStrings.Replace("\"", "%22")}&amp;page={i}\"><div class=\"circled-number\">{i.ToPersianNumbers()}</div></a>{Environment.NewLine}";
}
}
}
if (paginationMetadata.totalPages > (paginationMetadata.currentPage + 2))
{
htmlText += $"… <a href=\"{routeStartWithQueryStrings.Replace("\"", "%22")}&amp;page={paginationMetadata.totalPages}\"><div class=\"circled-number\">{paginationMetadata.totalPages.ToPersianNumbers()}</div></a>{Environment.NewLine}";
}
}
htmlText += $"</div>{Environment.NewLine}";
return htmlText;
}
public async Task<IActionResult> OnGetWordCountsByPoetAsync(string term, int poetId, int catId, bool blur)
{
if (term == null) return new BadRequestObjectResult("term is null");
term = term.Replace("\"", "");
if (!string.IsNullOrEmpty(term))
{
term = term.Trim();
}
else
{
if (term == null) return new BadRequestObjectResult("term is empty");
}
string url = $"{APIRoot.Url}/api/diwan/wordcounts/bycat?term={term}";
if (poetId != 0)
{
url += $"&poetId={poetId}";
}
if (catId != 0)
{
url += $"&catId={catId}";
}
var wordCountsResponse = await _httpClient.GetAsync(url);
if (!wordCountsResponse.IsSuccessStatusCode)
{
return new BadRequestObjectResult(await ReadErrorMessageAsync(wordCountsResponse));
}
var wordCounts = JsonConvert.DeserializeObject<PoetOrCatWordStat[]>(await wordCountsResponse.Content.ReadAsStringAsync());
string countStr = wordCountsResponse.Headers.GetValues("items-count").FirstOrDefault();
return Partial("~/Pages/Partials/Search/_CategoryWordsCountByCatPartial.cshtml", new _CategoryWordsCountByCatPartialModel()
{
Term = term,
WordStats = wordCounts,
Whole = catId == 0 && poetId == 0,
TotalCount = string.IsNullOrEmpty(countStr) ? 0 : int.Parse(countStr),
Blur = blur,
});
}
}
}