mirror of
https://github.com/R0m1k3/FlowReader.git
synced 2026-10-12 01:38:16 +02:00
feat(web): unified reader with reading settings, faster lists, a11y
- New Reader (desktop + mobile): loads full content on open, prefetches the next article, reading progress and time, "Aa" settings (light / sepia / dark / auto theme, serif / sans / Atkinson Hyperlegible font, size, line height, measure), Space-to-next, keyboard shortcuts, focus management, inert background, URL-driven (?a=) so Back closes it. - Dashboard: cursor pagination, unread/all filter, CSS grid (no masonry), no re-sorting under the reader, optimistic cache patches instead of refetch storms, "new articles" banner, error/offline states, j/k/o/m/s/v shortcuts and help dialog. - Cards: keyboard-openable, lazy <img>, memoized, touch-only swipe. - Focus mode: framer drag (drop @use-gesture), Read button, undo restores unread state, arrow keys. - WebSocket: single connection, backoff, cache patching, no console logs. - Contrast fixes (on-nature tokens), 44px targets, lang="fr", reduced motion, self-hosted fonts, theme applied before first paint. - Code splitting + vendor chunk, prompt-based PWA updates, smaller precache, offline cache for opened articles and images, caches cleared on logout; upgrade react-router (CVE), drop unused deps and assets. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
03e57e4308
commit
360f3cf012
38 files changed
+2557
-1242
No files matched your search
@@ -0,0 +1,74 @@
|
||||
import { useEffect, useRef } from 'react';
|
||||
|
||||
const GROUPS: { title: string; keys: [string, string][] }[] = [
|
||||
{
|
||||
title: 'Liste',
|
||||
keys: [
|
||||
['j / k', 'Article suivant / précédent'],
|
||||
['o · Entrée', 'Ouvrir l’article'],
|
||||
['m', 'Marquer lu / non lu'],
|
||||
['s · f', 'Favori'],
|
||||
['v', 'Ouvrir l’original'],
|
||||
['/', 'Rechercher'],
|
||||
['u', 'Basculer Non lus / Tous'],
|
||||
['r', 'Actualiser les flux'],
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Lecture',
|
||||
keys: [
|
||||
['Espace', 'Défiler, puis article suivant'],
|
||||
['j · → / k · ←', 'Suivant / précédent'],
|
||||
['+ / -', 'Taille du texte'],
|
||||
['Échap', 'Fermer'],
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export function ShortcutsHelp({ onClose }: { onClose: () => void }) {
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
ref.current?.focus();
|
||||
const onKey = (e: KeyboardEvent) => {
|
||||
if (e.key === 'Escape' || e.key === '?') {
|
||||
e.stopImmediatePropagation();
|
||||
onClose();
|
||||
}
|
||||
};
|
||||
window.addEventListener('keydown', onKey, true);
|
||||
return () => window.removeEventListener('keydown', onKey, true);
|
||||
}, [onClose]);
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 z-[60] flex items-center justify-center p-4 bg-nature-dark/50 animate-fade-in" onClick={onClose}>
|
||||
<div
|
||||
ref={ref}
|
||||
tabIndex={-1}
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-labelledby="shortcuts-title"
|
||||
className="surface-card w-full max-w-lg p-7 outline-none animate-rise"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<h2 id="shortcuts-title" className="text-2xl font-serif italic text-nature mb-5">Raccourcis clavier</h2>
|
||||
<div className="grid sm:grid-cols-2 gap-6">
|
||||
{GROUPS.map((g) => (
|
||||
<section key={g.title}>
|
||||
<h3 className="eyebrow text-paper-muted mb-3">{g.title}</h3>
|
||||
<dl className="space-y-2 text-sm">
|
||||
{g.keys.map(([k, label]) => (
|
||||
<div key={k} className="flex items-center justify-between gap-3">
|
||||
<dt className="text-paper-muted">{label}</dt>
|
||||
<dd className="kbd shrink-0">{k}</dd>
|
||||
</div>
|
||||
))}
|
||||
</dl>
|
||||
</section>
|
||||
))}
|
||||
</div>
|
||||
<button onClick={onClose} className="btn-ghost w-full mt-6">Fermer</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user