Selecting reading text shows a menu by the selection: معنی (word sidebar, single words), تلاش (search Divan, phrases quoted), کاپی (text with poet, title and link), شیئر (link to the page with the selection highlighted via ?hl=; native share sheet where available), قرآن (tanzil.net), گوگل. Escape or clearing the selection closes it; no layout shift. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
102 lines
5.0 KiB
Plaintext
102 lines
5.0 KiB
Plaintext
---
|
||
// Poet (/p238), category (/p238/shaeri/c641) and poem (/p238/shaeri/c641/sh6556) pages
|
||
import Base from '../layouts/Base.astro';
|
||
import Seg from '../components/Seg.astro';
|
||
import { api } from '../lib/api';
|
||
import { ud, years, highlighter, parts } from '../lib/urdu';
|
||
|
||
const url = '/' + (Astro.params.path ?? '');
|
||
const page = await api(`/api/page?url=${encodeURIComponent(url)}`);
|
||
if (!page) return new Response(null, { status: 404, statusText: 'Not found' });
|
||
|
||
// group verses into couplets / single lines / paragraphs for rendering
|
||
type Verse = { position: string; couplet: number; text: string };
|
||
const blocks: { kind: 'couplet' | 'single' | 'para'; lines: string[]; mark?: string }[] = [];
|
||
if (page.type === 'poem') {
|
||
const { matla, maqta } = page.poem;
|
||
for (const v of page.verses as Verse[]) {
|
||
if (v.position === 'Right') blocks.push({ kind: 'couplet', lines: [v.text],
|
||
mark: v.couplet === matla ? 'مطلع' : v.couplet === maqta ? 'مقطع' : undefined });
|
||
else if (v.position === 'Left' && blocks.at(-1)?.kind === 'couplet' && blocks.at(-1)!.lines.length === 1) blocks.at(-1)!.lines.push(v.text);
|
||
else if (v.position === 'Paragraph') blocks.push({ kind: 'para', lines: [v.text] });
|
||
else blocks.push({ kind: 'single', lines: [v.text] });
|
||
}
|
||
}
|
||
// pen name (marked ؔ in the text) set apart; split() with a capture group puts matches at odd indexes
|
||
// ?hl= (from search results) marks the searched words on the page; segments: [text, 'mark' | 'pen' | '']
|
||
const hlq = Astro.url.searchParams.get('hl') ?? '';
|
||
const re = highlighter(hlq);
|
||
const show = (line: string) => parts(line, re).flatMap((s, i): [string, string][] =>
|
||
i % 2 ? [[s, 'mark']] : s.split(/(\S*\u0614\S*)/).map((t, j) => [t, j % 2 ? 'pen' : '']));
|
||
// contents of a divan grouped by radif letter ("ردیف الف … ی"); ے is filed under ی as in printed divans
|
||
const LETTER = (l: string) => (l === 'ا' ? 'الف' : l === 'ے' ? 'ی' : l);
|
||
const groups: { letter: string | null; poems: any[] }[] = [];
|
||
for (const p of page.poems ?? []) {
|
||
const l = p.radif_letter ? LETTER(p.radif_letter) : null;
|
||
if (groups.at(-1)?.letter !== l || !groups.length) groups.push({ letter: l, poems: [] });
|
||
groups.at(-1)!.poems.push(p);
|
||
}
|
||
const crumbs = page.breadcrumbs ?? [];
|
||
const title = page.type === 'poem' ? page.poem.title : page.type === 'poet' ? page.poet.nickname : page.category.title;
|
||
---
|
||
<Base title={page.type === 'poem' ? `${page.poem.title} · ${page.poet.nickname}` : title} description={page.type === 'poet' ? page.poet.description?.slice(0, 160) : undefined}>
|
||
{page.type !== 'poet' && (
|
||
<nav class="crumbs">
|
||
{crumbs.map((c: any, i: number) => (
|
||
i < crumbs.length - 1 || page.type === 'poem' ? <span><a href={c.url}>{c.title}</a></span> : <span>{c.title}</span>
|
||
))}
|
||
</nav>
|
||
)}
|
||
|
||
{page.type === 'poet' && (
|
||
<>
|
||
<h1>{page.poet.name}</h1>
|
||
<p class="muted">{years(page.poet)}</p>
|
||
{page.poet.description && <div class="intro">{page.poet.description}</div>}
|
||
</>
|
||
)}
|
||
{page.type === 'category' && <h1>{page.category.title}</h1>}
|
||
|
||
{page.type !== 'poem' && page.children.length > 0 && (
|
||
<div class="cards">
|
||
{page.children.map((c: any) => (
|
||
<a class="card" href={c.url}>{c.title}{c.poems > 0 && <small>{ud(c.poems)}</small>}</a>
|
||
))}
|
||
</div>
|
||
)}
|
||
{page.type !== 'poem' && groups.map((g) => (
|
||
<>
|
||
{g.letter && <h2 class="radif-head">ردیف {g.letter}</h2>}
|
||
<ol class="toc">{g.poems.map((p: any) => <li><a href={p.url}>{p.title}</a></li>)}</ol>
|
||
</>
|
||
))}
|
||
|
||
{page.type === 'poem' && (
|
||
<>
|
||
<h1><Seg s={show(page.poem.title)} /></h1>
|
||
{re && (
|
||
<p class="hl-note muted">
|
||
«{hlq}» نمایاں · <a href={`/search?q=${encodeURIComponent(hlq)}`}>تلاش کے نتائج</a> · <a href={url}>نشان ہٹائیں</a>
|
||
</p>
|
||
)}
|
||
{page.poem.radif != null && <p class="muted radif">{page.poem.radif ? `ردیف: ${page.poem.radif}` : 'غیر مردف'}</p>}
|
||
<article class="poem" data-cite={`${page.poet.nickname}، ${page.poem.title}`}>
|
||
{blocks.map((b) =>
|
||
b.kind === 'couplet' ? <div class="couplet" data-mark={b.mark}>{b.lines.map((l) => <p><Seg s={show(l)} /></p>)}</div>
|
||
: b.kind === 'para' ? <p class="para"><Seg s={show(b.lines[0])} /></p>
|
||
: <p class="single"><Seg s={show(b.lines[0])} /></p>
|
||
)}
|
||
</article>
|
||
<p class="muted hint">کسی لفظ کو منتخب کریں تو اس کے معنی اور تلفظ ساتھ نظر آئیں گے۔</p>
|
||
<nav class="poem-nav">
|
||
{page.next ? <a href={page.next}>اگلا ›</a> : <span />}
|
||
{page.prev ? <a href={page.prev}>‹ پچھلا</a> : <span />}
|
||
</nav>
|
||
{page.poem.source_url && (
|
||
<p class="source">ماخذ: <a href={page.poem.source_url}>ویکی ماخذ</a> · {page.poet && <a href={page.poet.url}>{page.poet.nickname}</a>}</p>
|
||
)}
|
||
</>
|
||
)}
|
||
{re && <script is:inline>document.querySelector('.poem mark')?.scrollIntoView({ block: 'center' });</script>}
|
||
</Base>
|