Urdu on-screen keyboard and phonetic typing in search

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Anas Rashid 2026-10-08 21:11:57 +02:00
parent 87cea30f28
commit f82df122b9
2 changed files with 49 additions and 1 deletions

View File

@ -34,13 +34,15 @@ const fullTitle = title ? `${title} · دیوان` : 'دیوان · اردو ک
<div class="wrap"> <div class="wrap">
<a class="wordmark" href="/">دیوان</a> <a class="wordmark" href="/">دیوان</a>
<form class="search-form" action="/search" method="get" role="search"> <form class="search-form" action="/search" method="get" role="search">
<input type="search" name="q" value={q} placeholder="شعر، لفظ یا عبارت تلاش کریں" aria-label="تلاش" /> <input type="search" name="q" id="q" value={q} placeholder="شعر، لفظ یا عبارت تلاش کریں" aria-label="تلاش" autocomplete="off" />
<button type="button" id="kb-toggle" aria-label="اردو کی بورڈ" aria-pressed="false" title="اردو کی بورڈ (فونیٹک ٹائپنگ)">ا ب</button>
</form> </form>
<div class="toggles"> <div class="toggles">
<button type="button" id="font-toggle" aria-label="خط بدلیں">نسخ</button> <button type="button" id="font-toggle" aria-label="خط بدلیں">نسخ</button>
<button type="button" id="theme-toggle" aria-label="روشن یا تاریک">◐</button> <button type="button" id="theme-toggle" aria-label="روشن یا تاریک">◐</button>
</div> </div>
</div> </div>
<div id="kb" class="wrap kb" hidden></div>
</header> </header>
<main class="wrap"><slot /></main> <main class="wrap"><slot /></main>
<footer class="site-footer"> <footer class="site-footer">
@ -59,6 +61,40 @@ const fullTitle = title ? `${title} · دیوان` : 'دیوان · اردو ک
try { localStorage.setItem('divan-font', root.classList.contains('nastaliq') ? 'nastaliq' : 'naskh'); } catch (e) {} try { localStorage.setItem('divan-font', root.classList.contains('nastaliq') ? 'nastaliq' : 'naskh'); } catch (e) {}
label(); label();
}; };
// Urdu keyboard: on-screen keys + phonetic typing (Latin keys -> Urdu) while open
const PHONETIC = {
q:'ق', w:'و', e:'ع', r:'ر', t:'ت', y:'ے', u:'ء', i:'ی', o:'ہ', p:'پ',
a:'ا', s:'س', d:'د', f:'ف', g:'گ', h:'ح', j:'ج', k:'ک', l:'ل',
z:'ز', x:'ش', c:'چ', v:'ط', b:'ب', n:'ن', m:'م',
A:'آ', T:'ٹ', D:'ڈ', R:'ڑ', N:'ں', Z:'ذ', S:'ص', G:'غ', H:'ھ', K:'خ', C:'ث', V:'ظ', X:'ژ', J:'ض', I:'ئ', O:'ۂ', Y:'ؤ',
',':'،', '.':'۔', '?':'؟', ';':'؛',
};
const ROWS = ['qwertyuiop', 'asdfghjkl', 'zxcvbnm', 'ATDRNZSGHKCVXJIOY', ',.?;'];
const q = document.getElementById('q'), kb = document.getElementById('kb'), kbBtn = document.getElementById('kb-toggle');
const insert = (text) => {
const s = q.selectionStart ?? q.value.length, e = q.selectionEnd ?? q.value.length;
q.setRangeText(text, s, e, 'end'); q.focus();
};
kb.innerHTML = ROWS.map((r) => '<div>' + [...r].map((k) =>
`<button type="button" data-ch="${PHONETIC[k]}" title="${k}">${PHONETIC[k]}</button>`).join('') + '</div>').join('') +
'<div><button type="button" data-ch=" " class="wide">فاصلہ</button><button type="button" data-act="bs">⌫</button></div>';
kb.onmousedown = (ev) => ev.preventDefault(); // keep focus/caret in the search box
kb.onclick = (ev) => {
const b = ev.target.closest('button'); if (!b) return;
if (b.dataset.act === 'bs') {
const s = q.selectionStart, e = q.selectionEnd;
q.setRangeText('', s === e ? Math.max(0, s - 1) : s, e, 'end'); q.focus();
} else insert(b.dataset.ch);
};
kbBtn.onclick = () => {
kb.hidden = !kb.hidden; kbBtn.setAttribute('aria-pressed', String(!kb.hidden)); q.focus();
};
q.addEventListener('keydown', (ev) => {
if (kb.hidden || ev.ctrlKey || ev.metaKey || ev.altKey) return;
const ch = PHONETIC[ev.key];
if (ch) { ev.preventDefault(); insert(ch); }
});
document.getElementById('theme-toggle').onclick = () => { document.getElementById('theme-toggle').onclick = () => {
const dark = root.dataset.theme ? root.dataset.theme === 'dark' : matchMedia('(prefers-color-scheme: dark)').matches; const dark = root.dataset.theme ? root.dataset.theme === 'dark' : matchMedia('(prefers-color-scheme: dark)').matches;
root.dataset.theme = dark ? 'light' : 'dark'; root.dataset.theme = dark ? 'light' : 'dark';

View File

@ -106,3 +106,15 @@ h1::after { content: ''; display: block; width: 140px; height: 2px; background:
.site-footer { border-top: 1.5px solid var(--border); padding: 16px 0; font-size: .85rem; color: var(--muted); text-align: center; } .site-footer { border-top: 1.5px solid var(--border); padding: 16px 0; font-size: .85rem; color: var(--muted); text-align: center; }
h1 + .muted { text-align: center; margin-top: 0; } h1 + .muted { text-align: center; margin-top: 0; }
/* Urdu keyboard */
#kb-toggle { font: inherit; border: 1.5px solid var(--border); background: var(--inner); color: var(--ink); border-radius: 10px; margin-inline-start: 6px; padding: 0 12px; cursor: pointer; }
#kb-toggle[aria-pressed='true'] { border-color: var(--brand); color: var(--brand); }
.kb { padding-bottom: 10px; }
.kb div { display: flex; flex-wrap: wrap; justify-content: center; gap: 4px; margin-top: 4px; }
.kb button {
font-family: 'Noto Naskh Arabic', serif; font-size: 1.1rem; min-width: 2.4em; padding: 2px 6px; cursor: pointer;
border: 1.5px solid var(--border); background: var(--paper); color: var(--ink); border-radius: 8px;
}
.kb button:hover { border-color: var(--brand); }
.kb .wide { min-width: 12em; }