tajik site changes
This commit is contained in:
parent
978c6f6e0e
commit
133b65606c
@ -57,18 +57,60 @@
|
||||
else
|
||||
@if (Model.IsHomePage)
|
||||
{
|
||||
<div class="tg-poet-grid">
|
||||
@foreach (var poet in Model.Poets)
|
||||
{
|
||||
<a class="tg-poet-card" href="@poet.FullUrl" title="@poet.Name">
|
||||
<span class="tg-poet-portrait">
|
||||
<img src="/images/poets/@(poet.Id).png" alt="@poet.Nickname" loading="lazy"
|
||||
onerror="this.onerror=null;this.src='/images/poets/999.png';" />
|
||||
</span>
|
||||
<span class="tg-poet-name">@poet.Nickname</span>
|
||||
</a>
|
||||
}
|
||||
var popularGroup = Model.PoetGroups.SingleOrDefault(g => g.Id == 0);
|
||||
var centuryGroups = Model.PoetGroups.Where(g => g.Id != 0).ToList();
|
||||
@if (popularGroup != null)
|
||||
{
|
||||
<div class="tg-century-heading">Шоирони маъмул</div>
|
||||
<div class="tg-poet-grid">
|
||||
@foreach (var poet in popularGroup.Poets)
|
||||
{
|
||||
<a class="tg-poet-card" href="@poet.FullUrl" title="@poet.Name" data-value="@poet.Nickname">
|
||||
<span class="tg-poet-portrait">
|
||||
<img src="/images/poets/@(poet.Id).png" alt="@poet.Nickname" loading="lazy"
|
||||
onerror="this.onerror=null;this.src='/images/poets/999.png';" />
|
||||
</span>
|
||||
<span class="tg-poet-name">@poet.Nickname</span>
|
||||
</a>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="tg-poet-search">
|
||||
<input type="text" id="tg-findpoet" placeholder="Ҷустуҷӯи шоир" oninput="tgInlineSearch(this.value)" />
|
||||
<div class="tg-poet-grid" id="tg-found-poets"></div>
|
||||
</div>
|
||||
|
||||
@if (centuryGroups.Count > 0)
|
||||
{
|
||||
<div class="tg-century-heading">Гурӯҳбандӣ бар асоси аср</div>
|
||||
<div class="tg-century-nav">
|
||||
@foreach (var g in centuryGroups)
|
||||
{
|
||||
<a href="#tg-pg-@g.Id" class="circled-number">@(g.HalfCenturyOrder + 2)</a>
|
||||
}
|
||||
</div>
|
||||
|
||||
@foreach (var century in centuryGroups)
|
||||
{
|
||||
@if (century.ShowInTimeLine)
|
||||
{
|
||||
<div class="tg-century-heading" id="tg-pg-@century.Id">@century.Name</div>
|
||||
}
|
||||
<div class="tg-poet-grid">
|
||||
@foreach (var poet in century.Poets)
|
||||
{
|
||||
<a class="tg-poet-card" href="@poet.FullUrl" title="@poet.Name" data-value="@poet.Nickname">
|
||||
<span class="tg-poet-portrait">
|
||||
<img src="/images/poets/@(poet.Id).png" alt="@poet.Nickname" loading="lazy"
|
||||
onerror="this.onerror=null;this.src='/images/poets/999.png';" />
|
||||
</span>
|
||||
<span class="tg-poet-name">@poet.Nickname</span>
|
||||
</a>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@ -8,6 +8,7 @@ using RMuseum.Models.Ganjoor;
|
||||
using System.Net;
|
||||
using System.Text.RegularExpressions;
|
||||
using RSecurityBackend.Models.Generic;
|
||||
using RMuseum.Services.Implementation;
|
||||
|
||||
|
||||
|
||||
@ -32,6 +33,11 @@ namespace TajikGanjoor.Pages
|
||||
public string PagingToolsHtml { get; set; }
|
||||
|
||||
public List<GanjoorPoemCompleteViewModel> Poems { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// poets grouped by century, shown on the home page - mirrors GanjooRazor's Index.cshtml.cs
|
||||
/// </summary>
|
||||
public List<GanjoorCenturyViewModel> PoetGroups { get; set; }
|
||||
public async Task<IActionResult> OnGetAsync()
|
||||
{
|
||||
if (bool.Parse(Configuration["MaintenanceMode"] ?? false.ToString()))
|
||||
@ -46,6 +52,13 @@ namespace TajikGanjoor.Pages
|
||||
}
|
||||
IsSearchPage = !string.IsNullOrEmpty(Request.Query["s"]);
|
||||
IsHomePage = !IsSearchPage && Request.Path == "/";
|
||||
if (IsHomePage)
|
||||
{
|
||||
if (false == await PreparePoetGroupsAsync())
|
||||
{
|
||||
return Page();
|
||||
}
|
||||
}
|
||||
int pageNumber = 1;
|
||||
if (IsSearchPage)
|
||||
{
|
||||
@ -314,6 +327,77 @@ namespace TajikGanjoor.Pages
|
||||
Poets = poets ?? [];
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// fetches the same /api/ganjoor/centuries grouping GanjooRazor's homepage uses, then
|
||||
/// overlays Tajik nickname/description onto each group's poets the same way
|
||||
/// PreparePoetsAsync does, dropping poets with no Tajik translation and dropping any
|
||||
/// century that ends up empty as a result - century Name itself has no dedicated Tajik
|
||||
/// translation source anywhere, so it falls back to LanguageUtils.TransliteratePersianToTajik
|
||||
/// (a handful of these are short/fixed enough that hand-correcting the wording later,
|
||||
/// directly in this list, would be easy if the transliteration reads awkwardly)
|
||||
/// </summary>
|
||||
private async Task<bool> PreparePoetGroupsAsync()
|
||||
{
|
||||
var cacheKey = $"/api/ganjoor/tajik/centuries";
|
||||
if (!_memoryCache.TryGetValue(cacheKey, out List<GanjoorCenturyViewModel>? poetGroups))
|
||||
{
|
||||
try
|
||||
{
|
||||
var res1 = await _httpClient.GetAsync($"{APIRoot.Url}/api/ganjoor/centuries");
|
||||
if (!res1.IsSuccessStatusCode)
|
||||
{
|
||||
LastError = JsonConvert.DeserializeObject<string>(await res1.Content.ReadAsStringAsync()) ?? "!res1.IsSuccessStatusCode";
|
||||
return false;
|
||||
}
|
||||
var centuries = JArray.Parse(await res1.Content.ReadAsStringAsync()).ToObject<List<GanjoorCenturyViewModel>>();
|
||||
centuries = centuries ?? [];
|
||||
|
||||
var response = await _httpClient.GetAsync($"{APIRoot.Url}/api/ganjoor/tajik/poets");
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
LastError = JsonConvert.DeserializeObject<string>(await response.Content.ReadAsStringAsync()) ?? "!response.IsSuccessStatusCode";
|
||||
return false;
|
||||
}
|
||||
var tajikPoets = JArray.Parse(await response.Content.ReadAsStringAsync()).ToObject<List<GanjoorTajikPoet>>() ?? [];
|
||||
|
||||
poetGroups = new List<GanjoorCenturyViewModel>();
|
||||
foreach (var century in centuries)
|
||||
{
|
||||
var translatedPoets = new List<GanjoorPoetViewModel>();
|
||||
foreach (var poet in century.Poets)
|
||||
{
|
||||
var tajikPoet = tajikPoets.SingleOrDefault(p => p.Id == poet.Id);
|
||||
if (tajikPoet != null)
|
||||
{
|
||||
poet.Nickname = tajikPoet.TajikNickname;
|
||||
poet.Description = tajikPoet.TajikDescription;
|
||||
translatedPoets.Add(poet);
|
||||
}
|
||||
}
|
||||
if (translatedPoets.Count > 0)
|
||||
{
|
||||
century.Poets = translatedPoets;
|
||||
century.Name = LanguageUtils.TransliteratePersianToTajik(century.Name);
|
||||
poetGroups.Add(century);
|
||||
}
|
||||
}
|
||||
|
||||
if (AggressiveCacheEnabled)
|
||||
{
|
||||
_memoryCache.Set(cacheKey, poetGroups, TimeSpan.FromHours(1));
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
LastError = e.ToString();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
PoetGroups = poetGroups ?? [];
|
||||
return true;
|
||||
}
|
||||
private void PrepareNextPre()
|
||||
{
|
||||
if (GanjoorPage == null) return;
|
||||
|
||||
@ -69,6 +69,26 @@
|
||||
src: url('/fonts/ptsans/pt-sans-cyrillic-ext-700-italic.woff2') format('woff2');
|
||||
}
|
||||
|
||||
/* Persian original text (interlinear comparison lines) - Cyrillic fonts above have no
|
||||
Arabic-script glyphs at all, so this needs its own font. Vazirmatn is the same font
|
||||
the main ganjoor.net site self-hosts for Persian, reused here for a consistent,
|
||||
recognizable feel across the two sites. */
|
||||
@font-face {
|
||||
font-family: 'Vazirmatn';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url('/fonts/vazirmatn/vazirmatn-arabic-400-normal.woff2') format('woff2');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Vazirmatn';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
src: url('/fonts/vazirmatn/vazirmatn-arabic-700-normal.woff2') format('woff2');
|
||||
}
|
||||
|
||||
/* ---------- base page: warm paper tone, PT Sans chrome ---------- */
|
||||
|
||||
:root {
|
||||
@ -292,6 +312,27 @@ html, body {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* Persian original, shown interlinear directly beneath its Tajik line (per
|
||||
verse.OriginalText - see PrepareBilingualHtmlText). Deliberately smaller
|
||||
and quieter than the Tajik line above it: this is a Tajik-first site, the
|
||||
Persian is a reference/comparison aid, not the primary reading text.
|
||||
Selectors repeat the base verse rules' specificity + the .tg-fa class so
|
||||
this reliably wins regardless of source order. */
|
||||
.b .m1 p.tg-fa,
|
||||
.b .m2 p.tg-fa,
|
||||
.b2 p.tg-fa,
|
||||
.n p.tg-fa,
|
||||
.l p.tg-fa,
|
||||
.c p.tg-fa {
|
||||
font-family: 'Vazirmatn', Tahoma, sans-serif;
|
||||
font-size: 0.82em;
|
||||
line-height: 1.7;
|
||||
color: var(--tg-ink-soft);
|
||||
opacity: 0.85;
|
||||
margin: 0.2em 0 0 0;
|
||||
unicode-bidi: isolate;
|
||||
}
|
||||
|
||||
/* poem title */
|
||||
.text-center h1, .text-center h2 {
|
||||
font-family: 'PT Serif', serif;
|
||||
@ -581,6 +622,7 @@ html, body {
|
||||
border-radius: 50%;
|
||||
font-family: 'PT Sans', sans-serif;
|
||||
font-size: 0.9rem;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.circled-number {
|
||||
@ -598,6 +640,59 @@ html, body {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
/* ---------- homepage: century grouping + inline poet search ---------- */
|
||||
|
||||
.tg-century-heading {
|
||||
max-width: 980px;
|
||||
margin: 28px auto 4px auto;
|
||||
padding: 0 8px;
|
||||
font-family: 'PT Serif', serif;
|
||||
font-weight: 700;
|
||||
font-size: 1.05rem;
|
||||
color: var(--tg-accent-deep);
|
||||
text-align: start;
|
||||
border-bottom: 1px solid var(--tg-rule);
|
||||
padding-bottom: 6px;
|
||||
}
|
||||
|
||||
.tg-century-nav {
|
||||
max-width: 980px;
|
||||
margin: 8px auto 0 auto;
|
||||
padding: 0 8px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.tg-poet-search {
|
||||
max-width: 480px;
|
||||
margin: 20px auto 0 auto;
|
||||
padding: 0 8px;
|
||||
}
|
||||
|
||||
.tg-poet-search input {
|
||||
width: 100%;
|
||||
padding: 8px 12px;
|
||||
font-family: 'PT Sans', sans-serif;
|
||||
font-size: 0.95rem;
|
||||
border: 1px solid var(--tg-rule);
|
||||
border-radius: 6px;
|
||||
background: var(--tg-surface);
|
||||
color: var(--tg-ink);
|
||||
}
|
||||
|
||||
.tg-poet-search input:focus {
|
||||
outline: none;
|
||||
border-color: var(--tg-accent);
|
||||
}
|
||||
|
||||
/* search results grid only takes up space once populated by tgInlineSearch (site.js) */
|
||||
.tg-poet-search .tg-poet-grid:empty {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.tg-poet-search .tg-poet-grid {
|
||||
margin-top: 14px;
|
||||
}
|
||||
|
||||
/* ---------- error page ---------- */
|
||||
|
||||
.text-danger {
|
||||
|
||||
@ -7,14 +7,15 @@
|
||||
does the highlighting.
|
||||
|
||||
Verse elements are counted in document order across .m1, .m2 (the two
|
||||
halves of a couplet), .b2 > p (rubaʼi-style already-centered couplets),
|
||||
halves of a couplet), .b2 > p (rubaʼi-style already-centered couplets,
|
||||
excluding the interlinear Persian .tg-fa line added alongside each one),
|
||||
.n and .l (paragraph/single-line verses) - same shape PrepareHtmlText
|
||||
produces for both the Persian and Tajik sites, so the sync XML's
|
||||
VerseOrder values line up the same way. */
|
||||
(function () {
|
||||
function getVerseElements() {
|
||||
return Array.prototype.slice.call(
|
||||
document.querySelectorAll('.m1, .m2, .n, .l, .b2 > p')
|
||||
document.querySelectorAll('.m1, .m2, .n, .l, .b2 > p:not(.tg-fa)')
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -1,4 +1,23 @@
|
||||
// Please see documentation at https://learn.microsoft.com/aspnet/core/client-side/bundling-and-minification
|
||||
// for details on configuring this project to bundle and minify static web assets.
|
||||
|
||||
// Write your JavaScript code.
|
||||
// Homepage inline poet search - simplified port of onInlineSearch (GanjooRazor's wwwroot/js/bk.js):
|
||||
// substring-matches the typed text against each poet card's data-value (Cyrillic nickname) and
|
||||
// clones the matches into the results container. No numeral conversion needed here (unlike the
|
||||
// Persian site, which converts typed digits to Persian-Indic numerals first) since Tajik Cyrillic
|
||||
// uses plain Western digits.
|
||||
function tgInlineSearch(value) {
|
||||
var resultsNode = document.getElementById('tg-found-poets');
|
||||
if (!resultsNode) return;
|
||||
resultsNode.innerHTML = '';
|
||||
if (value.length === 0) return;
|
||||
|
||||
var needle = value.toLowerCase();
|
||||
var cards = document.querySelectorAll('.tg-poet-card[data-value]');
|
||||
for (var i = 0; i < cards.length; i++) {
|
||||
var haystack = cards[i].getAttribute('data-value').toLowerCase();
|
||||
if (haystack.indexOf(needle) !== -1) {
|
||||
resultsNode.appendChild(cards[i].cloneNode(true));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user