)
diff --git a/frontend/src/components/system/BookEditor.jsx b/frontend/src/components/system/BookEditor.jsx
index 01945ff..e601c6c 100644
--- a/frontend/src/components/system/BookEditor.jsx
+++ b/frontend/src/components/system/BookEditor.jsx
@@ -3,8 +3,9 @@ import { useTranslation } from 'react-i18next'
import { LuX } from 'react-icons/lu'
import api from '../../api'
import InlineTagEditor from '../maps/InlineTagEditor'
+import { saveBookPrefs, getBookPrefs } from '../../hooks/useBookPrefs'
-export default function BookEditor({ book, onSave, onClose }) {
+export default function BookEditor({ book, onSave, onClose, allTags = [] }) {
const { t } = useTranslation()
const [form, setForm] = useState({
title: book.title || '',
@@ -18,6 +19,8 @@ export default function BookEditor({ book, onSave, onClose }) {
const [tags, setTags] = useState(book.tags || [])
const [editingTags, setEditingTags] = useState(false)
const [saving, setSaving] = useState(false)
+ const [progressReset, setProgressReset] = useState(false)
+ const hasProgress = !!getBookPrefs(book.id).page
const field = (label, key, opts = {}) => (
@@ -83,6 +86,7 @@ export default function BookEditor({ book, onSave, onClose }) {
tags={tags}
onSave={setTags}
onCancel={() => setEditingTags(false)}
+ suggestions={allTags.filter(t => !tags.includes(t))}
/>
) : (
@@ -98,13 +102,22 @@ export default function BookEditor({ book, onSave, onClose }) {
)}
-
+
+ {(hasProgress || progressReset) && (
+
+ )}
)
diff --git a/frontend/src/locales/de-DE.json b/frontend/src/locales/de-DE.json
index 092ed13..482c05d 100644
--- a/frontend/src/locales/de-DE.json
+++ b/frontend/src/locales/de-DE.json
@@ -139,7 +139,16 @@
"addToFavorites": "Zu Favoriten hinzufügen",
"removeFromFavorites": "Aus Favoriten entfernen",
"bookmarkPage": "Seite als Lesezeichen speichern",
- "downloadFile": "Herunterladen"
+ "downloadFile": "Herunterladen",
+ "keyboardShortcuts": "Tastaturkürzel",
+ "shortcutPrevNext": "Vorherige / nächste Seite",
+ "shortcutPrevNextVertical": "Vorherige / nächste Seite (vertikal)",
+ "shortcutFavorite": "Favorit umschalten",
+ "shortcutToc": "Inhaltsverzeichnis",
+ "shortcutBookmarks": "Lesezeichen",
+ "shortcutSearch": "Suche",
+ "shortcutHelp": "Diese Hilfe",
+ "shortcutClose": "Panels schließen"
},
"search": {
"title": "Bibliothek durchsuchen",
@@ -771,7 +780,9 @@
"save": "Speichern",
"saving": "Speichern…",
"cancel": "Abbrechen",
- "failed": "Speichern fehlgeschlagen."
+ "failed": "Speichern fehlgeschlagen.",
+ "resetProgress": "Lesefortschritt zurücksetzen",
+ "progressReset": "Fortschritt zurückgesetzt"
},
"bookRow": {
"missingFile": "Fehlend",
diff --git a/frontend/src/locales/en-US.json b/frontend/src/locales/en-US.json
index 17ec206..bb764b0 100644
--- a/frontend/src/locales/en-US.json
+++ b/frontend/src/locales/en-US.json
@@ -139,7 +139,16 @@
"addToFavorites": "Add to favorites",
"removeFromFavorites": "Remove from favorites",
"bookmarkPage": "Bookmark this page",
- "downloadFile": "Download"
+ "downloadFile": "Download",
+ "keyboardShortcuts": "Keyboard Shortcuts",
+ "shortcutPrevNext": "Previous / next page",
+ "shortcutPrevNextVertical": "Previous / next page (vertical)",
+ "shortcutFavorite": "Toggle favorite",
+ "shortcutToc": "Table of contents",
+ "shortcutBookmarks": "Bookmarks",
+ "shortcutSearch": "Search",
+ "shortcutHelp": "This help",
+ "shortcutClose": "Close panels"
},
"search": {
"title": "Search Your Library",
@@ -771,7 +780,9 @@
"save": "Save",
"saving": "Saving…",
"cancel": "Cancel",
- "failed": "Failed to save."
+ "failed": "Failed to save.",
+ "resetProgress": "Reset reading progress",
+ "progressReset": "Progress reset"
},
"bookRow": {
"missingFile": "Missing",
diff --git a/frontend/src/views/ReaderView.jsx b/frontend/src/views/ReaderView.jsx
index f9faf65..cee7b79 100644
--- a/frontend/src/views/ReaderView.jsx
+++ b/frontend/src/views/ReaderView.jsx
@@ -3,7 +3,7 @@ import { useParams, useNavigate, useSearchParams } from 'react-router-dom'
import { useTranslation } from 'react-i18next'
import {
LuArrowLeft, LuChevronLeft, LuChevronRight, LuDownload,
- LuFileText, LuColumns2, LuFile, LuSearch, LuList, LuBookmark, LuBookmarkPlus, LuHeart,
+ LuFileText, LuColumns2, LuFile, LuSearch, LuList, LuBookmark, LuBookmarkPlus, LuHeart, LuKeyboard,
} from 'react-icons/lu'
import api, { mediaUrl } from '../api'
import Spinner from '../components/Spinner'
@@ -72,6 +72,7 @@ export default function ReaderView() {
const [pendingBookmark, setPendingBookmark] = useState(null)
const [pendingLabel, setPendingLabel] = useState('')
const [pendingNotes, setPendingNotes] = useState('')
+ const [showShortcuts, setShowShortcuts] = useState(false)
const [zoom, setZoom] = useState(1)
const [pan, setPan] = useState({ x: 0, y: 0 })
@@ -239,6 +240,8 @@ export default function ReaderView() {
if (e.key === 't') togglePanel('toc')
if (e.key === 'b') togglePanel('bookmarks')
if (e.key === 's') togglePanel('search')
+ if (e.key === '?') setShowShortcuts(v => !v)
+ if (e.key === 'Escape') setShowShortcuts(false)
}
window.addEventListener('keydown', handleKeyDown)
return () => window.removeEventListener('keydown', handleKeyDown)
@@ -386,8 +389,42 @@ export default function ReaderView() {
}}>
+
+
setShowShortcuts(false)}
+ style={{ position: 'fixed', inset: 0, background: 'rgba(0,0,0,0.5)', zIndex: 200, display: 'flex', alignItems: 'center', justifyContent: 'center' }}
+ >
+
e.stopPropagation()} style={{ background: 'var(--bg-panel)', border: '1px solid var(--border)', borderRadius: 10, padding: 24, minWidth: 280, boxShadow: '0 8px 32px rgba(0,0,0,0.4)' }}>
+
{t('reader.keyboardShortcuts')}
+ {[
+ ['← / →', t('reader.shortcutPrevNext')],
+ ['↑ / ↓', t('reader.shortcutPrevNextVertical')],
+ ['f', t('reader.shortcutFavorite')],
+ ['t', t('reader.shortcutToc')],
+ ['b', t('reader.shortcutBookmarks')],
+ ['s', t('reader.shortcutSearch')],
+ ['?', t('reader.shortcutHelp')],
+ ['Esc', t('reader.shortcutClose')],
+ ].map(([key, desc]) => (
+
+ {key}
+ {desc}
+
+ ))}
+
+