From 8429ba1ddb547da1cffdbe839f46bb83aa7d3952 Mon Sep 17 00:00:00 2001 From: Anas Rashid Date: Fri, 9 Oct 2026 14:30:06 +0200 Subject: [PATCH] Home sections editor: Urdu keyboard and Roman typing inside the name and section boxes, as in search The keyboard finds its box again by id when a page redraws it (Base.astro), so it keeps typing into the name box after a poet is added. Co-Authored-By: Claude Opus 5.5 --- web/src/layouts/Base.astro | 4 ++++ web/src/pages/mod/site.astro | 31 ++++++++++++++++++++++++------- web/src/styles/global.css | 3 ++- 3 files changed, 30 insertions(+), 8 deletions(-) diff --git a/web/src/layouts/Base.astro b/web/src/layouts/Base.astro index f4769ffe..ca5f62ef 100644 --- a/web/src/layouts/Base.astro +++ b/web/src/layouts/Base.astro @@ -113,8 +113,11 @@ const fullTitle = title ? `${title} · دیوان` : 'دیوان · اردو ک // (e.g. the author filter): whichever was focused last let target = q; document.addEventListener('focusin', (e) => { if (e.target.matches?.('[data-urdu]')) target = e.target; }); + // a page may redraw its boxes (e.g. the home sections editor): find the box again by its id + const live = () => (target.isConnected ? target : (target = (target.id && document.getElementById(target.id)) || q)); const typed = () => target.dispatchEvent(new Event('input', { bubbles: true })); // so suggestions update const insert = (text) => { + live(); // rich editors (the content editor) take typed text through the browser's own editing command if (target.isContentEditable) { target.focus(); document.execCommand('insertText', false, text); return; } const s = target.selectionStart ?? target.value.length, e = target.selectionEnd ?? target.value.length; @@ -125,6 +128,7 @@ const fullTitle = title ? `${title} · دیوان` : 'دیوان · اردو ک const b = ev.target.closest('button'); if (!b) return; if (b.dataset.act === 'shift') { shift = !shift; render(); return; } if (b.dataset.act === 'bs') { + live(); if (target.isContentEditable) { target.focus(); document.execCommand('delete'); return; } const s = target.selectionStart, e = target.selectionEnd; target.setRangeText('', s === e ? Math.max(0, s - 1) : s, e, 'end'); target.focus(); typed(); diff --git a/web/src/pages/mod/site.astro b/web/src/pages/mod/site.astro index 9234c024..08b29dca 100644 --- a/web/src/pages/mod/site.astro +++ b/web/src/pages/mod/site.astro @@ -59,6 +59,22 @@ const centuries = Object.fromEntries(Array.from({ length: 16 }, (_, c) => [c, ce const b = document.createElement('button'); b.type = 'button'; b.className = 'tool'; b.textContent = label; b.title = title; b.onclick = () => { fn(); draw(); }; return b; }; + // a box with the Urdu keyboard and Roman typing buttons inside it, as in search (Base.astro handles them) + const KB = ''; + // a redrawn box shows the keyboard/Roman state only if it is the box being typed in + let typing = ''; + host.addEventListener('focusin', (e) => { const t = e.target as HTMLElement; if (t.matches('input[data-urdu]')) typing = t.id; }); + const pressed = (header: string, id: string) => (id === typing && document.getElementById(header)?.getAttribute('aria-pressed')) || 'false'; + const keyed = (input: HTMLInputElement, id: string, cls = '') => { + input.id = id; input.setAttribute('data-urdu', ''); + const wrap = document.createElement('div'); wrap.className = `in-box-field ${cls}`; + const rom = document.createElement('button'); rom.type = 'button'; rom.className = 'in-box rom'; rom.textContent = 'اب'; + rom.title = 'رومن حروف سے اردو'; rom.dataset.urduRom = id; rom.setAttribute('aria-pressed', pressed('rom-toggle', id)); + const kb = document.createElement('button'); kb.type = 'button'; kb.className = 'in-box'; kb.innerHTML = KB; + kb.title = 'اردو کی بورڈ'; kb.setAttribute('aria-label', 'اردو کی بورڈ'); kb.dataset.urduKb = id; kb.setAttribute('aria-pressed', pressed('kb-toggle', id)); + wrap.append(input, rom, kb); + return wrap; + }; const move = (xs: any[], i: number, d: number) => { const j = i + d; if (j >= 0 && j < xs.length) [xs[i], xs[j]] = [xs[j], xs[i]]; }; const SORTS: [string, string][] = [['manual', 'دستی'], ['alpha', 'حروفِ تہجی'], ['timeline', 'زمانی']]; @@ -66,7 +82,7 @@ const centuries = Object.fromEntries(Array.from({ length: 16 }, (_, c) => [c, ce host.replaceChildren(...state.map((s, i) => { const box = document.createElement('fieldset'); box.className = 'site-section'; const head = document.createElement('div'); head.className = 'site-head'; - const title = document.createElement('input'); title.value = s.title; title.setAttribute('data-urdu', ''); + const title = document.createElement('input'); title.value = s.title; title.placeholder = s.century === null ? 'حصے کا نام' : data.centuries[s.century]; title.setAttribute('aria-label', 'حصے کا نام'); title.oninput = () => (s.title = title.value); const sort = document.createElement('select'); sort.className = 'tool'; sort.setAttribute('aria-label', 'ترتیب'); @@ -74,17 +90,17 @@ const centuries = Object.fromEntries(Array.from({ length: 16 }, (_, c) => [c, ce sort.onchange = () => { s.sort = sort.value; draw(); }; const kind = document.createElement('small'); kind.className = 'muted'; kind.textContent = s.century === null ? `منتخب شعرا (${s.poets.length})` : `صدی: ${data.centuries[s.century]}`; - head.append(title, sort, kind, + head.append(keyed(title, `site-title-${i}`, 'site-title'), sort, kind, btn('↑', 'حصہ اوپر', () => move(state, i, -1)), btn('↓', 'حصہ نیچے', () => move(state, i, 1)), btn('ہٹائیں', 'یہ حصہ ہٹائیں', () => { if (confirm('یہ حصہ ہٹا دیں؟')) state.splice(i, 1); })); box.append(head); - if (s.century === null) box.append(poetList(s)); + if (s.century === null) box.append(poetList(s, i)); return box; })); } // a chosen-poets section: its poets (↑ ↓ ✕ when sorted by hand) and a name box to add one - function poetList(s: any) { + function poetList(s: any, i: number) { const wrap = document.createElement('div'); const ol = document.createElement('ol'); ol.className = 'site-poets'; s.poets.forEach((id: number, j: number) => { @@ -94,16 +110,17 @@ const centuries = Object.fromEntries(Array.from({ length: 16 }, (_, c) => [c, ce ol.append(li); }); const find = document.createElement('div'); find.className = 'author-find'; - const box = document.createElement('input'); box.setAttribute('data-urdu', ''); box.placeholder = 'شاعر یا ادیب کا نام لکھ کر شامل کریں'; box.autocomplete = 'off'; + const box = document.createElement('input'); box.placeholder = 'شاعر یا ادیب کا نام لکھ کر شامل کریں'; box.autocomplete = 'off'; const list = document.createElement('div'); list.className = 'author-suggest'; list.hidden = true; box.oninput = () => { const t = norm(box.value.trim()); const hits = t ? data.poets.filter((p: any) => !s.poets.includes(p.id) && norm(p.name).includes(t)).slice(0, 10) : []; - list.replaceChildren(...hits.map((p: any) => btn(p.name, 'شامل کریں', () => s.poets.push(p.id)))); + // after adding, the (redrawn) name box keeps the focus for the next name + list.replaceChildren(...hits.map((p: any) => btn(p.name, 'شامل کریں', () => { s.poets.push(p.id); setTimeout(() => document.getElementById(`site-find-${i}`)?.focus()); }))); list.hidden = !hits.length; }; box.onkeydown = (e) => { if (e.key === 'Enter') { e.preventDefault(); (list.querySelector('button') as HTMLButtonElement | null)?.click(); } }; - find.append(box, list); + find.append(keyed(box, `site-find-${i}`), list); wrap.append(ol, find); return wrap; } diff --git a/web/src/styles/global.css b/web/src/styles/global.css index b44c89fa..1bf72d03 100644 --- a/web/src/styles/global.css +++ b/web/src/styles/global.css @@ -397,7 +397,8 @@ h1 + .muted { text-align: center; margin-top: 0; } /* home page sections editor (/mod/site) */ .site-section { border: 1px solid var(--border); border-radius: 10px; background: var(--paper); margin: 10px 0; padding: 8px 12px; } .site-head { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; } -.site-head input { flex: 1 1 14em; font: inherit; padding: 2px 10px; border: 1px solid var(--border); border-radius: 8px; background: var(--inner); color: var(--ink); } +.site-head .site-title { flex: 1 1 14em; } +.site-head input { width: 100%; font: inherit; padding: 2px 10px; border: 1px solid var(--border); border-radius: 8px; background: var(--inner); color: var(--ink); } .site-poets { list-style: urdu; margin: 8px 0; padding-inline-start: 2.5em; columns: 2 16em; } .site-poets li { break-inside: avoid; } .site-poets .tool { min-width: 1.6em; padding: 0 4px; margin-inline-start: 4px; font-size: .85rem; }