+
setIsOpen(false)} />
+
+ {items.map((it) => (
-
-
-
-
-
-
+ ))}
+
+
>
)}
diff --git a/web/src/components/ShortcutsHelp.tsx b/web/src/components/ShortcutsHelp.tsx
new file mode 100644
index 0000000..80614d7
--- /dev/null
+++ b/web/src/components/ShortcutsHelp.tsx
@@ -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
(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 (
+
+
e.stopPropagation()}
+ >
+
Raccourcis clavier
+
+ {GROUPS.map((g) => (
+
+ {g.title}
+
+ {g.keys.map(([k, label]) => (
+
+
- {label}
+ - {k}
+
+ ))}
+
+
+ ))}
+
+
+
+
+ );
+}
diff --git a/web/src/components/Sidebar.tsx b/web/src/components/Sidebar.tsx
index 3b5004a..9262d26 100644
--- a/web/src/components/Sidebar.tsx
+++ b/web/src/components/Sidebar.tsx
@@ -1,6 +1,6 @@
import { useState } from 'react';
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
-import { useAuthStore } from '../stores/authStore';
+import { useAuthStore, clearUserData } from '../stores/authStore';
import { authApi } from '../api/auth';
import { feedsApi } from '../api/feeds';
import { articlesApi } from '../api/articles';
@@ -23,7 +23,10 @@ export function Sidebar({ onSelectFeed, selectedFeedId, onEnterFocus, isFocusMod
const { data: feeds } = useQuery({ queryKey: ['feeds'], queryFn: () => feedsApi.list() });
const markAllReadMutation = useMutation({
- mutationFn: () => (selectedFeedId ? articlesApi.markAllRead(selectedFeedId) : articlesApi.markAllReadGlobal()),
+ mutationFn: () =>
+ selectedFeedId && selectedFeedId !== 'favorites'
+ ? articlesApi.markAllRead(selectedFeedId)
+ : articlesApi.markAllReadGlobal(),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['feeds'] });
queryClient.invalidateQueries({ queryKey: ['articles'] });
@@ -45,16 +48,19 @@ export function Sidebar({ onSelectFeed, selectedFeedId, onEnterFocus, isFocusMod
const logoutMutation = useMutation({
mutationFn: () => authApi.logout(),
- onSuccess: () => { logoutStore(); window.location.href = '/login'; },
+ onSettled: async () => {
+ logoutStore();
+ queryClient.clear();
+ await clearUserData();
+ window.location.href = '/login';
+ },
});
- const handleDelete = (e: React.MouseEvent, id: string) => {
- e.stopPropagation();
+ const handleDelete = (id: string) => {
if (window.confirm('Voulez-vous vraiment supprimer ce flux ?')) deleteFeedMutation.mutate(id);
};
- const handleRename = (e: React.MouseEvent, id: string, currentTitle: string) => {
- e.stopPropagation();
+ const handleRename = (id: string, currentTitle: string) => {
const newTitle = window.prompt('Renommer le flux :', currentTitle);
if (newTitle && newTitle.trim() !== '' && newTitle !== currentTitle) {
updateFeedMutation.mutate({ id, title: newTitle.trim() });
@@ -73,7 +79,7 @@ export function Sidebar({ onSelectFeed, selectedFeedId, onEnterFocus, isFocusMod
return (
<>
-