From 3305a514ba856a89ef1fe4c6267bd42482825437 Mon Sep 17 00:00:00 2001 From: Anas Rashid Date: Fri, 9 Oct 2026 15:34:38 +0200 Subject: [PATCH] =?UTF-8?q?Numerals:=20Eastern=20(=DB=B1=DB=B2=DB=B3)=20or?= =?UTF-8?q?=20Western=20(123)=20in=20the=20display=20settings;=20the=20ser?= =?UTF-8?q?ver=20sends=20pages=20with=20the=20chosen=20digits=20(scripts?= =?UTF-8?q?=20and=20styles=20untouched)=20and=20script-drawn=20numbers=20f?= =?UTF-8?q?ollow.=20Settings=20button=20lines=20up=20with=20the=20other=20?= =?UTF-8?q?icons?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- web/src/layouts/Base.astro | 20 ++++++++++++++++++++ web/src/middleware.ts | 12 ++++++++++-- web/src/styles/global.css | 2 +- 3 files changed, 31 insertions(+), 3 deletions(-) diff --git a/web/src/layouts/Base.astro b/web/src/layouts/Base.astro index d857f6bc..ec139c2d 100644 --- a/web/src/layouts/Base.astro +++ b/web/src/layouts/Base.astro @@ -29,6 +29,7 @@ import Icon from '../components/Icon.astro'; if (t) document.documentElement.dataset.theme = t; const pal = localStorage.getItem('divan-palette'); // sepia when unset if (pal) document.documentElement.dataset.palette = pal; + if (document.cookie.includes('divan-digits=latn')) document.documentElement.dataset.digits = 'latn'; // Western numerals } catch (e) {} @@ -66,6 +67,8 @@ import Icon from '../components/Icon.astro'; ))} +

ہندسے

+

روشنی

@@ -122,15 +125,32 @@ import Icon from '../components/Icon.astro'; panel.querySelectorAll('[data-font]').forEach((b) => b.setAttribute('aria-pressed', String(b.dataset.font === on.font))); panel.querySelectorAll('[data-palette]').forEach((b) => b.setAttribute('aria-pressed', String(b.dataset.palette === on.palette))); panel.querySelectorAll('[data-theme-pick]').forEach((b) => b.setAttribute('aria-pressed', String(b.dataset.themePick === on.theme))); + panel.querySelectorAll('[data-digits]').forEach((b) => b.setAttribute('aria-pressed', String(b.dataset.digits === (root.dataset.digits ?? '')))); }; panel.addEventListener('click', (e) => { const b = e.target.closest('button'); if (!b) return; if (b.dataset.font) { root.classList.toggle('nastaliq', b.dataset.font === 'nastaliq'); store('divan-font', b.dataset.font); } if (b.dataset.palette != null) { if (b.dataset.palette) root.dataset.palette = b.dataset.palette; else delete root.dataset.palette; store('divan-palette', b.dataset.palette); } + if (b.dataset.digits != null) { + // numerals: a cookie, so the server sends pages with them; switching back to Eastern reloads the page + document.cookie = `divan-digits=${b.dataset.digits}; path=/; max-age=${b.dataset.digits ? 31536000 : 0}; samesite=lax`; + if (!b.dataset.digits) { delete root.dataset.digits; location.reload(); return; } + root.dataset.digits = 'latn'; westernDigits(document.body); + } if (b.dataset.themePick != null) { if (b.dataset.themePick) root.dataset.theme = b.dataset.themePick; else delete root.dataset.theme; store('divan-theme', b.dataset.themePick); } marks(); + }); document.addEventListener('click', (e) => { if (panel.open && !panel.contains(e.target)) panel.open = false; }); // close when clicking elsewhere + // Western numerals also in what scripts add later (word sidebar, page numbers, suggestions) + function westernDigits(node) { + const walk = document.createTreeWalker(node, NodeFilter.SHOW_TEXT); + for (let t = walk.nextNode(); t; t = walk.nextNode()) if (/[۰-۹]/.test(t.data)) t.data = t.data.replace(/[۰-۹]/g, (d) => String(d.charCodeAt(0) - 0x6f0)); + } + new MutationObserver((list) => { + if (root.dataset.digits !== 'latn') return; + for (const m of list) for (const n of m.addedNodes) n.nodeType === 3 ? westernDigits(n.parentNode) : n.nodeType === 1 && westernDigits(n); + }).observe(document.body, { childList: true, subtree: true }); marks(); // Urdu on-screen keyboard, Gboard/Windows Urdu layout const BASE = [[...'طصھدٹپتبجح'], [...'مورنلہاکی'], [...'قفےسشغع']]; diff --git a/web/src/middleware.ts b/web/src/middleware.ts index d2ff4d2d..d0d91be4 100644 --- a/web/src/middleware.ts +++ b/web/src/middleware.ts @@ -1,4 +1,6 @@ -// The signed-in reader (or null) for every page, from the session cookie +// The signed-in reader (or null) for every page, from the session cookie; and the reader's numerals: pages are +// written with Eastern Arabic (Urdu) digits, and a reader who chose Western digits (cookie divan-digits=latn, set in +// the display settings) gets them in the page as sent, so nothing flashes import { defineMiddleware } from 'astro:middleware'; import { auth, COOKIE } from './lib/auth'; @@ -10,5 +12,11 @@ export const onRequest = defineMiddleware(async (ctx, next) => { if (r.ok) ctx.locals.user = r.data.user; else if (r.status === 401) ctx.cookies.delete(COOKIE, { path: '/' }); // expired or signed out elsewhere } - return next(); + const res = await next(); + if (ctx.cookies.get('divan-digits')?.value !== 'latn' || !res.headers.get('content-type')?.includes('text/html')) return res; + // text only: scripts and styles are left alone (the page's own scripts look for Eastern digits) + const html = (await res.text()).split(/(|)/) + .map((part, i) => (i % 2 ? part : part.replace(/[۰-۹]/g, (d) => String(d.charCodeAt(0) - 0x6f0)))).join(''); + const headers = new Headers(res.headers); headers.delete('content-length'); + return new Response(html, { status: res.status, statusText: res.statusText, headers }); }); diff --git a/web/src/styles/global.css b/web/src/styles/global.css index 55fde0b3..7da92a46 100644 --- a/web/src/styles/global.css +++ b/web/src/styles/global.css @@ -496,7 +496,7 @@ h1 + .muted { text-align: center; margin-top: 0; } .sr-only { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; } .icon-btn.small { width: 30px; height: 30px; } /* display settings in the header: a button and its panel (font, palette swatches, light/dark/automatic) */ -.display-menu { position: relative; } +.display-menu { position: relative; display: flex; } /* no line box: lines up with the other header icons */ .display-menu > summary { list-style: none; } .display-menu > summary::-webkit-details-marker { display: none; } .display-menu[open] > summary { border-color: var(--brand); color: var(--brand); }