Add bilingual action to book menu

This commit is contained in:
Codex
2026-07-09 00:45:12 +08:00
parent c4621a273e
commit dc7137d331
6 changed files with 45 additions and 6 deletions
@@ -69,6 +69,7 @@
"Imported {{count}} annotations_one": "Imported {{count}} annotation",
"Imported {{count}} annotations_other": "Imported {{count}} annotations",
"Recently read": "Recently read",
"Bilingual": "Bilingual",
"Show recently read": "Show recently read",
"Your books will appear here": "Your books will appear here",
"{{count}} results_one": "{{count}} result",
@@ -9,6 +9,7 @@
"Apply": "应用",
"Auto Mode": "自动主题",
"Behavior": "行为",
"Bilingual": "双语拆分",
"Book": "书籍",
"Bookmark": "书签",
"Cancel": "取消",
@@ -9,6 +9,7 @@
"Apply": "應用",
"Auto Mode": "自動主題",
"Behavior": "行為",
"Bilingual": "雙語拆分",
"Book": "書籍",
"Bookmark": "書籤",
"Cancel": "取消",
@@ -205,6 +205,7 @@ const Bookshelf: React.FC<BookshelfProps> = ({
const [showStatusAlert, setShowStatusAlert] = useState(false);
const [showGroupingModal, setShowGroupingModal] = useState(false);
const [showBilingualFilterAlert, setShowBilingualFilterAlert] = useState(false);
const [bilingualFilterBook, setBilingualFilterBook] = useState<Book | null>(null);
const [bilingualFilterProcessing, setBilingualFilterProcessing] = useState(false);
const [importBookUrl] = useState(searchParams?.get('url') || '');
@@ -468,18 +469,37 @@ const Bookshelf: React.FC<BookshelfProps> = ({
const showBilingualFilterSelection = () => {
const book = getSingleSelectedBook();
if (!book || book.format !== 'EPUB') return;
setBilingualFilterBook(null);
setShowSelectModeActions(false);
setShowBilingualFilterAlert(true);
};
const showBilingualFilterBook = useCallback(
(book: Book) => {
if (book.format !== 'EPUB') {
eventDispatcher.dispatch('toast', {
type: 'warning',
message: _('Only EPUB books can be filtered'),
timeout: 2500,
});
return;
}
setBilingualFilterBook(book);
setShowSelectModeActions(false);
setShowBilingualFilterAlert(true);
},
[_],
);
const closeBilingualFilterSelection = () => {
if (bilingualFilterProcessing) return;
setShowBilingualFilterAlert(false);
setShowSelectModeActions(true);
setBilingualFilterBook(null);
if (isSelectMode) setShowSelectModeActions(true);
};
const runBilingualFilter = async (options: BilingualFilterOptions) => {
const book = getSingleSelectedBook();
const book = bilingualFilterBook ?? getSingleSelectedBook();
if (!book || !appService) return;
if (book.format !== 'EPUB') {
eventDispatcher.dispatch('toast', {
@@ -515,6 +535,7 @@ const Bookshelf: React.FC<BookshelfProps> = ({
await updateBooks(envConfig, [importedBook]);
handlePushLibrary();
setSelectedBooks([]);
setBilingualFilterBook(null);
setShowBilingualFilterAlert(false);
handleSetSelectMode(false);
eventDispatcher.dispatch('toast', {
@@ -536,8 +557,9 @@ const Bookshelf: React.FC<BookshelfProps> = ({
message: _('Failed to filter bilingual EPUB'),
timeout: 3000,
});
setBilingualFilterBook(null);
setShowBilingualFilterAlert(false);
setShowSelectModeActions(true);
if (isSelectMode) setShowSelectModeActions(true);
} finally {
setLoading(false);
setBilingualFilterProcessing(false);
@@ -888,6 +910,7 @@ const Bookshelf: React.FC<BookshelfProps> = ({
handleBookDelete={handleBookDelete}
handleSetSelectMode={handleSetSelectMode}
handleShowDetailsBook={handleShowDetailsBook}
handleBilingualFilterBook={showBilingualFilterBook}
handleLibraryNavigation={handleLibraryNavigation}
handleUpdateReadingStatus={handleUpdateReadingStatus}
transferProgress={
@@ -913,6 +936,7 @@ const Bookshelf: React.FC<BookshelfProps> = ({
handleBookDelete,
handleSetSelectMode,
handleShowDetailsBook,
showBilingualFilterBook,
handleLibraryNavigation,
handleUpdateReadingStatus,
],
@@ -993,9 +1017,9 @@ const Bookshelf: React.FC<BookshelfProps> = ({
bilingualFilterEnabled={bilingualFilterEnabled}
/>
)}
{showBilingualFilterAlert && selectedBilingualBook && (
{showBilingualFilterAlert && (bilingualFilterBook || selectedBilingualBook) && (
<BilingualFilterAlert
bookTitle={selectedBilingualBook.title}
bookTitle={(bilingualFilterBook || selectedBilingualBook)?.title || ''}
safeAreaBottom={safeAreaInsets?.bottom || 0}
processing={bilingualFilterProcessing}
onCancel={closeBilingualFilterSelection}
@@ -117,6 +117,7 @@ interface BookshelfItemProps {
handleBookDelete: (book: Book, syncBooks?: boolean) => Promise<boolean>;
handleSetSelectMode: (selectMode: boolean) => void;
handleShowDetailsBook: (book: Book) => void;
handleBilingualFilterBook: (book: Book) => void;
handleLibraryNavigation: (targetGroup: string) => void;
handleUpdateReadingStatus: (book: Book, status: ReadingStatus | undefined) => void;
}
@@ -135,6 +136,7 @@ const BookshelfItem: React.FC<BookshelfItemProps> = ({
handleBookDownload,
handleSetSelectMode,
handleShowDetailsBook,
handleBilingualFilterBook,
handleLibraryNavigation,
handleUpdateReadingStatus,
}) => {
@@ -228,6 +230,12 @@ const BookshelfItem: React.FC<BookshelfItemProps> = ({
showBookDetailsModal(book);
},
},
bilingual: {
text: _('Bilingual'),
action: async () => {
handleBilingualFilterBook(book);
},
},
showInFinder: {
text: _(fileRevealLabel),
action: async () => {
@@ -366,6 +374,7 @@ const BookshelfItem: React.FC<BookshelfItemProps> = ({
appService?.isMobileApp,
handleBookDownload,
handleBookUpload,
handleBilingualFilterBook,
handleGroupBooks,
handleSetSelectMode,
handleUpdateReadingStatus,
@@ -651,6 +651,7 @@ export type BookContextMenuItemId =
| 'markAbandoned'
| 'clearStatus'
| 'showDetails'
| 'bilingual'
| 'showInFinder'
| 'searchGoodreads'
| 'download'
@@ -750,7 +751,9 @@ export const getBookContextMenuItemIds = (book: Book): BookContextMenuItemId[] =
) {
ids.push('clearStatus');
}
ids.push('showDetails', 'showInFinder', 'searchGoodreads');
ids.push('showDetails');
if (book.format === 'EPUB') ids.push('bilingual');
ids.push('showInFinder', 'searchGoodreads');
if (book.uploadedAt && !book.downloadedAt) ids.push('download');
if (!book.uploadedAt && book.downloadedAt) ids.push('upload');
// Share is offered for any local-or-uploaded book; the dialog uploads first