diff --git a/TajikGanjoor/Pages/Index.cshtml b/TajikGanjoor/Pages/Index.cshtml index bd2d945a..77d35a28 100644 --- a/TajikGanjoor/Pages/Index.cshtml +++ b/TajikGanjoor/Pages/Index.cshtml @@ -57,18 +57,60 @@ else @if (Model.IsHomePage) { -
- @foreach (var poet in Model.Poets) - { - - - @poet.Nickname - - @poet.Nickname - - } + var popularGroup = Model.PoetGroups.SingleOrDefault(g => g.Id == 0); + var centuryGroups = Model.PoetGroups.Where(g => g.Id != 0).ToList(); + @if (popularGroup != null) + { +
Шоирони маъмул
+
+ @foreach (var poet in popularGroup.Poets) + { + + + @poet.Nickname + + @poet.Nickname + + } +
+ } + + + + @if (centuryGroups.Count > 0) + { +
Гурӯҳбандӣ бар асоси аср
+
+ @foreach (var g in centuryGroups) + { + @(g.HalfCenturyOrder + 2) + } +
+ + @foreach (var century in centuryGroups) + { + @if (century.ShowInTimeLine) + { +
@century.Name
+ } +
+ @foreach (var poet in century.Poets) + { + + + @poet.Nickname + + @poet.Nickname + + } +
+ } + } } else { diff --git a/TajikGanjoor/Pages/Index.cshtml.cs b/TajikGanjoor/Pages/Index.cshtml.cs index b8f53776..d2347fdd 100644 --- a/TajikGanjoor/Pages/Index.cshtml.cs +++ b/TajikGanjoor/Pages/Index.cshtml.cs @@ -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 Poems { get; set; } + + /// + /// poets grouped by century, shown on the home page - mirrors GanjooRazor's Index.cshtml.cs + /// + public List PoetGroups { get; set; } public async Task 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; } + + /// + /// 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) + /// + private async Task PreparePoetGroupsAsync() + { + var cacheKey = $"/api/ganjoor/tajik/centuries"; + if (!_memoryCache.TryGetValue(cacheKey, out List? poetGroups)) + { + try + { + var res1 = await _httpClient.GetAsync($"{APIRoot.Url}/api/ganjoor/centuries"); + if (!res1.IsSuccessStatusCode) + { + LastError = JsonConvert.DeserializeObject(await res1.Content.ReadAsStringAsync()) ?? "!res1.IsSuccessStatusCode"; + return false; + } + var centuries = JArray.Parse(await res1.Content.ReadAsStringAsync()).ToObject>(); + centuries = centuries ?? []; + + var response = await _httpClient.GetAsync($"{APIRoot.Url}/api/ganjoor/tajik/poets"); + if (!response.IsSuccessStatusCode) + { + LastError = JsonConvert.DeserializeObject(await response.Content.ReadAsStringAsync()) ?? "!response.IsSuccessStatusCode"; + return false; + } + var tajikPoets = JArray.Parse(await response.Content.ReadAsStringAsync()).ToObject>() ?? []; + + poetGroups = new List(); + foreach (var century in centuries) + { + var translatedPoets = new List(); + 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; diff --git a/TajikGanjoor/wwwroot/css/tajik-ganjoor.css b/TajikGanjoor/wwwroot/css/tajik-ganjoor.css index 4d53e7e7..f9ec9a82 100644 --- a/TajikGanjoor/wwwroot/css/tajik-ganjoor.css +++ b/TajikGanjoor/wwwroot/css/tajik-ganjoor.css @@ -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 { diff --git a/TajikGanjoor/wwwroot/js/recitation-sync.js b/TajikGanjoor/wwwroot/js/recitation-sync.js index a60fe057..b3115109 100644 --- a/TajikGanjoor/wwwroot/js/recitation-sync.js +++ b/TajikGanjoor/wwwroot/js/recitation-sync.js @@ -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)') ); } diff --git a/TajikGanjoor/wwwroot/js/site.js b/TajikGanjoor/wwwroot/js/site.js index 09376573..3d0d6cb1 100644 --- a/TajikGanjoor/wwwroot/js/site.js +++ b/TajikGanjoor/wwwroot/js/site.js @@ -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)); + } + } +}