feat(library): search a book on Goodreads from the library and reader (#4543) (#4548)

Adds a quick "Search on Goodreads" action so readers can jump straight to
Goodreads to track a book instead of retyping the title there.

- Library: a Goodreads button in the Book Details view (works on web,
  desktop and mobile) searching the book's title + author, plus a
  "Search on Goodreads" item in the desktop right-click context menu.
- Reader: Goodreads is added as a built-in web-search provider so
  highlighted text (e.g. a short-story title inside a magazine) can be
  looked up on Goodreads. Disabled by default like the other built-ins;
  enable it in Settings -> Dictionaries.

Both surfaces are used because the native context menu is desktop-only;
the Book Details button covers web and mobile. Adds a shared
openExternalUrl() helper and translates "Search on Goodreads" across all
locales (the Goodreads brand name is kept verbatim).

Closes #4543

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Huang Xin
2026-06-12 14:30:17 +08:00
committed by GitHub
parent cfe2bb9116
commit ceddee3793
45 changed files with 212 additions and 39 deletions
@@ -9,9 +9,12 @@ import {
MdExpandMore,
MdExpandLess,
} from 'react-icons/md';
import { FaGoodreads } from 'react-icons/fa';
import { Book } from '@/types/book';
import { BookMetadata } from '@/libs/document';
import { openExternalUrl } from '@/utils/open';
import { getBookGoodreadsQuery, getGoodreadsSearchUrl } from '@/utils/goodreads';
import { useTranslation } from '@/hooks/useTranslation';
import { useSettingsStore } from '@/store/settingsStore';
import { useEnv } from '@/context/EnvContext';
@@ -98,6 +101,12 @@ const BookDetailView: React.FC<BookDetailViewProps> = ({
<MdOutlineEdit className='hover:fill-blue-500' />
</button>
)}
<button
onClick={() => openExternalUrl(getGoodreadsSearchUrl(getBookGoodreadsQuery(book)))}
title={_('Search on Goodreads')}
>
<FaGoodreads className='fill-base-content' />
</button>
{onDelete && (
<Dropdown
label={_('Delete Book Options')}