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
-
- }
+ var popularGroup = Model.PoetGroups.SingleOrDefault(g => g.Id == 0);
+ var centuryGroups = Model.PoetGroups.Where(g => g.Id != 0).ToList();
+ @if (popularGroup != null)
+ {
+
Шоирони маъмул
+
+ }
+
+
+
+ @if (centuryGroups.Count > 0)
+ {
+
Гурӯҳбандӣ бар асоси аср
+
+
+ @foreach (var century in centuryGroups)
+ {
+ @if (century.ShowInTimeLine)
+ {
+
@century.Name
+ }
+
+ }
+ }
}
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));
+ }
+ }
+}