tajik home page

This commit is contained in:
Hamid Reza Mohammadi 2026-08-16 17:40:26 +03:30
parent 133b65606c
commit c8782e73e2
4 changed files with 135 additions and 33 deletions

View File

@ -59,48 +59,25 @@
{ {
var popularGroup = Model.PoetGroups.SingleOrDefault(g => g.Id == 0); var popularGroup = Model.PoetGroups.SingleOrDefault(g => g.Id == 0);
var centuryGroups = Model.PoetGroups.Where(g => g.Id != 0).ToList(); 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"> <div class="tg-poet-search">
<input type="text" id="tg-findpoet" placeholder="Ҷустуҷӯи шоир" oninput="tgInlineSearch(this.value)" /> <input type="text" id="tg-findpoet" placeholder="Ҷустуҷӯи шоир" oninput="tgInlineSearch(this.value)" />
<div class="tg-poet-grid" id="tg-found-poets"></div> <div class="tg-poet-grid" id="tg-found-poets"></div>
</div> </div>
@if (centuryGroups.Count > 0) <div class="tg-view-switch">
{ <button type="button" class="tg-view-switch-btn active" data-view="century" onclick="tgSwitchView('century')">Аз рӯи аср</button>
<div class="tg-century-heading">Гурӯҳбандӣ бар асоси аср</div> <button type="button" class="tg-view-switch-btn" data-view="alpha" onclick="tgSwitchView('alpha')">Аз рӯи алифбо</button>
<div class="tg-century-nav"> </div>
@foreach (var g in centuryGroups)
{
<a href="#tg-pg-@g.Id" class="circled-number">@(g.HalfCenturyOrder + 2)</a>
}
</div>
@foreach (var century in centuryGroups) <div id="tg-view-century">
@if (popularGroup != null)
{ {
@if (century.ShowInTimeLine) <div class="tg-century-heading">Шоирони маъмул</div>
{
<div class="tg-century-heading" id="tg-pg-@century.Id">@century.Name</div>
}
<div class="tg-poet-grid"> <div class="tg-poet-grid">
@foreach (var poet in century.Poets) @foreach (var poet in popularGroup.Poets)
{ {
<a class="tg-poet-card" href="@poet.FullUrl" title="@poet.Name" data-value="@poet.Nickname"> <a class="tg-poet-card" href="@poet.FullUrl" title="@poet.Name" data-poet-id="@poet.Id" data-value="@poet.Nickname">
<span class="tg-poet-portrait"> <span class="tg-poet-portrait">
<img src="/images/poets/@(poet.Id).png" alt="@poet.Nickname" loading="lazy" <img src="/images/poets/@(poet.Id).png" alt="@poet.Nickname" loading="lazy"
onerror="this.onerror=null;this.src='/images/poets/999.png';" /> onerror="this.onerror=null;this.src='/images/poets/999.png';" />
@ -110,7 +87,54 @@
} }
</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-poet-id="@poet.Id" 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>
<div id="tg-view-alpha" style="display:none">
<div class="tg-century-heading">Аз рӯи алифбо</div>
<div class="tg-poet-grid">
@foreach (var poet in Model.AlphabeticalPoets)
{
<a class="tg-poet-card" href="@poet.FullUrl" title="@poet.Name" data-poet-id="@poet.Id" 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>
} }
else else
{ {

View File

@ -38,6 +38,13 @@ namespace TajikGanjoor.Pages
/// poets grouped by century, shown on the home page - mirrors GanjooRazor's Index.cshtml.cs /// poets grouped by century, shown on the home page - mirrors GanjooRazor's Index.cshtml.cs
/// </summary> /// </summary>
public List<GanjoorCenturyViewModel> PoetGroups { get; set; } public List<GanjoorCenturyViewModel> PoetGroups { get; set; }
/// <summary>
/// all translated poets, sorted alphabetically by Tajik nickname - alternate flat view,
/// switched to on the home page instead of the century grouping
/// </summary>
public List<GanjoorPoetViewModel> AlphabeticalPoets { get; set; }
public async Task<IActionResult> OnGetAsync() public async Task<IActionResult> OnGetAsync()
{ {
if (bool.Parse(Configuration["MaintenanceMode"] ?? false.ToString())) if (bool.Parse(Configuration["MaintenanceMode"] ?? false.ToString()))
@ -58,6 +65,21 @@ namespace TajikGanjoor.Pages
{ {
return Page(); return Page();
} }
// plain ordinal sort gets this wrong: the six Tajik-specific letters
// (ғ ӣ қ ӯ ҳ ҷ) sit outside the main Cyrillic code point block, so they'd
// sort out of alphabetical order (e.g. after "я") under Ordinal - a
// culture-aware comparer places them correctly relative to their base
// letter (ғ after г, etc). Falls back to Ordinal if "tg-TJ" collation
// data isn't available on the server, rather than failing the page.
try
{
var tajikCulture = System.Globalization.CultureInfo.GetCultureInfo("tg-TJ");
AlphabeticalPoets = Poets.OrderBy(p => p.Nickname, StringComparer.Create(tajikCulture, false)).ToList();
}
catch (System.Globalization.CultureNotFoundException)
{
AlphabeticalPoets = Poets.OrderBy(p => p.Nickname, StringComparer.Ordinal).ToList();
}
} }
int pageNumber = 1; int pageNumber = 1;
if (IsSearchPage) if (IsSearchPage)

View File

@ -693,6 +693,38 @@ html, body {
margin-top: 14px; margin-top: 14px;
} }
/* century-grouped vs. alphabetical homepage view switch */
.tg-view-switch {
display: flex;
justify-content: center;
gap: 8px;
max-width: 480px;
margin: 18px auto 0 auto;
padding: 0 8px;
}
.tg-view-switch-btn {
font-family: 'PT Sans', sans-serif;
font-size: 0.9rem;
padding: 7px 16px;
border: 1px solid var(--tg-rule);
border-radius: 20px;
background: var(--tg-surface);
color: var(--tg-ink-soft);
cursor: pointer;
}
.tg-view-switch-btn:hover {
border-color: var(--tg-accent);
color: var(--tg-accent-deep);
}
.tg-view-switch-btn.active {
background: var(--tg-accent-deep);
border-color: var(--tg-accent-deep);
color: #fff;
}
/* ---------- error page ---------- */ /* ---------- error page ---------- */
.text-danger { .text-danger {

View File

@ -6,6 +6,10 @@
// clones the matches into the results container. No numeral conversion needed here (unlike the // 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 // Persian site, which converts typed digits to Persian-Indic numerals first) since Tajik Cyrillic
// uses plain Western digits. // uses plain Western digits.
//
// A "popular" poet's card appears more than once in the page (once in the popular-poets section,
// again in their real century group, and again in the alphabetical listing) - deduped here by
// data-poet-id so a matching search doesn't show the same poet two or three times.
function tgInlineSearch(value) { function tgInlineSearch(value) {
var resultsNode = document.getElementById('tg-found-poets'); var resultsNode = document.getElementById('tg-found-poets');
if (!resultsNode) return; if (!resultsNode) return;
@ -14,10 +18,30 @@ function tgInlineSearch(value) {
var needle = value.toLowerCase(); var needle = value.toLowerCase();
var cards = document.querySelectorAll('.tg-poet-card[data-value]'); var cards = document.querySelectorAll('.tg-poet-card[data-value]');
var seenIds = {};
for (var i = 0; i < cards.length; i++) { for (var i = 0; i < cards.length; i++) {
var poetId = cards[i].getAttribute('data-poet-id');
if (poetId && seenIds[poetId]) continue;
var haystack = cards[i].getAttribute('data-value').toLowerCase(); var haystack = cards[i].getAttribute('data-value').toLowerCase();
if (haystack.indexOf(needle) !== -1) { if (haystack.indexOf(needle) !== -1) {
resultsNode.appendChild(cards[i].cloneNode(true)); resultsNode.appendChild(cards[i].cloneNode(true));
if (poetId) seenIds[poetId] = true;
} }
} }
} }
// Toggles the homepage between the century-grouped view and the flat alphabetical listing.
function tgSwitchView(view) {
var centuryView = document.getElementById('tg-view-century');
var alphaView = document.getElementById('tg-view-alpha');
if (!centuryView || !alphaView) return;
centuryView.style.display = view === 'century' ? '' : 'none';
alphaView.style.display = view === 'alpha' ? '' : 'none';
var buttons = document.querySelectorAll('.tg-view-switch-btn');
for (var i = 0; i < buttons.length; i++) {
buttons[i].classList.toggle('active', buttons[i].getAttribute('data-view') === view);
}
}